Compare commits

..

63 Commits

Author SHA1 Message Date
Andy Bitz
11bbda977d Publish Stable
- @now/frameworks@0.0.7
 - @now/next@2.3.11
2020-01-24 17:20:55 +01:00
Andy Bitz
62c050f394 Publish Canary
- now@17.0.0-canary.18
 - now-client@7.0.0-canary.1
2020-01-24 17:19:27 +01:00
Andy
bd5a013312 [now-client] (Major) Remove builds check from now-client and change events (#3648)
* [now-client][now-cli] Remove builds check from now-client

* [now-client] Adjust README and change version

* Change events and adjust build error

* Use message from error

* Update packages/now-cli/src/util/deploy/process-deployment.ts

Co-Authored-By: Luc <luc.leray@gmail.com>

* [now-cli] Rename event

* Make types more consistent

* Fix type in process-legacy-deployment

* Adjust type in test

* Update type

* Make events type simpler

Co-authored-by: Max <8418866+rdev@users.noreply.github.com>
Co-authored-by: Luc <luc.leray@gmail.com>
2020-01-24 17:18:56 +01:00
Andy Bitz
1823cf452e Publish Canary
- @now/next@2.3.11-canary.4
2020-01-24 16:09:18 +01:00
Andy
c426d72ccf [now-next] Include file mode for pseudo layers (#3655) 2020-01-24 16:08:39 +01:00
Alex Grover
ddf59c052d Remove duplicated line from .gitignore (#3651) 2020-01-24 11:03:51 +01:00
luc
1dcf6e7fb1 Publish Canary
- now@17.0.0-canary.17
2020-01-23 20:06:18 +01:00
Luc
d4f4792988 [now-cli] Add --confirm to help (#3625)
```
$ now --help
[...]
  -c, --confirm                  Confirm default options and skip questions
```
2020-01-23 19:00:55 +00:00
Steven
7e1f2bd10e Publish Canary
- @now/build-utils@1.3.7-canary.0
2020-01-23 12:29:22 -05:00
Steven
a80a1d0c1d [now-build-utils] Fix api directory detection (#3647)
There was an issue where `@now/next` was emitting an api directory with serverless functions but the functions should not be renamed.
2020-01-23 16:54:34 +00:00
Steven
8ff747b4d7 Publish Canary
- @now/next@2.3.11-canary.3
 - @now/routing-utils@1.5.2-canary.2
2020-01-23 09:37:30 -05:00
Steven
aa63b5a581 [github] Update codeowners (#3642)
Added a few more code owners
2020-01-23 00:25:25 +00:00
luc
2094ec3c99 Publish Canary
- now@17.0.0-canary.16
2020-01-23 00:57:56 +01:00
Luc
bf30d10211 [now-cli] Output error if NOW_PROJECT_ID/NOW_ORG_ID is defined without the other (#3630)
* output error if one of both env is missing

* add test

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2020-01-23 00:53:15 +01:00
Luc
ccc03c9c6e [now-cli] Add warning if linked project was deleted or access was removed (#3631)
![image](https://user-images.githubusercontent.com/6616955/72800302-4ff4a880-3c47-11ea-8d74-0ae0c18469da.png)
2020-01-22 23:27:30 +00:00
Steven
4b7367e2dc [now-routing-utils] Fix segments in query string (#3640)
This PR a regression when path segments are used in the query string.

Take a look at the following ASCII Table for why I had to delete certain parts of the parsed url before formatting again.

https://nodejs.org/api/url.html#url_url_strings_and_url_objects

Related to #3539
2020-01-22 22:46:47 +00:00
JJ Kasper
00aa56a095 [now-next] Add headers support for custom-routes (#3494)
This adds support for `headers` in custom-routes which was landed in Next.js. 

This also updates `@now/routing-utils` `convertHeaders` to call `sourceToRegex` to match behavior with Next.js and allow using `segments` to match in the header `source` as not being able to use the same syntax for a header `source` as a `redirect` source could get confusing
2020-01-22 20:45:36 +00:00
Steven
56ae93a2a5 [examples] Fix jekyll readme build command (#3639)
Fixes #3634
2020-01-22 18:19:12 +00:00
JJ Kasper
adb32a09d3 Publish Canary
- @now/next@2.3.11-canary.2
 - @now/routing-utils@1.5.2-canary.1
2020-01-22 11:46:57 -06:00
JJ Kasper
3358d8e44c [now-next] Add handle: miss and handle: hit for custom-routes (#3489)
This is required to match custom-routes behavior in Next.js by checking dynamic routes after each rewrite although is currently blocked on `now dev` also supporting the feature

This reverts commit 0da98a7f5d.
2020-01-22 17:39:27 +00:00
Steven
c3bd2698e8 [now-routing-utils] Disallow "status" in hit phase (#3637)
This will prevent any strange behavior since production already ignores status code in the hit phase.
2020-01-22 17:03:10 +00:00
Steven
a7baa4761d Publish Canary
- now@17.0.0-canary.15
2020-01-21 19:56:55 -05:00
Steven
5dd2daa970 [now dev] Add support for handle: miss and handle: hit (#3537)
- [x] Add tests from now-proxy for `handle: miss`
- [x] Add tests from now-proxy for `handle: hit`
- [x] Add file output renaming when `featHandleMiss` is true (also assign true for now dev)
2020-01-22 00:54:24 +00:00
JJ Kasper
dd36a489ed Publish Canary
- @now/frameworks@0.0.7-canary.0
 - @now/next@2.3.11-canary.1
 - @now/static-build@0.14.11-canary.0
2020-01-20 15:36:58 -06:00
JJ Kasper
2e742209e3 [now-next] Add initial support for static 404 (#3628)
This adds initial support for static 404 pages when enabled for Next.js applications > `9.2.1-canary.3` it also disables tracing/logging related to lambdas when there aren't any lambdas besides the `_error` when a static 404 is being used 

Closes: #3368
2020-01-20 20:55:08 +00:00
Andy
8d13464cba [frameworks][now-static-build] Use hugo -D --gc as build command (#3624)
* [frameworks][now-static-build] Use `hugo --gc` as build command

* Add -D option
2020-01-20 19:46:15 +01:00
Max Rovensky
20fdcfa0af Publish Stable
- @now/static-build@0.14.10
2020-01-18 04:00:08 +08:00
Max
fac004f83c [now-static-build] Remove legacy message from static build (#3618)
* Remove legacy message from static build

* Remove unused config

* Remove unused config
2020-01-17 20:59:10 +01:00
Andy Bitz
5fee4bbad1 Publish Stable
- @now/static-build@0.14.9
2020-01-17 20:21:52 +01:00
Andy
18e4b18839 [now-static-build] Ignore commands for non-zero-config (#3617) 2020-01-17 20:19:53 +01:00
Max Rovensky
b8627fd384 Publish Stable
- @now/static-build@0.14.8
2020-01-18 02:42:30 +08:00
Max
4e2db6f8a5 [now-static-build] Improve static build errors copy (#3616)
* Improve static build errors copy

* Update packages/now-static-build/src/index.ts

Co-Authored-By: Leo Lamprecht <mindrun@icloud.com>

* Update packages/now-static-build/src/index.ts

Co-Authored-By: Leo Lamprecht <mindrun@icloud.com>

Co-authored-by: Leo Lamprecht <mindrun@icloud.com>
2020-01-17 19:36:52 +01:00
Steven
b685a3afdd Publish Canary
- @now/next@2.3.11-canary.0
 - @now/node@1.3.6-canary.0
2020-01-17 13:09:32 -05:00
Steven
aea3f58970 [api] Fix build script (#3615)
I was getting errors when running `yarn build` locally because the public directory already exists.

This will make sure the public directory is deleted before generating it again.
2020-01-17 13:08:41 -05:00
Steven
b0bb90dc11 Bump node-file-trace to 0.5.1 (#3613)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2020-01-17 13:07:59 -05:00
Andy Bitz
2132c2463f Publish Stable
- @now/next@2.3.10
 - @now/static-build@0.14.7
2020-01-17 18:09:16 +01:00
JJ Kasper
60bf6e2420 [now-next] Update custom-routes tests (#3614)
This adds `permanent: boolean` to the redirects in the custom-routes tests for `@now/next` since this field is required for redirects in Next.js in the latest version 

unblocks: https://github.com/zeit/now/pull/3613
2020-01-17 16:42:55 +00:00
Andy Bitz
9badc9048c Publish Canary
- @now/static-build@0.14.7-canary.0
2020-01-17 00:52:38 +01:00
Andy
a74c1921f8 [now-static-build] Fix package.json command for frameworks (#3611)
* [now-static-build] Fix package.json command for frameworks

* Add test

* Rename function
2020-01-17 00:51:55 +01:00
luc
e6044f2e8d Publish Canary
- now@17.0.0-canary.14
2020-01-17 00:35:54 +01:00
Luc
78d1afa25e [now-cli] Add NOW_PROJECT_ID and NOW_ORG_ID env variable (#3588)
This PR adds `NOW_PROJECT_ID` and `NOW_ORG_ID` environment variables as a way to specify a project to deploy to when running `now`.

eg.
```
NOW_ORG_ID=abc NOW_PROJECT_ID=123 now
```

When using these environment variables:
- the folder is not linked to the project (ie. no `.now` is created)
- we interrupt the process and show an error message if the project is not found:
![image](https://user-images.githubusercontent.com/6616955/72369004-7a061200-36ff-11ea-8b37-168f5339a2bb.png)
2020-01-16 23:30:51 +00:00
luc
4792adf524 Publish Canary
- now@17.0.0-canary.13
2020-01-16 23:38:21 +01:00
Luc
4d8a99141b [now-cli] Improve formatting of new deploy command prompts (#3607)
- Adjust error prompt
- Add spinner while loading org and project
- Use lighter gray for infos
- Remove `{` in inspector url (was mistakenly added in https://github.com/zeit/now/pull/3599)
- Show error when entering a project name
- Fix bug with spinner not cleared when error is displayed
- "Finalizing" -> "Completing"

![image](https://user-images.githubusercontent.com/6616955/72535453-da6c8f00-3878-11ea-838d-dcb4367e5120.png)
2020-01-16 22:24:57 +00:00
Luc
7954ddc1d0 [now-cli] Create .gitignore if it doesn't exist (#3606)
When linking a new project, create `.gitignore` with `.now` if it doesn't exist yet. If it exists, adds `.now` to it.

Also adds a test for that.
2020-01-16 22:04:38 +00:00
Max
216a1fd9d2 [now-static-build] Use NowBuildError for static build errors (#3609)
This PR fixes error messages and uses `NowBuildError ` for static build errors
2020-01-16 18:41:28 +00:00
Luc
e5add6750c [now-cli] Remove .only and remove duplicate test (#3605) 2020-01-16 13:40:05 +01:00
Andy Bitz
b7533650e1 Publish Stable
- @now/frameworks@0.0.6
 - @now/build-utils@1.3.6
 - @now/static-build@0.14.6
2020-01-16 13:11:31 +01:00
luc
6272f5ce5c Publish Canary
- now@17.0.0-canary.12
2020-01-16 12:03:36 +01:00
Luc
bde0538efe [now-cli] Fix inspector url with custom domain suffix (#3599)
When the user is using custom domain suffix, the inspector url is a bit different:
```
https://zeit.co/{orgSlug}/{projectName}/{deploymentShortId}/{suffix}
```

This PR adds the necessary logic when constructing the inspector url.

cc @TooTallNate
2020-01-16 00:31:18 +00:00
Nathan Rajlich
5e6775fca0 [now-cli] Display assigned alias for v2 upgraded static legacy deployments (#3601)
Since v2 deployments are auto-assigned aliases, render as such to match more closely to the v2 pipeline when a static deployments was implicitly upgraded to the v2 platform.
2020-01-15 23:59:52 +00:00
Andy Bitz
e05e0dc40e Publish Canary
- @now/frameworks@0.0.6-canary.3
 - @now/build-utils@1.3.6-canary.0
 - @now/static-build@0.14.6-canary.0
2020-01-16 00:13:35 +01:00
Andy
96dbc6d348 [now-static-build][frameworks][examples] Fixes examples and adjust frameworks (#3584)
* [examples] Fix ionic-react example

* [examples] Fix vue example

* [examples] Fix mithril example

* [examples] Fix riot example

* Fix readmes

* [now-static-build] Add Zola

* Add tests

* [now-build-utils][frameworks] Adjust detect framework

* Move zola back

* Undo Hugo detection changes

* [examples] Fix Vue logo path

* [now-static-build] Use package.json script if defined instead of framework command

* [now-static-build] Add buildCommand everywhere

* Remove devCommand from frameworks.ts

* Fix type

* Change output directory

* [now-static-build] Remove minNodeRange

* Remove devCommand
2020-01-16 00:12:55 +01:00
luc
0a63bd47e8 Publish Canary
- now@17.0.0-canary.11
2020-01-15 23:27:08 +01:00
Luc
35dd50eb61 [now-cli] Pre-fill "project name" prompt (#3598)
* pre-fill project name prompt

* add test

* more tests

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2020-01-15 23:26:23 +01:00
Andy
f70ed94c8c [api] Add Sentry (#3597)
* [api] Add Sentry

* Use function name

* Add test error

* Revert "Add test error"

This reverts commit 39cf8a61dad9fcdcb616e418a0deb6ffe9e04ea9.

* Revert "Revert "Add test error""

This reverts commit c718f201da9d80743319ac87e0d4560e718fff53.

* Add logging

* Revert "Add logging"

This reverts commit 39cd46c8bbeef9024e71fe70478068480a51545b.

* Revert "Revert "Revert "Add test error"""

This reverts commit e6f63ee21fabb4ac8fc065b74281dbcdf5811216.

* Ignore .env

* Use init function for Sentry inline
2020-01-15 23:21:03 +01:00
luc
fb0c8600a2 Publish Canary
- now@17.0.0-canary.10
2020-01-15 22:39:11 +01:00
Luc
5023bdd25d [now-cli] Always add two spaces after emojis and update emojis (#3596)
Updates emojis to fix "spacing issues" with previous emojis and always add 2 spaces after emojis.
- ℹ️ -> 📝
- ⚠️ -> 
- ☑️ -> 🔗



Also fixes a typo:
```
Linked to <project> (created .now and added it to .nowignore)
->
Linked to <project> (created .now and added it to .gitignore)
```

It looks like this:

![image](https://user-images.githubusercontent.com/6616955/72445323-d590d800-37b1-11ea-92eb-a3b503a95774.png)

![image](https://user-images.githubusercontent.com/6616955/72445618-47692180-37b2-11ea-9aed-3b3268c82ad3.png)


![image](https://user-images.githubusercontent.com/6616955/72445492-11c43880-37b2-11ea-8091-1e1e8ac8b3a0.png)
2020-01-15 21:37:57 +00:00
Nathan Rajlich
bf4917ccf2 [now-cli] Handle Now v1 to v2 upgrade (#3593)
* Print warnings / notices from the API
* Print `v2` instead of `v1` when upgraded to v2
* Wait for "alias-assigned" event in v1 pipeline when upgraded to v2
2020-01-15 21:13:44 +00:00
Luc
64aff3aef4 [now-cli] Add tests for project linking and framework detection (#3591)
Adds two test cases:
- linking a project and overwriting build settings
- "overwrite settings" prompt should not appear when no framework is detected
2020-01-15 20:54:18 +00:00
Nathan Rajlich
62b87d1ed1 [now-cli] Remove unnecessary console.log() (#3594)
Seems to be a leftover from dd1d9d856.
2020-01-15 14:58:42 +00:00
Andy
5872114c87 Adjust CONTRIBUTING.md for frameworks (#3592) 2020-01-15 04:11:05 +01:00
luc
ce04246051 Publish Canary
- now@17.0.0-canary.9
2020-01-14 22:21:00 +01:00
Luc
02b03d4533 [now-cli] Hide "overwrite project settings?" prompt if no framework is detected (#3589)
This PR updates the framework auto-detection flow in `deploy` command to not show "overwrite project settings?" prompt when the API returns a `framework` property that is `null`.
2020-01-14 21:09:16 +00:00
287 changed files with 17124 additions and 4141 deletions

8
.github/CODEOWNERS vendored
View File

@@ -2,6 +2,7 @@
# https://help.github.com/en/articles/about-code-owners
* @tootallnate @leo
/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 +11,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

View File

@@ -94,3 +94,13 @@ 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
1. Add the framework to the `@now/frameworks` package.
The file is located in `packages/frameworks/frameworks.json`.
2. Add an example to the `examples/` directory.
The name of the directory should equal the slug of the framework in `@now/frameworks`.
The `.github/EXAMPLE_README_TEMPLATE.md` file can be used to create a README for the example.
3. Finally, `@now/static-build` must be adjusted.
The files `packages/now-static-build/src/frameworks.ts` has to be extended.

1
.gitignore vendored
View File

@@ -22,4 +22,5 @@ packages/now-cli/test/fixtures/integration
test/lib/deployment/failed-page.txt
.DS_Store
.next
.env
public

View 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;
}

View 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}`);
}
}

View File

@@ -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.',
},
});
}
};
}

View File

@@ -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",

View File

@@ -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"

View File

@@ -1,4 +1,4 @@
![Angular Logo](../packages/frameworks/logos/angular.svg)
![Angular Logo](../../packages/frameworks/logos/angular.svg)
# Angular Example

View File

@@ -1,4 +1,4 @@
![React Logo](../packages/frameworks/logos/react.svg)
![React Logo](../../packages/frameworks/logos/react.svg)
# React Example

View File

@@ -1,4 +1,4 @@
![Docusaurus Logo](../packages/frameworks/logos/docusaurus.svg)
![Docusaurus Logo](../../packages/frameworks/logos/docusaurus.svg)
# Docusaurus Example

View File

@@ -1,4 +1,4 @@
![Eleventy Logo](../packages/frameworks/logos/eleventy.svg)
![Eleventy Logo](../../packages/frameworks/logos/eleventy.svg)
# Eleventy Example

View File

@@ -1,4 +1,4 @@
![Ember Logo](../packages/frameworks/logos/ember.svg)
![Ember Logo](../../packages/frameworks/logos/ember.svg)
# Ember Example

View File

@@ -1,4 +1,4 @@
![Gatsby Logo](../packages/frameworks/logos/gatsby.svg)
![Gatsby Logo](../../packages/frameworks/logos/gatsby.svg)
# Gatsby Example

View File

@@ -1,4 +1,4 @@
![Gridsome Logo](../packages/frameworks/logos/gridsome.svg)
![Gridsome Logo](../../packages/frameworks/logos/gridsome.svg)
# Gridsome Example

View File

@@ -1,4 +1,4 @@
![Hexo Logo](../packages/frameworks/logos/hexo.svg)
![Hexo Logo](../../packages/frameworks/logos/hexo.svg)
# Hexo Example

View File

@@ -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

View File

@@ -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/

View File

@@ -1,8 +0,0 @@
{
"appId": "io.ionic.starter",
"appName": "ionic-react-conference-app",
"bundledWebRuntime": false,
"npmClient": "npm",
"webDir": "build",
"cordova": {}
}

View File

@@ -1,7 +1,5 @@
{
"name": "ionic-react-conference-app",
"integrations": {
"capacitor": {}
},
"name": "ionic-react",
"integrations": {},
"type": "react"
}

View File

@@ -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"
}

View File

@@ -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();
});

View File

@@ -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
});

View File

@@ -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>
`;

View File

@@ -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;

View File

@@ -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
});

View File

@@ -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;

View File

@@ -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)
})

View File

@@ -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
});

View File

@@ -1,4 +0,0 @@
.filter-icon {
margin: 7px 16px 7px 0;
}

View File

@@ -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
})

View File

@@ -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} /> &mdash;&nbsp;
<Time date={session.dateTimeEnd} /> &mdash;&nbsp;
{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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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
</>
)

View File

@@ -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>
)
});

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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 });
}
};

View File

@@ -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;
};

View File

@@ -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>;

View File

@@ -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 };
}
}
};

View File

@@ -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[];
}

View File

@@ -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>;

View File

@@ -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>;

View File

@@ -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 };
}
}

View File

@@ -1,7 +0,0 @@
export interface UserState {
isLoggedin: boolean;
username?: string;
darkMode: boolean;
hasSeenTutorial: boolean;
loading: boolean;
}

View File

@@ -1,5 +0,0 @@
export interface AppPage {
url: string;
icon: object;
title: string;
}

View File

@@ -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();

View File

@@ -1,6 +0,0 @@
export interface Location {
id: number;
name?: string;
lat: number;
lng: number;
}

View File

@@ -1,10 +0,0 @@
export interface Session {
id: number;
dateTimeStart: string;
dateTimeEnd: string;
name: string;
location: string;
description: string;
speakerIds: number[];
tracks: string[];
}

View File

@@ -1,5 +0,0 @@
import { Session } from './Session';
export interface SessionGroup {
startTime: string;
sessions: Session[];
}

View File

@@ -1,10 +0,0 @@
export interface Speaker {
id: number;
name: string;
profilePic: string;
twitter: string;
about: string;
location: string;
email: string;
phone: string;
}

View File

@@ -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;
}
}

View File

@@ -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 were 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);

View File

@@ -1,6 +0,0 @@
#account-page {
img {
max-width: 140px;
border-radius: 50%;
}
}

View File

@@ -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
})

View 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;

View File

@@ -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;
}
}

View File

@@ -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
})

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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
});

View File

@@ -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;
}
}

View File

@@ -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)
});

