mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-24 19:00:03 +00:00
Compare commits
195 Commits
@now/node@
...
@now/next@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
274fdeb49a | ||
|
|
d15c90b42f | ||
|
|
5be2917c47 | ||
|
|
5f08b24546 | ||
|
|
cc3026ffcb | ||
|
|
8cb6e56679 | ||
|
|
7da5ff4b1d | ||
|
|
5d1069d464 | ||
|
|
6e1a72f557 | ||
|
|
6f97a2cc7a | ||
|
|
6e402bffc3 | ||
|
|
e41d0f8e68 | ||
|
|
1a046744f2 | ||
|
|
a9bf011f2c | ||
|
|
60c76b3290 | ||
|
|
c7ff1e7044 | ||
|
|
e2deaef54a | ||
|
|
b2c7386c83 | ||
|
|
3dce84c3cf | ||
|
|
443b1ac158 | ||
|
|
be3dca3d94 | ||
|
|
cb135e4329 | ||
|
|
55c73b68bb | ||
|
|
eb793ba139 | ||
|
|
00908e8ebe | ||
|
|
5ce7ff86a4 | ||
|
|
4b34ee2993 | ||
|
|
646bd288ba | ||
|
|
8aba6f1ff8 | ||
|
|
35e5e328aa | ||
|
|
bdd4953d62 | ||
|
|
e938b298e2 | ||
|
|
b67f324e10 | ||
|
|
1dfa286af5 | ||
|
|
d4391bd4cc | ||
|
|
53eb71f26d | ||
|
|
92ffd654b5 | ||
|
|
36b83f1606 | ||
|
|
7a79f620c0 | ||
|
|
f3b286ecf3 | ||
|
|
adf31c3fcc | ||
|
|
deacdfc47c | ||
|
|
ac9badbe9e | ||
|
|
62beb0f78d | ||
|
|
5f9777f4af | ||
|
|
e00db4437a | ||
|
|
b0e4f2590d | ||
|
|
f0d58eac8c | ||
|
|
dae830d2b6 | ||
|
|
e3071e4e29 | ||
|
|
073d7ece23 | ||
|
|
071258ba33 | ||
|
|
c0e00dc69a | ||
|
|
6e5c136337 | ||
|
|
60428cd4cf | ||
|
|
c6b9d80eec | ||
|
|
953bdc10e5 | ||
|
|
becdbd2136 | ||
|
|
da9bb31259 | ||
|
|
8cbf036921 | ||
|
|
8f66e4a308 | ||
|
|
9627b612f2 | ||
|
|
8a9ded6d61 | ||
|
|
d89c772bd5 | ||
|
|
32137586b9 | ||
|
|
9a3e435175 | ||
|
|
01d5a10ebd | ||
|
|
040658fbfa | ||
|
|
51c00286a4 | ||
|
|
c3e274fc2f | ||
|
|
fe633c528a | ||
|
|
5e306d49f8 | ||
|
|
274259b7dd | ||
|
|
230e96c687 | ||
|
|
8c0c6e546d | ||
|
|
5e8541b936 | ||
|
|
300558f24e | ||
|
|
829f7d8aeb | ||
|
|
340f7db68a | ||
|
|
2fe987b5da | ||
|
|
5b3aa48cd6 | ||
|
|
080d96bfa1 | ||
|
|
7d9bf682b4 | ||
|
|
a913a4f59f | ||
|
|
2042c96d98 | ||
|
|
4ca0ff8426 | ||
|
|
554cc42d83 | ||
|
|
3f9e30d031 | ||
|
|
5a9ca8644c | ||
|
|
3f362d4b50 | ||
|
|
7360886c9a | ||
|
|
5b8a1b47b0 | ||
|
|
0506b262d2 | ||
|
|
17c4569f41 | ||
|
|
888ff83309 | ||
|
|
7191421470 | ||
|
|
e9c9aa5ce1 | ||
|
|
d98f3d8140 | ||
|
|
fbf659e4e1 | ||
|
|
6f7069aadd | ||
|
|
cef2c889f6 | ||
|
|
e42d6c422a | ||
|
|
2e9241b454 | ||
|
|
257a3b07fc | ||
|
|
df9322b392 | ||
|
|
28b5476bf3 | ||
|
|
70a61f045f | ||
|
|
238a8a8593 | ||
|
|
6f2d5c0045 | ||
|
|
fc798da398 | ||
|
|
0804483316 | ||
|
|
4a087f842e | ||
|
|
01c7bf0059 | ||
|
|
6c439516db | ||
|
|
0db4ee69ef | ||
|
|
aec8a7ac9a | ||
|
|
882bc8cb99 | ||
|
|
9aad701dfd | ||
|
|
f224f574be | ||
|
|
e201eaa315 | ||
|
|
b0ed1f2be0 | ||
|
|
4ac33e1c03 | ||
|
|
27ca517666 | ||
|
|
734e9c072d | ||
|
|
8db54f3093 | ||
|
|
886c0718d6 | ||
|
|
e160a2f21f | ||
|
|
e09a418423 | ||
|
|
52d4464368 | ||
|
|
4dc635e5f2 | ||
|
|
510fb7ee7e | ||
|
|
243451e94b | ||
|
|
11bbda977d | ||
|
|
62c050f394 | ||
|
|
bd5a013312 | ||
|
|
1823cf452e | ||
|
|
c426d72ccf | ||
|
|
ddf59c052d | ||
|
|
1dcf6e7fb1 | ||
|
|
d4f4792988 | ||
|
|
7e1f2bd10e | ||
|
|
a80a1d0c1d | ||
|
|
8ff747b4d7 | ||
|
|
aa63b5a581 | ||
|
|
2094ec3c99 | ||
|
|
bf30d10211 | ||
|
|
ccc03c9c6e | ||
|
|
4b7367e2dc | ||
|
|
00aa56a095 | ||
|
|
56ae93a2a5 | ||
|
|
adb32a09d3 | ||
|
|
3358d8e44c | ||
|
|
c3bd2698e8 | ||
|
|
a7baa4761d | ||
|
|
5dd2daa970 | ||
|
|
dd36a489ed | ||
|
|
2e742209e3 | ||
|
|
8d13464cba | ||
|
|
20fdcfa0af | ||
|
|
fac004f83c | ||
|
|
5fee4bbad1 | ||
|
|
18e4b18839 | ||
|
|
b8627fd384 | ||
|
|
4e2db6f8a5 | ||
|
|
b685a3afdd | ||
|
|
aea3f58970 | ||
|
|
b0bb90dc11 | ||
|
|
2132c2463f | ||
|
|
60bf6e2420 | ||
|
|
9badc9048c | ||
|
|
a74c1921f8 | ||
|
|
e6044f2e8d | ||
|
|
78d1afa25e | ||
|
|
4792adf524 | ||
|
|
4d8a99141b | ||
|
|
7954ddc1d0 | ||
|
|
216a1fd9d2 | ||
|
|
e5add6750c | ||
|
|
b7533650e1 | ||
|
|
6272f5ce5c | ||
|
|
bde0538efe | ||
|
|
5e6775fca0 | ||
|
|
e05e0dc40e | ||
|
|
96dbc6d348 | ||
|
|
0a63bd47e8 | ||
|
|
35dd50eb61 | ||
|
|
f70ed94c8c | ||
|
|
fb0c8600a2 | ||
|
|
5023bdd25d | ||
|
|
bf4917ccf2 | ||
|
|
64aff3aef4 | ||
|
|
62b87d1ed1 | ||
|
|
5872114c87 | ||
|
|
ce04246051 | ||
|
|
02b03d4533 |
@@ -1,501 +0,0 @@
|
||||
version: 2
|
||||
jobs:
|
||||
install:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
environment:
|
||||
GOPATH: $HOME/go
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "yarn.lock" }}
|
||||
# fallback to using the latest cache if no exact match is found
|
||||
- v1-dependencies-
|
||||
- run:
|
||||
name: Updating apt packages
|
||||
command: sudo apt-get update
|
||||
- run:
|
||||
name: Installing the latest version of Go
|
||||
command: sudo apt-get install golang-go
|
||||
- run:
|
||||
name: Installing Dependencies
|
||||
command: yarn install --check-files --frozen-lockfile
|
||||
- save_cache:
|
||||
paths:
|
||||
- node_modules
|
||||
- packages/gatsby-plugin-now/node_modules
|
||||
- packages/now-build-utils/node_modules
|
||||
- packages/now-cgi/node_modules
|
||||
- packages/now-cli/node_modules
|
||||
- packages/now-client/node_modules
|
||||
- packages/now-go/node_modules
|
||||
- packages/now-next/node_modules
|
||||
- packages/now-node/node_modules
|
||||
- packages/now-node-bridge/node_modules
|
||||
- packages/now-python/node_modules
|
||||
- packages/now-routing-utils/node_modules
|
||||
- packages/now-ruby/node_modules
|
||||
- packages/now-static-build/node_modules
|
||||
key: v1-dependencies-{{ checksum "yarn.lock" }}
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- node_modules
|
||||
- packages/gatsby-plugin-now/node_modules
|
||||
- packages/now-build-utils/node_modules
|
||||
- packages/now-cgi/node_modules
|
||||
- packages/now-cli/node_modules
|
||||
- packages/now-client/node_modules
|
||||
- packages/now-go/node_modules
|
||||
- packages/now-next/node_modules
|
||||
- packages/now-node/node_modules
|
||||
- packages/now-node-bridge/node_modules
|
||||
- packages/now-python/node_modules
|
||||
- packages/now-routing-utils/node_modules
|
||||
- packages/now-ruby/node_modules
|
||||
- packages/now-static-build/node_modules
|
||||
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Linking dependencies
|
||||
command: yarn bootstrap
|
||||
- run:
|
||||
name: Building
|
||||
command: yarn build
|
||||
- store_artifacts:
|
||||
path: packages/now-cli/dist
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- packages/gatsby-plugin-now/test/fixtures
|
||||
- packages/now-build-utils/dist
|
||||
- packages/now-cgi/dist
|
||||
- packages/now-cli/dist
|
||||
- packages/now-cli/assets
|
||||
- packages/now-client/dist
|
||||
- packages/now-go/dist
|
||||
- packages/now-next/dist
|
||||
- packages/now-node/dist
|
||||
- packages/now-node/test/fixtures/15-helpers/ts/types.d.ts
|
||||
- packages/now-node/test/fixtures/11-symlinks/symlink
|
||||
- packages/now-node-bridge/index.js
|
||||
- packages/now-node-bridge/bridge.js
|
||||
- packages/now-python/dist
|
||||
- packages/now-routing-utils/dist
|
||||
- packages/now-ruby/dist
|
||||
- packages/now-static-build/dist
|
||||
- packages/now-static-build/test/fixtures/10a-gatsby-redirects/plugins
|
||||
|
||||
test-lint:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Compiling `now dev` HTML error templates
|
||||
command: node packages/now-cli/scripts/compile-templates.js
|
||||
- run:
|
||||
name: Linting Code
|
||||
command: yarn test-lint
|
||||
|
||||
test-integration-macos-node-8:
|
||||
macos:
|
||||
xcode: '9.0.1'
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Running Integration Tests
|
||||
command: yarn test-integration --clean false
|
||||
|
||||
test-integration-macos-node-10:
|
||||
macos:
|
||||
xcode: '10.0.0'
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Running Integration Tests
|
||||
command: yarn test-integration --clean false
|
||||
|
||||
test-integration-macos-node-12:
|
||||
macos:
|
||||
xcode: '10.3.0'
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Running Integration Tests
|
||||
command: yarn test-integration --clean false
|
||||
|
||||
test-integration-linux-node-8:
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Running Integration Tests
|
||||
command: yarn test-integration --clean false
|
||||
|
||||
test-integration-linux-node-10:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Running Integration Tests
|
||||
command: yarn test-integration --clean false
|
||||
|
||||
test-integration-linux-node-12:
|
||||
docker:
|
||||
- image: circleci/node:12
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Running Integration Tests
|
||||
command: yarn test-integration --clean false
|
||||
|
||||
test-integration-macos-now-dev-node-8:
|
||||
macos:
|
||||
xcode: '9.0.1'
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Downloading Hugo
|
||||
command: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run:
|
||||
name: Running Integration Tests for `now dev`
|
||||
command: yarn test-integration-now-dev --clean false
|
||||
|
||||
test-integration-macos-now-dev-node-10:
|
||||
macos:
|
||||
xcode: '10.0.0'
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Downloading Hugo
|
||||
command: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run:
|
||||
name: Running Integration Tests for `now dev`
|
||||
command: yarn test-integration-now-dev --clean false
|
||||
|
||||
test-integration-macos-now-dev-node-12:
|
||||
macos:
|
||||
xcode: '10.3.0'
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Downloading Hugo
|
||||
command: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run:
|
||||
name: Running Integration Tests for `now dev`
|
||||
command: yarn test-integration-now-dev --clean false
|
||||
|
||||
test-integration-linux-now-dev-node-8:
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Downloading Hugo
|
||||
command: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.55.6/hugo_0.55.6_Linux-64bit.tar.gz && tar -xzf hugo_0.55.6_Linux-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run:
|
||||
name: Running Integration Tests for `now dev`
|
||||
command: yarn test-integration-now-dev --clean false
|
||||
|
||||
test-integration-linux-now-dev-node-10:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Downloading Hugo
|
||||
command: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.55.6/hugo_0.55.6_Linux-64bit.tar.gz && tar -xzf hugo_0.55.6_Linux-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run:
|
||||
name: Running Integration Tests for `now dev`
|
||||
command: yarn test-integration-now-dev --clean false
|
||||
|
||||
test-integration-linux-now-dev-node-12:
|
||||
docker:
|
||||
- image: circleci/node:12
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Downloading Hugo
|
||||
command: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.55.6/hugo_0.55.6_Linux-64bit.tar.gz && tar -xzf hugo_0.55.6_Linux-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run:
|
||||
name: Running Integration Tests for `now dev`
|
||||
command: yarn test-integration-now-dev --clean false
|
||||
|
||||
test-integration-once:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Running Integration Tests Once
|
||||
command: yarn test-integration-once --clean false
|
||||
|
||||
test-unit:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Compiling `now dev` HTML error templates
|
||||
command: node packages/now-cli/scripts/compile-templates.js
|
||||
- run:
|
||||
name: Output version
|
||||
command: node --version
|
||||
- run:
|
||||
name: Running Unit Tests
|
||||
command: yarn test-unit --clean false
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- packages/now-cli/.nyc_output
|
||||
|
||||
coverage:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Run coverage report
|
||||
command: yarn workspace now run coverage
|
||||
|
||||
source-maps:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Installing Sentry CLI
|
||||
command: npm install -g @sentry/cli
|
||||
- run:
|
||||
name: Creating a New Sentry Release
|
||||
command: sentry-cli releases new now-cli@`git describe --tags`
|
||||
- run:
|
||||
name: Upload Sourcemap Files
|
||||
command: sentry-cli releases files now-cli@`git describe --tags` upload-sourcemaps ./dist
|
||||
- run:
|
||||
name: Finalize Sentry Release
|
||||
command: sentry-cli releases finalize now-cli@`git describe --tags`
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
unscheduled:
|
||||
jobs:
|
||||
- install:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- build:
|
||||
requires:
|
||||
- install
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-lint:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-macos-node-8:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-macos-node-10:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-macos-node-12:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-linux-node-8:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-linux-node-10:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-linux-node-12:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-macos-now-dev-node-8:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-macos-now-dev-node-10:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-macos-now-dev-node-12:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-linux-now-dev-node-8:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-linux-now-dev-node-10:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-linux-now-dev-node-12:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-integration-once:
|
||||
requires:
|
||||
- build
|
||||
- test-unit:
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- coverage:
|
||||
requires:
|
||||
- test-integration-macos-node-8
|
||||
- test-integration-macos-node-10
|
||||
- test-integration-macos-node-12
|
||||
- test-integration-linux-node-8
|
||||
- test-integration-linux-node-10
|
||||
- test-integration-linux-node-12
|
||||
- test-integration-macos-now-dev-node-8
|
||||
- test-integration-macos-now-dev-node-10
|
||||
- test-integration-macos-now-dev-node-12
|
||||
- test-integration-linux-now-dev-node-8
|
||||
- test-integration-linux-now-dev-node-10
|
||||
- test-integration-linux-now-dev-node-12
|
||||
- test-integration-once
|
||||
- test-unit
|
||||
- test-lint
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
9
.github/CODEOWNERS
vendored
9
.github/CODEOWNERS
vendored
@@ -2,6 +2,8 @@
|
||||
# https://help.github.com/en/articles/about-code-owners
|
||||
|
||||
* @tootallnate @leo
|
||||
/.github/workflows @AndyBitz @styfle
|
||||
/packages/frameworks @AndyBitz
|
||||
/packages/now-cli/src/commands/dev/ @tootallnate @leo @styfle @AndyBitz
|
||||
/packages/now-cli/src/util/dev/ @tootallnate @leo @styfle @AndyBitz
|
||||
/packages/now-cli/src/commands/domains/ @javivelasco @mglagola @anatrajkovska
|
||||
@@ -10,13 +12,16 @@
|
||||
/packages/now-build-utils @styfle @AndyBitz
|
||||
/packages/now-node @styfle @tootallnate @lucleray
|
||||
/packages/now-node-bridge @styfle @tootallnate @lucleray
|
||||
/packages/now-next @Timer
|
||||
/packages/now-next @Timer @ijjk
|
||||
/packages/now-go @styfle @sophearak
|
||||
/packages/now-python @styfle @sophearak
|
||||
/packages/now-ruby @styfle @coetry @nathancahill
|
||||
/packages/now-static-build @styfle @AndyBitz
|
||||
/packages/now-routing-utils @dav-is
|
||||
/packages/now-routing-utils @styfle @dav-is @ijjk
|
||||
|
||||
/examples @msweeneydev @timothyis
|
||||
/examples/create-react-app @Timer
|
||||
/examples/nextjs @timneutkens
|
||||
/examples/hugo @msweeneydev @timothyis @styfle
|
||||
/examples/jekyll @msweeneydev @timothyis @sarupbanskota
|
||||
/examples/zola @msweeneydev @timothyis @styfle
|
||||
|
||||
9
.github/CONTRIBUTING.md
vendored
9
.github/CONTRIBUTING.md
vendored
@@ -94,3 +94,12 @@ Sometimes you want to test changes to a Builder against an existing project, may
|
||||
4. Run `now *.tgz` to upload the tarball file and get a URL
|
||||
5. Edit any existing `now.json` project and replace `use` with the URL
|
||||
6. Run `now` or `now dev` to deploy with the experimental Builder
|
||||
|
||||
## Add a New Framework
|
||||
|
||||
You can add support for a new Framework by creating a Pull Request for this repository and following the steps below:
|
||||
|
||||
1. Add the Framework to the `@now/frameworks` package: The file is located in `packages/frameworks/frameworks.json`. You can copy the structure of an existing one and adjust the required fields. Note that the `settings` property either contains a `value` or a `placeholder`. The `value` property is used when something is not configurable, the `placeholder` is used when something is configurable and can be changed with configuration. An example would be the Output Directory for Hugo, it's `public` by default but can be changed through its config file, so we use `placeholder` with an explanation of what can be used.
|
||||
2. Add an example to the `examples/` directory: The name of the directory should equal the slug of the framework used in `@now/frameworks`. The `.github/EXAMPLE_README_TEMPLATE.md` file can be used to create a `README.md` file for the example.
|
||||
3. Update the `@now/static-build` package: The file `packages/now-static-build/src/frameworks.ts` has to be extended. You can add default routes that will always be applied to projects that use this Framework or specify some paths that will be cached to speed up the build process.
|
||||
4. After your Pull Request has been merged and released, other users can select the example on the ZEIT Now dashboard and deploy it.
|
||||
|
||||
17
.github/workflows/cancel.yml
vendored
Normal file
17
.github/workflows/cancel.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: Cancel
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
- '!master'
|
||||
jobs:
|
||||
cancel:
|
||||
name: 'Cancel Previous Runs'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 3
|
||||
steps:
|
||||
- uses: styfle/cancel-workflow-action@master
|
||||
with:
|
||||
workflow_id: 435869
|
||||
access_token: ${{ secrets.GITHUB_WORKFLOW_TOKEN }}
|
||||
|
||||
109
.github/workflows/continuous-integration.yml
vendored
Normal file
109
.github/workflows/continuous-integration.yml
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '!*'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test-unit:
|
||||
name: Unit Tests
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
node: [10, 12]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: git fetch origin master --depth=10
|
||||
- run: git fetch origin ${{ github.ref }} --depth=10
|
||||
- uses: actions/setup-node@v1
|
||||
- run: yarn install
|
||||
- run: yarn run build
|
||||
- run: yarn run test-lint
|
||||
- run: yarn run test-unit --clean false
|
||||
- name: Upload Artifact
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.node == 12 # only run once
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: test-unit-output
|
||||
path: packages/now-cli/.nyc_output
|
||||
|
||||
test-integration:
|
||||
name: Integration Tests
|
||||
timeout-minutes: 120
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: git fetch origin master --depth=10
|
||||
- run: git fetch origin ${{ github.ref }} --depth=10
|
||||
- run: yarn install
|
||||
- run: yarn run build
|
||||
- run: yarn test-integration-once --clean false
|
||||
|
||||
test-now-cli:
|
||||
name: Now CLI Tests
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node: [10, 12]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: git fetch origin master --depth=10
|
||||
- run: git fetch origin ${{ github.ref }} --depth=10
|
||||
- name: Install Hugo
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run: yarn install
|
||||
- run: yarn run build
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: yarn test-integration --clean false
|
||||
|
||||
test-now-dev:
|
||||
name: "`now dev` Tests"
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
node: [10, 12]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: git fetch origin master --depth=10
|
||||
- run: git fetch origin ${{ github.ref }} --depth=10
|
||||
- name: Install Hugo
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/now-cli/test/dev/fixtures/08-hugo/
|
||||
- run: yarn install
|
||||
- run: yarn run build
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: yarn test-integration-now-dev --clean false
|
||||
|
||||
coverage:
|
||||
name: Coverage
|
||||
timeout-minutes: 10
|
||||
needs: [test-unit, test-now-cli, test-now-dev, test-integration]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: git fetch origin master --depth=10
|
||||
- run: git fetch origin ${{ github.ref }} --depth=10
|
||||
- uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: test-unit-output
|
||||
path: packages/now-cli/.nyc_output
|
||||
- run: yarn install
|
||||
- run: yarn workspace now run coverage
|
||||
15
.github/workflows/publish.yml
vendored
15
.github/workflows/publish.yml
vendored
@@ -4,25 +4,18 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- canary
|
||||
tags:
|
||||
- '!*'
|
||||
|
||||
jobs:
|
||||
Publish:
|
||||
|
||||
publish:
|
||||
name: Publish
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Checkout
|
||||
uses: actions/setup-node@v1
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
node-version: 10
|
||||
- name: Install
|
||||
run: yarn install --check-files --frozen-lockfile
|
||||
- name: Build
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -22,4 +22,5 @@ packages/now-cli/test/fixtures/integration
|
||||
test/lib/deployment/failed-page.txt
|
||||
.DS_Store
|
||||
.next
|
||||
.env
|
||||
public
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||

