From 83a137497c7997cd15fdfec1691831608badf0c6 Mon Sep 17 00:00:00 2001 From: zthomas Date: Sat, 9 Nov 2019 10:53:20 -0800 Subject: [PATCH] added new react templates with ejs delimiter --- .gitignore | 1 + go.mod | 2 +- internal/config/react.go | 18 - internal/templator/templator.go | 19 +- templates/commit0/commit0.tmpl | 23 - templates/react/.env | 6 + templates/react/.github/workflows/deploy.yml | 52 +++ templates/react/.gitignore | 5 +- templates/react/.prettierignore | 7 + templates/react/.prettierrc.js | 5 + templates/react/README.md | 91 ++-- templates/react/jsconfig.json | 5 - templates/react/package.json | 76 +++- templates/react/package.json.tmpl | 37 -- templates/react/public/favicon.ico | Bin 22382 -> 3870 bytes templates/react/public/index.html | 24 +- templates/react/public/index.html.tmpl | 44 -- templates/react/public/logo192.png | Bin 8581 -> 0 bytes templates/react/public/logo512.png | Bin 22920 -> 0 bytes templates/react/public/manifest.json | 10 - templates/react/public/robots.txt | 2 - templates/react/src/Admin.js | 27 ++ templates/react/src/App.js | 48 ++- templates/react/src/Routes.test.js | 101 +++++ templates/react/src/Routes.tsx | 72 ++++ .../react/src/assets/TermsOfService.json | 3 + templates/react/src/assets/locales/en.json | 33 ++ templates/react/src/bootstrap.js | 4 + templates/react/src/components/AppNavbar.js | 39 ++ .../react/src/components/AuthorizedImage.js | 43 ++ templates/react/src/components/Breadcrumbs.js | 50 +++ templates/react/src/components/Browser.js | 24 ++ .../react/src/components/Browser.module.css | 67 +++ templates/react/src/components/Centered.js | 29 ++ templates/react/src/components/ExampleForm.js | 111 +++++ templates/react/src/components/Footer.js | 73 ++++ templates/react/src/components/Head.js | 44 ++ .../react/src/components/LoadingIndicator.js | 9 + templates/react/src/components/Navbar.js | 136 ++++++ .../react/src/components/PageContainer.js | 29 ++ .../react/src/components/PipelineStepper.js | 53 +++ .../react/src/components/SessionLoader.js | 65 +++ templates/react/src/components/SimpleForm.js | 87 ++++ templates/react/src/components/Step.js | 101 +++++ .../react/src/components/SuccessIndicator.js | 15 + .../components/SuccessIndicator.module.css | 140 ++++++ templates/react/src/components/Text.js | 27 ++ .../react/src/components/admin/FormBuilder.js | 235 ++++++++++ .../react/src/components/admin/FormList.js | 119 ++++++ .../react/src/components/admin/Navbar.js | 40 ++ .../src/components/forms/CheckboxSelect.js | 75 ++++ .../react/src/components/forms/Currency.js | 28 ++ .../react/src/components/forms/FileUpload.js | 336 +++++++++++++++ .../src/components/forms/LabeledCheckbox.js | 30 ++ .../src/components/forms/NativeSelectField.js | 68 +++ templates/react/src/components/forms/Radio.js | 57 +++ .../src/components/forms/SelectCountry.js | 213 +++++++++ .../src/components/forms/TermsOfService.js | 85 ++++ .../react/src/components/forms/TextArea.js | 26 ++ templates/react/src/components/forms/index.js | 19 + .../react/src/components/graphql/Mutation.js | 38 ++ .../react/src/components/graphql/Query.js | 27 ++ .../src/components/graphql/SampleRestQuery.js | 30 ++ .../src/components/health-check/index.js | 27 -- .../components/health-check/status-icon.js | 27 -- .../src/components/layout/header/account.js | 52 --- .../src/components/layout/header/index.js | 34 -- .../layout/header/sidenav/content.js | 37 -- .../components/layout/header/sidenav/index.js | 37 -- .../react/src/components/layout/index.js | 15 - .../src/components/progress/FormProgress.js | 13 + .../react/src/components/survey/Completed.js | 25 ++ .../react/src/components/survey/NavBar.js | 64 +++ templates/react/src/config/index.js | 37 -- templates/react/src/config/index.js.tmpl | 32 -- templates/react/src/constants/index.js | 4 + templates/react/src/constants/keys.js | 7 + templates/react/src/constants/queries.js | 149 +++++++ templates/react/src/controllers/resolvers.js | 39 ++ templates/react/src/index.js | 18 - templates/react/src/index.ts | 19 + templates/react/src/logo.svg | 7 + templates/react/src/models/index.js | 4 + templates/react/src/models/typeDefs.js | 27 ++ templates/react/src/react-app-env.d.ts | 1 + templates/react/src/redux/actions.js | 3 - templates/react/src/redux/reducers/index.js | 6 - .../src/redux/reducers/object-reducer.js | 31 -- templates/react/src/redux/reducers/user.js | 9 - templates/react/src/redux/store.js | 4 - templates/react/src/serviceWorker.js | 135 ++++++ templates/react/src/services/auth/auth0.js | 56 +++ templates/react/src/services/auth/cognito.js | 104 +++++ templates/react/src/services/auth/index.js | 3 + templates/react/src/services/history.js | 8 + templates/react/src/services/index.js | 4 + templates/react/src/setupTests.js | 9 + templates/react/src/styles/animate.css | 64 +++ templates/react/src/styles/baseline.css | 404 ++++++++++++++++++ templates/react/src/theme/index.js | 22 - templates/react/src/types/react-enroute.d.ts | 1 + templates/react/src/utils/Interface.js | 10 + templates/react/src/utils/apollo.js | 66 +++ templates/react/src/utils/error.js | 14 + templates/react/src/utils/helpers.js | 58 +++ templates/react/src/utils/index.js | 12 + templates/react/src/utils/lang.js | 43 ++ templates/react/src/utils/logger.js | 51 +++ templates/react/src/utils/router.js | 40 ++ templates/react/src/utils/selectors.js | 125 ++++++ templates/react/src/utils/selectors.test.js | 20 + templates/react/src/utils/storage.js | 71 +++ templates/react/src/utils/theme.js | 218 ++++++++++ templates/react/src/views/AuthCallback.js | 18 + templates/react/src/views/Dashboard.js | 53 +++ templates/react/src/views/Error404.js | 21 + templates/react/src/views/Form.js | 190 ++++++++ templates/react/src/views/Landing.js | 37 ++ templates/react/src/views/Login.js | 107 +++++ templates/react/src/views/PasswordReset.js | 141 ++++++ templates/react/src/views/Signup.js | 87 ++++ templates/react/src/views/Terms.js | 35 ++ templates/react/src/views/account.js | 9 - templates/react/src/views/admin/FormEdit.js | 31 ++ templates/react/src/views/admin/FormNew.js | 23 + templates/react/src/views/admin/Forms.js | 38 ++ templates/react/src/views/home.js | 11 - templates/react/tsconfig.json | 25 ++ 128 files changed, 5727 insertions(+), 648 deletions(-) create mode 100644 templates/react/.env create mode 100644 templates/react/.github/workflows/deploy.yml create mode 100755 templates/react/.prettierignore create mode 100755 templates/react/.prettierrc.js delete mode 100644 templates/react/jsconfig.json delete mode 100644 templates/react/package.json.tmpl delete mode 100644 templates/react/public/index.html.tmpl delete mode 100644 templates/react/public/logo192.png delete mode 100644 templates/react/public/logo512.png delete mode 100644 templates/react/public/robots.txt create mode 100644 templates/react/src/Admin.js create mode 100644 templates/react/src/Routes.test.js create mode 100644 templates/react/src/Routes.tsx create mode 100644 templates/react/src/assets/TermsOfService.json create mode 100644 templates/react/src/assets/locales/en.json create mode 100644 templates/react/src/bootstrap.js create mode 100644 templates/react/src/components/AppNavbar.js create mode 100644 templates/react/src/components/AuthorizedImage.js create mode 100644 templates/react/src/components/Breadcrumbs.js create mode 100644 templates/react/src/components/Browser.js create mode 100644 templates/react/src/components/Browser.module.css create mode 100644 templates/react/src/components/Centered.js create mode 100644 templates/react/src/components/ExampleForm.js create mode 100644 templates/react/src/components/Footer.js create mode 100644 templates/react/src/components/Head.js create mode 100644 templates/react/src/components/LoadingIndicator.js create mode 100644 templates/react/src/components/Navbar.js create mode 100644 templates/react/src/components/PageContainer.js create mode 100644 templates/react/src/components/PipelineStepper.js create mode 100644 templates/react/src/components/SessionLoader.js create mode 100644 templates/react/src/components/SimpleForm.js create mode 100644 templates/react/src/components/Step.js create mode 100644 templates/react/src/components/SuccessIndicator.js create mode 100644 templates/react/src/components/SuccessIndicator.module.css create mode 100644 templates/react/src/components/Text.js create mode 100644 templates/react/src/components/admin/FormBuilder.js create mode 100644 templates/react/src/components/admin/FormList.js create mode 100644 templates/react/src/components/admin/Navbar.js create mode 100644 templates/react/src/components/forms/CheckboxSelect.js create mode 100644 templates/react/src/components/forms/Currency.js create mode 100644 templates/react/src/components/forms/FileUpload.js create mode 100644 templates/react/src/components/forms/LabeledCheckbox.js create mode 100644 templates/react/src/components/forms/NativeSelectField.js create mode 100644 templates/react/src/components/forms/Radio.js create mode 100644 templates/react/src/components/forms/SelectCountry.js create mode 100644 templates/react/src/components/forms/TermsOfService.js create mode 100644 templates/react/src/components/forms/TextArea.js create mode 100644 templates/react/src/components/forms/index.js create mode 100644 templates/react/src/components/graphql/Mutation.js create mode 100644 templates/react/src/components/graphql/Query.js create mode 100644 templates/react/src/components/graphql/SampleRestQuery.js delete mode 100644 templates/react/src/components/health-check/index.js delete mode 100644 templates/react/src/components/health-check/status-icon.js delete mode 100644 templates/react/src/components/layout/header/account.js delete mode 100644 templates/react/src/components/layout/header/index.js delete mode 100644 templates/react/src/components/layout/header/sidenav/content.js delete mode 100644 templates/react/src/components/layout/header/sidenav/index.js delete mode 100644 templates/react/src/components/layout/index.js create mode 100644 templates/react/src/components/progress/FormProgress.js create mode 100644 templates/react/src/components/survey/Completed.js create mode 100644 templates/react/src/components/survey/NavBar.js delete mode 100644 templates/react/src/config/index.js delete mode 100644 templates/react/src/config/index.js.tmpl create mode 100644 templates/react/src/constants/index.js create mode 100644 templates/react/src/constants/keys.js create mode 100644 templates/react/src/constants/queries.js create mode 100644 templates/react/src/controllers/resolvers.js delete mode 100644 templates/react/src/index.js create mode 100644 templates/react/src/index.ts create mode 100644 templates/react/src/logo.svg create mode 100644 templates/react/src/models/index.js create mode 100644 templates/react/src/models/typeDefs.js create mode 100644 templates/react/src/react-app-env.d.ts delete mode 100644 templates/react/src/redux/actions.js delete mode 100644 templates/react/src/redux/reducers/index.js delete mode 100644 templates/react/src/redux/reducers/object-reducer.js delete mode 100644 templates/react/src/redux/reducers/user.js delete mode 100644 templates/react/src/redux/store.js create mode 100644 templates/react/src/serviceWorker.js create mode 100644 templates/react/src/services/auth/auth0.js create mode 100644 templates/react/src/services/auth/cognito.js create mode 100644 templates/react/src/services/auth/index.js create mode 100644 templates/react/src/services/history.js create mode 100644 templates/react/src/services/index.js create mode 100644 templates/react/src/setupTests.js create mode 100644 templates/react/src/styles/animate.css create mode 100644 templates/react/src/styles/baseline.css delete mode 100644 templates/react/src/theme/index.js create mode 100644 templates/react/src/types/react-enroute.d.ts create mode 100644 templates/react/src/utils/Interface.js create mode 100644 templates/react/src/utils/apollo.js create mode 100644 templates/react/src/utils/error.js create mode 100644 templates/react/src/utils/helpers.js create mode 100644 templates/react/src/utils/index.js create mode 100644 templates/react/src/utils/lang.js create mode 100644 templates/react/src/utils/logger.js create mode 100644 templates/react/src/utils/router.js create mode 100644 templates/react/src/utils/selectors.js create mode 100644 templates/react/src/utils/selectors.test.js create mode 100644 templates/react/src/utils/storage.js create mode 100644 templates/react/src/utils/theme.js create mode 100644 templates/react/src/views/AuthCallback.js create mode 100644 templates/react/src/views/Dashboard.js create mode 100644 templates/react/src/views/Error404.js create mode 100644 templates/react/src/views/Form.js create mode 100644 templates/react/src/views/Landing.js create mode 100644 templates/react/src/views/Login.js create mode 100644 templates/react/src/views/PasswordReset.js create mode 100644 templates/react/src/views/Signup.js create mode 100644 templates/react/src/views/Terms.js delete mode 100644 templates/react/src/views/account.js create mode 100644 templates/react/src/views/admin/FormEdit.js create mode 100644 templates/react/src/views/admin/FormNew.js create mode 100644 templates/react/src/views/admin/Forms.js delete mode 100644 templates/react/src/views/home.js create mode 100644 templates/react/tsconfig.json diff --git a/.gitignore b/.gitignore index f99954e78..d5adcd17c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ main-packr.go packrd /commit0 .history/ +tmp/ \ No newline at end of file diff --git a/go.mod b/go.mod index e83a5c97b..944e097a7 100644 --- a/go.mod +++ b/go.mod @@ -18,4 +18,4 @@ require ( golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v2 v2.2.5 -) \ No newline at end of file +) diff --git a/internal/config/react.go b/internal/config/react.go index 7916afe8f..3636a4466 100644 --- a/internal/config/react.go +++ b/internal/config/react.go @@ -4,20 +4,6 @@ type reactApp struct { Name string } -type reactHeader struct { - Enabled bool -} - -type reactSidenavItem struct { - Path string - Label string - Icon string -} -type reactSidenav struct { - Enabled bool - Items []reactSidenavItem -} - type reactAccount struct { Enabled bool Required bool @@ -31,9 +17,5 @@ type reactView struct { type frontend struct { Framework string App reactApp - Account reactAccount - Header reactHeader - Sidenav reactSidenav - Views []reactView CI CI } diff --git a/internal/templator/templator.go b/internal/templator/templator.go index 1d53cf52f..d6a49a732 100644 --- a/internal/templator/templator.go +++ b/internal/templator/templator.go @@ -60,7 +60,7 @@ func NewTemplator(box *packr.Box) *Templator { GitIgnore: NewSingleFileTemplator(box, "util/gitignore.tmpl"), Readme: NewSingleFileTemplator(box, "util/README.tmpl"), Docker: NewDockerFileTemplator(box), - React: NewDirectoryTemplator(box, "react"), + React: NewEJSDirectoryTemplator(box, "react"), Kubernetes: NewDirectoryTemplator(box, "kubernetes"), CI: NewCITemplator(box), } @@ -154,6 +154,23 @@ func NewDirectoryTemplator(box *packr.Box, dir string) *DirectoryTemplator { } } +// TODO standardize and consolidate the templating syntax, also allow for a config struct to change delimiters +// NewEJSDirectoryTemplator +func NewEJSDirectoryTemplator(box *packr.Box, dir string) *DirectoryTemplator { + templates := []*template.Template{} + for _, file := range getFileNames(box, dir) { + templateSource, _ := box.FindString(file) + template, err := template.New(file).Delims("<%=", "%>").Funcs(util.FuncMap).Parse(templateSource) + if err != nil { + panic(err) + } + templates = append(templates, template) + } + return &DirectoryTemplator{ + Templates: templates, + } +} + func getFileNames(box *packr.Box, dir string) []string { keys := []string{} box.WalkPrefix(dir, func(path string, info file.File) error { diff --git a/templates/commit0/commit0.tmpl b/templates/commit0/commit0.tmpl index e37db9432..de507b11d 100644 --- a/templates/commit0/commit0.tmpl +++ b/templates/commit0/commit0.tmpl @@ -17,32 +17,9 @@ frontend: framework: react ci: system: circleci - buildImage: react/react buildTag: 1234 - buildCommand: make build - testCommand: make test app: name: {{.}} - header: - enabled: true - account: - enabled: true - required: false - sidenav: - enabled: true - items: - - path: / - label: Home - icon: home - - path: /account - label: Account - icon: account_circle - views: - - path: /account - component: account - - path: / - component: home - services: - name: User diff --git a/templates/react/.env b/templates/react/.env new file mode 100644 index 000000000..5f9bdbfce --- /dev/null +++ b/templates/react/.env @@ -0,0 +1,6 @@ +# https://create-react-app.dev/docs/adding-custom-environment-variables +REACT_APP_HOST=http://localhost:3000 +REACT_APP_AUTH_DOMAIN= +REACT_APP_AUTH_CLIENT_ID= +REACT_APP_GRAPHQL_HOST=http://localhost:4000 +REACT_APP_FILE_HOST=http://localhost:4000 \ No newline at end of file diff --git a/templates/react/.github/workflows/deploy.yml b/templates/react/.github/workflows/deploy.yml new file mode 100644 index 000000000..5ca39285a --- /dev/null +++ b/templates/react/.github/workflows/deploy.yml @@ -0,0 +1,52 @@ +name: Build and Deploy + +on: push + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v1 + + # https://github.com/marketplace/actions/setup-node-js-for-use-with-actions + - name: Setup Node + uses: actions/setup-node@v1 + with: + node-version: '12.x' + + - run: npm install + + - run: npm run build + + - name: upload build artifacts + uses: actions/upload-artifact@v1 + with: + name: build + path: build + + publish: + needs: build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v1 + with: + fetch-depth: 1 + + - name: Download build artifacts + uses: actions/download-artifact@v1 + with: + name: build + + # https://github.com/opspresso/action-s3-sync + - name: Publish to AWS S3 + uses: opspresso/action-s3-sync@master + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: "us-west-2" + FROM_PATH: "./build" + DEST_PATH: "s3://<%= .Config.Frontend.App.Name %>" + OPTIONS: "--acl public-read" \ No newline at end of file diff --git a/templates/react/.gitignore b/templates/react/.gitignore index 6ec1a4213..9ba963f18 100644 --- a/templates/react/.gitignore +++ b/templates/react/.gitignore @@ -4,20 +4,23 @@ /node_modules /.pnp .pnp.js -/package-lock.json # testing /coverage +test-report.xml # production /build # misc .DS_Store +.vscode .env.local .env.development.local .env.test.local .env.production.local +.idea/ +.firebase/ npm-debug.log* yarn-debug.log* diff --git a/templates/react/.prettierignore b/templates/react/.prettierignore new file mode 100755 index 000000000..b5271b65c --- /dev/null +++ b/templates/react/.prettierignore @@ -0,0 +1,7 @@ +node_modules +.vscode +build +.expo +.next +assets +static \ No newline at end of file diff --git a/templates/react/.prettierrc.js b/templates/react/.prettierrc.js new file mode 100755 index 000000000..bf63c80f2 --- /dev/null +++ b/templates/react/.prettierrc.js @@ -0,0 +1,5 @@ +module.exports = { + semi: false, + singleQuote: true, + trailingComma: 'es5', +} diff --git a/templates/react/README.md b/templates/react/README.md index 859d27a64..4fd40a5de 100644 --- a/templates/react/README.md +++ b/templates/react/README.md @@ -1,68 +1,81 @@ This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). -## Available Scripts - -In the project directory, you can run: +## Key Features -### `npm start` +- Dynamic loading of application +- Session handling +- Simple Routing using React Enroute +- Redux state management +- Simple server side rendering (React-snap) and SEO tag management (React-helmet) +- Redux Saga for side effect management +- Minimalist theme for MUI +- Simple language management, designed to be compatible and easily swappable with i18next +- Logger / analytics service +- In memory Localstorage fallback +- ScourJs as a immutability helper (optional) -Runs the app in the development mode.
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -The page will reload if you make edits.
-You will also see any lint errors in the console. +https://auth0.com/docs/universal-login +https://trellis.auth0.com/authorize?response_type=token&client_id=e8Sk7ToG3y7fJMpNN006iVhEU1W2yONN&redirect_uri=http://localhost:3000/auth/callback&state=STATE +&connection=CONNECTION -### `npm test` +## Code Formatting -Launches the test runner in the interactive watch mode.
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. +- following [AirBnB styleguide](https://github.com/airbnb/javascript) +- linting is built into react-scripts (CRA config source)[https://github.com/facebook/create-react-app/blob/master/packages/eslint-config-react-app/index.js] +- use prettier to automatically format code -### `npm run build` +## Styles -Builds the app for production to the `build` folder.
-It correctly bundles React in production mode and optimizes the build for the best performance. +We are using a hybrid approach to styling. The preferred system is CSS modules for its simplicity and statically compiled performance. However MUI forces us to use their style and theming system. -The build is minified and the filenames include the hashes.
-Your app is ready to be deployed! +- use MUI css-in-js styling for theme and theme dependent styling +- use CSS modules for components that are independent and reusable. Can support using css variable overrides to customize it. +- share variables using css variables +- using some global styles for reset css and libraries like animate.css -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. +## Documentation -### `npm run eject` +- use http://documentation.js.org/ it supports Flow syntax -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** +## Formatting -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. +## Tools: -Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. +We should strive to use minimalistic tools or write your own -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. +- https://bundlephobia.com/result?p=dayjs@1.7.8 +- https://github.com/zalmoxisus/redux-devtools-extension -## Learn More +## Bundling / Codesplitting -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). +https://facebook.github.io/create-react-app/docs/code-splitting +https://reactjs.org/docs/code-splitting.html -To learn React, check out the [React documentation](https://reactjs.org/). - -### Code Splitting +## Available Scripts -This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting +In the project directory, you can run: -### Analyzing the Bundle Size +### `npm start` -This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size +Runs the app in the development mode.
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser. -### Making a Progressive Web App +The page will reload if you make edits.
+You will also see any lint errors in the console. -This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app +### `npm test` -### Advanced Configuration +Launches the test runner in the interactive watch mode.
+See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. -This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration +### `npm run build` -### Deployment +Builds the app for production to the `build` folder.
+It correctly bundles React in production mode and optimizes the build for the best performance. -This section has moved here: https://facebook.github.io/create-react-app/docs/deployment +The build is minified and the filenames include the hashes.
+Your app is ready to be deployed! -### `npm run build` fails to minify +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. -This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). diff --git a/templates/react/jsconfig.json b/templates/react/jsconfig.json deleted file mode 100644 index ec2332eb4..000000000 --- a/templates/react/jsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": "src" - } -} diff --git a/templates/react/package.json b/templates/react/package.json index 271b240c9..bfc1edc21 100644 --- a/templates/react/package.json +++ b/templates/react/package.json @@ -1,38 +1,68 @@ { - "name": "commit0", + "name": "<%= .Config.Frontend.App.Name %>", "version": "0.1.0", "private": true, + "author": "", "dependencies": { - "@material-ui/core": "^4.5.1", - "@material-ui/icons": "^4.5.1", - "axios": "^0.19.0", - "react": "^16.10.2", - "react-dom": "^16.10.2", - "react-redux": "^7.1.1", - "react-router": "^5.1.2", - "react-router-dom": "^5.1.2", - "react-scripts": "3.2.0", - "redux": "^4.0.4" + "@auth0/auth0-spa-js": "^1.1.1", + "@material-ui/core": "^4.1.0", + "@material-ui/icons": "^4.1.0", + "@types/history": "^4.7.2", + "@types/jest": "^24.0.17", + "@types/node": "^12.7.2", + "@types/qs": "^6.5.3", + "@types/react": "^16.9.1", + "@types/react-dom": "^16.8.5", + "apollo-cache-inmemory": "^1.6.2", + "apollo-client": "^2.6.3", + "apollo-link": "^1.2.12", + "apollo-link-context": "^1.0.18", + "apollo-link-http": "^1.5.15", + "apollo-link-rest": "^0.7.3", + "classnames": "^2.2.6", + "country-list": "^2.1.1", + "dayjs": "^1.8.14", + "graphql": "^14.0.2", + "graphql-anywhere": "^4.2.4", + "graphql-tag": "^2.10.1", + "history": "^4.7.2", + "lodash.pickby": "^4.6.0", + "notistack": "^0.8.8", + "prop-types": "^15.6.2", + "qs": "^6.6.0", + "react": "^16.7.0", + "react-apollo": "^2.3.3", + "react-dom": "^16.7.0", + "react-enroute": "^2.0.0", + "react-helmet": "^5.2.0", + "react-redux": "^6.0.0", + "react-scripts": "^3.0.1", + "react-select": "^3.0.4", + "typescript": "^3.5.3" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", + "postbuild": "react-snap", "test": "react-scripts test", - "eject": "react-scripts eject" + "format": "prettier --write '**/*.js'", + "docs": "documentation build src/Routes.js -f html -o docs", + "index": "npx create-index ./src/constants ./src/models ./src/services ./src/utils" }, "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" - ] + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ], + "devDependencies": { + "prettier": "^1.15.3", + "react-snap": "^1.23.0" + }, + "reactSnap": { + "skipThirdPartyRequests": true } } diff --git a/templates/react/package.json.tmpl b/templates/react/package.json.tmpl deleted file mode 100644 index 44b0cad84..000000000 --- a/templates/react/package.json.tmpl +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "{{ .Config.Frontend.App.Name }}", - "version": "0.1.0", - "private": true, - "dependencies": { - "@material-ui/core": "^4.5.1", - "@material-ui/icons": "^4.5.1", - "react": "^16.10.2", - "react-dom": "^16.10.2", - "react-redux": "^7.1.1", - "react-router": "^5.1.2", - "react-router-dom": "^5.1.2", - "react-scripts": "3.2.0", - "redux": "^4.0.4" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" - }, - "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" - ] - } -} diff --git a/templates/react/public/favicon.ico b/templates/react/public/favicon.ico index c2c86b859eaa20639adf92ff979c2be8d580433e..a11777cc471a4344702741ab1c8a588998b1311a 100644 GIT binary patch literal 3870 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 22382 zcmeI4_m@>g631uH?hiA>Aq+VTNjPWS;EE`MGJt>?K?MV56hy>`7*J7EOc)R|k_>>b z$<4tHZVqnp59+N?_4~fpbLZao?tAP3ciB0o&waPMLU(m_RdsdWX>0pJ+ZWq9JKOBp z-M02iZEdsK+SiSaLvu*!^O)cRqLua3I8RFCx>l&hKB3cjSJ6zFeF@g z-Gs36nhD|g14F_Mw~Y%=?H(G=J$G{N{wBO{Tt6;cedBoNG3#s7!}dd+;l&R-!|7k2 z?sd?+X~WpweMtCUw|1QO6L~CO(H*wFH7q>-#;|bd%84$Yt8W_b{pcmH3V-;myMJ_e z^?QTD_Jf^a*T)0Gv+oY^^3E>?gclEYhS$D7D6D^IbhSS&1LJ?`6%)e?*4H)nj&a-v zo*xk|xwJc6v~*Iq@Zw4So4V}>I>XI(j&+#JubSv`IO~jQ;koyRgfqSo<-O^d5#Dcb z5C5wp9R6oVxbUJ$UU&bqBYf9Ok?o-)PVW>NbII z{e>4i;msH>yKE^q}`n)~3y(=tTZv8hr`oUPd|2D?8HpUlhP8hSs4`1eJ{ez=j zM$8%VduIO-FS|Y-=<-3f$QW5y^|c~@Xx(6Xvubsmf98+5qu>0d9UZ*x)^U-q@RIpQ zH?F*Xg3AFO%n3#O(5)NSk1f`b?n9xK?VqCcPO7W??0!adbwmeM+w@9;XFg>ly# zFKc|;-l5@@?+gx)yfQp2yR5qqo;Cf@3ti#mZw_{S=e-gARPNc|3wox8_y624y#9kh z;gOxg!`nX_=(dag%$qy4I%n7_Y%+EUo4V!I;chQJ_)EWV&cZ2GxGa6W7mfe!ts}#G zf9x0LoL2E!Cr=NnYz^-HP5-d(xBcCgA)h_J8W8sXu79}RY{+R}F?(rqI{Va<9Ok~K zM-<8n{`KY_`|$L>p?zS^K69G;jRT+d3yg8cw+Dxwv!?ceLnio_dOXwz$s&E<`O%>8 z@Q&e*pKZU)?6mv<_AJ}ZJGTAx9}ddJPvMJxzx=JiVasd7d(j4VkFLwpTP()w#Mf3mUJHwoQ+K!JyAMrit-JDs|!dpKb7~cHxK%aAH3LgKd z`4g|SUB8Rw=guvo!hRbk{smp6KX3koHP9Xo{k_A>hyRFA>Y{HP8qhgnmVKtD_dGGO z>G_{GXPW!m$jf2{qrLZ&0pXor4)8f=PVmXMZX6TVTH!Z!=)-PX3-Fb0Ag4PY9Tn!E zU0bh`Pbw3BpK0riSmNN{I=no&YgjwRMavOf1bi!%-W z;!7sEO~NM1&YgGuWQRd6JIr1(_Zk;6djC)T+&-nc3awezUu2DaTfAsWulZu`ktO|l z=*6zc+hPI6!@N)!Cw2&0=sY$P9AX~oRIsP4NBAHk>gnI*SI4zXT*kZV`Av+o$L1a$ zn!`uU7HfXvv)%auzhK_faL8b>k&3TVddP@AuUJ)y^9*N2h5z{%WMUv|g9fzVkBx&z zU;Z6c{=fjc_mloHJ~Mc5RFD&WX3VOC$6F?oH-0oIEHoLV{Y8erV`m>U{Q-x$NB&2w zp2BbG05HIU!@dA`(OYab3LRsvnRB(#P8+_9xx3x=0{9&2(IM>nZ4ZnNhtNNZ6RE!l z{WIOeFT$I;^Gr9q@JQa@Fbpmz+!a9!x$d3I2l>qzB%qq zz$NaTX?_rR6{lnCkF}|>VXqE;+F>?psMCNBYnwGLzQWHwnZpeakE;56WVz?p1H$f# z{bAheHjNHz?j94?+%qQJX0iXH=JSEe+8_GE-U^$R<)im(fAo&+#IIvZv8hk*AL@4h zi0LmfLe{ds=mGYL@iKPi9$ajR=;iUFt*9{f!~kjgZ2XVuDf&9cY#H{9d0}1Sn^IWz zoAf8G%l@WM)TwXj`_7Ln<}>}uwl6Xp_3j_~yDl*2{KkHYhTyTi=&s(g?RqbIGA8zn zdF`?p%sKnaJeY@fbcI)KZ=Khc))n=Su|4tjus;1qm+&>L5!MvH&rbA9^oUcpSLUyX zAFxGbhxkEYS!cvW_n0mg(Wjn%>8U>I{hS3; ze2nBLm`mad z4*6jAidWq*!E+;=tC6=-d!v0gW3M+p+FB{>*~m#ScIKTk&nz8sY^&{zk(?E9Dv#Ox zrh9te?tgAX)Sn6*d+&9d#`J;{eR6H&jG%{n&_lH~`)nkiqIS-hrSF{YraDF+(s%Mt z?B4{>dS)-jc?bD0av|umbo~j-57QKfCX3oPC`Z zL$eLV_%&X@z>l*S)`G8>7IFf}t&F_bqg=aYoX;`(DP2Vef80NHee?Rb=aT-5Ke04%IeV*I`pKN9?e%`oGTkC3 zPyMvUo<2E4>q+je`QErP26VQpU#Bm?5)X+!cVNAbCLin$`~E1`I1hr;lRY&0nc9nb zV=S6~&LEu^KC9xNd2(J)K8k%PI?Q^9x9*XU_d+k-Kbt=LJ=Tis=|>Si^^taBDriCj zTF|7Ob8)|;VfE-5_foj4#Jvc?D}TTpCC-rR@uMwUh(Fj)JbYx@VW=!MQr;y4*v-o@Cp3=k9~}6BkkE`#z(~7+Du79(S-gQ;l+n z{E0O0NAAtp98-u4UK?~1!q`rMZ$2gSKEJR<#ezgwiZvKM@!m+;1C z*YG<0-b~$fWHRB7pvJ*k7k+kM&i$O!&x0d8p3-N@iYNYG^ptDl9lxRRr11L9d=nR< z!@|$@N3fCy=l%E~bPgQu8v&#CEWF;aPtlLZ^$I@rQgRbMIooJQ&`s8sO6{GyID&^h zcO>wYf)ze@m&8AXr!IYGjeUqd8ZUgwe6ts6JHJ)1nex5di$K0=tM?>VRm)Se7TN>f zMx;2zXmK85yZpEK=^Yr|tq?x3vD$fJ|L`?xZ}j9F5#b}Z6h4&~_dlXewtKu;M!v%5 z?w<6SxljEa{P;#l?XfRW_o>r*Ry#IUym{i!J%?(rz>q&p+o50Yoo_Hio4co3c;S+F zjQz3ucxG!-*yy+39%m@~E2dv35b1x{L+FaO3W zq3`Ep6}T9q^u__7^Rko|>xlcKDQ$jZ%Tu_b-`uBk{W2W(oSd1Zu*jEp%~gzBu~Bvh z(qw@D!I!8#3y+P&7fHXkQ>OI{?-ZUmC;Gogzoz%`o%mk1Py9Fo5j|p2*CDfk#3b^6 zY5&C!o57u3!P7r-Hfk6BK2IADF&pqI+;#N(X^EVKr#KZ@@^{>|*0|WCDyJ!UWbmAQ z>nIrT_)T3_?}?o}H)ie31+f+1JqS1V&Ybn1?>4eI2Vc+DrhcT)to)EG=W4``(eK81 zfP2Rtb0u`hy+z&Z@bf?D@*Q5pv%C3~yX{$i#DSb^liNt|m;$H%5J!tYziA`Z7eD-( z;K>1yQzPzEJGjI@9`_`D7JcFJo>#~*az=x{lK({Z=piu=b4kob8#ak=Mxeuay>?|Ft+;_=-b;C4OUH zC*E=WMEfCl;t%ow?A`O|XZfjL^`6*rz6Is`5An`>V)wbD%6GNy6HNxdA&=beTN1g$ zA3Y|Y6USxWAedgU#?t&p9$)InzmcO!WnFJfYRk%_?z^ocVp+yR-Y@#q#DBYAxB2jA z^w+FM`iDHQiOdi7i97rh?!2>(-ZOf>UorST_(rc*S8G?UgSFVoco=Id1)r!~Cvw1N zu-=q2DDv;oHX$#@P+aob@!#x!IY)sf`1sSZ`;@$KBD1>lUC>_J&vf`V;F3M-gm{yE z7w6)flaU+K+C&eSGv&UO|0b7O)9ZG(yNr|X$5*bJ5Wn}V#3A&TSW58NTk&sYJSlG3 z-<&yUO!TuJ9^dx(zS_nPPHNADN8BZP%yXlhKKWI_r{7$OUNg^D<{zKRdZZAar~LBh zac_tD!d|dOs{aqb^oz43?a_hZyPQV!;`v+T&;Ga(d{H}nZKV)T$=|Lp{VYR+Z?N#4 z6yiF*>te0A9dV!9?s@Yvw1{=YpL>U8aPnY~3-v|vDQfo^ucFW70L$RxkBM^?Ki~(5 zE8;jheD8z*E7Kq1Bk4ajrVM|rw{CH%i?ku>NWd@20Ym>p2YQh+f$Yf zcP@mV&4-kwA-JqO%DnsdZ7s8&*_VhG&qnym>^>-c2iJ@JPR|G$;nVK>t_nWBw;BE^ zeSWu6*jvhfvri~XgS~+7fiu3J{8lsj1nyjjKmMmI|14aY`2r^^U-Br@zgsNVnQia$ zZgC^uxM^R6{*x;p4%GUm4cni`FH6sJRYn6knuJqqUp@vJM_1N0idKwLrYGuyxF-q?8KL+a+I8rUOfw_5oB2lD5f;d3X4 zJ5Ah&WdE1FA3)secO`Rp62r5<~m_U#9gdeUyLKJ9CbYQqHy>{e|9{^t8& zegB|7>&clAck|MNVbeFrktOvg3z02+fklW+i z75E(uea`*)|61hwI0Glv#osFTd$jcF+lgF`R(}5r_>AF1E=RLI|J&#PL|*%vcRjTR QPNaAH_W=L@JAozeZ|l@fegFUf diff --git a/templates/react/public/index.html b/templates/react/public/index.html index 807d6308d..4bfce9568 100644 --- a/templates/react/public/index.html +++ b/templates/react/public/index.html @@ -1,21 +1,15 @@ - - - - - - - + + + + - + - - - {{ .Config.Frontend.App.Name }} - - - -
- - - diff --git a/templates/react/public/logo192.png b/templates/react/public/logo192.png deleted file mode 100644 index fa313abf53936aefc517dbd583b724a57199d415..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8581 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�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 b!B~?t+WWKS5u5)CcMJGaRz{_UZlwPKNp@r} diff --git a/templates/react/public/manifest.json b/templates/react/public/manifest.json index 080d6c77a..1f2f141fa 100644 --- a/templates/react/public/manifest.json +++ b/templates/react/public/manifest.json @@ -6,16 +6,6 @@ "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" } ], "start_url": ".", diff --git a/templates/react/public/robots.txt b/templates/react/public/robots.txt deleted file mode 100644 index 01b0f9a10..000000000 --- a/templates/react/public/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * diff --git a/templates/react/src/Admin.js b/templates/react/src/Admin.js new file mode 100644 index 000000000..fde0bbe8a --- /dev/null +++ b/templates/react/src/Admin.js @@ -0,0 +1,27 @@ +import React from 'react' +import { Router, Route } from 'react-enroute' + +import Forms from './views/admin/Forms' +import FormEdit from './views/admin/FormEdit' +import SessionLoader from './components/SessionLoader' +import LoadingIndicator from './components/LoadingIndicator' +import { ApolloProvider } from 'react-apollo' +import { router, apollo } from './utils' + +export default function App({ location = '/' }) { + if (navigator.userAgent === 'ReactSnap') { + return + } + + return ( + + + + + + + + + + ) +} diff --git a/templates/react/src/App.js b/templates/react/src/App.js index dfc660513..1b258edff 100644 --- a/templates/react/src/App.js +++ b/templates/react/src/App.js @@ -1,26 +1,36 @@ -import React from 'react'; -import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; -import Layout from 'components/layout'; -import config from 'config'; +import React from 'react' +import { Router, Route } from 'react-enroute' +import { ApolloProvider } from 'react-apollo' -const renderView = (view) => { - return ( - - ) -} +import { router, apollo } from './utils' -export default function App() { +import Dashboard from './views/Dashboard' +import Form from './views/Form' +import SessionLoader from './components/SessionLoader' +import LoadingIndicator from './components/LoadingIndicator' +export default function App(props) { + if (navigator.userAgent === 'ReactSnap') { + return + } + const { location } = props + // TODO withParams for sessionLoader isn't necessary here, just there for demo return ( - - - - { - config.views && config.views.map(renderView) - } - + + + + + + + + + - - ); + + ) +} + +function withParams(Component, params) { + return props => } diff --git a/templates/react/src/Routes.test.js b/templates/react/src/Routes.test.js new file mode 100644 index 000000000..91c8d0d80 --- /dev/null +++ b/templates/react/src/Routes.test.js @@ -0,0 +1,101 @@ +import React from 'react' +import { Routes } from './Routes' +import { render, waitForElement, fireEvent } from 'react-testing-library' +import { setupPolly } from 'setup-polly-jest' +import FSPersister from '@pollyjs/persister-fs' +import { Polly } from '@pollyjs/core' +import 'jest-dom/extend-expect' +import HTTPAdapter from '@pollyjs/adapter-node-http' +import router from './utils/router' + +Polly.register(HTTPAdapter) +Polly.register(FSPersister) +// doc: https://github.com/kentcdodds/react-testing-library#readme +// doc: https://reactjs.org/docs/test-utils.html + +const nextLoop = new Promise(resolve => setTimeout(resolve, 0)) + +describe('Renders pages:', () => { + let context = setupPolly({ + adapters: ['node-http'], + persister: 'fs', + }) + + it('/ - should render landing page', () => { + const { getByTestId } = render() + expect(getByTestId('start-btn')).toHaveTextContent('start') + }) + + it('/login', () => { + const { getByTestId } = render() + expect(getByTestId('login-btn')).toBeVisible() + }) + + it('/signup', () => { + const { getByTestId } = render() + expect(getByTestId('signup-btn')).toBeVisible() + }) + + it('/password-reset', () => { + const { getByTestId } = render() + expect(getByTestId('reset-password-btn')).toBeVisible() + }) + + it('/app - should not render when unauthenticated', async () => { + const errorLogger = global.console.error + global.console.error = jest.fn() + const { queryByTestId } = render() + + // waiting for next event loop to render out components, for some reason, not working with waitForElement + await nextLoop + expect( + queryByTestId(document.documentElement, 'dashboard-nav') + ).not.toBeInTheDocument() + expect(console.error).toBeCalled() + + global.console.error = errorLogger + }) + + it('/login - should be able to login and trigger redirect', async () => { + // NOTE: because aws creates unique Ids we need to create custom recorders for this + context.polly.stop() + const assignMock = jest.fn() + global.location.assign = assignMock + + const { queryByTestId, getByLabelText } = render( + + ) + setInputValue(getByLabelText('email'), 'thomas@meta.re') + setInputValue(getByLabelText('password'), '@Testing1') + fireEvent.click(queryByTestId('login-btn')) + await waitForElement(() => { + expect(window.location.assign).toBeCalled() + return queryByTestId('login-btn') + }) + }) + + describe('Authenticated Routes:', () => { + it('/app/ - should render investor dashboard', async () => { + const { getByTestId } = render() + + await waitForElement(() => { + return getByTestId('app-navbar') + }) + }) + it('/admin - should redirect away from admin routes', async () => { + render() + const spy = jest.spyOn(router, 'push') + + await waitForElement(() => { + expect(spy).toHaveBeenCalledWith('/app') + return true + }) + }) + }) +}) + +function setInputValue(inputEl, value) { + fireEvent.change(inputEl, { + target: { value }, + }) +} diff --git a/templates/react/src/Routes.tsx b/templates/react/src/Routes.tsx new file mode 100644 index 000000000..1ec901419 --- /dev/null +++ b/templates/react/src/Routes.tsx @@ -0,0 +1,72 @@ +import React, { useState, useEffect } from 'react' +import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles' +import { Router, Route } from 'react-enroute' +import queryString from 'qs' +import { SnackbarProvider } from 'notistack' + +import appTheme from './utils/theme' +import AuthCallback from './views/AuthCallback' +import LoginView from './views/Login' +import SignupView from './views/Signup' +import PasswordResetView from './views/PasswordReset' +import Error404 from './views/Error404' +import LandingPage from './views/Landing' + +import LoadingIndicator from './components/LoadingIndicator' +import history from './services/history' + +const App = React.lazy(() => import('./App')) +const Admin = React.lazy(() => import('./Admin')) +const Terms = React.lazy(() => import('./views/Terms')) + +export default function ConnectedRoutes() { + const [state, setState] = useState({ + pathname: window.location.pathname, + query: parseQuery(window.location.search), + }) + + useEffect(() => { + // Returning the cleanup handler directly + return history.listen(location => { + const { pathname } = location + setState({ + pathname, + query: parseQuery(location.search), + }) + }) + }, []) // disabling rerun on updates by passing a constant InputIdentityList + + return +} + +export function Routes({ location = '/' }) { + return ( + + + + + + + + + + + + + + + + ) +} + +function parseQuery(query = '') { + return queryString.parse(query, { ignoreQueryPrefix: true }) +} + +function load(Component: React.LazyExoticComponent, location: string) { + return (props: object) => ( + }> + + + ) +} diff --git a/templates/react/src/assets/TermsOfService.json b/templates/react/src/assets/TermsOfService.json new file mode 100644 index 000000000..7c709e549 --- /dev/null +++ b/templates/react/src/assets/TermsOfService.json @@ -0,0 +1,3 @@ +{ + "termsText": "Lorem ipsum dolor sit amet, \n\nconsectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." +} diff --git a/templates/react/src/assets/locales/en.json b/templates/react/src/assets/locales/en.json new file mode 100644 index 000000000..2c813bb44 --- /dev/null +++ b/templates/react/src/assets/locales/en.json @@ -0,0 +1,33 @@ +{ + "home": "home", + "dashboard": "dashboard", + "about": "about", + "next": "next", + "forms": "forms", + "inbox": "inbox", + "menu": "menu", + "item": "item", + "start": "start", + "login": "login", + "signup": "Sign up", + "submit": "submit", + "exit": "exit", + "logout": "logout", + "tos": "Terms of Service", + "not_found": "Not Found", + "back": "back", + "continue": "continue", + "reset_password": "reset password", + "see_all": "See all", + "download": "Download", + "phrase": { + "forgot_password": "Forgot your password?", + "login_invalid": "Credentials Invalid", + "unexpected_error": "Unexpected Error", + "home_title": "App Title", + "home_welcome": "Welcome", + "copyright": "Copyright © 2018 All rights reserved", + "tos_read": "Please read and agree to the terms of service", + "tos_agree": "I agree to the Terms of Service" + } +} diff --git a/templates/react/src/bootstrap.js b/templates/react/src/bootstrap.js new file mode 100644 index 000000000..c0224951c --- /dev/null +++ b/templates/react/src/bootstrap.js @@ -0,0 +1,4 @@ +// clear all SSR artifacts generated by MUI, it's creating conflicts +document + .querySelectorAll('[data-jss]') + .forEach(e => e.parentNode.removeChild(e)) diff --git a/templates/react/src/components/AppNavbar.js b/templates/react/src/components/AppNavbar.js new file mode 100644 index 000000000..7d222c854 --- /dev/null +++ b/templates/react/src/components/AppNavbar.js @@ -0,0 +1,39 @@ +import React from 'react' +import Tabs from '@material-ui/core/Tabs/Tabs' +import Tab from '@material-ui/core/Tab/Tab' +import PropTypes from 'prop-types' +import { withStyles } from '@material-ui/core/styles' + +import { Text } from './Text' +import Navbar from './Navbar' +import { router } from '../utils' + +const AppNavbar = ({ location, classes }) => { + const tab = (location.split('/')[2] || '').toLowerCase() + const _handleTab = (e, tab) => router.push(`/app/${tab}`) + + return ( + + + home} /> + + + ) +} + +AppNavbar.propTypes = { + location: PropTypes.string, +} + +const styles = theme => ({ + navbar: { + borderBottom: `1px solid ${theme.palette.divider}`, + }, +}) + +export default withStyles(styles)(AppNavbar) diff --git a/templates/react/src/components/AuthorizedImage.js b/templates/react/src/components/AuthorizedImage.js new file mode 100644 index 000000000..be19efc34 --- /dev/null +++ b/templates/react/src/components/AuthorizedImage.js @@ -0,0 +1,43 @@ +import React, { useState, useEffect } from 'react' +import PropTypes from 'prop-types' +import LoadingIndicator from './LoadingIndicator' + +export default function AuthorizedImage(props) { + const [img, setImg] = useState('') + + async function fetchImage() { + await fetch(props.url, props.requestOptions).then(res => { + return res.blob() + }).then(imageBlob => { + let imgUrl = URL.createObjectURL(imageBlob) + setImg(imgUrl) + }) + .catch(err => { + // console.log(err) + }) + } + + useEffect(() => { + setImg('') + fetchImage() + }, [props.url]) + + function _handleImageClick() { + window.open(img) + } + + function _renderImage() { + if (img === '') { + return + } + + return + } + + return _renderImage() +} + +AuthorizedImage.propTypes = { + url: PropTypes.string.isRequired, + requestOptions: PropTypes.object.isRequired, +} diff --git a/templates/react/src/components/Breadcrumbs.js b/templates/react/src/components/Breadcrumbs.js new file mode 100644 index 000000000..6922b37ea --- /dev/null +++ b/templates/react/src/components/Breadcrumbs.js @@ -0,0 +1,50 @@ +import React from 'react' +import Button from '@material-ui/core/Button' +import { withStyles } from '@material-ui/core/styles' +import Divider from '@material-ui/icons/KeyboardArrowRight' +import { router } from '../utils' +import PropTypes from 'prop-types' + +class Breadcrumbs extends React.Component { + render() { + const { location = '', classes } = this.props + const segments = location + .replace(/^\//, '') + .split('/') + .filter(a => a) + + const paths = [segments[0]] + segments.reduce((prev, curr, i) => { + paths[i] = `${prev}/${curr}` + return paths[i] + }) + + return ( +

+ {paths.map((p, i) => ( + + {i !== 0 && } + + + ))} +
+ ) + } +} + +const styles = theme => ({ + breadcrumbs: { + width: '100%', + marginBottom: theme.spacing(2), + }, + divider: { + verticalAlign: 'middle', + }, +}) + +Breadcrumbs.propTypes = { + location: PropTypes.string, + classes: PropTypes.object, +} + +export default withStyles(styles)(Breadcrumbs) diff --git a/templates/react/src/components/Browser.js b/templates/react/src/components/Browser.js new file mode 100644 index 000000000..709600e74 --- /dev/null +++ b/templates/react/src/components/Browser.js @@ -0,0 +1,24 @@ +import React from 'react' +import PropTypes from 'prop-types' +import cn from './Browser.module.css' +import cx from 'classnames' + +const Browser = ({ src = '', width, height, children }) => ( + +
+
+ + {children} +
+
+
+ +) + +Browser.proptTypes = { + src: PropTypes.string.required, + width: PropTypes.number, + height: PropTypes.number, +} + +export default Browser diff --git a/templates/react/src/components/Browser.module.css b/templates/react/src/components/Browser.module.css new file mode 100644 index 000000000..b266d290c --- /dev/null +++ b/templates/react/src/components/Browser.module.css @@ -0,0 +1,67 @@ +.container { + position: relative; + padding: 25px 25px 50px 25px; +} +.browser { + border-radius: 4px; + overflow: hidden; + width: 100%; + margin: auto; + z-index: 2; + box-shadow: 0 20px 30px 0 rgba(177, 170, 170, 0.1); + position: relative; + border: 1px solid #e2e5e5; +} +.browser:before { + content: ''; + height: 30px; + line-height: 30px; + display: block; + width: 100%; + position: relative; + background: linear-gradient(-180deg, #fafbfc 0, #f1f4f7 100%); + border-bottom: 1px solid #e2e5e5; +} +.browser:after { + content: ''; + width: 12px; + height: 12px; + background: #e2e5e5; + position: absolute; + border-radius: 50%; + top: 10px; + left: 8px; + box-shadow: 18px 0 0 #e2e5e5, 36px 0 0 #e2e5e5; +} +.browser img.source { + width: 100%; + display: block; +} +.overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient( + to bottom, + rgba(255, 255, 255, 0) 60%, + rgba(255, 255, 255, 1) 90% + ); + pointer-events: none; + z-index: 3; +} +.overlay.left { + background: linear-gradient( + to right, + rgba(255, 255, 255, 0) 60%, + rgba(255, 255, 255, 1) 95% + ); +} +.overlay.right { + background: linear-gradient( + to left, + rgba(255, 255, 255, 0) 60%, + rgba(255, 255, 255, 1) 95% + ); +} \ No newline at end of file diff --git a/templates/react/src/components/Centered.js b/templates/react/src/components/Centered.js new file mode 100644 index 000000000..f1dbee12f --- /dev/null +++ b/templates/react/src/components/Centered.js @@ -0,0 +1,29 @@ +import React from 'react' +import Grid from '@material-ui/core/Grid' +import PropTypes from 'prop-types' + +const Centered = props => { + const { children, container, horizontal, ...passThrough } = props + const style = 'container' in props ? { flex: '1 0 auto' } : {} + const justify = 'horizontal' in props ? null : 'center' + return ( + + {children} + + ) +} + +Centered.propTypes = { + container: PropTypes.bool, + horizontal: PropTypes.bool, +} + +export default Centered diff --git a/templates/react/src/components/ExampleForm.js b/templates/react/src/components/ExampleForm.js new file mode 100644 index 000000000..a6d3a33eb --- /dev/null +++ b/templates/react/src/components/ExampleForm.js @@ -0,0 +1,111 @@ +import React from 'react' +import { compose, withApollo } from 'react-apollo' +import PropTypes from 'prop-types' +import _pickBy from 'lodash.pickby' +import Grid from '@material-ui/core/Grid' + +import { SimpleForm, Field } from './SimpleForm' +import Queries from '../constants/queries' +import Query from './graphql/Query' +import { trackException } from '../utils/error' +import formFields from './forms' +import { H5, P } from './Text' + +class ExampleForm extends React.Component { + render() { + const { templateId, orgId } = this.props + return ( + + {this._renderForm} + + ) + } + + _renderForm = ({ formTemplate, myProfile }) => { + const { templateId } = this.props + const formResponse = + myProfile.formResponses.find(r => r.formTemplateId === templateId) || {} + const { formFields = [] } = formResponse + const fieldMap = formFields.reduce((curr, val) => { + curr[val.name] = val.value + return curr + }, {}) + + if (formTemplate) { + return ( + + + +
{formTemplate.title}
+ {formTemplate.description &&

{formTemplate.description}

} +
+ {formTemplate.fieldTypes.map(this._renderField(fieldMap))} + {this.props.children} +
+
+ ) + } + return null + } + + _handleSubmit = formResponse => async formData => { + try { + const { orgId, templateId, onCompleted, client } = this.props + const formFields = Object.keys(formData).reduce((arr, key) => { + const value = formData[key] + const subObj = { + name: key, + value: value instanceof File ? value.name : value, + } + return arr.concat(subObj) + }, []) + await client.mutate({ + mutation: Queries.SUBMIT_FORM_MUTATION, + variables: _pickBy( + { + responseId: formResponse.id, + templateId, + formFields, + orgId, + }, + val => val !== undefined + ), + }) + if (onCompleted) { + onCompleted(formData) + } + } catch (e) { + trackException(e) + } + } + + _renderField = fieldMap => ({ + width = 4, + fieldset, + component, + ...fieldProps + }) => { + const props = { + ...fieldProps, + defaultValue: fieldMap[fieldProps.name], + } + return ( + + {formFields[component] ? ( + React.createElement(formFields[component], props) + ) : ( + + )} + + ) + } +} + +ExampleForm.propTypes = { + orgId: PropTypes.string.isRequired, + templateId: PropTypes.string.isRequired, + onCompleted: PropTypes.func, + children: PropTypes.node, +} + +export default compose(withApollo)(ExampleForm) diff --git a/templates/react/src/components/Footer.js b/templates/react/src/components/Footer.js new file mode 100644 index 000000000..1bc86edd5 --- /dev/null +++ b/templates/react/src/components/Footer.js @@ -0,0 +1,73 @@ +import React from 'react' +import PropTypes from 'prop-types' +import Grid from '@material-ui/core/Grid' +import { withStyles } from '@material-ui/core/styles' + +import { H5, H6, P, Small } from '../components/Text' +import theme from '../utils/theme' +import { Link } from '../utils/router' + +// NOTE: need to make sure react-snap have access to crawl all the static pages +function Footer({ classes }) { + return ( +
+ + +
+ logo +
+
phrase.home_title
+
+
+ phrase.copyright +
+ +
menu
+