View File

@@ -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;
}
}

View File

@@ -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} /> &ndash; <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)
})

View File

@@ -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
})

View File

@@ -1,11 +0,0 @@
#speaker-detail {
img {
max-width: 140px;
border-radius: 50%;
}
p {
color: #60646B;
}
}

View File

@@ -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
});

View File

@@ -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;
}
}

View File

@@ -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)
});

View File

@@ -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
})

View File

@@ -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);
}
}
}

View File

@@ -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
});

View 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();
});
}
}

View 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';

View File

@@ -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 apps 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;
}

View File

@@ -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 apps 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;
}

View File

@@ -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>;

View File

@@ -13,7 +13,7 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve"
"jsx": "react"
},
"include": ["src"]
}

View File

@@ -1,3 +0,0 @@
{
"extends": ["tslint-react"]
}

View File

@@ -26,17 +26,17 @@ You can deploy your new Jekyll project with a single command from your terminal
$ now
```
### Example Changes
### Build Command
This example adds a `package.json` file with the following:
The default build command is `jekyll build`.
If you wish to change the build command, add a `package.json` file with the following:
```json
{
"private": true,
"scripts": {
"build": "jekyll build && mv _site public"
"build": "jekyll build"
}
}
```
This instructs ZEIT Now to build the Jekyll website and move the output to the public directory.

View File

@@ -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
});

View File

@@ -13,7 +13,6 @@
# misc
.DS_Store
.env
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@@ -1,4 +1,4 @@
![Next.js Logo](../packages/frameworks/logos/next.svg)
![Next.js Logo](../../packages/frameworks/logos/next.svg)
# Next.js Example

View File

@@ -1,4 +1,4 @@
![Polymer Logo](../packages/frameworks/logos/polymer.svg)
![Polymer Logo](../../packages/frameworks/logos/polymer.svg)
# Polymer Example

View File

@@ -1,4 +1,4 @@
![Preact Logo](../packages/frameworks/logos/preact.svg)
![Preact Logo](../../packages/frameworks/logos/preact.svg)
# Preact Example

View File

@@ -3,5 +3,5 @@ import { component } from 'riot';
import Random from './random.riot';
component(Random)(document.getElementById('app'), {
title: 'Hi there!',
title: 'Hi there!'
});

View File

@@ -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']
}
}
}
]
}
};

View File

@@ -1,4 +1,4 @@
![Saber Logo](../packages/frameworks/logos/saber.svg)
![Saber Logo](../../packages/frameworks/logos/saber.svg)
# Saber Example

View File

@@ -1,4 +1,4 @@
![UmiJS Logo](../packages/frameworks/logos/umi.svg)
![UmiJS Logo](../../packages/frameworks/logos/umi.svg)
# UmiJS Example

View File

@@ -1,4 +1,4 @@
![Vue.js Logo](../packages/frameworks/logos/vue.svg)
![Vue.js Logo](../../packages/frameworks/logos/vue.svg)
# Vue.js Example

View File

@@ -1,3 +1,5 @@
module.exports = {
presets: ['@vue/app'],
};
presets: [
'@vue/app'
]
}

View File

@@ -21,6 +21,20 @@
"eslint-plugin-vue": "^5.0.0",
"vue-template-compiler": "^2.6.10"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},
"postcss": {
"plugins": {
"autoprefixer": {}

View File

@@ -1,8 +1,8 @@
import Vue from 'vue';
import App from './App.vue';
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false;
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app');
}).$mount('#app')

View File

@@ -1,14 +0,0 @@
{
"name": "my_site",
"version": "1.0.0",
"private": true,
"description": "A Zola project, ready for deployment with ZEIT Now.",
"main": "index.js",
"scripts": {
"build": "zola build",
"dev": "zola serve --port $PORT"
},
"keywords": [],
"author": "",
"license": "ISC"
}

View File

@@ -7,7 +7,8 @@
}
],
"env": {
"GITHUB_ACCESS_TOKEN": "@now-api-examples-github-token"
"GITHUB_ACCESS_TOKEN": "@now-api-examples-github-token",
"SENTRY_DSN": "@sentry-product-dsn"
},
"headers": [
{

View File

@@ -43,7 +43,7 @@
"lint": "eslint . --ext .ts,.js"
},
"lint-staged": {
"*.{js,ts}": [
"./{*,{api,packages,test,utils}/**/*}.{js,ts}": [
"prettier --write",
"eslint",
"git add"

View File

@@ -10,7 +10,7 @@
"detectors": {
"every": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"next\":\\s*\".+?\"[^}]*}"
}
]
@@ -38,7 +38,7 @@
"detectors": {
"every": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"gatsby\":\\s*\".+?\"[^}]*}"
}
]
@@ -65,7 +65,7 @@
"detectors": {
"every": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"hexo\":\\s*\".+?\"[^}]*}"
}
]
@@ -92,7 +92,7 @@
"detectors": {
"every": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@11ty\\/eleventy\":\\s*\".+?\"[^}]*}"
}
]
@@ -119,11 +119,11 @@
"detectors": {
"some": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"docusaurus\":\\s*\".+?\"[^}]*}"
},
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@docusaurus\\/core\":\\s*\".+?\"[^}]*}"
}
]
@@ -151,7 +151,7 @@
"detectors": {
"every": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"preact-cli\":\\s*\".+?\"[^}]*}"
}
]
@@ -178,7 +178,7 @@
"detectors": {
"every": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"ember-cli\":\\s*\".+?\"[^}]*}"
}
]
@@ -206,7 +206,7 @@
"detectors": {
"every": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@vue\\/cli-service\":\\s*\".+?\"[^}]*}"
}
]
@@ -232,7 +232,7 @@
"detectors": {
"every": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@scullyio\\/init\":\\s*\".+?\"[^}]*}"
}
]
@@ -260,7 +260,7 @@
"detectors": {
"every": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@angular\\/cli\":\\s*\".+?\"[^}]*}"
}
]
@@ -287,7 +287,7 @@
"detectors": {
"every": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"polymer-cli\":\\s*\".+?\"[^}]*}"
}
]
@@ -315,7 +315,7 @@
"detectors": {
"every": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"sirv-cli\":\\s*\".+?\"[^}]*}"
}
]
@@ -332,6 +332,34 @@
}
}
},
{
"name": "Ionic React",
"slug": "ionic-react",
"demo": "https://ionic-react.now-examples.now.sh",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ionic-react.svg",
"tagline": "Ionic React allows you to build mobile PWAs with React and the Ionic Framework.",
"description": "An Ionic React site, created with the Ionic CLI.",
"website": "https://ionicframework.com",
"detectors": {
"every": [
{
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@ionic\\/react\":\\s*\".+?\"[^}]*}"
}
]
},
"settings": {
"buildCommand": {
"placeholder": "`build` from `package.json`"
},
"devCommand": {
"value": "stencil build --dev --watch --serve --port $PORT"
},
"outputDirectory": {
"value": "public"
}
}
},
{
"name": "Create React App",
"slug": "create-react-app",
@@ -343,11 +371,11 @@
"detectors": {
"some": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"react-scripts\":\\s*\".+?\"[^}]*}"
},
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"react-dev-utils\":\\s*\".+?\"[^}]*}"
}
]
@@ -374,7 +402,7 @@
"detectors": {
"every": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"gridsome\":\\s*\".+?\"[^}]*}"
}
]
@@ -402,7 +430,7 @@
"detectors": {
"every": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"umi\":\\s*\".+?\"[^}]*}"
}
]
@@ -430,7 +458,7 @@
"detectors": {
"every": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"sapper\":\\s*\".+?\"[^}]*}"
}
]
@@ -457,7 +485,7 @@
"detectors": {
"every": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"saber\":\\s*\".+?\"[^}]*}"
}
]
@@ -484,7 +512,7 @@
"detectors": {
"every": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"@stencil\\/core\":\\s*\".+?\"[^}]*}"
}
]
@@ -512,7 +540,7 @@
"detectors": {
"every": [
{
"file": "package.json",
"path": "package.json",
"matchContent": "\"(dev)?(d|D)ependencies\":\\s*{[^}]*\"nuxt\":\\s*\".+?\"[^}]*}"
}
]
@@ -540,19 +568,19 @@
"detectors": {
"some": [
{
"file": "config.yaml"
"path": "config.yaml"
},
{
"file": "config.toml"
"path": "config.toml"
},
{
"file": "config.json"
"path": "config.json"
}
]
},
"settings": {
"buildCommand": {
"value": "hugo"
"value": "hugo -D --gc"
},
"devCommand": {
"value": "hugo server -D -w -p $PORT"
@@ -572,7 +600,7 @@
"detectors": {
"every": [
{
"file": "_config.yml"
"path": "_config.yml"
}
]
},
@@ -598,7 +626,7 @@
"detectors": {
"every": [
{
"file": "brunch-config.js"
"path": "brunch-config.js"
}
]
},
@@ -624,7 +652,7 @@
"detectors": {
"every": [
{
"file": "config.rb"
"path": "config.rb"
}
]
},
@@ -773,15 +801,6 @@
"tagline": "A static site generator for Grunt.js and Yeoman, Assemble makes it dead simple to build modular sites and blogs.",
"description": "An Assemble site, created from the Assemble quickstart."
},
{
"name": "Ionic React",
"slug": "ionic-react",
"demo": "https://ionic-react.now-examples.now.sh",
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ionic-react.svg",
"tagline": "Ionic React allows you to build mobile PWAs with React and the Ionic Framework.",
"description": "An Ionic React site, created with the Ionic CLI.",
"website": "https://ionicframework.com"
},
{
"name": "Foundation",
"slug": "foundation",

Some files were not shown because too many files have changed in this diff Show More