|
||||

|
||||
|
||||
[](https://circleci.com/gh/zeit/workflows/now/tree/master)
|
||||
[](https://github.com/zeit/now/actions?workflow=CI)
|
||||
[](https://spectrum.chat/zeit)
|
||||
|
||||
## Usage
|
||||
@@ -16,7 +16,6 @@ To quickly start a new project, run the following commands:
|
||||
```
|
||||
now init # Pick an example project to clone
|
||||
cd <PROJECT> # Change directory to the newly created project
|
||||
now dev # Run locally during development
|
||||
now # Deploy to the cloud
|
||||
```
|
||||
|
||||
|
||||
9
api/_lib/util/assert-env.ts
Normal file
9
api/_lib/util/assert-env.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export function assertEnv(name: string) {
|
||||
const value = process.env[name];
|
||||
|
||||
if (!value) {
|
||||
throw new Error(`Missing env "${name}"`);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
43
api/_lib/util/error-handler.ts
Normal file
43
api/_lib/util/error-handler.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { init, captureException, withScope } from '@sentry/node';
|
||||
import { assertEnv } from './assert-env';
|
||||
|
||||
const serviceName = 'api-frameworks';
|
||||
|
||||
let sentryInitDone = false;
|
||||
|
||||
function initSentry() {
|
||||
if (sentryInitDone) {
|
||||
return;
|
||||
}
|
||||
|
||||
sentryInitDone = true;
|
||||
|
||||
init({
|
||||
dsn: assertEnv('SENTRY_DSN'),
|
||||
environment: process.env.NODE_ENV || 'production',
|
||||
release: `${serviceName}`,
|
||||
});
|
||||
}
|
||||
|
||||
export function errorHandler(error: Error, extras?: { [key: string]: any }) {
|
||||
if (!process.env.SENTRY_DSN) {
|
||||
return;
|
||||
}
|
||||
|
||||
initSentry();
|
||||
|
||||
try {
|
||||
withScope(scope => {
|
||||
scope.setTag('service', serviceName);
|
||||
scope.setTag('function_name', assertEnv('AWS_LAMBDA_FUNCTION_NAME'));
|
||||
|
||||
for (const [k, v] of Object.entries(extras)) {
|
||||
scope.setExtra(k, v);
|
||||
}
|
||||
|
||||
captureException(error);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`Failed to report error to Sentry: ${e}`);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NowRequest, NowResponse } from '@now/node';
|
||||
import { errorHandler } from './error-handler';
|
||||
|
||||
type Handler = (req: NowRequest, res: NowResponse) => Promise<any>;
|
||||
|
||||
@@ -24,6 +25,20 @@ export function withApiHandler(handler: Handler): Handler {
|
||||
});
|
||||
}
|
||||
|
||||
return handler(req, res);
|
||||
try {
|
||||
const result = await handler(req, res);
|
||||
return result;
|
||||
} catch (error) {
|
||||
errorHandler(error, {
|
||||
url: req.url,
|
||||
});
|
||||
|
||||
return res.status(500).json({
|
||||
error: {
|
||||
code: 'unexpected_error',
|
||||
message: 'An unexpected error occurred.',
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,17 +2,19 @@ import { NowRequest, NowResponse } from '@now/node';
|
||||
import { withApiHandler } from './_lib/util/with-api-handler';
|
||||
import frameworkList, { Framework } from '../packages/frameworks';
|
||||
|
||||
const frameworks: Framework[] = (frameworkList as Framework[]).map(
|
||||
framework => {
|
||||
delete framework.detectors;
|
||||
const frameworks = (frameworkList as Framework[]).map(frameworkItem => {
|
||||
const framework = {
|
||||
...frameworkItem,
|
||||
hasDetectors: Boolean(frameworkItem.detectors),
|
||||
detectors: undefined,
|
||||
};
|
||||
|
||||
if (framework.logo) {
|
||||
framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`;
|
||||
}
|
||||
|
||||
return framework;
|
||||
if (framework.logo) {
|
||||
framework.logo = `https://res.cloudinary.com/zeit-inc/image/fetch/${framework.logo}`;
|
||||
}
|
||||
);
|
||||
|
||||
return framework;
|
||||
});
|
||||
|
||||
export default withApiHandler(async function(
|
||||
req: NowRequest,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"build": "yarn --cwd .. && node ../utils/run.js build all"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/node": "5.11.1",
|
||||
"got": "10.2.1",
|
||||
"node-fetch": "2.6.0",
|
||||
"parse-github-url": "1.0.2",
|
||||
|
||||
119
api/yarn.lock
119
api/yarn.lock
@@ -9,6 +9,85 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@sentry/apm@5.11.1":
|
||||
version "5.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/apm/-/apm-5.11.1.tgz#cc89fa4150056fbf009f92eca94fccc3980db34e"
|
||||
integrity sha512-4iZH11p/7w9IMLT9hqNY1+EqLESltiIoF6/YsbpK93sXWGEs8VQ83IuvGuKWxajvHgDmj4ND0TxIliTsYqTqFw==
|
||||
dependencies:
|
||||
"@sentry/browser" "5.11.1"
|
||||
"@sentry/hub" "5.11.1"
|
||||
"@sentry/minimal" "5.11.1"
|
||||
"@sentry/types" "5.11.0"
|
||||
"@sentry/utils" "5.11.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/browser@5.11.1":
|
||||
version "5.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.11.1.tgz#337ffcb52711b23064c847a07629e966f54a5ebb"
|
||||
integrity sha512-oqOX/otmuP92DEGRyZeBuQokXdeT9HQRxH73oqIURXXNLMP3PWJALSb4HtT4AftEt/2ROGobZLuA4TaID6My/Q==
|
||||
dependencies:
|
||||
"@sentry/core" "5.11.1"
|
||||
"@sentry/types" "5.11.0"
|
||||
"@sentry/utils" "5.11.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/core@5.11.1":
|
||||
version "5.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.11.1.tgz#9e2da485e196ae32971545c1c49ee6fe719930e2"
|
||||
integrity sha512-BpvPosVNT20Xso4gAV54Lu3KqDmD20vO63HYwbNdST5LUi8oYV4JhvOkoBraPEM2cbBwQvwVcFdeEYKk4tin9A==
|
||||
dependencies:
|
||||
"@sentry/hub" "5.11.1"
|
||||
"@sentry/minimal" "5.11.1"
|
||||
"@sentry/types" "5.11.0"
|
||||
"@sentry/utils" "5.11.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/hub@5.11.1":
|
||||
version "5.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.11.1.tgz#ddcb865563fae53852d405885c46b4c6de68a91b"
|
||||
integrity sha512-ucKprYCbGGLLjVz4hWUqHN9KH0WKUkGf5ZYfD8LUhksuobRkYVyig0ZGbshECZxW5jcDTzip4Q9Qimq/PkkXBg==
|
||||
dependencies:
|
||||
"@sentry/types" "5.11.0"
|
||||
"@sentry/utils" "5.11.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/minimal@5.11.1":
|
||||
version "5.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.11.1.tgz#0e705d01a567282d8fbbda2aed848b4974cc3cec"
|
||||
integrity sha512-HK8zs7Pgdq7DsbZQTThrhQPrJsVWzz7MaluAbQA0rTIAJ3TvHKQpsVRu17xDpjZXypqWcKCRsthDrC4LxDM1Bg==
|
||||
dependencies:
|
||||
"@sentry/hub" "5.11.1"
|
||||
"@sentry/types" "5.11.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/node@5.11.1":
|
||||
version "5.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.11.1.tgz#2a9c18cd1209cfdf7a69b9d91303413149d2c910"
|
||||
integrity sha512-FbJs0blJ36gEzE0rc2yBfA/KE+kXOLl8MUfFTcyJCBdCGF8XMETDCmgINnJ4TyBUJviwKoPw2TCk9TL2pa/A1w==
|
||||
dependencies:
|
||||
"@sentry/apm" "5.11.1"
|
||||
"@sentry/core" "5.11.1"
|
||||
"@sentry/hub" "5.11.1"
|
||||
"@sentry/types" "5.11.0"
|
||||
"@sentry/utils" "5.11.1"
|
||||
cookie "^0.3.1"
|
||||
https-proxy-agent "^4.0.0"
|
||||
lru_map "^0.3.3"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/types@5.11.0":
|
||||
version "5.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.11.0.tgz#40f0f3174362928e033ddd9725d55e7c5cb7c5b6"
|
||||
integrity sha512-1Uhycpmeo1ZK2GLvrtwZhTwIodJHcyIS6bn+t4IMkN9MFoo6ktbAfhvexBDW/IDtdLlCGJbfm8nIZerxy0QUpg==
|
||||
|
||||
"@sentry/utils@5.11.1":
|
||||
version "5.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.11.1.tgz#aa19fcc234cf632257b2281261651d2fac967607"
|
||||
integrity sha512-O0Zl4R2JJh8cTkQ8ZL2cDqGCmQdpA5VeXpuBbEl1v78LQPkBDISi35wH4mKmLwMsLBtTVpx2UeUHBj0KO5aLlA==
|
||||
dependencies:
|
||||
"@sentry/types" "5.11.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sindresorhus/is@^1.0.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-1.2.0.tgz#63ce3638cb85231f3704164c90a18ef816da3fb7"
|
||||
@@ -62,6 +141,11 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
agent-base@5:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
|
||||
integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==
|
||||
|
||||
binary@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79"
|
||||
@@ -121,6 +205,18 @@ clone-response@^1.0.2:
|
||||
dependencies:
|
||||
mimic-response "^1.0.0"
|
||||
|
||||
cookie@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
|
||||
integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
|
||||
|
||||
debug@4:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
|
||||
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
decompress-response@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-5.0.0.tgz#7849396e80e3d1eba8cb2f75ef4930f76461cb0f"
|
||||
@@ -182,6 +278,14 @@ http-cache-semantics@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz#495704773277eeef6e43f9ab2c2c7d259dda25c5"
|
||||
integrity sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew==
|
||||
|
||||
https-proxy-agent@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b"
|
||||
integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==
|
||||
dependencies:
|
||||
agent-base "5"
|
||||
debug "4"
|
||||
|
||||
inherits@^2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
@@ -204,6 +308,11 @@ lowercase-keys@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
|
||||
integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
|
||||
|
||||
lru_map@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd"
|
||||
integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=
|
||||
|
||||
mimic-response@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
|
||||
@@ -226,6 +335,11 @@ mkdirp@^0.5.1:
|
||||
dependencies:
|
||||
minimist "0.0.8"
|
||||
|
||||
ms@^2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
node-fetch@2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
|
||||
@@ -320,6 +434,11 @@ to-readable-stream@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9"
|
||||
integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=
|
||||
|
||||
tslib@^1.9.3:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
|
||||
|
||||
type-fest@^0.8.0:
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Angular Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# React Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Docusaurus Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Eleventy Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Ember Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Gatsby Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Gridsome Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Hexo Example
|
||||
|
||||
|
||||
2
examples/hugo/themes/ananke/.gitignore
vendored
2
examples/hugo/themes/ananke/.gitignore
vendored
@@ -28,3 +28,5 @@ npm-debug.log
|
||||
|
||||
/junit.xml
|
||||
partials/structure/stylesheet.html
|
||||
|
||||
!dist
|
||||
|
||||
3
examples/hugo/themes/ananke/static/dist/css/app.d98f2eb6bcd1eaedb7edf166bd16af26.css
vendored
Normal file
3
examples/hugo/themes/ananke/static/dist/css/app.d98f2eb6bcd1eaedb7edf166bd16af26.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
examples/hugo/themes/ananke/static/dist/js/app.3fc0f988d21662902933.js
vendored
Normal file
1
examples/hugo/themes/ananke/static/dist/js/app.3fc0f988d21662902933.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(n){function t(e){if(r[e])return r[e].exports;var o=r[e]={i:e,l:!1,exports:{}};return n[e].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var r={};t.m=n,t.c=r,t.i=function(n){return n},t.d=function(n,r,e){t.o(n,r)||Object.defineProperty(n,r,{configurable:!1,enumerable:!0,get:e})},t.n=function(n){var r=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(r,"a",r),r},t.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},t.p="",t(t.s=1)}([function(n,t){},function(n,t,r){"use strict";var e=r(0);!function(n){n&&n.__esModule}(e)}]);
|
||||
@@ -1,15 +0,0 @@
|
||||
# http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
||||
67
examples/ionic-react/.gitignore
vendored
67
examples/ionic-react/.gitignore
vendored
@@ -1,67 +1,9 @@
|
||||
# Logs
|
||||
.firebase
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
@@ -75,9 +17,8 @@ typings/
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.vscode
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
.stencil/
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"appId": "io.ionic.starter",
|
||||
"appName": "ionic-react-conference-app",
|
||||
"bundledWebRuntime": false,
|
||||
"npmClient": "npm",
|
||||
"webDir": "build",
|
||||
"cordova": {}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
{
|
||||
"name": "ionic-react-conference-app",
|
||||
"integrations": {
|
||||
"capacitor": {}
|
||||
},
|
||||
"name": "ionic-react",
|
||||
"integrations": {},
|
||||
"type": "react"
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
{
|
||||
"name": "ionic-react-conference-app",
|
||||
"name": "ionic-react",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@capacitor/core": "1.3.0",
|
||||
"@ionic/react": "^4.11.4",
|
||||
"@ionic/react-router": "^4.11.4",
|
||||
"@types/jest": "24.0.18",
|
||||
"@types/node": "12.7.5",
|
||||
"@types/react": "^16.9.2",
|
||||
"@types/react-dom": "^16.9.0",
|
||||
"@types/react-router": "^5.0.3",
|
||||
"@types/react-router-dom": "^4.3.1",
|
||||
"date-fns": "^2.6.0",
|
||||
"@ionic/react": "^4.11.0",
|
||||
"@ionic/react-router": "^4.11.0",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.4.0",
|
||||
"@testing-library/user-event": "^8.0.3",
|
||||
"@types/jest": "^24.0.25",
|
||||
"@types/node": "^12.12.24",
|
||||
"@types/react": "^16.9.17",
|
||||
"@types/react-dom": "^16.9.4",
|
||||
"@types/react-router": "^5.1.4",
|
||||
"@types/react-router-dom": "^5.1.3",
|
||||
"ionicons": "^4.6.3",
|
||||
"node-sass": "^4.13.0",
|
||||
"react": "^16.9.0",
|
||||
"react-dom": "^16.9.0",
|
||||
"react-router": "^5.0.1",
|
||||
"react-router-dom": "^5.0.1",
|
||||
"react-scripts": "3.2.0",
|
||||
"reselect": "^4.0.0"
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-router": "^5.1.2",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-scripts": "3.3.0",
|
||||
"typescript": "3.7.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
@@ -28,17 +28,20 @@
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all"
|
||||
],
|
||||
"description": "An Ionic project",
|
||||
"devDependencies": {
|
||||
"@capacitor/cli": "1.3.0",
|
||||
"@testing-library/react": "^9.3.1",
|
||||
"@types/googlemaps": "^3.38.0",
|
||||
"typescript": "3.6.3"
|
||||
}
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"description": "An Ionic project"
|
||||
}
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { render } from '@testing-library/react';
|
||||
import App from './App';
|
||||
import { render, fireEvent, waitForElement } from '@testing-library/react'
|
||||
|
||||
it('renders without crashing', () => {
|
||||
// const div = document.createElement('div');
|
||||
// ReactDOM.render(<App />, div);
|
||||
// ReactDOM.unmountComponentAtNode(div);
|
||||
const { asFragment, container } = render(<App />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
test('renders without crashing', () => {
|
||||
const { baseElement } = render(<App />);
|
||||
expect(baseElement).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import { Redirect, Route } from 'react-router-dom';
|
||||
import { IonApp, IonRouterOutlet, IonSplitPane } from '@ionic/react';
|
||||
import { IonApp, IonRouterOutlet } from '@ionic/react';
|
||||
import { IonReactRouter } from '@ionic/react-router';
|
||||
|
||||
import Menu from './components/Menu';
|
||||
import Home from './pages/Home';
|
||||
|
||||
/* Core CSS required for Ionic components to work properly */
|
||||
import '@ionic/react/css/core.css';
|
||||
@@ -23,85 +22,16 @@ import '@ionic/react/css/display.css';
|
||||
|
||||
/* Theme variables */
|
||||
import './theme/variables.css';
|
||||
import MainTabs from './pages/MainTabs';
|
||||
import { connect } from './data/connect';
|
||||
import { AppContextProvider } from './data/AppContext';
|
||||
import { loadConfData } from './data/sessions/sessions.actions';
|
||||
import { setIsLoggedIn, setUsername, loadUserData } from './data/user/user.actions';
|
||||
import Account from './pages/Account';
|
||||
import Login from './pages/Login';
|
||||
import Signup from './pages/Signup';
|
||||
import Support from './pages/Support';
|
||||
import Tutorial from './pages/Tutorial';
|
||||
import HomeOrTutorial from './components/HomeOrTutorial';
|
||||
import { Session } from "./models/Session";
|
||||
|
||||
const App: React.FC = () => {
|
||||
return (
|
||||
<AppContextProvider>
|
||||
<IonicAppConnected />
|
||||
</AppContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
interface StateProps {
|
||||
darkMode: boolean,
|
||||
sessions: Session[],
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
loadConfData: typeof loadConfData;
|
||||
loadUserData: typeof loadUserData;
|
||||
setIsLoggedIn: typeof setIsLoggedIn;
|
||||
setUsername: typeof setUsername;
|
||||
}
|
||||
|
||||
interface IonicAppProps extends StateProps, DispatchProps { }
|
||||
|
||||
const IonicApp: React.FC<IonicAppProps> = ({ darkMode, sessions, setIsLoggedIn, setUsername, loadConfData, loadUserData }) => {
|
||||
|
||||
useEffect(() => {
|
||||
loadUserData();
|
||||
loadConfData();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
return (
|
||||
sessions.length === 0 ? (
|
||||
<div></div>
|
||||
) : (
|
||||
<IonApp className={`${darkMode ? 'dark-theme' : ''}`}>
|
||||
<IonReactRouter>
|
||||
<IonSplitPane contentId="main">
|
||||
<Menu />
|
||||
<IonRouterOutlet id="main">
|
||||
<Route path="/tabs" component={MainTabs} />
|
||||
<Route path="/account" component={Account} />
|
||||
<Route path="/login" component={Login} />
|
||||
<Route path="/signup" component={Signup} />
|
||||
<Route path="/support" component={Support} />
|
||||
<Route path="/tutorial" component={Tutorial} />
|
||||
<Route path="/logout" render={() => {
|
||||
setIsLoggedIn(false);
|
||||
setUsername(undefined);
|
||||
return <Redirect to="/tabs" />
|
||||
}} />
|
||||
<Route path="/" component={HomeOrTutorial} exact />
|
||||
</IonRouterOutlet>
|
||||
</IonSplitPane>
|
||||
</IonReactRouter>
|
||||
</IonApp>
|
||||
)
|
||||
)
|
||||
}
|
||||
const App: React.FC = () => (
|
||||
<IonApp>
|
||||
<IonReactRouter>
|
||||
<IonRouterOutlet>
|
||||
<Route path="/home" component={Home} exact={true} />
|
||||
<Route exact path="/" render={() => <Redirect to="/home" />} />
|
||||
</IonRouterOutlet>
|
||||
</IonReactRouter>
|
||||
</IonApp>
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
||||
const IonicAppConnected = connect<{}, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
darkMode: state.user.darkMode,
|
||||
sessions: state.data.sessions
|
||||
}),
|
||||
mapDispatchToProps: { loadConfData, loadUserData, setIsLoggedIn, setUsername },
|
||||
component: IonicApp
|
||||
});
|
||||
|
||||
@@ -1,280 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders without crashing 1`] = `
|
||||
<DocumentFragment>
|
||||
<ion-app>
|
||||
<ion-split-pane
|
||||
content-id="main"
|
||||
>
|
||||
<ion-menu
|
||||
content-id="main"
|
||||
>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>
|
||||
Menu
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content
|
||||
class="outer-content"
|
||||
>
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
Navigate
|
||||
</ion-list-header>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
Schedule
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
Speakers
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
Map
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
About
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
</ion-list>
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
Account
|
||||
</ion-list-header>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
Account
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
Support
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
<ion-menu-toggle
|
||||
auto-hide="false"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
<ion-label>
|
||||
Logout
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
</ion-list>
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
Tutorial
|
||||
</ion-list-header>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
/>
|
||||
Show Tutorial
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
</ion-menu>
|
||||
<ion-router-outlet
|
||||
id="main"
|
||||
>
|
||||
<div
|
||||
style="display: flex; position: absolute; top: 0px; left: 0px; right: 0px; bottom: 0px; flex-direction: column; width: 100%; height: 100%; contain: layout size style;"
|
||||
>
|
||||
<div
|
||||
class="tabs-inner"
|
||||
style="position: relative; flex: 1; contain: layout size style;"
|
||||
>
|
||||
<ion-router-outlet>
|
||||
<div
|
||||
class="ion-page ion-page-invisible"
|
||||
>
|
||||
<ion-header>
|
||||
<ion-toolbar
|
||||
color="primary"
|
||||
>
|
||||
<ion-buttons
|
||||
slot="start"
|
||||
>
|
||||
<ion-menu-button />
|
||||
</ion-buttons>
|
||||
<ion-segment>
|
||||
<ion-segment-button
|
||||
value="all"
|
||||
>
|
||||
All
|
||||
</ion-segment-button>
|
||||
<ion-segment-button
|
||||
value="favorites"
|
||||
>
|
||||
Favorites
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
<ion-buttons
|
||||
slot="end"
|
||||
>
|
||||
<ion-button>
|
||||
<ion-icon
|
||||
slot="icon-only"
|
||||
/>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar
|
||||
color="primary"
|
||||
>
|
||||
<ion-searchbar
|
||||
placeholder="Search"
|
||||
/>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher
|
||||
slot="fixed"
|
||||
>
|
||||
<ion-refresher-content />
|
||||
</ion-refresher>
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
No Sessions Found
|
||||
</ion-list-header>
|
||||
</ion-list>
|
||||
<ion-list
|
||||
style="display: none;"
|
||||
/>
|
||||
</ion-content>
|
||||
<ion-fab
|
||||
horizontal="end"
|
||||
slot="fixed"
|
||||
vertical="bottom"
|
||||
>
|
||||
<ion-fab-button>
|
||||
<ion-icon />
|
||||
</ion-fab-button>
|
||||
<ion-fab-list
|
||||
side="top"
|
||||
>
|
||||
<ion-fab-button
|
||||
color="vimeo"
|
||||
>
|
||||
<ion-icon />
|
||||
</ion-fab-button>
|
||||
<ion-fab-button
|
||||
color="google"
|
||||
>
|
||||
<ion-icon />
|
||||
</ion-fab-button>
|
||||
<ion-fab-button
|
||||
color="twitter"
|
||||
>
|
||||
<ion-icon />
|
||||
</ion-fab-button>
|
||||
<ion-fab-button
|
||||
color="facebook"
|
||||
>
|
||||
<ion-icon />
|
||||
</ion-fab-button>
|
||||
</ion-fab-list>
|
||||
</ion-fab>
|
||||
</div>
|
||||
</ion-router-outlet>
|
||||
</div>
|
||||
<ion-tab-bar
|
||||
current-path="/tabs/schedule"
|
||||
selected-tab="schedule"
|
||||
slot="bottom"
|
||||
>
|
||||
<ion-tab-button
|
||||
href="/tabs/schedule"
|
||||
tab="schedule"
|
||||
>
|
||||
<ion-icon />
|
||||
<ion-label>
|
||||
Schedule
|
||||
</ion-label>
|
||||
</ion-tab-button>
|
||||
<ion-tab-button
|
||||
href="/tabs/speakers"
|
||||
tab="speakers"
|
||||
>
|
||||
<ion-icon />
|
||||
<ion-label>
|
||||
Speakers
|
||||
</ion-label>
|
||||
</ion-tab-button>
|
||||
<ion-tab-button
|
||||
href="/tabs/map"
|
||||
tab="map"
|
||||
>
|
||||
<ion-icon />
|
||||
<ion-label>
|
||||
Map
|
||||
</ion-label>
|
||||
</ion-tab-button>
|
||||
<ion-tab-button
|
||||
href="/tabs/about"
|
||||
tab="about"
|
||||
>
|
||||
<ion-icon />
|
||||
<ion-label>
|
||||
About
|
||||
</ion-label>
|
||||
</ion-tab-button>
|
||||
</ion-tab-bar>
|
||||
</div>
|
||||
</ion-router-outlet>
|
||||
</ion-split-pane>
|
||||
</ion-app>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import { IonList, IonItem, IonLabel } from '@ionic/react';
|
||||
|
||||
interface AboutPopoverProps {
|
||||
dismiss: () => void;
|
||||
};
|
||||
|
||||
const AboutPopover: React.FC<AboutPopoverProps> = ({dismiss}) => {
|
||||
|
||||
const close = (url: string) => {
|
||||
window.open(url, '_blank');
|
||||
dismiss();
|
||||
};
|
||||
|
||||
return (
|
||||
<IonList>
|
||||
<IonItem button onClick={() => close('https://ionicframework.com/getting-started')}>
|
||||
<IonLabel>Learn Ionic</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem button onClick={() => close('https://ionicframework.com/docs/react')}>
|
||||
<IonLabel>Documentation</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem button onClick={() => close('https://showcase.ionicframework.com')}>
|
||||
<IonLabel>Showcase</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem button onClick={() => close('https://github.com/ionic-team/ionic')}>
|
||||
<IonLabel>GitHub Repo</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem button onClick={dismiss}>
|
||||
<IonLabel>Support</IonLabel>
|
||||
</IonItem>
|
||||
</IonList >
|
||||
)
|
||||
}
|
||||
|
||||
export default AboutPopover;
|
||||
@@ -1,18 +0,0 @@
|
||||
import React from 'react';
|
||||
import { connect } from '../data/connect';
|
||||
import { Redirect } from 'react-router';
|
||||
|
||||
interface StateProps {
|
||||
hasSeenTutorial: boolean;
|
||||
}
|
||||
|
||||
const HomeOrTutorial: React.FC<StateProps> = ({ hasSeenTutorial }) => {
|
||||
return hasSeenTutorial ? <Redirect to="/tabs/schedule" /> : <Redirect to="/tutorial" />
|
||||
};
|
||||
|
||||
export default connect<{}, StateProps, {}>({
|
||||
mapStateToProps: (state) => ({
|
||||
hasSeenTutorial: state.user.hasSeenTutorial
|
||||
}),
|
||||
component: HomeOrTutorial
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import { Location } from '../models/Location';
|
||||
|
||||
interface MapProps {
|
||||
locations: Location[]
|
||||
mapCenter: Location
|
||||
}
|
||||
|
||||
const Map: React.FC<MapProps> = ({ mapCenter, locations }) => {
|
||||
const mapEle = useRef<HTMLDivElement>(null);
|
||||
const map = useRef<google.maps.Map>();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
map.current = new google.maps.Map(mapEle.current, {
|
||||
center: {
|
||||
lat: mapCenter.lat,
|
||||
lng: mapCenter.lng
|
||||
},
|
||||
zoom: 16
|
||||
});
|
||||
|
||||
addMarkers();
|
||||
|
||||
google.maps.event.addListenerOnce(map.current, 'idle', () => {
|
||||
if (mapEle.current) {
|
||||
mapEle.current.classList.add('show-map');
|
||||
}
|
||||
});
|
||||
|
||||
function addMarkers() {
|
||||
locations.forEach((markerData) => {
|
||||
let infoWindow = new google.maps.InfoWindow({
|
||||
content: `<h5>${markerData.name}</h5>`
|
||||
});
|
||||
|
||||
let marker = new google.maps.Marker({
|
||||
position: new google.maps.LatLng(markerData.lat, markerData.lng),
|
||||
map: map.current!,
|
||||
title: markerData.name
|
||||
});
|
||||
|
||||
marker.addListener('click', () => {
|
||||
infoWindow.open(map.current!, marker);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}, [mapCenter, locations]);
|
||||
|
||||
return (
|
||||
<div ref={mapEle} className="map-canvas"></div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Map;
|
||||
@@ -1,119 +0,0 @@
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonListHeader,
|
||||
IonMenu,
|
||||
IonMenuToggle,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonToggle
|
||||
} from '@ionic/react';
|
||||
import { calendar, contacts, hammer, help, informationCircle, logIn, logOut, map, person, personAdd } from 'ionicons/icons';
|
||||
import React, { useState } from 'react';
|
||||
import { connect } from '../data/connect';
|
||||
import { RouteComponentProps, withRouter } from 'react-router';
|
||||
import { setDarkMode } from '../data/user/user.actions';
|
||||
|
||||
const routes = {
|
||||
appPages: [
|
||||
{ title: 'Schedule', path: '/tabs/schedule', icon: calendar },
|
||||
{ title: 'Speakers', path: '/tabs/speakers', icon: contacts },
|
||||
{ title: 'Map', path: '/tabs/map', icon: map },
|
||||
{ title: 'About', path: '/tabs/about', icon: informationCircle }
|
||||
],
|
||||
loggedInPages: [
|
||||
{ title: 'Account', path: '/account', icon: person },
|
||||
{ title: 'Support', path: '/support', icon: help },
|
||||
{ title: 'Logout', path: '/logout', icon: logOut }
|
||||
],
|
||||
loggedOutPages: [
|
||||
{ title: 'Login', path: '/login', icon: logIn },
|
||||
{ title: 'Support', path: '/support', icon: help },
|
||||
{ title: 'Signup', path: '/signup', icon: personAdd }
|
||||
]
|
||||
};
|
||||
|
||||
interface Pages {
|
||||
title: string,
|
||||
path: string,
|
||||
icon: { ios: string, md: string },
|
||||
routerDirection?: string
|
||||
}
|
||||
interface StateProps {
|
||||
darkMode: boolean;
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
setDarkMode: typeof setDarkMode
|
||||
}
|
||||
|
||||
interface MenuProps extends RouteComponentProps, StateProps, DispatchProps { }
|
||||
|
||||
const Menu: React.FC<MenuProps> = ({ darkMode, history, isAuthenticated, setDarkMode }) => {
|
||||
const [disableMenu, setDisableMenu] = useState(false);
|
||||
|
||||
function renderlistItems(list: Pages[]) {
|
||||
return list
|
||||
.filter(route => !!route.path)
|
||||
.map(p => (
|
||||
<IonMenuToggle key={p.title} auto-hide="false">
|
||||
<IonItem button routerLink={p.path} routerDirection="none">
|
||||
<IonIcon slot="start" icon={p.icon} />
|
||||
<IonLabel>{p.title}</IonLabel>
|
||||
</IonItem>
|
||||
</IonMenuToggle>
|
||||
));
|
||||
}
|
||||
|
||||
return (
|
||||
<IonMenu type="overlay" disabled={disableMenu} contentId="main">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Menu</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent class="outer-content">
|
||||
<IonList>
|
||||
<IonListHeader>Navigate</IonListHeader>
|
||||
{renderlistItems(routes.appPages)}
|
||||
</IonList>
|
||||
<IonList>
|
||||
<IonListHeader>Account</IonListHeader>
|
||||
{isAuthenticated ? renderlistItems(routes.loggedInPages) : renderlistItems(routes.loggedOutPages)}
|
||||
</IonList>
|
||||
<IonList>
|
||||
<IonListHeader>Tutorial</IonListHeader>
|
||||
<IonItem onClick={() => {
|
||||
setDisableMenu(true);
|
||||
history.push('/tutorial');
|
||||
}}>
|
||||
<IonIcon slot="start" icon={hammer} />
|
||||
Show Tutorial
|
||||
</IonItem>
|
||||
</IonList>
|
||||
<IonList>
|
||||
<IonItem>
|
||||
<IonLabel>Dark Theme</IonLabel>
|
||||
<IonToggle checked={darkMode} onClick={() => setDarkMode(!darkMode)} />
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</IonMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<{}, StateProps, {}>({
|
||||
mapStateToProps: (state) => ({
|
||||
darkMode: state.user.darkMode,
|
||||
isAuthenticated: state.user.isLoggedin
|
||||
}),
|
||||
mapDispatchToProps: ({
|
||||
setDarkMode
|
||||
}),
|
||||
component: withRouter(Menu)
|
||||
})
|
||||
@@ -1,92 +0,0 @@
|
||||
import { IonItemDivider, IonItemGroup, IonLabel, IonList, IonListHeader, IonAlert, AlertButton } from '@ionic/react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { Session } from '../models/Session';
|
||||
import SessionListItem from './SessionListItem';
|
||||
import { SessionGroup } from '../models/SessionGroup';
|
||||
import { Time } from '../components/Time';
|
||||
import { connect } from '../data/connect';
|
||||
import { addFavorite, removeFavorite } from '../data/sessions/sessions.actions';
|
||||
|
||||
interface OwnProps {
|
||||
sessionGroups: SessionGroup[]
|
||||
listType: 'all' | 'favorites'
|
||||
hide: boolean;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
favoriteSessions: number[];
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
addFavorite: typeof addFavorite;
|
||||
removeFavorite: typeof removeFavorite;
|
||||
}
|
||||
|
||||
interface SessionListProps extends OwnProps, StateProps, DispatchProps { };
|
||||
|
||||
const SessionList: React.FC<SessionListProps> = ({ addFavorite, removeFavorite, favoriteSessions, hide, sessionGroups, listType }) => {
|
||||
|
||||
const [showAlert, setShowAlert] = useState(false);
|
||||
const [alertHeader, setAlertHeader] = useState('');
|
||||
const [alertButtons, setAlertButtons] = useState<(AlertButton | string)[]>([]);
|
||||
|
||||
const handleShowAlert = useCallback((header: string, buttons: AlertButton[]) => {
|
||||
setAlertHeader(header);
|
||||
setAlertButtons(buttons);
|
||||
setShowAlert(true);
|
||||
}, []);
|
||||
|
||||
if (sessionGroups.length === 0 && !hide) {
|
||||
return (
|
||||
<IonList>
|
||||
<IonListHeader>
|
||||
No Sessions Found
|
||||
</IonListHeader>
|
||||
</IonList>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonList style={hide ? { display: 'none' } : {}}>
|
||||
{sessionGroups.map((group, index: number) => (
|
||||
<IonItemGroup key={`group-${index}`}>
|
||||
<IonItemDivider sticky>
|
||||
<IonLabel>
|
||||
<Time date={group.startTime} />
|
||||
</IonLabel>
|
||||
</IonItemDivider>
|
||||
{group.sessions.map((session: Session, sessionIndex: number) => (
|
||||
<SessionListItem
|
||||
onShowAlert={handleShowAlert}
|
||||
isFavorite={favoriteSessions.indexOf(session.id) > -1}
|
||||
onAddFavorite={addFavorite}
|
||||
onRemoveFavorite={removeFavorite}
|
||||
key={`group-${index}-${sessionIndex}`}
|
||||
session={session}
|
||||
listType={listType}
|
||||
/>
|
||||
))}
|
||||
</IonItemGroup>
|
||||
))}
|
||||
</IonList>
|
||||
<IonAlert
|
||||
isOpen={showAlert}
|
||||
header={alertHeader}
|
||||
buttons={alertButtons}
|
||||
onDidDismiss={() => setShowAlert(false)}
|
||||
></IonAlert>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect({
|
||||
mapStateToProps: (state) => ({
|
||||
favoriteSessions: state.data.favorites
|
||||
}),
|
||||
mapDispatchToProps: ({
|
||||
addFavorite,
|
||||
removeFavorite
|
||||
}),
|
||||
component: SessionList
|
||||
});
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
.filter-icon {
|
||||
margin: 7px 16px 7px 0;
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
import React from 'react';
|
||||
import { IonHeader, IonToolbar, IonButtons, IonButton, IonTitle, IonContent, IonList, IonListHeader, IonItem, IonLabel, IonToggle, IonFooter, IonIcon } from '@ionic/react';
|
||||
import { logoAngular, call, document, logoIonic, hammer, restaurant, cog, colorPalette, construct, compass } from 'ionicons/icons';
|
||||
import './SessionListFilter.css'
|
||||
import { connect } from '../data/connect';
|
||||
import { updateFilteredTracks } from '../data/sessions/sessions.actions';
|
||||
|
||||
interface OwnProps {
|
||||
onDismissModal: () => void;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
allTracks: string[],
|
||||
filteredTracks: string[]
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
updateFilteredTracks: typeof updateFilteredTracks;
|
||||
}
|
||||
|
||||
type SessionListFilterProps = OwnProps & StateProps & DispatchProps;
|
||||
|
||||
const SessionListFilter: React.FC<SessionListFilterProps> = ({ allTracks, filteredTracks, onDismissModal, updateFilteredTracks }) => {
|
||||
|
||||
const toggleTrackFilter = (track: string) => {
|
||||
if (filteredTracks.indexOf(track) > -1) {
|
||||
updateFilteredTracks(filteredTracks.filter(x => x !== track));
|
||||
} else {
|
||||
updateFilteredTracks([...filteredTracks, track]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeselectAll = () => {
|
||||
updateFilteredTracks([]);
|
||||
};
|
||||
|
||||
const handleSelectAll = () => {
|
||||
updateFilteredTracks([...allTracks]);
|
||||
};
|
||||
|
||||
const iconMap: { [key: string]: any } = {
|
||||
'Angular': logoAngular,
|
||||
'Documentation': document,
|
||||
'Food': restaurant,
|
||||
'Ionic': logoIonic,
|
||||
'Tooling': hammer,
|
||||
'Design': colorPalette,
|
||||
'Services': cog,
|
||||
'Workshop': construct,
|
||||
'Navigation': compass,
|
||||
'Communication': call
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>
|
||||
Filter Sessions
|
||||
</IonTitle>
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={onDismissModal} strong>Done</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent class="outer-content">
|
||||
<IonList>
|
||||
<IonListHeader>Tracks</IonListHeader>
|
||||
{allTracks.map((track, index) => (
|
||||
<IonItem key={track}>
|
||||
<IonIcon className="filter-icon" icon={iconMap[track]} color="medium" />
|
||||
<IonLabel>{track}</IonLabel>
|
||||
<IonToggle
|
||||
onClick={() => toggleTrackFilter(track)}
|
||||
checked={filteredTracks.indexOf(track) !== -1}
|
||||
color="success"
|
||||
value={track}
|
||||
></IonToggle>
|
||||
</IonItem>
|
||||
))}
|
||||
</IonList>
|
||||
</IonContent>
|
||||
|
||||
<IonFooter>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={handleDeselectAll}>Deselect All</IonButton>
|
||||
</IonButtons>
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={handleSelectAll}>Select All</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonFooter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
allTracks: state.data.allTracks,
|
||||
filteredTracks: state.data.filteredTracks
|
||||
}),
|
||||
mapDispatchToProps: {
|
||||
updateFilteredTracks
|
||||
},
|
||||
component: SessionListFilter
|
||||
})
|
||||
@@ -1,83 +0,0 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { IonItemSliding, IonAlert, IonItem, IonLabel, IonItemOptions, IonItemOption, AlertButton } from '@ionic/react';
|
||||
import { Time } from './Time';
|
||||
import { Session } from '../models/Session';
|
||||
|
||||
interface SessionListItemProps {
|
||||
session: Session;
|
||||
listType: "all" | "favorites";
|
||||
onAddFavorite: (id: number) => void;
|
||||
onRemoveFavorite: (id: number) => void;
|
||||
onShowAlert: (header: string, buttons: AlertButton[]) => void;
|
||||
isFavorite: boolean;
|
||||
}
|
||||
|
||||
const SessionListItem: React.FC<SessionListItemProps> = ({ isFavorite, onAddFavorite, onRemoveFavorite, onShowAlert, session, listType }) => {
|
||||
const ionItemSlidingRef = useRef<HTMLIonItemSlidingElement>(null)
|
||||
|
||||
const dismissAlert = () => {
|
||||
ionItemSlidingRef.current && ionItemSlidingRef.current.close();
|
||||
}
|
||||
|
||||
const removeFavoriteSession = () => {
|
||||
onAddFavorite(session.id);
|
||||
onShowAlert('Favorite already added', [
|
||||
{
|
||||
text: 'Cancel',
|
||||
handler: dismissAlert
|
||||
},
|
||||
{
|
||||
text: 'Remove',
|
||||
handler: () => {
|
||||
onRemoveFavorite(session.id);
|
||||
dismissAlert();
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
const addFavoriteSession = () => {
|
||||
if (isFavorite) {
|
||||
// woops, they already favorited it! What shall we do!?
|
||||
// prompt them to remove it
|
||||
removeFavoriteSession();
|
||||
} else {
|
||||
// remember this session as a user favorite
|
||||
onAddFavorite(session.id);
|
||||
onShowAlert('Favorite Added', [
|
||||
{
|
||||
text: 'OK',
|
||||
handler: dismissAlert
|
||||
}
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonItemSliding ref={ionItemSlidingRef} class={'track-' + session.tracks[0].toLowerCase()}>
|
||||
<IonItem routerLink={`/tabs/schedule/${session.id}`}>
|
||||
<IonLabel>
|
||||
<h3>{session.name}</h3>
|
||||
<p>
|
||||
<Time date={session.dateTimeStart} /> —
|
||||
<Time date={session.dateTimeEnd} /> —
|
||||
{session.location}
|
||||
</p>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
<IonItemOptions>
|
||||
{listType === "favorites" ?
|
||||
<IonItemOption color="danger" onClick={() => removeFavoriteSession()}>
|
||||
Remove
|
||||
</IonItemOption>
|
||||
:
|
||||
<IonItemOption color="favorite" onClick={addFavoriteSession}>
|
||||
Favorite
|
||||
</IonItemOption>
|
||||
}
|
||||
</IonItemOptions>
|
||||
</IonItemSliding>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SessionListItem);
|
||||
@@ -1,46 +0,0 @@
|
||||
import { IonLoading, IonFab, IonFabButton, IonIcon, IonFabList } from "@ionic/react"
|
||||
import { share, logoVimeo, logoGoogleplus, logoTwitter, logoFacebook } from "ionicons/icons"
|
||||
import React, { useState } from "react"
|
||||
|
||||
const ShareSocialFab: React.FC = () => {
|
||||
const [loadingMessage, setLoadingMessage] = useState('')
|
||||
const [showLoading, setShowLoading] = useState(false);
|
||||
|
||||
const openSocial = (network: string) => {
|
||||
setLoadingMessage(`Posting to ${network}`);
|
||||
setShowLoading(true);
|
||||
};
|
||||
|
||||
return(
|
||||
<>
|
||||
<IonLoading
|
||||
isOpen={showLoading}
|
||||
message={loadingMessage}
|
||||
duration={2000}
|
||||
spinner="crescent"
|
||||
onDidDismiss={() => setShowLoading(false)}
|
||||
/>
|
||||
<IonFab slot="fixed" vertical="bottom" horizontal="end">
|
||||
<IonFabButton>
|
||||
<IonIcon icon={share} />
|
||||
</IonFabButton>
|
||||
<IonFabList side="top">
|
||||
<IonFabButton color="vimeo" onClick={() => openSocial('Vimeo')}>
|
||||
<IonIcon icon={logoVimeo} />
|
||||
</IonFabButton>
|
||||
<IonFabButton color="google" onClick={() => openSocial('Google+')}>
|
||||
<IonIcon icon={logoGoogleplus} />
|
||||
</IonFabButton>
|
||||
<IonFabButton color="twitter" onClick={() => openSocial('Twitter')}>
|
||||
<IonIcon icon={logoTwitter} />
|
||||
</IonFabButton>
|
||||
<IonFabButton color="facebook" onClick={() => openSocial('Facebook')}>
|
||||
<IonIcon icon={logoFacebook} />
|
||||
</IonFabButton>
|
||||
</IonFabList>
|
||||
</IonFab>
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
export default ShareSocialFab;
|
||||
@@ -1,125 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Session } from '../models/Session';
|
||||
import { Speaker } from '../models/Speaker';
|
||||
import { IonCard, IonCardHeader, IonItem, IonAvatar, IonCardContent, IonList, IonRow, IonCol, IonButton, IonIcon, IonActionSheet } from '@ionic/react';
|
||||
import { logoTwitter, shareAlt, chatboxes } from 'ionicons/icons';
|
||||
import { ActionSheetButton } from '@ionic/core';
|
||||
|
||||
interface SpeakerItemProps {
|
||||
speaker: Speaker;
|
||||
sessions: Session[];
|
||||
}
|
||||
|
||||
const SpeakerItem: React.FC<SpeakerItemProps> = ({ speaker, sessions }) => {
|
||||
const [showActionSheet, setShowActionSheet] = useState(false);
|
||||
const [actionSheetButtons, setActionSheetButtons] = useState<ActionSheetButton[]>([]);
|
||||
const [actionSheetHeader, setActionSheetHeader] = useState('');
|
||||
|
||||
function openSpeakerShare(speaker: Speaker) {
|
||||
setActionSheetButtons([
|
||||
{
|
||||
text: 'Copy Link',
|
||||
handler: () => {
|
||||
console.log('Copy Link clicked');
|
||||
}
|
||||
},
|
||||
{
|
||||
text: 'Share via ...',
|
||||
handler: () => {
|
||||
console.log('Share via clicked');
|
||||
}
|
||||
},
|
||||
{
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
handler: () => {
|
||||
console.log('Cancel clicked');
|
||||
}
|
||||
}
|
||||
]);
|
||||
setActionSheetHeader(`Share ${speaker.name}`);
|
||||
setShowActionSheet(true);
|
||||
}
|
||||
|
||||
function openContact(speaker: Speaker) {
|
||||
setActionSheetButtons([
|
||||
{
|
||||
text: `Email ( ${speaker.email} )`,
|
||||
handler: () => {
|
||||
window.open('mailto:' + speaker.email);
|
||||
}
|
||||
},
|
||||
{
|
||||
text: `Call ( ${speaker.phone} )`,
|
||||
handler: () => {
|
||||
window.open('tel:' + speaker.phone);
|
||||
}
|
||||
}
|
||||
]);
|
||||
setActionSheetHeader(`Share ${speaker.name}`);
|
||||
setShowActionSheet(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonCard className="speaker-card">
|
||||
<IonCardHeader>
|
||||
<IonItem button detail={false} routerLink={`/tabs/speakers/${speaker.id}`} lines="none">
|
||||
<IonAvatar slot="start">
|
||||
<img src={process.env.PUBLIC_URL + speaker.profilePic} alt="Speaker profile pic" />
|
||||
</IonAvatar>
|
||||
{speaker.name}
|
||||
</IonItem>
|
||||
</IonCardHeader>
|
||||
|
||||
<IonCardContent class="outer-content">
|
||||
<IonList>
|
||||
{sessions.map(session => (
|
||||
<IonItem routerLink={`/tabs/speakers/sessions/${session.id}`} key={session.name}>
|
||||
<h3>{session.name}</h3>
|
||||
</IonItem>
|
||||
))}
|
||||
<IonItem button routerLink={`/tabs/speakers/${speaker.id}`}>
|
||||
<h3>About {speaker.name}</h3>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonCardContent>
|
||||
|
||||
<IonRow justify-content-center>
|
||||
<IonCol text-left size="4">
|
||||
<IonButton
|
||||
fill="clear"
|
||||
size="small"
|
||||
color="primary"
|
||||
href={`https://www.twitter.com/${speaker.twitter}`}
|
||||
target="_blank"
|
||||
>
|
||||
<IonIcon slot="start" icon={logoTwitter} />
|
||||
Tweet
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
<IonCol text-left size="4">
|
||||
<IonButton fill="clear" size="small" color="primary" onClick={() => openSpeakerShare(speaker)}>
|
||||
<IonIcon slot="start" icon={shareAlt} />
|
||||
Share
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
<IonCol text-left size="4">
|
||||
<IonButton fill="clear" size="small" color="primary" onClick={() => openContact(speaker)}>
|
||||
<IonIcon slot="start" icon={chatboxes} />
|
||||
Contact
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCard>
|
||||
<IonActionSheet
|
||||
isOpen={showActionSheet}
|
||||
header={actionSheetHeader}
|
||||
onDidDismiss={() => setShowActionSheet(false)}
|
||||
buttons={actionSheetButtons}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpeakerItem;
|
||||
@@ -1,8 +0,0 @@
|
||||
import React from 'react';
|
||||
import { format, parseISO as parseDate } from 'date-fns';
|
||||
|
||||
export const Time: React.FC<{ date: string }> = ({ date }) => (
|
||||
<>
|
||||
{format(parseDate(date), "h:mm aaaaa")}m
|
||||
</>
|
||||
)
|
||||
@@ -1,26 +0,0 @@
|
||||
import React, { createContext, useReducer } from 'react';
|
||||
import { initialState, AppState, reducers } from './state'
|
||||
|
||||
export interface AppContextState {
|
||||
state: AppState;
|
||||
dispatch: React.Dispatch<any>;
|
||||
}
|
||||
|
||||
export const AppContext = createContext<AppContextState>({
|
||||
state: initialState,
|
||||
dispatch: () => undefined
|
||||
});
|
||||
|
||||
export const AppContextProvider: React.FC = (props => {
|
||||
|
||||
const [store, dispatch] = useReducer(reducers, initialState);
|
||||
|
||||
return (
|
||||
<AppContext.Provider value={{
|
||||
state: store,
|
||||
dispatch
|
||||
}}>
|
||||
{props.children}
|
||||
</AppContext.Provider>
|
||||
)
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
export function combineReducers<R extends any>(reducers: R) {
|
||||
type keys = keyof typeof reducers;
|
||||
type returnType = { [K in keys]: ReturnType<typeof reducers[K]> };
|
||||
const combinedReducer = (state: any, action: any) => {
|
||||
const newState: returnType = {} as any;
|
||||
const keys = Object.keys(reducers);
|
||||
keys.forEach(key => {
|
||||
const result = reducers[key](state[key], action);
|
||||
newState[key as keys] = result || state[key];
|
||||
});
|
||||
return newState;
|
||||
};
|
||||
return combinedReducer;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import React, { useContext, useEffect, useState, useMemo } from 'react';
|
||||
import { AppContext } from './AppContext';
|
||||
import { DispatchObject } from '../util/types';
|
||||
import { AppState } from './state';
|
||||
|
||||
interface ConnectParams<TOwnProps, TStateProps, TDispatchProps> {
|
||||
mapStateToProps?: (state: AppState, props: TOwnProps) => TStateProps,
|
||||
mapDispatchToProps?: TDispatchProps,
|
||||
component: React.ComponentType<any>
|
||||
};
|
||||
|
||||
export function connect<TOwnProps = any, TStateProps = any, TDispatchProps = any>({ mapStateToProps = () => ({} as TStateProps), mapDispatchToProps = {} as TDispatchProps, component }: ConnectParams<TOwnProps, TStateProps, TDispatchProps>): React.FunctionComponent<TOwnProps> {
|
||||
|
||||
const Connect = (ownProps: TOwnProps) => {
|
||||
const context = useContext(AppContext);
|
||||
|
||||
const dispatchFuncs = useMemo(() => {
|
||||
const dispatchFuncs: { [key: string]: any } = {};
|
||||
Object.keys(mapDispatchToProps).forEach((key) => {
|
||||
const oldFunc = (mapDispatchToProps as any)[key];
|
||||
const newFunc = (...args: any) => {
|
||||
const dispatchFunc = oldFunc(...args);
|
||||
if (typeof dispatchFunc === 'object') {
|
||||
context.dispatch(dispatchFunc);
|
||||
} else {
|
||||
const result = dispatchFunc(context.dispatch)
|
||||
if (typeof result === 'object' && result.then) {
|
||||
result.then((dispatchObject?: DispatchObject) => {
|
||||
if (dispatchObject && dispatchObject.type) {
|
||||
context.dispatch(dispatchObject);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
dispatchFuncs[key] = newFunc
|
||||
});
|
||||
return dispatchFuncs;
|
||||
}, [mapDispatchToProps])
|
||||
|
||||
|
||||
const props = useMemo(() => {
|
||||
return Object.assign({}, ownProps, mapStateToProps(context.state, ownProps), dispatchFuncs);
|
||||
}, [ownProps, context.state]);
|
||||
|
||||
return React.createElement<TOwnProps>(component, props);
|
||||
}
|
||||
return React.memo(Connect as any);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import { Plugins } from '@capacitor/core';
|
||||
import { Session } from '../models/Session';
|
||||
import { Speaker } from '../models/Speaker';
|
||||
import { Location } from '../models/Location';
|
||||
|
||||
const { Storage } = Plugins;
|
||||
|
||||
const locationsUrl = '/assets/data/locations.json';
|
||||
const sessionsUrl = '/assets/data/sessions.json';
|
||||
const speakersUrl = '/assets/data/speakers.json';
|
||||
|
||||
const HAS_LOGGED_IN = 'hasLoggedIn';
|
||||
const HAS_SEEN_TUTORIAL = 'hasSeenTutorial';
|
||||
const USERNAME = 'username';
|
||||
|
||||
export const getConfData = async () => {
|
||||
const response = await Promise.all([
|
||||
fetch(sessionsUrl),
|
||||
fetch(locationsUrl),
|
||||
fetch(speakersUrl),
|
||||
]);
|
||||
const sessions = (await response[0].json()) as Session[];
|
||||
const locations = (await response[1].json()) as Location[];
|
||||
const speakers = (await response[2].json()) as Speaker[];
|
||||
const allTracks = sessions
|
||||
.reduce((all, session) => all.concat(session.tracks), [] as string[])
|
||||
.filter((trackName, index, array) => array.indexOf(trackName) === index)
|
||||
.sort();
|
||||
const data = {
|
||||
sessions,
|
||||
locations,
|
||||
speakers,
|
||||
allTracks,
|
||||
filteredTracks: [...allTracks],
|
||||
};
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getUserData = async () => {
|
||||
const response = await Promise.all([
|
||||
Storage.get({ key: HAS_LOGGED_IN }),
|
||||
Storage.get({ key: HAS_SEEN_TUTORIAL }),
|
||||
Storage.get({ key: USERNAME }),
|
||||
]);
|
||||
const isLoggedin = (await response[0].value) === 'true';
|
||||
const hasSeenTutorial = (await response[1].value) === 'true';
|
||||
const username = (await response[2].value) || undefined;
|
||||
const data = {
|
||||
isLoggedin,
|
||||
hasSeenTutorial,
|
||||
username,
|
||||
};
|
||||
return data;
|
||||
};
|
||||
|
||||
export const setIsLoggedInData = async (isLoggedIn: boolean) => {
|
||||
await Storage.set({ key: HAS_LOGGED_IN, value: JSON.stringify(isLoggedIn) });
|
||||
};
|
||||
|
||||
export const setHasSeenTutorialData = async (hasSeenTutorial: boolean) => {
|
||||
await Storage.set({
|
||||
key: HAS_SEEN_TUTORIAL,
|
||||
value: JSON.stringify(hasSeenTutorial),
|
||||
});
|
||||
};
|
||||
|
||||
export const setUsernameData = async (username?: string) => {
|
||||
if (!username) {
|
||||
await Storage.remove({ key: USERNAME });
|
||||
} else {
|
||||
await Storage.set({ key: USERNAME, value: username });
|
||||
}
|
||||
};
|
||||
@@ -1,139 +0,0 @@
|
||||
import { createSelector } from 'reselect';
|
||||
import { parseISO as parseDate } from 'date-fns';
|
||||
import { Session } from '../models/Session';
|
||||
import { SessionGroup } from '../models/SessionGroup';
|
||||
import { AppState } from './state';
|
||||
|
||||
const getSessions = (state: AppState) => state.data.sessions;
|
||||
export const getSpeakers = (state: AppState) => state.data.speakers;
|
||||
const getFilteredTracks = (state: AppState) => state.data.filteredTracks;
|
||||
const getFavoriteIds = (state: AppState) => state.data.favorites;
|
||||
const getSearchText = (state: AppState) => state.data.searchText;
|
||||
|
||||
export const getFilteredSessions = createSelector(
|
||||
getSessions,
|
||||
getFilteredTracks,
|
||||
(sessions, filteredTracks) => {
|
||||
return sessions.filter(session => {
|
||||
let include = false;
|
||||
session.tracks.forEach(track => {
|
||||
if (filteredTracks.indexOf(track) > -1) {
|
||||
include = true;
|
||||
}
|
||||
});
|
||||
return include;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export const getSearchedSessions = createSelector(
|
||||
getFilteredSessions,
|
||||
getSearchText,
|
||||
(sessions, searchText) => {
|
||||
if (!searchText) {
|
||||
return sessions;
|
||||
}
|
||||
return sessions.filter(
|
||||
session =>
|
||||
session.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const getGroupedSessions = createSelector(
|
||||
getSearchedSessions,
|
||||
sessions => {
|
||||
return groupSessions(sessions);
|
||||
}
|
||||
);
|
||||
|
||||
export const getFavorites = createSelector(
|
||||
getSearchedSessions,
|
||||
getFavoriteIds,
|
||||
(sessions, favoriteIds) =>
|
||||
sessions.filter(x => favoriteIds.indexOf(x.id) > -1)
|
||||
);
|
||||
|
||||
export const getGroupedFavorites = createSelector(
|
||||
getFavorites,
|
||||
sessions => {
|
||||
return groupSessions(sessions);
|
||||
}
|
||||
);
|
||||
|
||||
const getIdParam = (_state: AppState, props: any) => {
|
||||
const stringParam = props.match.params['id'];
|
||||
return parseInt(stringParam, 10);
|
||||
};
|
||||
|
||||
export const getSession = createSelector(
|
||||
getSessions,
|
||||
getIdParam,
|
||||
(sessions, id) => sessions.find(x => x.id === id)
|
||||
);
|
||||
|
||||
function groupSessions(sessions: Session[]) {
|
||||
return sessions
|
||||
.sort(
|
||||
(a, b) =>
|
||||
parseDate(a.dateTimeStart).valueOf() -
|
||||
parseDate(b.dateTimeStart).valueOf()
|
||||
)
|
||||
.reduce(
|
||||
(groups, session) => {
|
||||
let starterHour = parseDate(session.dateTimeStart);
|
||||
starterHour.setMinutes(0);
|
||||
starterHour.setSeconds(0);
|
||||
const starterHourStr = starterHour.toJSON();
|
||||
const foundGroup = groups.find(
|
||||
group => group.startTime === starterHourStr
|
||||
);
|
||||
if (foundGroup) {
|
||||
foundGroup.sessions.push(session);
|
||||
} else {
|
||||
groups.push({
|
||||
startTime: starterHourStr,
|
||||
sessions: [session],
|
||||
});
|
||||
}
|
||||
return groups;
|
||||
},
|
||||
[] as SessionGroup[]
|
||||
);
|
||||
}
|
||||
|
||||
export const getSpeaker = createSelector(
|
||||
getSpeakers,
|
||||
getIdParam,
|
||||
(speakers, id) => speakers.find(x => x.id === id)
|
||||
);
|
||||
|
||||
export const getSpeakerSessions = createSelector(
|
||||
getSessions,
|
||||
sessions => {
|
||||
const speakerSessions: { [key: number]: Session[] } = {};
|
||||
sessions.forEach(session => {
|
||||
session.speakerIds.forEach(speakerId => {
|
||||
if (speakerSessions[speakerId]) {
|
||||
speakerSessions[speakerId].push(session);
|
||||
} else {
|
||||
speakerSessions[speakerId] = [session];
|
||||
}
|
||||
});
|
||||
});
|
||||
return speakerSessions;
|
||||
}
|
||||
);
|
||||
|
||||
export const mapCenter = (state: AppState) => {
|
||||
const item = state.data.locations.find(l => l.id === state.data.mapCenterId);
|
||||
if (item == null) {
|
||||
return {
|
||||
id: 1,
|
||||
name: 'Map Center',
|
||||
lat: 43.071584,
|
||||
lng: -89.38012,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
};
|
||||
@@ -1,54 +0,0 @@
|
||||
import { getConfData } from '../dataApi';
|
||||
import { ActionType } from '../../util/types';
|
||||
import { SessionsState } from './sessions.state';
|
||||
|
||||
export const loadConfData = () => async (dispatch: React.Dispatch<any>) => {
|
||||
dispatch(setLoading(true));
|
||||
const data = await getConfData();
|
||||
dispatch(setData(data));
|
||||
dispatch(setLoading(false));
|
||||
};
|
||||
|
||||
export const setLoading = (isLoading: boolean) =>
|
||||
({
|
||||
type: 'set-conf-loading',
|
||||
isLoading,
|
||||
} as const);
|
||||
|
||||
export const setData = (data: Partial<SessionsState>) =>
|
||||
({
|
||||
type: 'set-conf-data',
|
||||
data,
|
||||
} as const);
|
||||
|
||||
export const addFavorite = (sessionId: number) =>
|
||||
({
|
||||
type: 'add-favorite',
|
||||
sessionId,
|
||||
} as const);
|
||||
|
||||
export const removeFavorite = (sessionId: number) =>
|
||||
({
|
||||
type: 'remove-favorite',
|
||||
sessionId,
|
||||
} as const);
|
||||
|
||||
export const updateFilteredTracks = (filteredTracks: string[]) =>
|
||||
({
|
||||
type: 'update-filtered-tracks',
|
||||
filteredTracks,
|
||||
} as const);
|
||||
|
||||
export const setSearchText = (searchText?: string) =>
|
||||
({
|
||||
type: 'set-search-text',
|
||||
searchText,
|
||||
} as const);
|
||||
|
||||
export type SessionsActions =
|
||||
| ActionType<typeof setLoading>
|
||||
| ActionType<typeof setData>
|
||||
| ActionType<typeof addFavorite>
|
||||
| ActionType<typeof removeFavorite>
|
||||
| ActionType<typeof updateFilteredTracks>
|
||||
| ActionType<typeof setSearchText>;
|
||||
@@ -1,31 +0,0 @@
|
||||
import { SessionsActions } from './sessions.actions';
|
||||
import { SessionsState } from './sessions.state';
|
||||
|
||||
export const sessionsReducer = (
|
||||
state: SessionsState,
|
||||
action: SessionsActions
|
||||
): SessionsState => {
|
||||
switch (action.type) {
|
||||
case 'set-conf-loading': {
|
||||
return { ...state, loading: action.isLoading };
|
||||
}
|
||||
case 'set-conf-data': {
|
||||
return { ...state, ...action.data };
|
||||
}
|
||||
case 'add-favorite': {
|
||||
return { ...state, favorites: [...state.favorites, action.sessionId] };
|
||||
}
|
||||
case 'remove-favorite': {
|
||||
return {
|
||||
...state,
|
||||
favorites: [...state.favorites.filter(x => x !== action.sessionId)],
|
||||
};
|
||||
}
|
||||
case 'update-filtered-tracks': {
|
||||
return { ...state, filteredTracks: action.filteredTracks };
|
||||
}
|
||||
case 'set-search-text': {
|
||||
return { ...state, searchText: action.searchText };
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Location } from '../../models/Location';
|
||||
import { Speaker } from '../../models/Speaker';
|
||||
import { Session } from '../../models/Session';
|
||||
export interface SessionsState {
|
||||
sessions: Session[];
|
||||
speakers: Speaker[];
|
||||
favorites: number[];
|
||||
locations: Location[];
|
||||
filteredTracks: string[];
|
||||
searchText?: string;
|
||||
mapCenterId?: number;
|
||||
loading?: boolean;
|
||||
allTracks: string[];
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { combineReducers } from './combineReducers';
|
||||
import { sessionsReducer } from './sessions/sessions.reducer';
|
||||
import { userReducer } from './user/user.reducer';
|
||||
|
||||
export const initialState: AppState = {
|
||||
data: {
|
||||
sessions: [],
|
||||
speakers: [],
|
||||
favorites: [],
|
||||
locations: [],
|
||||
allTracks: [],
|
||||
filteredTracks: [],
|
||||
mapCenterId: 0,
|
||||
loading: false,
|
||||
},
|
||||
user: {
|
||||
hasSeenTutorial: false,
|
||||
darkMode: false,
|
||||
isLoggedin: false,
|
||||
loading: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const reducers = combineReducers({
|
||||
data: sessionsReducer,
|
||||
user: userReducer,
|
||||
});
|
||||
|
||||
export type AppState = ReturnType<typeof reducers>;
|
||||
@@ -1,76 +0,0 @@
|
||||
import {
|
||||
getUserData,
|
||||
setIsLoggedInData,
|
||||
setUsernameData,
|
||||
setHasSeenTutorialData,
|
||||
} from '../dataApi';
|
||||
import { ActionType } from '../../util/types';
|
||||
import { UserState } from './user.state';
|
||||
|
||||
export const loadUserData = () => async (dispatch: React.Dispatch<any>) => {
|
||||
dispatch(setLoading(true));
|
||||
const data = await getUserData();
|
||||
dispatch(setData(data));
|
||||
dispatch(setLoading(false));
|
||||
};
|
||||
|
||||
export const setLoading = (isLoading: boolean) =>
|
||||
({
|
||||
type: 'set-user-loading',
|
||||
isLoading,
|
||||
} as const);
|
||||
|
||||
export const setData = (data: Partial<UserState>) =>
|
||||
({
|
||||
type: 'set-user-data',
|
||||
data,
|
||||
} as const);
|
||||
|
||||
export const logoutUser = () => async (dispatch: React.Dispatch<any>) => {
|
||||
await setIsLoggedInData(false);
|
||||
dispatch(setUsername());
|
||||
};
|
||||
|
||||
export const setIsLoggedIn = (loggedIn: boolean) => async (
|
||||
dispatch: React.Dispatch<any>
|
||||
) => {
|
||||
await setIsLoggedInData(loggedIn);
|
||||
return {
|
||||
type: 'set-is-loggedin',
|
||||
loggedIn,
|
||||
} as const;
|
||||
};
|
||||
|
||||
export const setUsername = (username?: string) => async (
|
||||
dispatch: React.Dispatch<any>
|
||||
) => {
|
||||
await setUsernameData(username);
|
||||
return {
|
||||
type: 'set-username',
|
||||
username,
|
||||
} as const;
|
||||
};
|
||||
|
||||
export const setHasSeenTutorial = (hasSeenTutorial: boolean) => async (
|
||||
dispatch: React.Dispatch<any>
|
||||
) => {
|
||||
await setHasSeenTutorialData(hasSeenTutorial);
|
||||
return {
|
||||
type: 'set-has-seen-tutorial',
|
||||
hasSeenTutorial,
|
||||
} as const;
|
||||
};
|
||||
|
||||
export const setDarkMode = (darkMode: boolean) =>
|
||||
({
|
||||
type: 'set-dark-mode',
|
||||
darkMode,
|
||||
} as const);
|
||||
|
||||
export type UserActions =
|
||||
| ActionType<typeof setLoading>
|
||||
| ActionType<typeof setData>
|
||||
| ActionType<typeof setIsLoggedIn>
|
||||
| ActionType<typeof setUsername>
|
||||
| ActionType<typeof setHasSeenTutorial>
|
||||
| ActionType<typeof setDarkMode>;
|
||||
@@ -1,19 +0,0 @@
|
||||
import { UserActions } from './user.actions';
|
||||
import { UserState } from './user.state';
|
||||
|
||||
export function userReducer(state: UserState, action: UserActions): UserState {
|
||||
switch (action.type) {
|
||||
case 'set-user-loading':
|
||||
return { ...state, loading: action.isLoading };
|
||||
case 'set-user-data':
|
||||
return { ...state, ...action.data };
|
||||
case 'set-username':
|
||||
return { ...state, username: action.username };
|
||||
case 'set-has-seen-tutorial':
|
||||
return { ...state, hasSeenTutorial: action.hasSeenTutorial };
|
||||
case 'set-dark-mode':
|
||||
return { ...state, darkMode: action.darkMode };
|
||||
case 'set-is-loggedin':
|
||||
return { ...state, isLoggedin: action.loggedIn };
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
export interface UserState {
|
||||
isLoggedin: boolean;
|
||||
username?: string;
|
||||
darkMode: boolean;
|
||||
hasSeenTutorial: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export interface AppPage {
|
||||
url: string;
|
||||
icon: object;
|
||||
title: string;
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||
serviceWorker.unregister();
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
export interface Location {
|
||||
id: number;
|
||||
name?: string;
|
||||
lat: number;
|
||||
lng: number;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export interface Session {
|
||||
id: number;
|
||||
dateTimeStart: string;
|
||||
dateTimeEnd: string;
|
||||
name: string;
|
||||
location: string;
|
||||
description: string;
|
||||
speakerIds: number[];
|
||||
tracks: string[];
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { Session } from './Session';
|
||||
export interface SessionGroup {
|
||||
startTime: string;
|
||||
sessions: Session[];
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export interface Speaker {
|
||||
id: number;
|
||||
name: string;
|
||||
profilePic: string;
|
||||
twitter: string;
|
||||
about: string;
|
||||
location: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
#about-page {
|
||||
.about-header {
|
||||
background-color: #222;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
height: 30%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.about-header img {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.about-info p {
|
||||
color: var(--ion-color-dark);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.about-info ion-icon {
|
||||
margin-inline-end: 32px;
|
||||
}
|
||||
|
||||
.ios .about-info {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, IonButtons, IonMenuButton, IonButton, IonIcon, IonDatetime, IonSelectOption, IonList, IonItem, IonLabel, IonSelect, IonPopover } from '@ionic/react';
|
||||
import './About.scss';
|
||||
import { calendar, pin, more } from 'ionicons/icons';
|
||||
import AboutPopover from '../components/AboutPopover';
|
||||
|
||||
interface AboutProps { }
|
||||
|
||||
const About: React.FC<AboutProps> = () => {
|
||||
|
||||
const [showPopover, setShowPopover] = useState(false);
|
||||
const [popoverEvent, setPopoverEvent] = useState();
|
||||
|
||||
const presentPopover = (e: React.MouseEvent) => {
|
||||
setPopoverEvent(e.nativeEvent);
|
||||
setShowPopover(true);
|
||||
};
|
||||
const conferenceDate = '2047-05-17';
|
||||
|
||||
return (
|
||||
<IonPage id="about-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton></IonMenuButton>
|
||||
</IonButtons>
|
||||
<IonTitle>About</IonTitle>
|
||||
<IonButtons slot="end">
|
||||
<IonButton icon-only onClick={presentPopover}>
|
||||
<IonIcon slot="icon-only" icon={more}></IonIcon>
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
|
||||
<div className="about-header">
|
||||
<img src="assets/img/ionic-logo-white.svg" alt="ionic logo" />
|
||||
</div>
|
||||
<div className="about-info">
|
||||
<h4 className="ion-padding-start">Ionic Conference</h4>
|
||||
|
||||
<IonList lines="none">
|
||||
<IonItem>
|
||||
<IonIcon icon={calendar} slot="start"></IonIcon>
|
||||
<IonLabel position="stacked">Date</IonLabel>
|
||||
<IonDatetime displayFormat="MMM DD, YYYY" max="2056" value={conferenceDate}></IonDatetime>
|
||||
</IonItem>
|
||||
|
||||
<IonItem>
|
||||
<IonIcon icon={pin} slot="start"></IonIcon>
|
||||
<IonLabel position="stacked">Location</IonLabel>
|
||||
<IonSelect>
|
||||
<IonSelectOption value="madison" selected>Madison, WI</IonSelectOption>
|
||||
<IonSelectOption value="austin">Austin, TX</IonSelectOption>
|
||||
<IonSelectOption value="chicago">Chicago, IL</IonSelectOption>
|
||||
<IonSelectOption value="seattle">Seattle, WA</IonSelectOption>
|
||||
</IonSelect>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
|
||||
<p className="ion-padding-start ion-padding-end">
|
||||
The Ionic Conference is a one-day conference featuring talks from the Ionic team. It is focused on Ionic applications being
|
||||
built with Ionic 2. This includes migrating apps from Ionic 1 to Ionic 2, Angular concepts, Webpack, Sass, and many
|
||||
other technologies used in Ionic 2. Tickets are completely sold out, and we’re expecting more than 1000 developers
|
||||
– making this the largest Ionic conference ever!
|
||||
</p>
|
||||
</div>
|
||||
</IonContent>
|
||||
<IonPopover
|
||||
isOpen={showPopover}
|
||||
event={popoverEvent}
|
||||
onDidDismiss={() => setShowPopover(false)}
|
||||
>
|
||||
<AboutPopover dismiss={() => setShowPopover(false)} />
|
||||
</IonPopover>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(About);
|
||||
@@ -1,6 +0,0 @@
|
||||
#account-page {
|
||||
img {
|
||||
max-width: 140px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, IonButtons, IonMenuButton, IonList, IonItem, IonAlert } from '@ionic/react';
|
||||
import './Account.scss';
|
||||
import { setUsername } from '../data/user/user.actions';
|
||||
import { connect } from '../data/connect';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
|
||||
interface OwnProps extends RouteComponentProps { }
|
||||
|
||||
interface StateProps {
|
||||
username?: string;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
setUsername: typeof setUsername;
|
||||
}
|
||||
|
||||
interface AccountProps extends OwnProps, StateProps, DispatchProps { }
|
||||
|
||||
const Account: React.FC<AccountProps> = ({ setUsername, username }) => {
|
||||
|
||||
const [showAlert, setShowAlert] = useState(false);
|
||||
|
||||
const clicked = (text: string) => {
|
||||
console.log(`Clicked ${text}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage id="account-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton></IonMenuButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Account</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
{username &&
|
||||
(<div className="ion-padding-top ion-text-center">
|
||||
<img src="https://www.gravatar.com/avatar?d=mm&s=140" alt="avatar" />
|
||||
<h2>{ username }</h2>
|
||||
<IonList inset>
|
||||
<IonItem onClick={() => clicked('Update Picture')}>Update Picture</IonItem>
|
||||
<IonItem onClick={() => setShowAlert(true)}>Change Username</IonItem>
|
||||
<IonItem onClick={() => clicked('Change Password')}>Change Password</IonItem>
|
||||
<IonItem routerLink="/support" routerDirection="none">Support</IonItem>
|
||||
<IonItem routerLink="/logout" routerDirection="none">Logout</IonItem>
|
||||
</IonList>
|
||||
</div>)
|
||||
}
|
||||
</IonContent>
|
||||
<IonAlert
|
||||
isOpen={showAlert}
|
||||
header="Change Username"
|
||||
buttons={[
|
||||
'Cancel',
|
||||
{
|
||||
text: 'Ok',
|
||||
handler: (data) => {
|
||||
setUsername(data.username);
|
||||
}
|
||||
}
|
||||
]}
|
||||
inputs={[
|
||||
{
|
||||
type: 'text',
|
||||
name: 'username',
|
||||
value: username,
|
||||
placeholder: 'username'
|
||||
}
|
||||
]}
|
||||
onDidDismiss={() => setShowAlert(false)}
|
||||
/>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
username: state.user.username
|
||||
}),
|
||||
mapDispatchToProps: {
|
||||
setUsername,
|
||||
},
|
||||
component: Account
|
||||
})
|
||||
26
examples/ionic-react/src/pages/Home.tsx
Normal file
26
examples/ionic-react/src/pages/Home.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
|
||||
import React from 'react';
|
||||
|
||||
const Home: React.FC = () => {
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Ionic Blank</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent className="ion-padding">
|
||||
The world is your oyster.
|
||||
<p>
|
||||
If you get lost, the{' '}
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/">
|
||||
docs
|
||||
</a>{' '}
|
||||
will be your guide.
|
||||
</p>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
@@ -1,16 +0,0 @@
|
||||
#login-page, #signup-page, #support-page {
|
||||
.login-logo {
|
||||
padding: 20px 0;
|
||||
min-height: 200px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-logo img {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, IonButtons, IonMenuButton, IonRow, IonCol, IonButton, IonList, IonItem, IonLabel, IonInput, IonText } from '@ionic/react';
|
||||
import './Login.scss';
|
||||
import { setIsLoggedIn, setUsername } from '../data/user/user.actions';
|
||||
import { connect } from '../data/connect';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
|
||||
interface OwnProps extends RouteComponentProps {}
|
||||
|
||||
interface DispatchProps {
|
||||
setIsLoggedIn: typeof setIsLoggedIn;
|
||||
setUsername: typeof setUsername;
|
||||
}
|
||||
|
||||
interface LoginProps extends OwnProps, DispatchProps { }
|
||||
|
||||
const Login: React.FC<LoginProps> = ({setIsLoggedIn, history, setUsername: setUsernameAction}) => {
|
||||
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [formSubmitted, setFormSubmitted] = useState(false);
|
||||
const [usernameError, setUsernameError] = useState(false);
|
||||
const [passwordError, setPasswordError] = useState(false);
|
||||
|
||||
const login = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setFormSubmitted(true);
|
||||
if(!username) {
|
||||
setUsernameError(true);
|
||||
}
|
||||
if(!password) {
|
||||
setPasswordError(true);
|
||||
}
|
||||
|
||||
if(username && password) {
|
||||
await setIsLoggedIn(true);
|
||||
await setUsernameAction(username);
|
||||
history.push('/tabs/schedule', {direction: 'none'});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="login-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton></IonMenuButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Login</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
|
||||
<div className="login-logo">
|
||||
<img src="assets/img/appicon.svg" alt="Ionic logo" />
|
||||
</div>
|
||||
|
||||
<form noValidate onSubmit={login}>
|
||||
<IonList>
|
||||
<IonItem>
|
||||
<IonLabel position="stacked" color="primary">Username</IonLabel>
|
||||
<IonInput name="username" type="text" value={username} spellCheck={false} autocapitalize="off" onIonChange={e => setUsername(e.detail.value!)}
|
||||
required>
|
||||
</IonInput>
|
||||
</IonItem>
|
||||
|
||||
{formSubmitted && usernameError && <IonText color="danger">
|
||||
<p className="ion-padding-start">
|
||||
Username is required
|
||||
</p>
|
||||
</IonText>}
|
||||
|
||||
<IonItem>
|
||||
<IonLabel position="stacked" color="primary">Password</IonLabel>
|
||||
<IonInput name="password" type="password" value={password} onIonChange={e => setPassword(e.detail.value!)}>
|
||||
</IonInput>
|
||||
</IonItem>
|
||||
|
||||
{formSubmitted && passwordError && <IonText color="danger">
|
||||
<p className="ion-padding-start">
|
||||
Password is required
|
||||
</p>
|
||||
</IonText>}
|
||||
</IonList>
|
||||
|
||||
<IonRow>
|
||||
<IonCol>
|
||||
<IonButton type="submit" expand="block">Login</IonButton>
|
||||
</IonCol>
|
||||
<IonCol>
|
||||
<IonButton routerLink="/signup" color="light" expand="block">Signup</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</form>
|
||||
|
||||
</IonContent>
|
||||
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, {}, DispatchProps>({
|
||||
mapDispatchToProps: {
|
||||
setIsLoggedIn,
|
||||
setUsername
|
||||
},
|
||||
component: Login
|
||||
})
|
||||
@@ -1,54 +0,0 @@
|
||||
import React from 'react';
|
||||
import { IonTabs, IonRouterOutlet, IonTabBar, IonTabButton, IonIcon, IonLabel } from '@ionic/react';
|
||||
import { Route, Redirect } from 'react-router';
|
||||
import { calendar, contacts, map, informationCircle } from 'ionicons/icons';
|
||||
import SchedulePage from './SchedulePage';
|
||||
import SpeakerList from './SpeakerList';
|
||||
import SpeakerDetail from './SpeakerDetail';
|
||||
import SessionDetail from './SessionDetail';
|
||||
import MapView from './MapView';
|
||||
import About from './About';
|
||||
|
||||
interface MainTabsProps { }
|
||||
|
||||
const MainTabs: React.FC<MainTabsProps> = () => {
|
||||
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet>
|
||||
<Redirect exact path="/tabs" to="/tabs/schedule" />
|
||||
{/*
|
||||
Using the render method prop cuts down the number of renders your components will have due to route changes.
|
||||
Use the component prop when your component depends on the RouterComponentProps passed in automatically.
|
||||
*/}
|
||||
<Route path="/tabs/schedule" render={() => <SchedulePage />} exact={true} />
|
||||
<Route path="/tabs/speakers" render={() => <SpeakerList />} exact={true} />
|
||||
<Route path="/tabs/speakers/:id" component={SpeakerDetail} exact={true} />
|
||||
<Route path="/tabs/schedule/:id" component={SessionDetail} />
|
||||
<Route path="/tabs/speakers/sessions/:id" component={SessionDetail} />
|
||||
<Route path="/tabs/map" render={() => <MapView />} exact={true} />
|
||||
<Route path="/tabs/about" render={() => <About />} exact={true} />
|
||||
</IonRouterOutlet>
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton tab="schedule" href="/tabs/schedule">
|
||||
<IonIcon icon={calendar} />
|
||||
<IonLabel>Schedule</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="speakers" href="/tabs/speakers">
|
||||
<IonIcon icon={contacts} />
|
||||
<IonLabel>Speakers</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="map" href="/tabs/map">
|
||||
<IonIcon icon={map} />
|
||||
<IonLabel>Map</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="about" href="/tabs/about">
|
||||
<IonIcon icon={informationCircle} />
|
||||
<IonLabel>About</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainTabs;
|
||||
@@ -1,18 +0,0 @@
|
||||
#map-view {
|
||||
.map-canvas {
|
||||
position: absolute;
|
||||
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
background-color: transparent;
|
||||
|
||||
opacity: 0;
|
||||
transition: opacity 250ms ease-in;
|
||||
}
|
||||
|
||||
.show-map {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import React from 'react';
|
||||
import Map from '../components/Map';
|
||||
import { IonHeader, IonToolbar, IonButtons, IonMenuButton, IonTitle, IonContent, IonPage } from '@ionic/react';
|
||||
import { Location } from '../models/Location';
|
||||
import { connect } from '../data/connect';
|
||||
import * as selectors from '../data/selectors';
|
||||
import './MapView.scss';
|
||||
|
||||
interface OwnProps { }
|
||||
|
||||
interface StateProps {
|
||||
locations: Location[];
|
||||
mapCenter: Location;
|
||||
}
|
||||
|
||||
interface DispatchProps { }
|
||||
|
||||
interface MapViewProps extends OwnProps, StateProps, DispatchProps { };
|
||||
|
||||
const MapView: React.FC<MapViewProps> = ({ locations, mapCenter }) => {
|
||||
return (
|
||||
<IonPage id="map-view">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton></IonMenuButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Map</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent class="map-page">
|
||||
<Map locations={locations} mapCenter={mapCenter} />
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
)};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
locations: state.data.locations,
|
||||
mapCenter: selectors.mapCenter(state)
|
||||
}),
|
||||
component: MapView
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
#schedule-page {
|
||||
ion-item-sliding.track-ionic ion-label {
|
||||
border-left: 2px solid var(--ion-color-primary);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-angular ion-label {
|
||||
border-left: 2px solid var(--ion-color-angular);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-communication ion-label {
|
||||
border-left: 2px solid var(--ion-color-communication);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-tooling ion-label {
|
||||
border-left: 2px solid var(--ion-color-tooling);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-services ion-label {
|
||||
border-left: 2px solid var(--ion-color-services);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-design ion-label {
|
||||
border-left: 2px solid var(--ion-color-design);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-workshop ion-label {
|
||||
border-left: 2px solid var(--ion-color-workshop);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-food ion-label {
|
||||
border-left: 2px solid var(--ion-color-food);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-documentation ion-label {
|
||||
border-left: 2px solid var(--ion-color-documentation);
|
||||
padding-left: 10px;
|
||||
}
|
||||
ion-item-sliding.track-navigation ion-label {
|
||||
border-left: 2px solid var(--ion-color-navigation);
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { IonToolbar, IonContent, IonPage, IonButtons, IonMenuButton, IonSegment, IonSegmentButton, IonButton, IonIcon, IonSearchbar, IonRefresher, IonRefresherContent, IonToast, IonModal, IonHeader, getConfig } from '@ionic/react';
|
||||
import { connect } from '../data/connect';
|
||||
import { options } from 'ionicons/icons';
|
||||
import SessionList from '../components/SessionList';
|
||||
import SessionListFilter from '../components/SessionListFilter';
|
||||
import './SchedulePage.scss'
|
||||
import * as selectors from '../data/selectors';
|
||||
import { setSearchText, addFavorite, removeFavorite } from '../data/sessions/sessions.actions';
|
||||
import ShareSocialFab from '../components/ShareSocialFab';
|
||||
import { SessionGroup } from '../models/SessionGroup';
|
||||
|
||||
interface OwnProps { }
|
||||
|
||||
interface StateProps {
|
||||
sessionGroups: SessionGroup[];
|
||||
favoriteGroups: SessionGroup[];
|
||||
mode: 'ios' | 'md'
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
setSearchText: typeof setSearchText;
|
||||
}
|
||||
|
||||
type SchedulePageProps = OwnProps & StateProps & DispatchProps;
|
||||
|
||||
const SchedulePage: React.FC<SchedulePageProps> = ({ favoriteGroups, sessionGroups, setSearchText, mode }) => {
|
||||
const [segment, setSegment] = useState<'all' | 'favorites'>('all');
|
||||
const [showFilterModal, setShowFilterModal] = useState(false);
|
||||
const ionRefresherRef = useRef<HTMLIonRefresherElement>(null);
|
||||
const [showCompleteToast, setShowCompleteToast] = useState(false);
|
||||
|
||||
const doRefresh = () => {
|
||||
setTimeout(() => {
|
||||
ionRefresherRef.current!.complete();
|
||||
setShowCompleteToast(true);
|
||||
}, 2500)
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="schedule-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
|
||||
<IonSegment onIonChange={(e) => setSegment(e.detail.value as any)}>
|
||||
<IonSegmentButton value="all" checked={segment === 'all'}>
|
||||
All
|
||||
</IonSegmentButton>
|
||||
<IonSegmentButton value="favorites" checked={segment === 'favorites'}>
|
||||
Favorites
|
||||
</IonSegmentButton>
|
||||
</IonSegment>
|
||||
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={() => setShowFilterModal(true)}>
|
||||
{mode === 'ios' ? 'Filter' : <IonIcon icon={options} slot="icon-only" />}
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
|
||||
<IonToolbar>
|
||||
<IonSearchbar
|
||||
placeholder="Search"
|
||||
onIonChange={(e: CustomEvent) => setSearchText(e.detail.value)}
|
||||
/>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent>
|
||||
<IonRefresher slot="fixed" ref={ionRefresherRef} onIonRefresh={doRefresh}>
|
||||
<IonRefresherContent />
|
||||
</IonRefresher>
|
||||
<IonToast
|
||||
isOpen={showCompleteToast}
|
||||
message="Refresh complete"
|
||||
duration={2000}
|
||||
onDidDismiss={() => setShowCompleteToast(false)}
|
||||
/>
|
||||
|
||||
<SessionList
|
||||
sessionGroups={sessionGroups}
|
||||
listType={segment}
|
||||
hide={segment === 'favorites'}
|
||||
/>
|
||||
<SessionList
|
||||
sessionGroups={favoriteGroups}
|
||||
listType={segment}
|
||||
hide={segment === 'all'}
|
||||
/>
|
||||
</IonContent>
|
||||
|
||||
<IonModal
|
||||
isOpen={showFilterModal}
|
||||
onDidDismiss={() => setShowFilterModal(false)}
|
||||
>
|
||||
<SessionListFilter
|
||||
onDismissModal={() => setShowFilterModal(false)}
|
||||
/>
|
||||
</IonModal>
|
||||
|
||||
<ShareSocialFab />
|
||||
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
sessionGroups: selectors.getGroupedSessions(state),
|
||||
favoriteGroups: selectors.getGroupedFavorites(state),
|
||||
mode: getConfig()!.get('mode')
|
||||
}),
|
||||
mapDispatchToProps: {
|
||||
setSearchText
|
||||
},
|
||||
component: React.memo(SchedulePage)
|
||||
});
|
||||
@@ -1,73 +0,0 @@
|
||||
#session-detail-page {
|
||||
.session-track-ionic {
|
||||
color: var(--ion-color-primary);
|
||||
}
|
||||
|
||||
.session-track-angular {
|
||||
color: var(--ion-color-angular);
|
||||
}
|
||||
|
||||
.session-track-communication {
|
||||
color: var(--ion-color-communication);
|
||||
}
|
||||
|
||||
.session-track-tooling {
|
||||
color: var(--ion-color-tooling);
|
||||
}
|
||||
|
||||
.session-track-services {
|
||||
color: var(--ion-color-services);
|
||||
}
|
||||
|
||||
.session-track-design {
|
||||
color: var(--ion-color-design);
|
||||
}
|
||||
|
||||
.session-track-workshop {
|
||||
color: var(--ion-color-workshop);
|
||||
}
|
||||
|
||||
.session-track-food {
|
||||
color: var(--ion-color-food);
|
||||
}
|
||||
|
||||
.session-track-documentation {
|
||||
color: var(--ion-color-documentation);
|
||||
}
|
||||
|
||||
.session-track-navigation {
|
||||
color: var(--ion-color-navigation);
|
||||
}
|
||||
|
||||
.show-favorite {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.icon-heart-empty {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
transform: scale(1);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.icon-heart {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
transform: scale(0);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.show-favorite .icon-heart {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.show-favorite .icon-heart-empty {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
import React from 'react';
|
||||
import { IonHeader, IonToolbar, IonContent, IonPage, IonButtons, IonBackButton, IonButton, IonIcon, IonText, IonList, IonItem, IonLabel } from '@ionic/react';
|
||||
import { connect } from '../data/connect';
|
||||
import { withRouter, RouteComponentProps } from 'react-router';
|
||||
import * as selectors from '../data/selectors';
|
||||
import { starOutline, star, share, cloudDownload } from 'ionicons/icons';
|
||||
import './SessionDetail.scss';
|
||||
import { Time } from '../components/Time';
|
||||
import { addFavorite, removeFavorite } from '../data/sessions/sessions.actions';
|
||||
import { Session } from '../models/Session';
|
||||
|
||||
interface OwnProps extends RouteComponentProps { };
|
||||
|
||||
interface StateProps {
|
||||
session?: Session;
|
||||
favoriteSessions: number[],
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
addFavorite: typeof addFavorite;
|
||||
removeFavorite: typeof removeFavorite;
|
||||
}
|
||||
|
||||
type SessionDetailProps = OwnProps & StateProps & DispatchProps;
|
||||
|
||||
const SessionDetail: React.FC<SessionDetailProps> = ({ session, addFavorite, removeFavorite, favoriteSessions }) => {
|
||||
|
||||
if (!session) {
|
||||
return <div>Session not found</div>
|
||||
}
|
||||
|
||||
const isFavorite = favoriteSessions.indexOf(session.id) > -1;
|
||||
|
||||
const toggleFavorite = () => {
|
||||
isFavorite ? removeFavorite(session.id) : addFavorite(session.id);
|
||||
};
|
||||
const shareSession = () => { };
|
||||
const sessionClick = (text: string) => {
|
||||
console.log(`Clicked ${text}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="session-detail-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton defaultHref="/tabs/schedule"></IonBackButton>
|
||||
</IonButtons>
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={() => toggleFavorite()}>
|
||||
{isFavorite ?
|
||||
<IonIcon slot="icon-only" icon={star}></IonIcon> :
|
||||
<IonIcon slot="icon-only" icon={starOutline}></IonIcon>
|
||||
}
|
||||
</IonButton>
|
||||
<IonButton onClick={() => shareSession}>
|
||||
<IonIcon slot="icon-only" icon={share}></IonIcon>
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<div className="ion-padding">
|
||||
<h1>{session.name}</h1>
|
||||
{session.tracks.map(track => (
|
||||
<span key={track} className={`session-track-${track.toLowerCase()}`}>{track}</span>
|
||||
))}
|
||||
<p>{session.description}</p>
|
||||
<IonText color="medium">
|
||||
<Time date={session.dateTimeStart} /> – <Time date={session.dateTimeEnd} />
|
||||
<br />
|
||||
{session.location}
|
||||
</IonText>
|
||||
</div>
|
||||
<IonList>
|
||||
<IonItem onClick={() => sessionClick('watch')} button>
|
||||
<IonLabel color="primary">Watch</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem onClick={() => sessionClick('add to calendar')} button>
|
||||
<IonLabel color="primary">Add to Calendar</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem onClick={() => sessionClick('mark as unwatched')} button>
|
||||
<IonLabel color="primary">Mark as Unwatched</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem onClick={() => sessionClick('download video')} button>
|
||||
<IonLabel color="primary">Download Video</IonLabel>
|
||||
<IonIcon slot="end" color="primary" size="small" icon={cloudDownload}></IonIcon>
|
||||
</IonItem>
|
||||
<IonItem onClick={() => sessionClick('leave feedback')} button>
|
||||
<IonLabel color="primary">Leave Feedback</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state, OwnProps) => ({
|
||||
session: selectors.getSession(state, OwnProps),
|
||||
favoriteSessions: state.data.favorites
|
||||
}),
|
||||
mapDispatchToProps: {
|
||||
addFavorite,
|
||||
removeFavorite
|
||||
},
|
||||
component: withRouter(SessionDetail)
|
||||
})
|
||||
@@ -1,111 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, IonButtons, IonMenuButton, IonRow, IonCol, IonButton, IonList, IonItem, IonLabel, IonInput, IonText } from '@ionic/react';
|
||||
import './Login.scss';
|
||||
import { setIsLoggedIn, setUsername } from '../data/user/user.actions';
|
||||
import { connect } from '../data/connect';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
|
||||
interface OwnProps extends RouteComponentProps {}
|
||||
|
||||
interface DispatchProps {
|
||||
setIsLoggedIn: typeof setIsLoggedIn;
|
||||
setUsername: typeof setUsername;
|
||||
}
|
||||
|
||||
interface LoginProps extends OwnProps, DispatchProps { }
|
||||
|
||||
const Login: React.FC<LoginProps> = ({setIsLoggedIn, history, setUsername: setUsernameAction}) => {
|
||||
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [formSubmitted, setFormSubmitted] = useState(false);
|
||||
const [usernameError, setUsernameError] = useState(false);
|
||||
const [passwordError, setPasswordError] = useState(false);
|
||||
|
||||
const login = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setFormSubmitted(true);
|
||||
if(!username) {
|
||||
setUsernameError(true);
|
||||
}
|
||||
if(!password) {
|
||||
setPasswordError(true);
|
||||
}
|
||||
|
||||
if(username && password) {
|
||||
await setIsLoggedIn(true);
|
||||
await setUsernameAction(username);
|
||||
history.push('/tabs/schedule', {direction: 'none'});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="signup-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton></IonMenuButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Signup</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
|
||||
<div className="login-logo">
|
||||
<img src="assets/img/appicon.svg" alt="Ionic logo" />
|
||||
</div>
|
||||
|
||||
<form noValidate onSubmit={login}>
|
||||
<IonList>
|
||||
<IonItem>
|
||||
<IonLabel position="stacked" color="primary">Username</IonLabel>
|
||||
<IonInput name="username" type="text" value={username} spellCheck={false} autocapitalize="off" onIonChange={e => {
|
||||
setUsername(e.detail.value!);
|
||||
setUsernameError(false);
|
||||
}}
|
||||
required>
|
||||
</IonInput>
|
||||
</IonItem>
|
||||
|
||||
{formSubmitted && usernameError && <IonText color="danger">
|
||||
<p className="ion-padding-start">
|
||||
Username is required
|
||||
</p>
|
||||
</IonText>}
|
||||
|
||||
<IonItem>
|
||||
<IonLabel position="stacked" color="primary">Password</IonLabel>
|
||||
<IonInput name="password" type="password" value={password} onIonChange={e => {
|
||||
setPassword(e.detail.value!);
|
||||
setPasswordError(false);
|
||||
}}>
|
||||
</IonInput>
|
||||
</IonItem>
|
||||
|
||||
{formSubmitted && passwordError && <IonText color="danger">
|
||||
<p className="ion-padding-start">
|
||||
Password is required
|
||||
</p>
|
||||
</IonText>}
|
||||
</IonList>
|
||||
|
||||
<IonRow>
|
||||
<IonCol>
|
||||
<IonButton type="submit" expand="block">Create</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</form>
|
||||
|
||||
</IonContent>
|
||||
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, {}, DispatchProps>({
|
||||
mapDispatchToProps: {
|
||||
setIsLoggedIn,
|
||||
setUsername
|
||||
},
|
||||
component: Login
|
||||
})
|
||||
@@ -1,11 +0,0 @@
|
||||
#speaker-detail {
|
||||
img {
|
||||
max-width: 140px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
p {
|
||||
color: #60646B;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { IonIcon, IonHeader, IonToolbar, IonButtons, IonTitle, IonContent, IonButton, IonBackButton, IonPage } from '@ionic/react'
|
||||
import './SpeakerDetail.scss';
|
||||
import { logoTwitter, logoGithub, logoInstagram } from 'ionicons/icons';
|
||||
import { connect } from '../data/connect';
|
||||
import * as selectors from '../data/selectors';
|
||||
import { Speaker } from '../models/Speaker';
|
||||
|
||||
interface OwnProps extends RouteComponentProps {
|
||||
speaker?: Speaker;
|
||||
};
|
||||
|
||||
interface StateProps {};
|
||||
|
||||
interface DispatchProps {};
|
||||
|
||||
interface SpeakerDetailProps extends OwnProps, StateProps, DispatchProps {};
|
||||
|
||||
const SpeakerDetail: React.FC<SpeakerDetailProps> = ({ speaker }) => {
|
||||
|
||||
if (!speaker) {
|
||||
return <div>Speaker not found</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage id="speaker-detail">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton defaultHref="/tabs/speakers" />
|
||||
</IonButtons>
|
||||
<IonTitle>{speaker.name}</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent className="ion-padding speaker-detail speaker-page-list">
|
||||
<div className="ion-text-center">
|
||||
<img src={speaker.profilePic} alt={speaker.name} />
|
||||
<br />
|
||||
<IonButton fill="clear" size="small" color="twitter">
|
||||
<IonIcon icon={logoTwitter} slot="icon-only"></IonIcon>
|
||||
</IonButton>
|
||||
<IonButton fill="clear" size="small" color="github">
|
||||
<IonIcon icon={logoGithub} slot="icon-only"></IonIcon>
|
||||
</IonButton>
|
||||
<IonButton fill="clear" size="small" color="instagram">
|
||||
<IonIcon icon={logoInstagram} slot="icon-only"></IonIcon>
|
||||
</IonButton>
|
||||
</div>
|
||||
|
||||
<p>{speaker.about}</p>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default connect({
|
||||
mapStateToProps: (state, ownProps) => ({
|
||||
speaker: selectors.getSpeaker(state, ownProps)
|
||||
}),
|
||||
component: SpeakerDetail
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
#speaker-list {
|
||||
.scroll {
|
||||
background: #ededed;
|
||||
}
|
||||
|
||||
.speaker-card {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.speaker-card ion-card-header {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.speaker-card ion-card-header .item {
|
||||
padding: 4px 16px;
|
||||
}
|
||||
|
||||
.speaker-card ion-card-content {
|
||||
flex: 1 1 auto;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import React from 'react';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, IonButtons, IonMenuButton, IonList, IonGrid, IonRow, IonCol } from '@ionic/react';
|
||||
import SpeakerItem from '../components/SpeakerItem';
|
||||
import { Speaker } from '../models/Speaker';
|
||||
import { Session } from '../models/Session';
|
||||
import { connect } from '../data/connect';
|
||||
import * as selectors from '../data/selectors';
|
||||
import './SpeakerList.scss';
|
||||
|
||||
interface OwnProps { };
|
||||
|
||||
interface StateProps {
|
||||
speakers: Speaker[];
|
||||
speakerSessions: { [key: number]: Session[] };
|
||||
};
|
||||
|
||||
interface DispatchProps { };
|
||||
|
||||
interface SpeakerListProps extends OwnProps, StateProps, DispatchProps { };
|
||||
|
||||
const SpeakerList: React.FC<SpeakerListProps> = ({ speakers, speakerSessions }) => {
|
||||
|
||||
return (
|
||||
<IonPage id="speaker-list">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Speakers</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent className={`outer-content`}>
|
||||
<IonList>
|
||||
<IonGrid fixed>
|
||||
<IonRow align-items-stretch>
|
||||
{speakers.map(speaker => (
|
||||
<IonCol size="12" size-md="6" key={speaker.id}>
|
||||
<SpeakerItem
|
||||
key={speaker.id}
|
||||
speaker={speaker}
|
||||
sessions={speakerSessions[speaker.id]}
|
||||
/>
|
||||
</IonCol>
|
||||
))}
|
||||
</IonRow>
|
||||
</IonGrid>
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, StateProps, DispatchProps>({
|
||||
mapStateToProps: (state) => ({
|
||||
speakers: selectors.getSpeakers(state),
|
||||
speakerSessions: selectors.getSpeakerSessions(state)
|
||||
}),
|
||||
component: React.memo(SpeakerList)
|
||||
});
|
||||
@@ -1,83 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IonHeader, IonToolbar, IonTitle, IonContent, IonPage, IonButtons, IonMenuButton, IonRow, IonCol, IonButton, IonList, IonItem, IonLabel, IonText, IonTextarea, IonToast } from '@ionic/react';
|
||||
import './Login.scss';
|
||||
import { connect } from '../data/connect';
|
||||
|
||||
interface OwnProps { }
|
||||
|
||||
interface DispatchProps { }
|
||||
|
||||
interface SupportProps extends OwnProps, DispatchProps { }
|
||||
|
||||
const Support: React.FC<SupportProps> = () => {
|
||||
|
||||
const [message, setMessage] = useState('');
|
||||
const [formSubmitted, setFormSubmitted] = useState(false);
|
||||
const [messageError, setMessageError] = useState(false);
|
||||
const [showToast, setShowToast] = useState(false);
|
||||
|
||||
const send = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setFormSubmitted(true);
|
||||
if (!message) {
|
||||
setMessageError(true);
|
||||
}
|
||||
if (message) {
|
||||
setMessage('');
|
||||
setShowToast(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="support-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton></IonMenuButton>
|
||||
</IonButtons>
|
||||
<IonTitle>Support</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
|
||||
<div className="login-logo">
|
||||
<img src="assets/img/appicon.svg" alt="Ionic logo" />
|
||||
</div>
|
||||
|
||||
<form noValidate onSubmit={send}>
|
||||
<IonList>
|
||||
<IonItem>
|
||||
<IonLabel position="stacked" color="primary">Enter your support message below</IonLabel>
|
||||
<IonTextarea name="message" value={message} spellCheck={false} autocapitalize="off" rows={6} onIonChange={e => setMessage(e.detail.value!)}
|
||||
required>
|
||||
</IonTextarea>
|
||||
</IonItem>
|
||||
|
||||
{formSubmitted && messageError && <IonText color="danger">
|
||||
<p className="ion-padding-start">
|
||||
Support message is required
|
||||
</p>
|
||||
</IonText>}
|
||||
</IonList>
|
||||
|
||||
<IonRow>
|
||||
<IonCol>
|
||||
<IonButton type="submit" expand="block">Submit</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</form>
|
||||
|
||||
</IonContent>
|
||||
|
||||
<IonToast
|
||||
isOpen={showToast}
|
||||
duration={3000}
|
||||
message="Your support request has been sent"
|
||||
onDidDismiss={() => setShowToast(false)} />
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, {}, DispatchProps>({
|
||||
component: Support
|
||||
})
|
||||
@@ -1,38 +0,0 @@
|
||||
#tutorial-page {
|
||||
ion-toolbar {
|
||||
// TODO test transparent and fullscreen
|
||||
--background: transparent;
|
||||
--border-color: transparent;
|
||||
}
|
||||
|
||||
.swiper-slide {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.slide-title {
|
||||
margin-top: 2.8rem;
|
||||
}
|
||||
|
||||
.slide-image {
|
||||
max-height: 50%;
|
||||
max-width: 60%;
|
||||
margin: 36px 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
b {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 0 40px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: var(--ion-color-step-600, #60646b);
|
||||
|
||||
b {
|
||||
color: var(--ion-text-color, #000000);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { IonContent, IonPage, IonHeader, IonToolbar, IonButtons, IonButton, IonSlides, IonSlide, IonIcon } from '@ionic/react';
|
||||
import { arrowForward } from 'ionicons/icons';
|
||||
import { setHasSeenTutorial } from '../data/user/user.actions';
|
||||
import './Tutorial.scss';
|
||||
import { connect } from '../data/connect';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
|
||||
interface OwnProps extends RouteComponentProps {};
|
||||
|
||||
interface DispatchProps {
|
||||
setHasSeenTutorial: typeof setHasSeenTutorial
|
||||
}
|
||||
|
||||
interface TutorialProps extends OwnProps, DispatchProps { };
|
||||
|
||||
const Tutorial: React.FC<TutorialProps> = ({ history, setHasSeenTutorial }) => {
|
||||
const [showSkip, setShowSkip] = useState(true);
|
||||
const slideRef = useRef<HTMLIonSlidesElement>(null);
|
||||
|
||||
const startApp = async () => {
|
||||
await setHasSeenTutorial(true);
|
||||
history.push('/tabs/schedule', { direction: 'none' });
|
||||
};
|
||||
|
||||
const handleSlideChangeStart = () => {
|
||||
slideRef.current!.isEnd().then(isEnd => setShowSkip(!isEnd));
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage id="tutorial-page">
|
||||
<IonHeader no-border>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="end">
|
||||
{showSkip && <IonButton color='primary' onClick={startApp}>Skip</IonButton>}
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
|
||||
<IonSlides ref={slideRef} onIonSlideWillChange={handleSlideChangeStart} pager={false}>
|
||||
<IonSlide>
|
||||
<img src="assets/img/ica-slidebox-img-1.png" alt="" className="slide-image" />
|
||||
<h2 className="slide-title">
|
||||
Welcome to <b>ICA</b>
|
||||
</h2>
|
||||
<p>
|
||||
The <b>ionic conference app</b> is a practical preview of the ionic framework in action, and a demonstration of proper code use.
|
||||
</p>
|
||||
</IonSlide>
|
||||
|
||||
<IonSlide>
|
||||
<img src="assets/img/ica-slidebox-img-2.png" alt="" className="slide-image" />
|
||||
<h2 className="slide-title">What is Ionic?</h2>
|
||||
<p>
|
||||
<b>Ionic Framework</b> is an open source SDK that enables developers to build high quality mobile apps with web technologies like HTML, CSS, and JavaScript.
|
||||
</p>
|
||||
</IonSlide>
|
||||
|
||||
<IonSlide>
|
||||
<img src="assets/img/ica-slidebox-img-3.png" alt="" className="slide-image" />
|
||||
<h2 className="slide-title">What is Ionic Appflow?</h2>
|
||||
<p>
|
||||
<b>Ionic Appflow</b> is a powerful set of services and features built on top of Ionic Framework that brings a totally new level of app development agility to mobile dev teams.
|
||||
</p>
|
||||
</IonSlide>
|
||||
|
||||
<IonSlide>
|
||||
<img src="assets/img/ica-slidebox-img-4.png" alt="" className="slide-image" />
|
||||
<h2 className="slide-title">Ready to Play?</h2>
|
||||
<IonButton fill="clear" onClick={startApp}>
|
||||
Continue
|
||||
<IonIcon slot="end" icon={arrowForward} />
|
||||
</IonButton>
|
||||
</IonSlide>
|
||||
</IonSlides>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect<OwnProps, {}, DispatchProps>({
|
||||
mapDispatchToProps: ({
|
||||
setHasSeenTutorial
|
||||
}),
|
||||
component: Tutorial
|
||||
});
|
||||
145
examples/ionic-react/src/serviceWorker.ts
Normal file
145
examples/ionic-react/src/serviceWorker.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
// This optional code is used to register a service worker.
|
||||
// register() is not called by default.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on subsequent visits to a page, after all the
|
||||
// existing tabs open on the page have been closed, since previously cached
|
||||
// resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model and instructions on how to
|
||||
// opt-in, read https://bit.ly/CRA-PWA
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
);
|
||||
|
||||
type Config = {
|
||||
onSuccess?: (registration: ServiceWorkerRegistration) => void;
|
||||
onUpdate?: (registration: ServiceWorkerRegistration) => void;
|
||||
};
|
||||
|
||||
export function register(config?: Config) {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(
|
||||
process.env.PUBLIC_URL,
|
||||
window.location.href
|
||||
);
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl, config);
|
||||
|
||||
// Add some additional logging to localhost, pointing developers to the
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
'This web app is being served cache-first by a service ' +
|
||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// Is not localhost. Just register service worker
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl: string, config?: Config) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
if (installingWorker == null) {
|
||||
return;
|
||||
}
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the updated precached content has been fetched,
|
||||
// but the previous service worker will still serve the older
|
||||
// content until all client tabs are closed.
|
||||
console.log(
|
||||
'New content is available and will be used when all ' +
|
||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
||||
);
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onUpdate) {
|
||||
config.onUpdate(registration);
|
||||
}
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.');
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onSuccess) {
|
||||
config.onSuccess(registration);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl: string, config?: Config) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl, {
|
||||
headers: { 'Service-Worker': 'script' }
|
||||
})
|
||||
.then(response => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (
|
||||
response.status === 404 ||
|
||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister();
|
||||
});
|
||||
}
|
||||
}
|
||||
5
examples/ionic-react/src/setupTests.ts
Normal file
5
examples/ionic-react/src/setupTests.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
@@ -1,179 +0,0 @@
|
||||
|
||||
/* Ionic Variables and Theming. For more information, please see
|
||||
// https://beta.ionicframework.com/docs/theming/
|
||||
// The app direction is used to include
|
||||
// rtl styles in your app. For more information, please see
|
||||
// https://beta.ionicframework.com/docs/layout/rtl
|
||||
// $app-direction: ltr;
|
||||
// Ionic Colors
|
||||
// --------------------------------------------------
|
||||
// Named colors makes it easy to reuse colors on various components.
|
||||
// It's highly recommended to change the default colors
|
||||
// to match your app's branding. Ionic provides eight layered colors
|
||||
// that can be changed to theme an app. Additional colors can be
|
||||
// added as well (see below). For more information, please see
|
||||
// https://beta.ionicframework.com/docs/theming/advanced
|
||||
// To easily create custom color palettes for your app’s UI,
|
||||
// check out our color generator:
|
||||
// https://beta.ionicframework.com/docs/theming/color-generator
|
||||
*/
|
||||
|
||||
:root {
|
||||
--ion-color-angular: #ac282b;
|
||||
--ion-color-communication: #8e8d93;
|
||||
--ion-color-tooling: #fe4c52;
|
||||
--ion-color-services: #fd8b2d;
|
||||
--ion-color-design: #fed035;
|
||||
--ion-color-workshop: #69bb7b;
|
||||
--ion-color-food: #3bc7c4;
|
||||
--ion-color-documentation: #b16be3;
|
||||
--ion-color-navigation: #6600cc;
|
||||
|
||||
--ion-color-primary: #3880ff;
|
||||
--ion-color-primary-rgb: 56, 128, 255;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
--ion-color-primary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-primary-shade: #3171e0;
|
||||
--ion-color-primary-tint: #4c8dff;
|
||||
|
||||
--ion-color-secondary: #0cd1e8;
|
||||
--ion-color-secondary-rgb: 12, 209, 232;
|
||||
--ion-color-secondary-contrast: #ffffff;
|
||||
--ion-color-secondary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-secondary-shade: #0bb8cc;
|
||||
--ion-color-secondary-tint: #24d6ea;
|
||||
|
||||
--ion-color-tertiary: #7044ff;
|
||||
--ion-color-tertiary-rgb: 112, 68, 255;
|
||||
--ion-color-tertiary-contrast: #ffffff;
|
||||
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-tertiary-shade: #633ce0;
|
||||
--ion-color-tertiary-tint: #7e57ff;
|
||||
|
||||
--ion-color-success: #10dc60;
|
||||
--ion-color-success-rgb: 16, 220, 96;
|
||||
--ion-color-success-contrast: #ffffff;
|
||||
--ion-color-success-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-success-shade: #0ec254;
|
||||
--ion-color-success-tint: #28e070;
|
||||
|
||||
--ion-color-warning: #ffce00;
|
||||
--ion-color-warning-rgb: 255, 206, 0;
|
||||
--ion-color-warning-contrast: #ffffff;
|
||||
--ion-color-warning-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-warning-shade: #e0b500;
|
||||
--ion-color-warning-tint: #ffd31a;
|
||||
|
||||
--ion-color-danger: #f04141;
|
||||
--ion-color-danger-rgb: 245, 61, 61;
|
||||
--ion-color-danger-contrast: #ffffff;
|
||||
--ion-color-danger-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-danger-shade: #d33939;
|
||||
--ion-color-danger-tint: #f25454;
|
||||
|
||||
--ion-color-dark: #222428;
|
||||
--ion-color-dark-rgb: 34, 34, 34;
|
||||
--ion-color-dark-contrast: #ffffff;
|
||||
--ion-color-dark-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-dark-shade: #1e2023;
|
||||
--ion-color-dark-tint: #383a3e;
|
||||
|
||||
--ion-color-medium: #989aa2;
|
||||
--ion-color-medium-rgb: 152, 154, 162;
|
||||
--ion-color-medium-contrast: #ffffff;
|
||||
--ion-color-medium-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-medium-shade: #86888f;
|
||||
--ion-color-medium-tint: #a2a4ab;
|
||||
|
||||
--ion-color-light: #f4f5f8;
|
||||
--ion-color-light-rgb: 244, 244, 244;
|
||||
--ion-color-light-contrast: #000000;
|
||||
--ion-color-light-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-light-shade: #d7d8da;
|
||||
--ion-color-light-tint: #f5f6f9;
|
||||
}
|
||||
|
||||
/* Additional Ionic Colors
|
||||
// --------------------------------------------------
|
||||
// In order to add colors to be used with Ionic components,
|
||||
// the color should be added as a class with the convention `.ion-color-{COLOR}`
|
||||
// where `{COLOR}` is the color to be used on the Ionic component
|
||||
// and each variant is defined for the color. For more information, please see
|
||||
// https://beta.ionicframework.com/docs/theming/advanced
|
||||
*/
|
||||
|
||||
.ion-color-favorite {
|
||||
--ion-color-base: #69bb7b;
|
||||
--ion-color-base-rgb: 105, 187, 123;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #5ca56c;
|
||||
--ion-color-tint: #78c288;
|
||||
}
|
||||
|
||||
.ion-color-twitter {
|
||||
--ion-color-base: #1da1f4;
|
||||
--ion-color-base-rgb: 29, 161, 244;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #1a8ed7;
|
||||
--ion-color-tint: #34aaf5;
|
||||
}
|
||||
|
||||
.ion-color-google {
|
||||
--ion-color-base: #dc4a38;
|
||||
--ion-color-base-rgb: 220, 74, 56;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #c24131;
|
||||
--ion-color-tint: #e05c4c;
|
||||
}
|
||||
|
||||
.ion-color-vimeo {
|
||||
--ion-color-base: #23b6ea;
|
||||
--ion-color-base-rgb: 35, 182, 234;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #1fa0ce;
|
||||
--ion-color-tint: #39bdec;
|
||||
}
|
||||
|
||||
.ion-color-facebook {
|
||||
--ion-color-base: #3b5998;
|
||||
--ion-color-base-rgb: 59, 89, 152;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #344e86;
|
||||
--ion-color-tint: #4f6aa2;
|
||||
}
|
||||
|
||||
/* Shared Variables
|
||||
// --------------------------------------------------
|
||||
// To customize the look and feel of this app, you can override
|
||||
// the CSS variables found in Ionic's source files.
|
||||
// To view all the possible Ionic variables, see:
|
||||
// https://beta.ionicframework.com/docs/theming/css-variables#ionic-variables
|
||||
*/
|
||||
|
||||
:root {
|
||||
--ion-headings-font-weight: 300;
|
||||
|
||||
--ion-color-angular: #ac282b;
|
||||
--ion-color-communication: #8e8d93;
|
||||
--ion-color-tooling: #fe4c52;
|
||||
--ion-color-services: #fd8b2d;
|
||||
--ion-color-design: #fed035;
|
||||
--ion-color-workshop: #69bb7b;
|
||||
--ion-color-food: #3bc7c4;
|
||||
--ion-color-documentation: #b16be3;
|
||||
--ion-color-navigation: #6600cc;
|
||||
}
|
||||
|
||||
.md {
|
||||
--ion-toolbar-background: var(--ion-color-primary);
|
||||
--ion-toolbar-color: #fff;
|
||||
--ion-toolbar-color-activated: #fff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,34 +1,9 @@
|
||||
/* Ionic Variables and Theming. For more info, please see:
|
||||
http://ionicframework.com/docs/theming/ */
|
||||
|
||||
/* Ionic Variables and Theming. For more information, please see
|
||||
// https://beta.ionicframework.com/docs/theming/
|
||||
// The app direction is used to include
|
||||
// rtl styles in your app. For more information, please see
|
||||
// https://beta.ionicframework.com/docs/layout/rtl
|
||||
// $app-direction: ltr;
|
||||
// Ionic Colors
|
||||
// --------------------------------------------------
|
||||
// Named colors makes it easy to reuse colors on various components.
|
||||
// It's highly recommended to change the default colors
|
||||
// to match your app's branding. Ionic provides eight layered colors
|
||||
// that can be changed to theme an app. Additional colors can be
|
||||
// added as well (see below). For more information, please see
|
||||
// https://beta.ionicframework.com/docs/theming/advanced
|
||||
// To easily create custom color palettes for your app’s UI,
|
||||
// check out our color generator:
|
||||
// https://beta.ionicframework.com/docs/theming/color-generator
|
||||
*/
|
||||
|
||||
/** Ionic CSS Variables **/
|
||||
:root {
|
||||
--ion-color-angular: #ac282b;
|
||||
--ion-color-communication: #8e8d93;
|
||||
--ion-color-tooling: #fe4c52;
|
||||
--ion-color-services: #fd8b2d;
|
||||
--ion-color-design: #fed035;
|
||||
--ion-color-workshop: #69bb7b;
|
||||
--ion-color-food: #3bc7c4;
|
||||
--ion-color-documentation: #b16be3;
|
||||
--ion-color-navigation: #6600cc;
|
||||
|
||||
/** primary **/
|
||||
--ion-color-primary: #3880ff;
|
||||
--ion-color-primary-rgb: 56, 128, 255;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
@@ -36,6 +11,7 @@
|
||||
--ion-color-primary-shade: #3171e0;
|
||||
--ion-color-primary-tint: #4c8dff;
|
||||
|
||||
/** secondary **/
|
||||
--ion-color-secondary: #0cd1e8;
|
||||
--ion-color-secondary-rgb: 12, 209, 232;
|
||||
--ion-color-secondary-contrast: #ffffff;
|
||||
@@ -43,6 +19,7 @@
|
||||
--ion-color-secondary-shade: #0bb8cc;
|
||||
--ion-color-secondary-tint: #24d6ea;
|
||||
|
||||
/** tertiary **/
|
||||
--ion-color-tertiary: #7044ff;
|
||||
--ion-color-tertiary-rgb: 112, 68, 255;
|
||||
--ion-color-tertiary-contrast: #ffffff;
|
||||
@@ -50,6 +27,7 @@
|
||||
--ion-color-tertiary-shade: #633ce0;
|
||||
--ion-color-tertiary-tint: #7e57ff;
|
||||
|
||||
/** success **/
|
||||
--ion-color-success: #10dc60;
|
||||
--ion-color-success-rgb: 16, 220, 96;
|
||||
--ion-color-success-contrast: #ffffff;
|
||||
@@ -57,6 +35,7 @@
|
||||
--ion-color-success-shade: #0ec254;
|
||||
--ion-color-success-tint: #28e070;
|
||||
|
||||
/** warning **/
|
||||
--ion-color-warning: #ffce00;
|
||||
--ion-color-warning-rgb: 255, 206, 0;
|
||||
--ion-color-warning-contrast: #ffffff;
|
||||
@@ -64,6 +43,7 @@
|
||||
--ion-color-warning-shade: #e0b500;
|
||||
--ion-color-warning-tint: #ffd31a;
|
||||
|
||||
/** danger **/
|
||||
--ion-color-danger: #f04141;
|
||||
--ion-color-danger-rgb: 245, 61, 61;
|
||||
--ion-color-danger-contrast: #ffffff;
|
||||
@@ -71,6 +51,7 @@
|
||||
--ion-color-danger-shade: #d33939;
|
||||
--ion-color-danger-tint: #f25454;
|
||||
|
||||
/** dark **/
|
||||
--ion-color-dark: #222428;
|
||||
--ion-color-dark-rgb: 34, 34, 34;
|
||||
--ion-color-dark-contrast: #ffffff;
|
||||
@@ -78,6 +59,7 @@
|
||||
--ion-color-dark-shade: #1e2023;
|
||||
--ion-color-dark-tint: #383a3e;
|
||||
|
||||
/** medium **/
|
||||
--ion-color-medium: #989aa2;
|
||||
--ion-color-medium-rgb: 152, 154, 162;
|
||||
--ion-color-medium-contrast: #ffffff;
|
||||
@@ -85,6 +67,7 @@
|
||||
--ion-color-medium-shade: #86888f;
|
||||
--ion-color-medium-tint: #a2a4ab;
|
||||
|
||||
/** light **/
|
||||
--ion-color-light: #f4f5f8;
|
||||
--ion-color-light-rgb: 244, 244, 244;
|
||||
--ion-color-light-contrast: #000000;
|
||||
@@ -92,229 +75,3 @@
|
||||
--ion-color-light-shade: #d7d8da;
|
||||
--ion-color-light-tint: #f5f6f9;
|
||||
}
|
||||
|
||||
/* Additional Ionic Colors
|
||||
// --------------------------------------------------
|
||||
// In order to add colors to be used with Ionic components,
|
||||
// the color should be added as a class with the convention `.ion-color-{COLOR}`
|
||||
// where `{COLOR}` is the color to be used on the Ionic component
|
||||
// and each variant is defined for the color. For more information, please see
|
||||
// https://beta.ionicframework.com/docs/theming/advanced
|
||||
*/
|
||||
|
||||
.ion-color-favorite {
|
||||
--ion-color-base: #69bb7b;
|
||||
--ion-color-base-rgb: 105, 187, 123;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #5ca56c;
|
||||
--ion-color-tint: #78c288;
|
||||
}
|
||||
|
||||
.ion-color-twitter {
|
||||
--ion-color-base: #1da1f4;
|
||||
--ion-color-base-rgb: 29, 161, 244;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #1a8ed7;
|
||||
--ion-color-tint: #34aaf5;
|
||||
}
|
||||
|
||||
.ion-color-google {
|
||||
--ion-color-base: #dc4a38;
|
||||
--ion-color-base-rgb: 220, 74, 56;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #c24131;
|
||||
--ion-color-tint: #e05c4c;
|
||||
}
|
||||
|
||||
.ion-color-vimeo {
|
||||
--ion-color-base: #23b6ea;
|
||||
--ion-color-base-rgb: 35, 182, 234;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #1fa0ce;
|
||||
--ion-color-tint: #39bdec;
|
||||
}
|
||||
|
||||
.ion-color-facebook {
|
||||
--ion-color-base: #3b5998;
|
||||
--ion-color-base-rgb: 59, 89, 152;
|
||||
--ion-color-contrast: #ffffff;
|
||||
--ion-color-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-shade: #344e86;
|
||||
--ion-color-tint: #4f6aa2;
|
||||
}
|
||||
|
||||
/* Shared Variables
|
||||
// --------------------------------------------------
|
||||
// To customize the look and feel of this app, you can override
|
||||
// the CSS variables found in Ionic's source files.
|
||||
// To view all the possible Ionic variables, see:
|
||||
// https://beta.ionicframework.com/docs/theming/css-variables#ionic-variables
|
||||
*/
|
||||
|
||||
:root {
|
||||
--ion-headings-font-weight: 300;
|
||||
|
||||
--ion-color-angular: #ac282b;
|
||||
--ion-color-communication: #8e8d93;
|
||||
--ion-color-tooling: #fe4c52;
|
||||
--ion-color-services: #fd8b2d;
|
||||
--ion-color-design: #fed035;
|
||||
--ion-color-workshop: #69bb7b;
|
||||
--ion-color-food: #3bc7c4;
|
||||
--ion-color-documentation: #b16be3;
|
||||
--ion-color-navigation: #6600cc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dark Theme
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
.dark-theme {
|
||||
--ion-color-primary: #428cff;
|
||||
--ion-color-primary-rgb: 66,140,255;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
--ion-color-primary-contrast-rgb: 255,255,255;
|
||||
--ion-color-primary-shade: #3a7be0;
|
||||
--ion-color-primary-tint: #5598ff;
|
||||
|
||||
--ion-color-secondary: #50c8ff;
|
||||
--ion-color-secondary-rgb: 80,200,255;
|
||||
--ion-color-secondary-contrast: #ffffff;
|
||||
--ion-color-secondary-contrast-rgb: 255,255,255;
|
||||
--ion-color-secondary-shade: #46b0e0;
|
||||
--ion-color-secondary-tint: #62ceff;
|
||||
|
||||
--ion-color-tertiary: #6a64ff;
|
||||
--ion-color-tertiary-rgb: 106,100,255;
|
||||
--ion-color-tertiary-contrast: #ffffff;
|
||||
--ion-color-tertiary-contrast-rgb: 255,255,255;
|
||||
--ion-color-tertiary-shade: #5d58e0;
|
||||
--ion-color-tertiary-tint: #7974ff;
|
||||
|
||||
--ion-color-success: #2fdf75;
|
||||
--ion-color-success-rgb: 47,223,117;
|
||||
--ion-color-success-contrast: #000000;
|
||||
--ion-color-success-contrast-rgb: 0,0,0;
|
||||
--ion-color-success-shade: #29c467;
|
||||
--ion-color-success-tint: #44e283;
|
||||
|
||||
--ion-color-warning: #ffd534;
|
||||
--ion-color-warning-rgb: 255,213,52;
|
||||
--ion-color-warning-contrast: #000000;
|
||||
--ion-color-warning-contrast-rgb: 0,0,0;
|
||||
--ion-color-warning-shade: #e0bb2e;
|
||||
--ion-color-warning-tint: #ffd948;
|
||||
|
||||
--ion-color-danger: #ff4961;
|
||||
--ion-color-danger-rgb: 255,73,97;
|
||||
--ion-color-danger-contrast: #ffffff;
|
||||
--ion-color-danger-contrast-rgb: 255,255,255;
|
||||
--ion-color-danger-shade: #e04055;
|
||||
--ion-color-danger-tint: #ff5b71;
|
||||
|
||||
--ion-color-dark: #f4f5f8;
|
||||
--ion-color-dark-rgb: 244,245,248;
|
||||
--ion-color-dark-contrast: #000000;
|
||||
--ion-color-dark-contrast-rgb: 0,0,0;
|
||||
--ion-color-dark-shade: #d7d8da;
|
||||
--ion-color-dark-tint: #f5f6f9;
|
||||
|
||||
--ion-color-medium: #989aa2;
|
||||
--ion-color-medium-rgb: 152,154,162;
|
||||
--ion-color-medium-contrast: #000000;
|
||||
--ion-color-medium-contrast-rgb: 0,0,0;
|
||||
--ion-color-medium-shade: #86888f;
|
||||
--ion-color-medium-tint: #a2a4ab;
|
||||
|
||||
--ion-color-light: #222428;
|
||||
--ion-color-light-rgb: 34,36,40;
|
||||
--ion-color-light-contrast: #ffffff;
|
||||
--ion-color-light-contrast-rgb: 255,255,255;
|
||||
--ion-color-light-shade: #1e2023;
|
||||
--ion-color-light-tint: #383a3e;
|
||||
}
|
||||
|
||||
/*
|
||||
* iOS Dark Theme
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
.dark-theme.ios {
|
||||
--ion-background-color: #000000;
|
||||
--ion-background-color-rgb: 0,0,0;
|
||||
|
||||
--ion-text-color: #ffffff;
|
||||
--ion-text-color-rgb: 255,255,255;
|
||||
|
||||
--ion-color-step-50: #0d0d0d;
|
||||
--ion-color-step-100: #1a1a1a;
|
||||
--ion-color-step-150: #262626;
|
||||
--ion-color-step-200: #333333;
|
||||
--ion-color-step-250: #404040;
|
||||
--ion-color-step-300: #4d4d4d;
|
||||
--ion-color-step-350: #595959;
|
||||
--ion-color-step-400: #666666;
|
||||
--ion-color-step-450: #737373;
|
||||
--ion-color-step-500: #808080;
|
||||
--ion-color-step-550: #8c8c8c;
|
||||
--ion-color-step-600: #999999;
|
||||
--ion-color-step-650: #a6a6a6;
|
||||
--ion-color-step-700: #b3b3b3;
|
||||
--ion-color-step-750: #bfbfbf;
|
||||
--ion-color-step-800: #cccccc;
|
||||
--ion-color-step-850: #d9d9d9;
|
||||
--ion-color-step-900: #e6e6e6;
|
||||
--ion-color-step-950: #f2f2f2;
|
||||
|
||||
--ion-toolbar-background: #0d0d0d;
|
||||
|
||||
--ion-item-background: #1c1c1c;
|
||||
--ion-item-background-activated: #313131;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Material Design Dark Theme
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
.dark-theme.md {
|
||||
--ion-background-color: #121212;
|
||||
--ion-background-color-rgb: 18,18,18;
|
||||
|
||||
--ion-text-color: #ffffff;
|
||||
--ion-text-color-rgb: 255,255,255;
|
||||
|
||||
--ion-border-color: #222222;
|
||||
|
||||
--ion-color-step-50: #1e1e1e;
|
||||
--ion-color-step-100: #2a2a2a;
|
||||
--ion-color-step-150: #363636;
|
||||
--ion-color-step-200: #414141;
|
||||
--ion-color-step-250: #4d4d4d;
|
||||
--ion-color-step-300: #595959;
|
||||
--ion-color-step-350: #656565;
|
||||
--ion-color-step-400: #717171;
|
||||
--ion-color-step-450: #7d7d7d;
|
||||
--ion-color-step-500: #898989;
|
||||
--ion-color-step-550: #949494;
|
||||
--ion-color-step-600: #a0a0a0;
|
||||
--ion-color-step-650: #acacac;
|
||||
--ion-color-step-700: #b8b8b8;
|
||||
--ion-color-step-750: #c4c4c4;
|
||||
--ion-color-step-800: #d0d0d0;
|
||||
--ion-color-step-850: #dbdbdb;
|
||||
--ion-color-step-900: #e7e7e7;
|
||||
--ion-color-step-950: #f3f3f3;
|
||||
|
||||
--ion-item-background: #1e1e1e;
|
||||
|
||||
--ion-toolbar-background: #1f1f1f;
|
||||
|
||||
--ion-tab-bar-background: #1f1f1f;
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
export interface DispatchObject {
|
||||
[key: string]: any;
|
||||
type: string;
|
||||
}
|
||||
|
||||
type PromiseResolveValue<T> = T extends Promise<infer R> ? R : T;
|
||||
type EffectType<T extends (...args: any) => any> = ReturnType<ReturnType<T>>;
|
||||
type EffectReturnValue<T extends (...args: any) => any> = PromiseResolveValue<
|
||||
EffectType<T>
|
||||
>;
|
||||
export type ActionType<T extends (...args: any) => any> = ReturnType<
|
||||
T
|
||||
> extends DispatchObject
|
||||
? ReturnType<T>
|
||||
: EffectReturnValue<T>;
|
||||
@@ -13,7 +13,7 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "preserve"
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": ["tslint-react"]
|
||||
}
|
||||
@@ -25,18 +25,3 @@ You can deploy your new Jekyll project with a single command from your terminal
|
||||
```shell
|
||||
$ now
|
||||
```
|
||||
|
||||
### Example Changes
|
||||
|
||||
This example adds a `package.json` file with the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "jekyll build && mv _site public"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This instructs ZEIT Now to build the Jekyll website and move the output to the public directory.
|
||||
|
||||
@@ -11,12 +11,12 @@ var Hello = {
|
||||
{
|
||||
onclick: function() {
|
||||
count++;
|
||||
},
|
||||
}
|
||||
},
|
||||
count + ' Clicks'
|
||||
),
|
||||
)
|
||||
]);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var Splash = {
|
||||
@@ -24,14 +24,14 @@ var Splash = {
|
||||
return m(
|
||||
'a',
|
||||
{
|
||||
href: '#!/hello',
|
||||
href: '#!/hello'
|
||||
},
|
||||
'Enter!'
|
||||
);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
m.route(root, '/splash', {
|
||||
'/splash': Splash,
|
||||
'/hello': Hello,
|
||||
'/hello': Hello
|
||||
});
|
||||
|
||||
1
examples/nextjs/.gitignore
vendored
1
examples/nextjs/.gitignore
vendored
@@ -13,7 +13,6 @@
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Next.js Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Polymer Example
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Preact Example
|
||||
|
||||
|
||||
@@ -3,5 +3,5 @@ import { component } from 'riot';
|
||||
import Random from './random.riot';
|
||||
|
||||
component(Random)(document.getElementById('app'), {
|
||||
title: 'Hi there!',
|
||||
title: 'Hi there!'
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ module.exports = {
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'public'),
|
||||
publicPath: '/public/',
|
||||
filename: 'bundle.js',
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
devtool: 'inline',
|
||||
module: {
|
||||
@@ -18,10 +18,10 @@ module.exports = {
|
||||
{
|
||||
loader: '@riotjs/webpack-loader',
|
||||
options: {
|
||||
hot: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
hot: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
@@ -29,10 +29,10 @@ module.exports = {
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/preset-env'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
presets: ['@babel/preset-env']
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Saber Example
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user