├── .ackrc ├── .circleci └── config.yml ├── .dockerignore ├── .env.example ├── .gitignore ├── .jshintignore ├── .jshintrc ├── .node-version ├── .nvmrc ├── .prettierrc.json ├── .storybook ├── addons.js ├── config.js ├── preview-head.html └── webpack.config.js ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── Procfile ├── README.md ├── __generated__ └── globalTypes.ts ├── __mocks__ ├── fileMock.js ├── react.js └── styleMock.js ├── acceptance.jestrc.json ├── app.json ├── codegen.yml ├── config ├── default.js ├── development.js ├── production.js └── test.js ├── db ├── helpers.ts ├── migrate-and-seed.ts └── migrations │ ├── .gitkeep │ ├── 20181203090529_sketch-out-db.ts │ ├── 20190110233521_create-event-log.ts │ └── 20190127135006_add-effect-to-eventlog.ts ├── docker-compose.yml ├── docker ├── Dockerfile ├── docker.env ├── entrypoint.sh └── supervisord.conf ├── entry ├── client.tsx ├── index.html ├── scripts │ ├── .gitkeep │ ├── job-worker.ts │ ├── main-queue-processor.ts │ ├── migrate-and-seed.ts │ └── unit-test-before-all.ts └── server.ts ├── jest.config.js ├── knexfile.ts ├── modules ├── __tests__ │ ├── db-helpers.ts │ └── setup-enzyme.js ├── atomic-object │ ├── blueprints │ │ ├── blueprint.ts │ │ ├── canvas.ts │ │ └── index.ts │ ├── cache │ │ ├── index.ts │ │ ├── stores │ │ │ ├── memory.ts │ │ │ ├── null.ts │ │ │ └── redis.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── cqrs │ │ ├── __test__ │ │ │ ├── actions.test.ts │ │ │ └── background-actions.test.ts │ │ ├── actions.ts │ │ └── dispatch.ts │ ├── error-notifier.ts │ ├── forms │ │ ├── core.ts │ │ ├── index.tsx │ │ ├── mutation-form.tsx │ │ ├── use-mutation-form.tsx │ │ ├── use-query-for-initial-form.ts │ │ └── use-translated-validation-schema.ts │ ├── i18n │ │ ├── __tests__ │ │ │ └── index.test.ts │ │ ├── component.tsx │ │ └── index.ts │ ├── jobs │ │ ├── __tests__ │ │ │ └── job-runner.test.ts │ │ ├── index.ts │ │ ├── mapping.ts │ │ └── processing-function.ts │ ├── logger.ts │ ├── option.ts │ ├── readme.md │ ├── records.ts │ └── result.ts ├── blueprints │ ├── __tests__ │ │ └── builder.test.ts │ └── index.ts ├── client │ ├── __tests__ │ │ ├── storybook-helper.ts │ │ └── translations.test.tsx │ ├── actions │ │ └── index.ts │ ├── analytics │ │ ├── index.ts │ │ └── load-ga.js │ ├── bootstrap-mui.ts │ ├── components │ │ ├── app-header │ │ │ └── index.tsx │ │ ├── app │ │ │ └── index.tsx │ │ ├── button-link │ │ │ ├── button-link-stories.tsx │ │ │ └── index.tsx │ │ ├── copy │ │ │ └── index.tsx │ │ ├── error-boundary │ │ │ ├── error-boundary.stories.tsx │ │ │ └── index.tsx │ │ ├── error │ │ │ ├── error.stories.tsx │ │ │ └── index.tsx │ │ ├── form │ │ │ ├── autosave.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── error-list.tsx │ │ │ ├── select.tsx │ │ │ ├── show-form-data.tsx │ │ │ ├── text-field.tsx │ │ │ └── time-select.tsx │ │ ├── loading-dialog │ │ │ ├── index.tsx │ │ │ └── loading-dialog.stories.tsx │ │ └── section-provider │ │ │ └── index.tsx │ ├── core │ │ └── index.tsx │ ├── en-US.json │ ├── express.d.ts │ ├── ga.d.ts │ ├── graphql-types.ts │ ├── graphql │ │ ├── __tests__ │ │ │ ├── local-date.test.ts │ │ │ └── local-name.test.ts │ │ ├── client-context.ts │ │ ├── client.ts │ │ ├── core.ts │ │ ├── error-link.ts │ │ ├── fragments │ │ │ ├── .gitkeep │ │ │ └── UserInfo.graphql │ │ ├── hooks.ts │ │ ├── mutations │ │ │ └── ChangeLocalName.graphql │ │ ├── queries │ │ │ ├── GetLocalName.graphql │ │ │ ├── GetLoggedInUser.graphql │ │ │ └── LocalDate.graphql │ │ ├── resolvers │ │ │ ├── index.ts │ │ │ ├── mutation.ts │ │ │ └── query.ts │ │ ├── schema.graphql │ │ └── state-link.ts │ ├── index.tsx │ ├── material-ui-core-styles-create-mui-theme.d.ts │ ├── material-ui-styles.d.ts │ ├── pages │ │ ├── error │ │ │ └── error-loaders.tsx │ │ └── home │ │ │ ├── __tests__ │ │ │ └── home-page.test.tsx │ │ │ ├── home-page-ui.stories.tsx │ │ │ ├── home-page-ui.tsx │ │ │ └── index.tsx │ ├── react.d.ts │ ├── routes │ │ └── authentication-routes.tsx │ ├── stories.ts │ ├── storybook-addon-viewport.d.ts │ ├── storybook-decorators.tsx │ ├── styles │ │ ├── index.tsx │ │ └── mui-theme.tsx │ ├── test-helpers │ │ └── mock-provider.tsx │ └── translations.ts ├── core │ ├── .gitkeep │ ├── __tests__ │ │ ├── date-iso.test.ts │ │ └── time-iso.test.ts │ ├── date-iso.ts │ ├── env.d.ts │ ├── index.ts │ ├── permissions.ts │ ├── schemas │ │ ├── date-iso.schema.json │ │ ├── email-address.schema.json │ │ ├── index.ts │ │ └── time-iso.schema.json │ └── time-iso.ts ├── db │ ├── __tests__ │ │ └── db.test.ts │ ├── index.ts │ └── redis.ts ├── fixtures │ ├── index.ts │ └── scenarios │ │ └── default.ts ├── graphql-api │ ├── __tests__ │ │ └── caching.test.ts │ ├── context.ts │ ├── index.ts │ ├── resolvers │ │ ├── __tests__ │ │ │ └── query.test.ts │ │ ├── index.ts │ │ ├── mutation │ │ │ └── index.ts │ │ ├── query.ts │ │ └── user.ts │ ├── schema-base.ts │ └── schema.graphql ├── helpers │ ├── assert-assignable.ts │ ├── dataloader.ts │ ├── index.ts │ └── json.ts ├── records │ ├── __tests__ │ │ └── event-log.test.ts │ ├── event-log.ts │ ├── impl │ │ ├── base.ts │ │ └── core.ts │ ├── index.ts │ └── user.ts ├── server │ ├── authentication.ts │ ├── context.ts │ ├── core │ │ └── index.ts │ ├── index.ts │ └── middleware.ts └── services │ ├── core │ └── index.ts │ ├── index.ts │ └── permissions │ ├── index.ts │ └── permissions.test.ts ├── package.json ├── package.md ├── scripts ├── codegen-type-constants.js └── json-schema-types.js ├── styleguide.config.js ├── tests └── acceptance │ ├── example.test.ts │ ├── helpers.ts │ └── nightmare.d.ts ├── tsconfig.base.json ├── tsconfig.client.json ├── tsconfig.json ├── tsconfig.server.json ├── tslint.json ├── webpack ├── client.config.js ├── loaders.js ├── server.config.js ├── storybook.config.js └── webpack-dev-server.js ├── yarn-bash-completion └── yarn.lock /.ackrc: -------------------------------------------------------------------------------- 1 | --ignore-dir=__generated__ 2 | --ignore-dir=__generated__ 3 | --ignore-dir=dist 4 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Validate from CLI with circleci config process ./.circleci/config.yml 2 | 3 | version: 2.1 4 | 5 | # To continuously deploy your feature branch to a review server, 6 | # fill in the 'review_branch' and 'review_server' fields below. 7 | aliases: 8 | - &review_branch feature/some-feature 9 | - &review_server my-app-dev-1 10 | - &staging_branch master 11 | - &staging_server my-app-staging 12 | 13 | jobs: 14 | build: 15 | environment: 16 | NODE_ENV: test 17 | docker: 18 | # specify the version you desire here 19 | - image: circleci/node:10-browsers 20 | - image: circleci/redis:4 21 | - image: circleci/postgres:10.5 22 | environment: 23 | POSTGRES_USER: root 24 | POSTGRES_DB: test 25 | 26 | working_directory: ~/repo 27 | 28 | steps: 29 | - checkout 30 | 31 | # Download and cache dependencies 32 | - restore_cache: 33 | keys: 34 | - node-{{ .Branch }}-{{ checksum "yarn.lock" }} 35 | # fallback to using the latest cache if no exact match is found 36 | - node- 37 | 38 | - run: 39 | name: "Install deps" 40 | command: yarn install 41 | 42 | - save_cache: 43 | paths: 44 | - node_modules 45 | key: node-{{ .Branch }}-{{ checksum "yarn.lock" }} 46 | 47 | - run: 48 | command: yarn db:migrate:latest 49 | environment: 50 | DATABASE_URL: postgres://root@127.0.0.1:5432/test 51 | - run: 52 | name: "Build" 53 | command: yarn test:acceptance:build 54 | environment: 55 | DATABASE_URL: postgres://root@127.0.0.1:5432/test 56 | REDIS_URL: redis://localhost:6379 57 | 58 | - run: 59 | name: "Unit tests" 60 | command: yarn test:unit:ci 61 | environment: 62 | DATABASE_URL: postgres://root@127.0.0.1:5432/test 63 | REDIS_URL: redis://localhost:6379 64 | JEST_JUNIT_OUTPUT: "test-results/unit/unit-test-results.xml" 65 | 66 | - run: 67 | name: "Acceptance tests" 68 | command: yarn test:acceptance 69 | environment: 70 | DATABASE_URL: postgres://root@127.0.0.1:5432/test 71 | REDIS_URL: redis://localhost:6379 72 | JEST_JUNIT_OUTPUT: "test-results/acceptance/acceptance-test-results.xml" 73 | 74 | - run: 75 | name: "Lint" 76 | command: yarn lint 77 | 78 | - store_test_results: 79 | path: test-results 80 | 81 | deploy_heroku: 82 | description: "Deploy current branch to specified heroku app" 83 | parameters: 84 | heroku_app: 85 | description: "Where to deploy" 86 | type: string 87 | extra_git_push_args: 88 | description: "More git push flags (e.g. -f)" 89 | default: "" 90 | type: string 91 | docker: 92 | - image: circleci/node:10-browsers 93 | working_directory: ~/repo 94 | steps: 95 | - checkout 96 | - run: 97 | name: Deploy branch to Heroku 98 | command: git push << parameters.extra_git_push_args >> https://heroku:$HEROKU_API_KEY@git.heroku.com/<< parameters.heroku_app >>.git $CIRCLE_BRANCH:master 99 | 100 | workflows: 101 | version: 2 102 | build-and-deploy: 103 | jobs: 104 | - build 105 | - deploy_heroku: 106 | heroku_app: *review_server 107 | extra_git_push_args: -f 108 | requires: 109 | - build 110 | filters: 111 | branches: 112 | only: *review_branch 113 | - deploy_heroku: 114 | heroku_app: *staging_server 115 | requires: 116 | - build 117 | filters: 118 | branches: 119 | only: *staging_branch -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | log 3 | tmp 4 | vendor 5 | bin/deploy 6 | docker/Dockerfile 7 | .env 8 | dist 9 | node_modules 10 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | DEV_DATABASE_URL=postgres://root@127.0.0.1:5432/development 3 | TEST_DATABASE_URL=postgres://root@127.0.0.1:5432/test 4 | PORT=3001 5 | DEV_SERVER_DISABLE_HOST_CHECK=0 6 | REDIS_URL=redis://localhost:6379/0 7 | USE_FAKE_DATA=true 8 | enableDeveloperLogin=true 9 | __TEST__=false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # IntelliJ 61 | .idea/ 62 | msl-website.iml 63 | 64 | # dist 65 | dist/*.js 66 | dist/**/*.* 67 | 68 | # shared 69 | shared/**/*.js 70 | 71 | # ignore generated js files under server/src 72 | server/src/**/*.js 73 | server/tests/**/*.js 74 | 75 | # ignore generated js files under server/src 76 | client/src/**/*.js 77 | client/src/**/*.jsx 78 | client/tests/**/*.js 79 | client/tests/**/*.jsx 80 | 81 | # DS_Store 82 | *.DS_Store 83 | 84 | # .env 85 | .env 86 | 87 | # Generated schema used in typescript type generation 88 | modules/graphql-api/schema.json 89 | 90 | # Add any directories, files, or patterns you don't want to be tracked by version control 91 | **/.idea 92 | .cache 93 | 94 | junit.xml 95 | 96 | # Storybook Snapshots. We're relying on manual testing to verify that these look good. 97 | /modules/client/__tests__/__snapshots__/stories.test.ts.snap 98 | **/*.gen.ts 99 | **/*.gen.tsx 100 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | 4 | "curly": true, 5 | "latedef": true, 6 | "quotmark": true, 7 | "undef": true, 8 | "unused": true, 9 | "trailing": true 10 | } 11 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 10.11.0 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v10 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5" 3 | } 4 | -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | require("@storybook/addon-actions/register"); 2 | require("@storybook/addon-viewport/register"); 3 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import "client/bootstrap-mui"; // this must be the first import 2 | import { 3 | withTheme 4 | } from "../modules/client/storybook-decorators"; 5 | const { 6 | configure, 7 | addDecorator 8 | } = require("@storybook/react"); 9 | 10 | addDecorator(withTheme); 11 | 12 | function loadStories() { 13 | require("../modules/client/stories.ts"); 14 | // You can require as many stories as you need. 15 | } 16 | 17 | configure(loadStories, module); -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 5 |
6 | 12 | 13 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const loaders = require("../webpack/loaders"); 3 | const ExtractTextPlugin = require("extract-text-webpack-plugin"); 4 | var HappyPack = require("happypack"); 5 | var ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin"); 6 | const os = require("os"); 7 | const webpack = require("webpack"); 8 | 9 | // Export a function. Accept the base config as the only param. 10 | module.exports = (storybookBaseConfig, configType) => { 11 | // configType has a value of 'DEVELOPMENT' or 'PRODUCTION' 12 | // You can change the configuration based on that. 13 | // 'PRODUCTION' is used when building the static version of storybook. 14 | 15 | // Make whatever fine-grained changes you need 16 | storybookBaseConfig.module.rules.push( 17 | { 18 | test: /\.s?css$/, 19 | loaders: [ 20 | "style-loader", 21 | "css-loader", 22 | { 23 | loader: "sass-loader", 24 | options: { includePaths: [path.resolve(__dirname, "../modules")] }, 25 | }, 26 | ], 27 | include: path.resolve(__dirname, "../"), 28 | }, 29 | loaders.mjs, 30 | loaders.clientSideTypeScript, 31 | loaders.graphql, 32 | ...loaders.allImagesAndFontsArray 33 | ); 34 | 35 | storybookBaseConfig.resolve.extensions.push(".ts", ".tsx"); 36 | storybookBaseConfig.resolve.modules.unshift( 37 | path.resolve(__dirname, "../modules") 38 | ); 39 | 40 | storybookBaseConfig.plugins.push( 41 | // new HappyPack({ 42 | // id: "ts", 43 | // threads: Math.max(1, os.cpus().length / 2 - 1), 44 | // loaders: [ 45 | // { 46 | // path: "ts-loader", 47 | // query: { happyPackMode: true, configFile: "tsconfig.client.json" } 48 | // } 49 | // ] 50 | // }), 51 | new ForkTsCheckerWebpackPlugin({ 52 | // https://github.com/Realytics/fork-ts-checker-webpack-plugin#options 53 | useTypescriptIncrementalApi: true, 54 | // checkSyntacticErrors: true 55 | }), 56 | new webpack.DefinePlugin({ 57 | // Flag to detect non-production 58 | __TEST__: "false", 59 | }) 60 | ); 61 | // Return the altered config 62 | return storybookBaseConfig; 63 | }; 64 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "mikestead.dotenv", 4 | "kumar-harsh.graphql-for-vscode", 5 | "eamodio.gitlens", 6 | "christian-kohler.npm-intellisense", 7 | "esbenp.prettier-vscode", 8 | "ms-vscode.vscode-typescript-tslint-plugin", 9 | "tobermory.es6-string-html", 10 | "tberman.json-schema-validator" 11 | ] 12 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [{ 7 | "type": "node", 8 | "request": "attach", 9 | "name": "Attach", 10 | "port": 9229, 11 | "smartStep": true 12 | }] 13 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "search.exclude": { 4 | "**/dist": true, 5 | "**/node_modules": true, 6 | "**/bower_components": true 7 | }, 8 | "typescript.tsdk": "node_modules/typescript/lib", 9 | "editor.tabSize": 2 10 | } 11 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node --optimize_for_size --gc_interval=100 --max_old_space_size=${NODE_MAX_OLD_SIZE:-460} ./dist/server.js 2 | worker: node --optimize_for_size --gc_interval=100 --max_old_space_size=${NODE_MAX_OLD_SIZE:-460} ./dist/scripts/job-worker.js 3 | release: yarn heroku-release -------------------------------------------------------------------------------- /__generated__/globalTypes.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | // This file was automatically generated and should not be edited. 3 | 4 | //============================================================== 5 | // START Enums and Input Objects 6 | //============================================================== 7 | 8 | //============================================================== 9 | // END Enums and Input Objects 10 | //============================================================== 11 | -------------------------------------------------------------------------------- /__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = "test-file-stub"; 2 | -------------------------------------------------------------------------------- /__mocks__/react.js: -------------------------------------------------------------------------------- 1 | // There is uncertainty around enzyme's support of React.memo 2 | // todo remove later when Enzyme supports this. 3 | const react = require("react"); 4 | module.exports = { ...react, memo: x => x }; -------------------------------------------------------------------------------- /__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; -------------------------------------------------------------------------------- /acceptance.jestrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "\\.(gql|graphql)$": "jest-transform-graphql", 4 | "\\.(js|jsx|ts|tsx)$": "ts-jest" 5 | }, 6 | "transformIgnorePatterns": ["
19 |
18 | {JSON.stringify(ctx.values, null, 2)} 19 | 20 | {JSON.stringify(values, null, 2)} 21 |22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /modules/client/components/form/text-field.tsx: -------------------------------------------------------------------------------- 1 | import { I18nProps, useTranslator } from "client/translations"; 2 | import { Field, FieldAttributes, useField } from "formik"; 3 | import * as FMUI from "formik-material-ui"; 4 | 5 | import * as React from "react"; 6 | import { makeStyles } from "client/styles"; 7 | 8 | export type TextFieldProps = { 9 | name: string; 10 | translation?: I18nProps; 11 | } & FieldAttributes<{}>; 12 | /** A thin wrapper around Material UI TextField supporting Formik and translations. */ 13 | export const TextField: React.SFC
>(
11 | importFn: () => Promise
32 | | StyleRulesCallback ;
33 |
34 | export type StyleRulesWithProps