item

+

item

+
+ +
about
+ +

tos

+ +
+ +
+
+ ) +} + +Footer.propTypes = { + classes: PropTypes.object.isRequired, +} + +export default withStyles({ + pageFooter: { + flex: 0, + background: 'black', + padding: theme.spacing(3), + color: 'white', + }, + a: { + color: 'white', + textDecoration: 'none', + }, + logo: { + marginTop: theme.spacing(1), + marginBottom: theme.spacing(1), + }, + img: { + verticalAlign: 'middle', + height: theme.spacing(4), + }, + text: { + display: 'inline-block', + marginLeft: theme.spacing(2), + verticalAlign: 'middle', + }, +})(Footer) diff --git a/templates/react/src/components/Head.js b/templates/react/src/components/Head.js new file mode 100644 index 000000000..0e6d7aa96 --- /dev/null +++ b/templates/react/src/components/Head.js @@ -0,0 +1,44 @@ +import React from 'react' +import { Helmet } from 'react-helmet' +import PropTypes from 'prop-types' + +const defaultDescription = '' +const defaultOGURL = '' +const defaultOGImage = '' + +const Head = props => ( + + + {props.title || ''} + + + + + + + + + + + + + + + + +) + +Head.propTypes = { + title: PropTypes.string, + description: PropTypes.string, + url: PropTypes.string, + ogImage: PropTypes.string, +} + +export default Head diff --git a/templates/react/src/components/LoadingIndicator.js b/templates/react/src/components/LoadingIndicator.js new file mode 100644 index 000000000..5afcfb57c --- /dev/null +++ b/templates/react/src/components/LoadingIndicator.js @@ -0,0 +1,9 @@ +import React from 'react' +import CircularProgress from '@material-ui/core/CircularProgress' +import Centered from './Centered' + +export default () => ( + + + +) diff --git a/templates/react/src/components/Navbar.js b/templates/react/src/components/Navbar.js new file mode 100644 index 000000000..013c5e28f --- /dev/null +++ b/templates/react/src/components/Navbar.js @@ -0,0 +1,136 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { withApollo } from 'react-apollo' + +import AppBar from '@material-ui/core/AppBar' +import Hidden from '@material-ui/core/Hidden' +import Button from '@material-ui/core/Button' +import Menu from '@material-ui/core/Menu' +import MenuItem from '@material-ui/core/MenuItem' +import Divider from '@material-ui/core/Divider' +import Drawer from '@material-ui/core/Drawer' +import IconButton from '@material-ui/core/IconButton' +import MenuIcon from '@material-ui/icons/Menu' +import HomeIcon from '@material-ui/icons/Home' +import UserIcon from '@material-ui/icons/PersonOutline' + +import { router } from '../utils' +import { Text } from '../components/Text' +import { queries } from '../constants' + +const DropdownContainerStyle = { + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', +} + +class NavBar extends React.Component { + state = { + dropdownAnchor: null, + drawer: false, + } + + render() { + const { user, children, ...props } = this.props + return ( + + + + + +
+ {children} + + + + {this.renderMenuItems()} + + + + + + + {this.renderDrawer()} + +
+
+ ) + } + + renderUserName = () => { + const { user = '' } = this.props + return [, user && user.replace(/@.*/gi, '')] + } + + renderMenuItems = () => { + return [ + + forms + , + + dashboard + , + + logout + , + ] + } + + toggleDropdown = e => { + this.setState({ + dropdownAnchor: this.state.dropdownAnchor ? null : e.currentTarget, + }) + } + + hideDropdown = () => { + this.setState({ + dropdownAnchor: null, + }) + } + + logout = () => { + this.props.client.mutate({ mutation: queries.LOGOUT }) + } + + renderDrawer = () => { + const { drawer } = this.state + return ( + +
+
+ {this.renderUserName()} + + {this.renderMenuItems()} +
+
+
+ ) + } + + openDrawer = () => { + this.setState({ drawer: true }) + } + + closeDrawer = () => { + this.setState({ drawer: false }) + } +} + +NavBar.propTypes = { + user: PropTypes.node, + client: PropTypes.object.isRequired, +} + +export default withApollo(NavBar) diff --git a/templates/react/src/components/PageContainer.js b/templates/react/src/components/PageContainer.js new file mode 100644 index 000000000..304a6870c --- /dev/null +++ b/templates/react/src/components/PageContainer.js @@ -0,0 +1,29 @@ +import React from 'react' +import AppBar from '@material-ui/core/AppBar' +import Toolbar from '@material-ui/core/Toolbar' +import Button from '@material-ui/core/Button' + +import { Text } from './Text' +import { Link } from '../utils/router' +import Footer from './Footer' + +export default ({ children }) => ( + + + + + + + + + + + + {children} +