commit 4ce981c5b6a476a322444a1ecae1cf0d1ff42fa9 Author: Valdior Date: Wed May 14 21:49:03 2025 +0200 init commit diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..feaf73f --- /dev/null +++ b/.eslintignore @@ -0,0 +1,6 @@ +dist/** +src/index.html +flow-typed/** +node_modules/** +seed/node_modules/** +dashboard/node_modules/** diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..504bcb0 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,13 @@ +module.exports = { + "extends": "./seed/.eslintrc.js", + "settings": { + "import/resolver": { + "webpack": { + config: require.resolve('./seed/config/webpack/default.js') + }, + "node": { + "paths": ["src", "seed/src"] + }, + } + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..372e1f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.idea +node_modules +package-lock.json +dist +build +ssr +.DS_Store \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..74cf995 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + singleQuote: true, + trailingComma: 'all', + printWidth: 100, + tabWidth: 2, + useTabs: false, +}; diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..919731f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:8500", + "webRoot": "${workspaceFolder}" + } + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..abf94d9 --- /dev/null +++ b/README.md @@ -0,0 +1,123 @@ +workinflex-frontend + +# Project + +Creobis admin + +# Author + +Nicolas Bernier + +# Git modules + +## Seed react + +Add this git as a submodule + +git@gitlab.com:makeit-group/apps/seed-react.git + +(you must have the right access) + +Add it as seed/ (the name/path => very important!) + +## Seed admin + +Add this git as a submodule + +git@gitlab.com:makeit-group/apps/seed-admin.git + +(you must have the right access) + +Add it as admin/ (the name/path => very important!) + +## Strucutre + +the final structure of a project containing this seed shoudl be + +-- project-name/ +---- admin (seed-admin) +---- seed (seed-react) + +# Installation + +(after gitmodules) + +npm i +cd seed +npm i +cd ../admin +npm i + +or directly + +npm i && cd seed && npm i && cd ../admin && npm i && cd ../seed + +# Structure Project + +/components : react part + +- /enhancers : high order components +- /formItems : components used by redux-form +- /forms : redux-from forms +- /global : global components of the app (Navigation, Header, ...) +- /items : components used in multiple other components +- /layouts : layout components of the app +- /listItems : components used in list +- /pageItems : specific sub-part of pages (if used several times, shoudl be moved to items) +- /routes : page components (endpoint of the react-router) +- /structure : structural components taking children + +/store : redux part + +- /actions +- /apis +- /reducers +- /sagas +- /utils + +/styles : general scss + +/types : flow types + +# Generation + +Make sure to have plop installed globally + +sudo npm i -g plop + +then simply run the following command and follow the terminal instructions + +plop + +# Seed + +The seed containes all the shared logic accross projects + +# Storybook + +cd ./seed +yarn storybook + +it will also look at the parent stories + +# Test + +cd ./seed +yarn test + +it will also look at the parent test + +# Start build + +cd ./seed +yarn start + +yarn build + +# Icons + +Font Awesome is installed in the seed and imported in the admin + +# Zeus + +zeus https://dev-api.work-in-flex.com/app ./src/config --ts diff --git a/assets/underline-1.svg b/assets/underline-1.svg new file mode 100644 index 0000000..42b1010 --- /dev/null +++ b/assets/underline-1.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/underline-2.svg b/assets/underline-2.svg new file mode 100644 index 0000000..f96d599 --- /dev/null +++ b/assets/underline-2.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/underline-3.svg b/assets/underline-3.svg new file mode 100644 index 0000000..721999d --- /dev/null +++ b/assets/underline-3.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/underline-4.svg b/assets/underline-4.svg new file mode 100644 index 0000000..257f19c --- /dev/null +++ b/assets/underline-4.svg @@ -0,0 +1,3 @@ + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..96f99d4 --- /dev/null +++ b/package.json @@ -0,0 +1,68 @@ +{ + "name": "workin-flex", + "version": "1.0.0", + "description": "Work'in Flex app", + "main": "seed/src/main.tsx", + "author": "Nicolas Bernier", + "license": "MIT", + "private": true, + "deploy": { + "awsProfile": "workinflex", + "locale": { + "frontendUrl": "http://localhost:8500/" + }, + "dev": { + "bucket": "s3://dev.work-in-flex.com/app", + "frontendUrl": "/app/" + }, + "staging": { + "bucket": "s3://staging.work-in-flex.com/app", + "frontendUrl": "/app/" + }, + "production": { + "bucket": "s3://production.work-in-flex.com/app", + "frontendUrl": "/app/" + } + }, + "scripts": { + "eslint": "eslint ./node_modules/.bin/eslint src/. --ext .js --ext .tsx --fix", + "zeus": "zeus https://dev-api.work-in-flex.com/app ./src/config --ts", + "zeus:staging": "zeus https://staging-api.work-in-flex.com/app ./src/config --ts" + }, + "dependencies": { + "@stripe/react-stripe-js": "^1.1.2", + "@stripe/stripe-js": "^1.11.0", + "date-fns": "^2.16.1", + "google-map-react": "^2.1.9", + "points-cluster": "^0.1.4", + "rc-slider": "^9.5.1", + "react-date-range": "^1.1.3", + "react-scroll": "^1.8.1" + }, + "devDependencies": { + "@types/jest": "^24.0.18", + "@types/node": "^11.15.14", + "@types/react": "^16.9.4", + "@types/react-dom": "^16.9.1", + "@types/react-redux": "^7.1.9", + "@typescript-eslint/eslint-plugin": "^1.13.0", + "@typescript-eslint/parser": "^1.13.0", + "babel-eslint": "^10.0.2", + "eslint": "^5.16.0", + "eslint-config-airbnb": "^17.1.1", + "eslint-config-airbnb-base": "^13.2.0", + "eslint-config-airbnb-typescript": "^4.0.1", + "eslint-config-prettier": "^6.0.0", + "eslint-import-resolver-webpack": "^0.10.1", + "eslint-plugin-babel": "^5.1.0", + "eslint-plugin-import": "^2.18.2", + "eslint-plugin-jest": "^22.14.1", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-prettier": "^3.1.0", + "eslint-plugin-react": "^7.15.1", + "eslint-plugin-react-hooks": "4.3.0", + "prettier": "^1.18.2", + "prettier-stylelint": "^0.4.2", + "typescript": "^3.9.5" + } +} diff --git a/plopfile.js b/plopfile.js new file mode 100644 index 0000000..8ed143a --- /dev/null +++ b/plopfile.js @@ -0,0 +1,17 @@ +/* eslint-disable import/no-extraneous-dependencies */ +const path = require('path'); + +const templateDir = 'seed/plop-templates'; + +const getTemplatePath = filename => path.join(templateDir, filename); +const componentPath = 'src/components'; +const storePath = 'src/store'; + +const plopBase = require('./seed/plopbase'); + +module.exports = plopBase({ + getTemplatePath, + path, + storePath, + componentPath, +}); diff --git a/seed/.eslintignore b/seed/.eslintignore new file mode 100644 index 0000000..3c8ce5a --- /dev/null +++ b/seed/.eslintignore @@ -0,0 +1,2 @@ +node_modules/** +flow-typed/** diff --git a/seed/.eslintrc.js b/seed/.eslintrc.js new file mode 100644 index 0000000..b6dec32 --- /dev/null +++ b/seed/.eslintrc.js @@ -0,0 +1,72 @@ +module.exports = { + parser: '@typescript-eslint/parser', + extends: [ + 'airbnb', + 'plugin:react/recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:jest/recommended', + // config-prettier + plugin-prettier to integrate prettier into eslint + 'prettier', + 'plugin:prettier/recommended', + ], + plugins: ['@typescript-eslint', 'react-hooks', 'react', 'jest', 'prettier'], + env: { + browser: true, + es6: true, + }, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + // global variables + globals: { + __LOCALE__: false, + __DEV__: false, + __STAGING__: false, + __BROWSER__: true, + __SSR__: false, + __TEST__: false, + __PLATFORM__: 'readonly', + __ENV__: 'readonly', + __PROVIDER__: 'readonly', + }, + rules: { + 'linebreak-style': 'off', + 'global-require': 'off', + 'no-restricted-globals': 'off', + 'func-names': 'off', + 'no-console': 'off', + 'no-continue': 'off', + 'no-param-reassign': 'off', + 'no-plusplus': 'off', + 'no-loop-func': 'off', + //"class-methods-use-this": "off", + 'jsx-a11y/no-static-element-interactions': 'off', + 'jsx-a11y/click-events-have-key-events': 'off', + 'react-hooks/exhaustive-deps': 'warn', + 'react/prop-types': 'off', + 'react/require-default-props': 'off', + 'react/jsx-filename-extension': 'off', + 'react/no-array-index-key': 'off', + 'react/default-props-match-prop-types': 'off', + 'react/jsx-props-no-multi-spaces': 'off', + 'import/no-webpack-loader-syntax': 'off', + 'import/no-extraneous-dependencies': 'off', + 'no-underscore-dangle': 'off', + 'prefer-destructuring': 'off', + 'jsx-a11y/no-noninteractive-element-interactions': 'off', + 'jsx-a11y/label-has-associated-control': 'off', + 'jsx-a11y/label-has-for': 'off', + 'jsx-a11y/no-noninteractive-tabindex': 'off', + 'react/prefer-stateless-function': 0, + 'prettier/prettier': 'error', + 'import/extensions': 'off', + // Typescript + '@typescript-eslint/explicit-member-accessibility': 'off', + '@typescript-eslint/indent': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-empty-interface': 'off', + }, +}; diff --git a/seed/.gitignore b/seed/.gitignore new file mode 100644 index 0000000..a73d470 --- /dev/null +++ b/seed/.gitignore @@ -0,0 +1,15 @@ +.idea +node_modules +package-lock.json +dist +build + +sls/ + +ssr/client +ssr/server +ssr/serverless + +.serverless/* +src/server/manifest.json +.DS_Store diff --git a/seed/.htaccess b/seed/.htaccess new file mode 100644 index 0000000..bd3204e --- /dev/null +++ b/seed/.htaccess @@ -0,0 +1,56 @@ +Options +FollowSymLinks -Indexes -MultiViews +#redirection + + +RewriteEngine On + + +# Don't rewrite files or directories +RewriteCond %{REQUEST_FILENAME} -f [OR] +RewriteCond %{REQUEST_FILENAME} -d +RewriteRule ^ - [L] + +# Rewrite everything else to index.html to allow html5 state links +RewriteRule ^ index.html [L] + + + + + Header set Connection keep-alive + + Header set Access-Control-Allow-Origin "*" + + + Header set Cache-Control "max-age=604800, public" + + + Header set Cache-Control "max-age=3600, public" + + + + +############################################ +## enable apache served files compression +## http://developer.yahoo.com/performance/rules.html#gzip + + # Insert filter on all content + SetOutputFilter DEFLATE + + # Insert filter on selected content types only + # AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript + + # Netscape 4.x has some problems... + BrowserMatch ^Mozilla/4 gzip-only-text/html + + # Netscape 4.06-4.08 have some more problems + BrowserMatch ^Mozilla/4\.0[678] no-gzip + + # MSIE masquerades as Netscape, but it is fine + BrowserMatch \bMSIE !no-gzip !gzip-only-text/html + + # Don't compress images + #SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary + + # Make sure proxies don't deliver the wrong content + Header append Vary User-Agent env=!dont-vary + \ No newline at end of file diff --git a/seed/.prettierignore b/seed/.prettierignore new file mode 100644 index 0000000..95d17c8 --- /dev/null +++ b/seed/.prettierignore @@ -0,0 +1,2 @@ +.gitlab-ci.yml +**/*.hbs \ No newline at end of file diff --git a/seed/.prettierrc.js b/seed/.prettierrc.js new file mode 100644 index 0000000..63a5de5 --- /dev/null +++ b/seed/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + singleQuote: true, + trailingComma: 'all', + printWidth: 150, + tabWidth: 2, + useTabs: false, +}; diff --git a/seed/.storybook/addons.js b/seed/.storybook/addons.js new file mode 100644 index 0000000..9aca375 --- /dev/null +++ b/seed/.storybook/addons.js @@ -0,0 +1,11 @@ +// Check here https://storybook.js.org/addons/addon-gallery/ + +import '@storybook/addons'; +import '@storybook/addon-actions/register'; +// import '@storybook/addon-a11y/register'; +import '@storybook/addon-notes/register' +import '@storybook/addon-options/register'; +import '@storybook/addon-knobs/register'; +import '@storybook/addon-viewport/register'; +import '@storybook/addon-backgrounds/register'; + diff --git a/seed/.storybook/config.js b/seed/.storybook/config.js new file mode 100644 index 0000000..515a99c --- /dev/null +++ b/seed/.storybook/config.js @@ -0,0 +1,110 @@ +// Polyfills +import * as React from 'react'; + +// Storybook +import { configure, addDecorator } from '@storybook/react'; +// React router +import StoryRouter from 'storybook-react-router'; +// Edit properties +import { withKnobs } from '@storybook/addon-knobs/react'; +// Special options +import { withOptions } from '@storybook/addon-options'; +// Check compliance +// import { checkA11y } from '@storybook/addon-a11y'; +// Add notes +import { withNotes } from '@storybook/addon-notes'; +// Viewport +import { configureViewport, INITIAL_VIEWPORTS } from '@storybook/addon-viewport'; +// Background +import { withBackgrounds } from '@storybook/addon-backgrounds'; + +// Style +import 'styles/seedMain.scss'; + +// Global components +import Modals from 'components/global/Modals'; +import Loader from 'components/global/Loader'; +// FONT AWESOME +import { library } from '@fortawesome/fontawesome-svg-core'; +// free icons +import { fab } from '@fortawesome/free-brands-svg-icons'; +import { fas } from '@fortawesome/free-solid-svg-icons'; +import { far } from '@fortawesome/free-regular-svg-icons'; + +// store +import { Provider } from 'react-redux'; + +import setupStore from 'store/setup'; + +import rootSaga from 'store/rootSaga'; +import { routerMiddleware } from 'connected-react-router'; + +// Config icons +library.add(fab, fas, far); + +const setup = setupStore({}); +// run saga +setup.sagaRun(rootSaga); + +const appDecorator = storyFn => ( + +
+ + +
+
{storyFn()}
+
+
+
+); + +// automatically import all files ending in *.stories.js +const req = require.context('../src', true, /.stories.js$/); +const reqParent = require.context('../../src', true, /.stories.js$/); +// can be empty +let reqDahboard; +try { + reqDahboard = require.context('../../admin', true, /.stories.js$/); +} catch (e) { + // continue +} + +function loadStories() { + req.keys().forEach(filename => req(filename)); + reqParent.keys().forEach(filename => reqParent(filename)); + if (reqDahboard) reqDahboard.keys().forEach(filename => reqDahboard(filename)); +} + +configureViewport({ + viewports: { + ...INITIAL_VIEWPORTS, + }, +}); +addDecorator( + withOptions({ + /* name: 'CRA Kitchen Sink', + goFullScreen: false, + showAddonsPanel: true, + showSearchBox: false, + addonPanelInRight: true, + sortStoriesByKind: false, + hierarchySeparator: /\./, + hierarchyRootSeparator: /\|/, + enableShortcuts: true, */ + }), +); +addDecorator( + withBackgrounds([ + { name: 'white', value: '#FFF' }, + { name: 'dark', value: '#333' }, + { name: 'grey', value: '#BBB' }, + ]), +); +// addDecorator(checkA11y); +addDecorator(appDecorator); +addDecorator(withKnobs); +addDecorator(withNotes); +addDecorator(StoryRouter()); + +// Configure +configure(loadStories, module); diff --git a/seed/.storybook/main.js b/seed/.storybook/main.js new file mode 100644 index 0000000..26dfeaf --- /dev/null +++ b/seed/.storybook/main.js @@ -0,0 +1,10 @@ +module.exports = { + "stories": [ + "../src/**/*.stories.mdx", + "../src/**/*.stories.@(js|jsx|ts|tsx)" + ], + "addons": [ + "@storybook/addon-links", + "@storybook/addon-essentials" + ] +} \ No newline at end of file diff --git a/seed/.storybook/preview.js b/seed/.storybook/preview.js new file mode 100644 index 0000000..48afd56 --- /dev/null +++ b/seed/.storybook/preview.js @@ -0,0 +1,9 @@ +export const parameters = { + actions: { argTypesRegex: "^on[A-Z].*" }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, +} \ No newline at end of file diff --git a/seed/.storybook/webpack.config.js b/seed/.storybook/webpack.config.js new file mode 100644 index 0000000..6ddffa3 --- /dev/null +++ b/seed/.storybook/webpack.config.js @@ -0,0 +1,32 @@ +// you can use this file to add your custom webpack plugins, loaders and anything you like. +// This is just the basic way to add additional webpack configurations. +// For more information refer the docs: https://storybook.js.org/configurations/custom-webpack-config + +// IMPORTANT +// When you add this file, we won't add the default configurations which is similar +// to "React Create App". This only has babel loader to load JavaScript. + +// libs +const merge = require('webpack-merge'); +const webpack = require('webpack'); + +const parts = require('../config/webpack/parts'); +const paths = require('../config/paths'); + +module.exports = merge([ + parts.modulePathResolve(Object.values(paths.src), Object.values(paths.modules)), + parts.loadFonts(), + parts.loadImages(), + parts.loadJavaScript(), + parts.loadCSS({ + styleLoader: true, + }), + parts.setVariables({ + __BROWSER__: true, + __SSR__: false, + __TEST__: false, + __ENV__: 'default', + __PLATFORM__: 'default', + __PROVIDER__: 'default', + }), +]); diff --git a/seed/README.md b/seed/README.md new file mode 100644 index 0000000..99609f6 --- /dev/null +++ b/seed/README.md @@ -0,0 +1,285 @@ +# Make it Seed + +## Author + +Nicolas Bernier + +## Infos + +This project needs to be the submodule of a project. +He is not supposed to be launched on its own. + +## Firebase + +If you are using firebase, do not fodrget to add the following in the index.html file of the project + + + + + + +## Structure + +/components : react part + +- /enhancers : high order components +- /formItems : components used by redux-form +- /forms : redux-from forms +- /global : global components of the app (Navigation, Header, ...) +- /items : components used in multiple other components +- /layouts : layout components of the app +- /listItems : components used in list +- /pageItems : specific sub-part of pages (if used several times, shoudl be moved to items) +- /routes : page components (endpoint of the react-router) +- /structure : structural components taking children + +/store : redux part + +- /app // app resource +- /auth // auth resource +- /content // content ressource +- /setup // setup of the store +- /utils +- rootReducer.ts // root reducers +- rootSage.ts // root sagas + +/styles : general scss + +## Icons + +We use this librairie (check in dependencies) + +https://fontawesome.com/icons?d=gallery&s=brands,light,regular,solid&m=free + +For project specific font-type icons: + +### Creating a font zip on Icomoon +1. Get your folder with icons in `.svg` +2. Ensure your naming is correct, if not, do **not** rename in Icomoon but in your own folder, else it may break things +3. Go to https://icomoon.io/app and import that folder into a new Set that you create +4. Click on “Generate Font” in the bottom right area, then once on the page is loaded, a “Download” button appears where “Generate Font” was, click and download the zip + +### Linking the created font to your project +1. In your project, replace your `[project]-font` with what was exported from Icomoon above *(example: `mygms-font`)* +2. Take everything in the `/font` folder and copy/paste it into `styling/components/fonts`, making sure that the names stay the same (Icomoon…) +3. Go to your `ProjectIcon` component and copy/paste the `style.css` file from your newly created `[project]-font` folder into `ProjectIcon.styled.ts` (from L.26 `.icon- … ` ) +4. Select all instances of `.icon-` and replace with `&.icon-` +5. Select all instances of `'\` and replace with `'\\` + +And you’re done! + +## Usage +Use component `ProjectIcon` +Name in `icon` prop has to be what is after the `icon-` in the icon’s list (find the icon list and names in the `demo.html` from your `[project]-font` folder) + +`` + + +## Eslint + +Could be removed as it should be present in the parent for the IDE + +Eslint package related modules to add in the parent : + +```json +{ + "babel-eslint": "^10.0.2", + "eslint": "^5.16.0", + "eslint-config-airbnb": "^17.1.1", + "eslint-config-airbnb-base": "^13.2.0", + "eslint-config-airbnb-typescript": "^4.0.1", + "eslint-config-prettier": "^6.0.0", + "eslint-import-resolver-webpack": "^0.10.1", + "eslint-plugin-babel": "^5.1.0", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-jest": "^22.14.1", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-prettier": "^3.1.0", + "eslint-plugin-react": "^7.14.3", + "eslint-plugin-react-hooks": "4.3.0", + "prettier": "^1.18.2", + "prettier-stylelint": "^0.4.2", + "typescript": "^3.9.5" +} +``` + +## Reorder package.json + +```bash +npm remove -S example && npm remove -D example; +``` + +## Styles + +Add a file in the parent + +stylelint.config.js + +```javascript +module.exports = { + extends: './seed/stylelint.config.js', +}; +``` + +## Code formating (prettier && eslint) + +Prettier handle code formating (tabs, space, ...) +Eslint handle code error + +You need the plugins _eslint_ and _prettier - code formatter_ from VS Code + +### Config + +You only need the plugin eslint + +1. In you eslint config, include the prettier config as below: + +```javascript +'prettier/prettier': 'error', +``` + +2. Then edit your VSCode as below + +```json +{ + "javascript.validate.enable": false, + "typescript.validate.enable": false, + // Editor + "editor.formatOnSave": true, + // Eslint + "editor.codeActionsOnSave": { + "source.fixAll": true + } +} +``` + +3. Add a .prettierrc.js (or equivalent) in your root folder + +```javascript +module.exports = { + singleQuote: true, + trailingComma: 'all', + printWidth: 100, + tabWidth: 2, + useTabs: false, +}; +``` + +## Other VSCode extansions + +Make sure to have the following extensions : + +SCSS intelliSence +stylelint + +## Typescript + +Add tsconfig.json in the parent + +```json +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react", + "baseUrl": ".", + "paths": { + "*": ["src/*", "admin/src/*", "seed/src/*"] + } + } +} +``` + +## Store + +### Structure + +a resource is a folder made of + +resourceName/ +- actions +- api +- constants +- index : gather everything (module based) +- reducer +- sagas +- selectors + +In the parent : + +create appSaga.ts in the store folder of this form + +```typescript +import { fork } from 'redux-saga/effects'; + +import YOUR_RESROURCE from 'store/YOUR_RESROURCE'; + +import bootupSaga from './bootup'; + +export default function* appSaga(): void { + yield fork(bootupSaga); + yield fork(YOUR_RESROURCE.saga); + ... +} + +``` + +create appReducers.ts in the store folder of this form + +```typescript +import YOUR_RESROURCE from 'store/YOUR_RESROURCE'; +... + +const reducers = { + resource1: YOUR_RESROURCE.reducer, + ... +}; + +export type AppStoreState = { + resource1: ReturnType; +}; + +export default reducers; + +``` + + +# Zeus + +graphql-zeus is a helper to perform graphql actions (query, mutation, subscription) + +With v4, some minor syntax changes occured : + +```ts +const { Zeus, $ } = zeus; + +Zeus.query({ + ... +}) +Zeus.mutation({ + ... +}) +``` + +have been replaced by + +```ts +const { Zeus, $ } = zeus; +Zeus('query', { + ... +}) +Zeus('mutation', { + ... +}) +``` \ No newline at end of file diff --git a/seed/babel.config.js b/seed/babel.config.js new file mode 100644 index 0000000..12c0f2c --- /dev/null +++ b/seed/babel.config.js @@ -0,0 +1,30 @@ +module.exports = { + presets: [ + '@babel/preset-typescript', + + [ + '@babel/preset-env', + { + // add debug information for babel + // debug: true, + // Because of this, preset-env's behavior is different than browserslist: it does not use the defaults query when there are no targets are found in your Babel or browserslist config(s). If you want to use the defaults query, you will need to explicitly pass it as a target: + targets: 'defaults', + }, + ], + '@babel/preset-react', + '@babel/preset-flow', + ], + plugins: [ + '@loadable/babel-plugin', + '@emotion', + '@babel/plugin-proposal-object-rest-spread', + '@babel/plugin-proposal-class-properties', + '@babel/plugin-transform-runtime', + [ + 'const-enum', + { + transform: 'constObject', + }, + ], + ], +}; diff --git a/seed/config/jest/fileMock.js b/seed/config/jest/fileMock.js new file mode 100644 index 0000000..86059f3 --- /dev/null +++ b/seed/config/jest/fileMock.js @@ -0,0 +1 @@ +module.exports = 'test-file-stub'; diff --git a/seed/config/jest/setup.js b/seed/config/jest/setup.js new file mode 100644 index 0000000..135148a --- /dev/null +++ b/seed/config/jest/setup.js @@ -0,0 +1,4 @@ +const Enzyme = require('enzyme'); +const Adapter = require('enzyme-adapter-react-16'); + +Enzyme.configure({ adapter: new Adapter() }); diff --git a/seed/config/jest/styleMock.js b/seed/config/jest/styleMock.js new file mode 100644 index 0000000..f053ebf --- /dev/null +++ b/seed/config/jest/styleMock.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/seed/config/paths.js b/seed/config/paths.js new file mode 100644 index 0000000..2d192da --- /dev/null +++ b/seed/config/paths.js @@ -0,0 +1,58 @@ +const path = require('path'); + +const baseDir = path.join(__dirname, '..', '..'); + +const srcDir = 'src'; +const adminDir = 'admin'; +const seedDir = 'seed'; +const blogDir = 'blog'; + +const buildDir = 'dist'; + +// classic ssr +const ssrBuild = 'ssr/'; +const clientBuild = 'ssr/client'; +const serverBuild = 'ssr/server'; + +// serverless ssr +const slsBuild = 'sls/'; +const slsServerBuild = 'sls/server'; +const slsClientBuild = 'sls/client'; + +const inApp = path.resolve.bind(path, baseDir); +const inAppSrc = file => inApp(srcDir, file); + +module.exports = { + baseDir, + main: path.resolve('src/main'), + server: path.resolve('src/server'), + serverless: path.resolve('src/server/serverless'), + publicPath: '/', + src: { + app: path.resolve(baseDir, 'src'), + admin: path.join(baseDir, adminDir, srcDir), + blog: path.join(baseDir, blogDir, srcDir), + seed: path.join(baseDir, seedDir, srcDir), + }, + modules: { + appModules: path.join(baseDir, 'node_modules'), + // relative over direct + // A relative path will be scanned similarly to how Node scans for node_modules, by looking through the current directory as well as its ancestors (i.e. ./node_modules, ../node_modules, and on). + modules: 'node_modules', + adminModules: path.join(baseDir, adminDir, 'node_modules'), + blogModules: path.join(baseDir, blogDir, 'node_modules'), + seedModules: path.join(baseDir, seedDir, 'node_modules'), + }, + defaultBuild: path.join(baseDir, buildDir), + // ssr + ssrBuild: path.resolve(ssrBuild), + clientBuild: path.resolve(clientBuild), + serverBuild: path.resolve(serverBuild), + // serverless + slsBuild: path.resolve(slsBuild), + slsServerBuild: path.resolve(slsServerBuild), + slsClientBuild: path.resolve(slsClientBuild), + // functions + inApp, + inAppSrc, +}; diff --git a/seed/config/webpack/common.js b/seed/config/webpack/common.js new file mode 100644 index 0000000..9e68ee0 --- /dev/null +++ b/seed/config/webpack/common.js @@ -0,0 +1,25 @@ +/* eslint-disable import/no-extraneous-dependencies */ +const merge = require('webpack-merge'); +const LoadablePlugin = require('@loadable/webpack-plugin'); +const webpack = require('webpack'); +const paths = require('../paths'); +const parts = require('./parts'); +// const ManifestPlugin = require('webpack-manifest-plugin'); + +module.exports = merge([ + parts.modulePathResolve(Object.values(paths.src), Object.values(paths.modules)), + parts.loadFonts(), + parts.loadImages(), + parts.loadVideos(), + parts.loadTxts(), + parts.loadGQL(), + { + plugins: [ + new LoadablePlugin(), + new webpack.IgnorePlugin({ + resourceRegExp: /^\.\/locale$/, + contextRegExp: /moment$/, + }), + ], + }, +]); diff --git a/seed/config/webpack/cordova.js b/seed/config/webpack/cordova.js new file mode 100644 index 0000000..7d570c4 --- /dev/null +++ b/seed/config/webpack/cordova.js @@ -0,0 +1,73 @@ +const HTMLWebpackPlugin = require('html-webpack-plugin'); +const path = require('path'); +const WebpackBar = require('webpackbar'); +const Dotenv = require('dotenv-webpack'); + +const merge = require('webpack-merge'); +const webpack = require('webpack'); +// local +const parts = require('./parts'); +const commonConfig = require('./common'); +const paths = require('../paths'); + +const createConfig = config => { + const isProduction = config.mode === 'production'; + + return merge([ + // clean in prod + parts.clean(paths.defaultBuild, paths.baseDir), + { + name: 'app', + // Entries configuration + entry: { + main: [paths.main], + }, + // Source mapping or not + devtool: config.prod ? 'none' : 'eval-source-map', + output: { + path: paths.defaultBuild, + filename: 'main.js', + publicPath: './', + }, + }, + parts.loadJavaScript({ + prod: config.prod, + }), + parts.loadCSS({ + prod: config.prod, + }), + parts.setVariables({ + __BROWSER__: true, + __SSR__: false, + __TEST__: false, + __PLATFORM__: config.platform || 'default', + __PROVIDER__: config.provider || 'default', + __ENV__: config.environment || 'default', + }), + { + plugins: [ + new HTMLWebpackPlugin({ + template: paths.inAppSrc('index.html'), + }), + new WebpackBar(), + ], + }, + // Prod plugins + isProduction && parts.imageOptimization(), + parts.optimize(), + ]); +}; + +module.exports = (env, params) => { + const config = createConfig({ + mode: params.locale ? 'development' : 'production', + port: params.port, + analyze: params.analyze, + platform: params.platform, + provider: params.provider, + environment: params.environment, + }); + + // mode : Possible values for mode are: none, development or production(default). + return merge(config, commonConfig); +}; diff --git a/seed/config/webpack/default.js b/seed/config/webpack/default.js new file mode 100644 index 0000000..2d2aa39 --- /dev/null +++ b/seed/config/webpack/default.js @@ -0,0 +1,116 @@ +const HTMLWebpackPlugin = require('html-webpack-plugin'); +const CopyPlugin = require('copy-webpack-plugin'); +const path = require('path'); +const WebpackBar = require('webpackbar'); +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; + +const merge = require('webpack-merge'); + +// Parent package +const package = require('../../../package.json'); + +const deploy = package && package.deploy; + +// local +const parts = require('./parts'); +const commonConfig = require('./common'); +const paths = require('../paths'); + +const createConfig = config => { + const isProduction = config.mode === 'production'; + // const isDevelopment = config.mode === 'development'; + + return merge([ + // clean in prod + isProduction && parts.clean(paths.defaultBuild, paths.baseDir), + { + mode: config.mode, + name: 'app', + // Entries configuration + entry: { + main: [paths.main], + }, + // Source mapping or not + devtool: isProduction ? 'none' : 'eval-source-map', + }, + // default + parts.outputPath({ + publicPath: (isProduction && deploy && deploy.publicPath) || paths.publicPath, + filename: isProduction ? '[name].[contenthash].js' : undefined, + }), + parts.loadJavaScript({ + prod: isProduction, + }), + parts.loadCSS({ + prod: isProduction, + }), + parts.setVariables({ + __BROWSER__: true, + __SSR__: false, + __TEST__: false, + // used for application with different "mode" + __PLATFORM__: config.platform || 'default', + __PROVIDER__: config.provider || 'default', + // environment targeted (dev vs staging vs prod vs beta ...) + __ENV__: config.environment || 'default', + }), + // launch dev server + !isProduction && { + devServer: { + // Enable gzip compression for everything served: + compress: true, + port: config.port || 4000, + // When using the HTML5 History API, the index.html page will likely have to be served in place of any 404 r + historyApiFallback: true, + hot: true, + allowedHosts: ['.lvh.me'], + }, + }, + config.analyze && { + plugins: [ + // analyze dependencies sizes + new BundleAnalyzerPlugin({ + analyzerPort: config.port ? config.port + 1000 : 5000, + }), + ], + }, + // Common plugins + { + plugins: [ + new CopyPlugin({ + patterns: [ + { + from: path.join(paths.baseDir, config.environment === 'staging' ? 'static-staging' : 'static'), + to: path.join(paths.defaultBuild, paths.publicPath), + noErrorOnMissing: true, + }, + ], + }), + new HTMLWebpackPlugin({ + template: paths.inAppSrc('index.html'), + }), + new WebpackBar(), + ], + }, + // Prod plugins + isProduction && parts.imageOptimization(), + parts.optimize(), + ]); +}; + +// env can be given with --env.variable +// other params will be in params +module.exports = (env, params) => { + params = params || {}; + const config = createConfig({ + mode: params.locale ? 'development' : 'production', + port: params.port, + analyze: params.analyze, + platform: params.platform, + provider: params.provider, + environment: params.environment, + }); + + // mode : Possible values for mode are: none, development or production(default). + return merge(config, commonConfig); +}; diff --git a/seed/config/webpack/electron.js b/seed/config/webpack/electron.js new file mode 100644 index 0000000..5e66247 --- /dev/null +++ b/seed/config/webpack/electron.js @@ -0,0 +1,79 @@ +// const nodeExternals = require('webpack-node-externals'); +const merge = require('webpack-merge'); +const WebpackBar = require('webpackbar'); + +// local +const HTMLWebpackPlugin = require('html-webpack-plugin'); +const parts = require('./parts'); +const commonConfig = require('./common'); +const paths = require('.././paths'); +// Forces webpack-dev-server program to write bundle files to the file system. + +const createConfig = config => { + const isProduction = config.mode === 'production'; + + return merge([ + parts.clean(paths.defaultBuild, paths.baseDir), + { + name: 'electron', + entry: { + main: [paths.main], + }, + target: 'electron-main', + // node: { __dirname: false }, + // in prod we need the packages + node: { + // tell webpack that we actually want a working __dirname value + // (ref: https://webpack.js.org/configuration/node/#node-__dirname) + __dirname: false, + __filename: false, + }, + output: { + path: paths.defaultBuild, + filename: 'main.js', + publicPath: './', + }, + }, + // investigate why adding prod true destory ssr in prod + parts.loadJavaScript(), + // need css loader to not fail building + parts.loadCSS(), + parts.setVariables({ + __ELECTRON__: true, + __TEST__: false, + __BROWSER__: false, + __SSR__: false, + __PLATFORM__: config.platform || 'default', + __ENV__: config.environment || 'default', + __PROVIDER__: config.provider || 'default', + }), + { + plugins: [ + new HTMLWebpackPlugin({ + template: paths.inAppSrc('index.html'), + }), + new WebpackBar({ + name: 'Electron', + color: 'orange', + }), + ], + }, + // Prod plugins + isProduction && parts.imageOptimization(), + parts.optimize(), + ]); +}; + +module.exports = (env, params) => { + const config = createConfig({ + mode: params.locale ? 'development' : 'production', + port: params.port, + analyze: params.analyze, + platform: params.platform, + provider: params.provider, + environment: params.environment, + }); + + // mode : Possible values for mode are: none, development or production(default). + return merge(commonConfig, config); +}; diff --git a/seed/config/webpack/parts.js b/seed/config/webpack/parts.js new file mode 100644 index 0000000..33a1f00 --- /dev/null +++ b/seed/config/webpack/parts.js @@ -0,0 +1,348 @@ +/* eslint-disable import/no-extraneous-dependencies */ +const webpack = require('webpack'); +const { CleanWebpackPlugin } = require('clean-webpack-plugin'); // installed via npm +const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); +const TerserPlugin = require('terser-webpack-plugin'); +// const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin'); +const ImageminPlugin = require('imagemin-webpack-plugin').default; + +const CompressionPlugin = require('compression-webpack-plugin'); +const zlib = require('zlib'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +const merge = require('webpack-merge'); +// local +const paths = require('../paths'); + +/* +Need to load the configs here so they can be applied to the external folders as wells +*/ +const babelConfig = require('../../babel.config'); +// Need required in the file for parent files +const postcssConfig = require('../../postcss.config'); + +/** + * Resolve paths + */ +exports.modulePathResolve = (srcList, modulesList) => ({ + resolve: { + modules: [...srcList, ...modulesList], + extensions: ['*', '.js', '.jsx', '.ts', '.tsx', '.json', '.mjs', '.gql', '.graphql'], + }, + resolveLoader: { + modules: modulesList, + }, +}); +/** + * Clean + */ +exports.clean = (path, root) => ({ + plugins: [new CleanWebpackPlugin()], +}); + +/** + * JS (see also .babelrc) + */ +exports.loadJavaScript = ({ include, exclude, prod } = {}) => ({ + module: { + rules: [ + { + test: /\.(js|jsx|ts|mjs|tsx)$/, + include, + // node_modules(?!([\\/](attr-accept|moment))) + exclude: exclude || /node_modules/, + use: [ + { + loader: 'babel-loader', + options: { + presets: babelConfig.presets, + plugins: prod ? babelConfig.plugins.concat('transform-remove-console') : babelConfig.plugins, + }, + // */ + }, + ], + }, + ], + }, +}); + +/** + * Default output path + */ +exports.outputPath = ({ + path = paths.defaultBuild, + filename = '[name].js', + chunkFilename = '[name].[contenthash].js', + publicPath = paths.publicPath, +} = {}) => ({ + output: { + path, + filename, + chunkFilename, + // This option specifies the public URL of the output directory when referenced in a browser. A relative URL is resolved relative to the HTML page (or tag). + publicPath, + // https://github.com/webpack/webpack/issues/11660 + // addition for webpack 5 + // chunkLoading: false, + // wasmLoading: false, + }, +}); + +/** + * (S)CSS + */ +exports.loadCSS = ({ include, exclude, prod = false, styleLoader = false, server = false } = {}) => { + const fileName = prod ? '[name].[contenthash].css' : '[name].css'; + + // use style loader in dev (no ssr) + const useStyleLoader = styleLoader; + + const pluginOptions = { + filename: fileName, + ignoreOrder: !prod, // Enable to remove warnings about conflicting order + }; + + const plugin = new MiniCssExtractPlugin(pluginOptions); + + const loaderOptions = { + // disabling esModule prevent from having warning for empty css files => should not be disabled in prod + esModule: prod, + }; + + const usages = [ + { + loader: 'css-loader', + options: { + sourceMap: true, + // The option importLoaders allows you to configure how many loaders before css-loader should be applied to @imported resources. + importLoaders: 2, + // to use with localIdentName + modules: { + localIdentName: '[local]--[hash:base64:5]', + }, + }, + }, + { + loader: 'postcss-loader', + options: { + postcssOptions: postcssConfig, + }, + }, + { + loader: 'sass-loader', + options: {}, + }, + ]; + + if (!server) { + // if not loaded on server part, module styleIdentifiers will not be usable in the server + usages.unshift({ + loader: useStyleLoader ? 'style-loader' : MiniCssExtractPlugin.loader, + options: loaderOptions, + }); + } + return { + module: { + rules: [ + { + test: /\.(css|sass|scss)$/, + include, + exclude, + use: usages, + }, + ], + }, + plugins: [plugin], + }; +}; + +exports.setVariables = obj => { + const env = {}; + const keys = Object.keys(obj); + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + const value = obj[key]; + env[key] = JSON.stringify(value); + } + return { + plugins: [new webpack.DefinePlugin(env)], + }; +}; + +/** + * FONTS + */ +const FONTLIST = [ + ['woff', 'application/font-woff'], + ['woff2', 'application/font-woff2'], + ['otf', 'font/opentype'], + ['ttf', 'application/octet-stream'], + ['eot', 'application/vnd.ms-fontobject'], + ['svg', 'image/svg+xml'], +]; + +exports.loadFonts = () => { + let rsltConf = { + module: { + rules: [], + }, + }; + + FONTLIST.forEach(font => { + const extension = font[0]; + const mimetype = font[1]; + + const rule = { + module: { + rules: [ + { + test: new RegExp(`\\.${extension}$`), + loader: 'file-loader', + include: [/fonts?/], + options: { + name: 'fonts/[name].[ext]', + // limit: 10000, + mimetype, + }, + }, + ], + }, + }; + rsltConf = merge(rsltConf, rule); + }); + + return rsltConf; +}; + +/** + * Images + */ +exports.loadImages = () => ({ + module: { + rules: [ + { + test: /\.(png|jpe?g|gif|svg|ico)$/, + loader: 'file-loader', + exclude: [/fonts?/], + options: { + name: 'images/[name].[contenthash].[ext]', + // for url loader + // limit: 8192, + }, + }, + ], + }, +}); + +/** + * Images + */ +exports.loadVideos = () => ({ + module: { + rules: [ + { + test: /\.(mp4|mov)$/, + loader: 'file-loader', + options: { + name: 'videos/[name].[contenthash].[ext]', + }, + }, + ], + }, +}); + +// Idea of splitting vendors +exports.optimize = () => ({ + // https://webpack.js.org/plugins/compression-webpack-plugin/ + plugins: [ + // new CompressionPlugin({ + // filename: '[path][base].br', + // algorithm: 'brotliCompress', + // test: /\.(js|css|html|svg)$/, + // compressionOptions: { + // params: { + // [zlib.constants.BROTLI_PARAM_QUALITY]: 11, + // }, + // }, + // threshold: 10240, + // minRatio: 0.8, + // deleteOriginalAssets: false, + // }), + ], + optimization: { + minimizer: [ + new TerserPlugin({ + terserOptions: { + safari10: true, + }, + }), + new OptimizeCSSAssetsPlugin({}), + ], + // use default splitChunks config with chnks all + // https://webpack.js.org/plugins/split-chunks-plugin/#optimizationsplitchunks + splitChunks: { + chunks: 'all', + // cacheGroups: { + // vendor: { + // // test with exclusion : /[\\/]node_modules[\\/](?!(attr-accept|moment))/ + // test: /[\\/]node_modules[\\/]/, + // name: 'vendor', + // }, + // }, + }, + // Source : create-react-app + // Keep the runtime chunk separated to enable long term caching + // https://twitter.com/wSokra/status/969679223278505985 + // https://github.com/facebook/create-react-app/issues/5358 + runtimeChunk: { + name: entrypoint => `runtime-${entrypoint.name}`, + }, + }, +}); + +// Text loader +exports.loadTxts = () => ({ + module: { + rules: [ + { + test: /\.txt$/i, + use: 'raw-loader', + }, + ], + }, +}); + +// GraphQL loader +exports.loadGQL = () => ({ + module: { + rules: [ + // fixes https://github.com/graphql/graphql-js/issues/1272 + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto', + }, + ], + }, +}); + +exports.imageOptimization = () => ({ + plugins: [new ImageminPlugin({ test: /\.(jpe?g|png|gif)$/i })], +}); + +// Add stats +exports.stats = () => ({ + // To discover + stats: { + cached: false, + cachedAssets: false, + chunks: false, + chunkModules: false, + colors: true, + hash: false, + modules: false, + reasons: false, + timings: true, + version: false, + }, +}); diff --git a/seed/config_example.js b/seed/config_example.js new file mode 100644 index 0000000..c0b5e01 --- /dev/null +++ b/seed/config_example.js @@ -0,0 +1,8 @@ +// This file is an example of project config with fake data +module.exports = { + frontends: { + staging: 'https://absolute_url_to_staging', + prod: 'https://absolute_url_to_prod', + dev: 'http://localhost:4000/', + }, +}; diff --git a/seed/jest.config.js b/seed/jest.config.js new file mode 100644 index 0000000..11ca7b0 --- /dev/null +++ b/seed/jest.config.js @@ -0,0 +1,26 @@ +module.exports = { + setupFilesAfterEnv: ['/seed/config/jest/setup.js'], + testURL: 'http://localhost/', + testEnvironment: 'node', + verbose: true, + moduleNameMapper: { + '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/seed/config/jest/fileMock.js', + '\\.(css|scss)$': '/seed/config/jest/styleMock.js', + }, + testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$', + transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\](?!(@amcharts)\\/).+\\.js$'], + rootDir: '../', + moduleDirectories: ['src', 'admin/src', 'blog/src', 'seed/src', 'node_modules', 'admin/node_modules', 'blog/node_modules', 'seed/node_modules'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + globals: { + __BROWSER__: true, + __TEST__: true, + __ENV__: 'default', + __PLATFORM__: 'default', + __PROVIDER__: 'default', + __LOCALE__: false, + __DEV__: false, + __STAGING__: false, + __SSR__: false, + }, +}; diff --git a/seed/package.json b/seed/package.json new file mode 100644 index 0000000..a3ea697 --- /dev/null +++ b/seed/package.json @@ -0,0 +1,268 @@ +{ + "name": "seed-react", + "version": "1.0.0", + "private": true, + "author": "Nicolas Bernier", + "main": "main.tsx", + "scripts": { + "preinstall": "([ ! -f package-lock.json ] && npm install --package-lock-only --ignore-scripts --no-audit); npx npm-force-resolutions", + "install:deps": "npm i && cd .. && npm i && cd seed", + "start": "webpack-dev-server --config config/webpack/default.js --locale", + "start:header": "NODE_OPTIONS='--max-http-header-size=100000' webpack-dev-server --config config/webpack/default.js --locale", + "electron": "webpack --watch --locale --config config/webpack/electron.js", + "cordova": "webpack --watch --locale --config config/webpack/cordova.js", + "build:dev": "webpack --environment=dev --config config/webpack/default.js", + "build:staging": "webpack --environment=staging --config config/webpack/default.js", + "build:prod": "webpack --environment=prod --config config/webpack/default.js", + "build": "webpack --config config/webpack/default.js", + "ssr": "node scripts/start.js", + "ssr:header": "NODE_OPTIONS='--max-http-header-size=100000 --max_old_space_size=2048' node scripts/start.js", + "serverless-ssr": "sls offline start --config ./serverless.yaml --environment=dev --port=3000", + "serverless-client": "webpack-dev-server --config config/webpack/ssr/slsClient.js", + "serverless-client-build": "webpack --environment production --config config/webpack/ssr/slsClient.js", + "deploy:sls": "node scripts/sls-deploy.js", + "deploy:sls:dev": "node scripts/sls-deploy.js dev", + "deploy:sls:staging": "node scripts/sls-deploy.js staging", + "deploy:sls:production": "node scripts/sls-deploy.js production", + "eslint": "eslint ./node_modules/.bin/eslint src/. --ext .js --fix", + "plop": "plop", + "storybook": "start-storybook -p 6006", + "test": "jest --env=jsdom", + "test:watch": "jest --env=jsdom --watch", + "build-storybook": "build-storybook" + }, + "browserslist": [ + "Explorer 10", + "Explorer 11", + "last 4 version", + "> 0.1%", + "maintained node versions" + ], + "reactSnap": { + "source": "../dist", + "minifyHtml": { + "collapseWhitespace": false, + "removeComments": true + } + }, + "jest": { + "globals": { + "__LOCALE__": false, + "__DEV__": false, + "__STAGING__": false, + "__BROWSER__": true, + "__SSR__": false, + "__TEST__": false, + "__PLATFORM__": "default", + "__ENV__": "default", + "__PROVIDER__": "default" + } + }, + "dependencies": { + "@apollo/client": "3.3.7", + "@apollo/react-components": "^4.0.0", + "@emotion/react": "11.4.1", + "@emotion/styled": "11.3.0", + "@fortawesome/fontawesome-svg-core": "^1.2.28", + "@fortawesome/free-brands-svg-icons": "^5.13.0", + "@fortawesome/free-regular-svg-icons": "^5.13.0", + "@fortawesome/free-solid-svg-icons": "^5.13.0", + "@fortawesome/react-fontawesome": "^0.1.9", + "@loadable/component": "^5.14.1", + "@loadable/server": "^5.14.0", + "axios": "^0.21.1", + "classnames": "^2.2.6", + "connected-react-router": "6.9.1", + "cookies": "^0.8.0", + "cookies-js": "^1.2.3", + "core-js": "^3.15.2", + "cropperjs": "^1.5.5", + "cross-fetch": "^3.0.6", + "es-abstract": "^1.18.0-next.1", + "express-manifest-helpers": "^0.6.0", + "final-form": "^4.20.2", + "final-form-arrays": "^3.0.2", + "fuse.js": "^6.4.6", + "global": "^4.4.0", + "graphql": "15.5.3", + "graphql-tag": "^2.12.5", + "history": "^4.10.1", + "html-react-parser": "^0.14.2", + "iban": "0.0.12", + "immer": "9.0.7", + "is-mobile": "^2.2.2", + "js-cookie": "^3.0.1", + "lodash": "^4.17.21", + "moment": "^2.29.1", + "moment-range": "^4.0.2", + "moment-timezone": "^0.5.33", + "nodemon": "^2.0.7", + "payment": "^2.3.0", + "postcss": "^8.3.0", + "prismjs": "1.25.0", + "promise-polyfill": "8.1.0", + "qs": "^6.10.3", + "query-string": "^7.1.0", + "react": "^17.0.1", + "react-app-polyfill": "^2.0.0", + "react-code-input": "^3.10.0", + "react-color": "^2.18.1", + "react-date-picker": "^7.10.0", + "react-datetime": "^3.0.4", + "react-dnd": "^14.0.4", + "react-dnd-html5-backend": "^14.0.2", + "react-dom": "^17.0.1", + "react-dropzone": "^11.2.4", + "react-facebook-login": "^4.1.1", + "react-favicon": "0.0.18", + "react-final-form": "^6.5.3", + "react-final-form-arrays": "^3.1.3", + "react-final-form-hooks": "^2.0.2", + "react-google-login": "^5.2.1", + "react-helmet": "^6.0.0", + "react-icons": "^4.2.0", + "react-lazy-load-image-component": "^1.5.0", + "react-number-format": "^4.5.3", + "react-paginate": "^7.0.0", + "react-phone-input-2": "^2.14.0", + "react-player": "^2.9.0", + "react-redux": "^7.2.0", + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0", + "react-simple-code-editor": "^0.11.0", + "react-switch": "^5.0.1", + "react-transition-group": "^4.4.1", + "react-virtualized": "^9.21.2", + "redux": "^4.0.5", + "redux-form": "^8.3.5", + "redux-persist": "6.0.0", + "redux-persist-cookie-storage": "^1.0.0", + "redux-persist-expire": "^1.1.0", + "redux-saga": "^0.16.0", + "sanitize-html": "^2.4.0", + "scheduler": "^0.15.0", + "serverless-http": "^2.4.1", + "tus-js-client": "^2.3.0", + "unflatten": "1.0.4", + "uniqid": "^5.2.0", + "universal-cookie": "^4.0.3", + "universal-cookie-express": "^4.0.3", + "unorm": "^1.6.0" + }, + "devDependencies": { + "@babel/core": "^7.12.10", + "@babel/plugin-proposal-class-properties": "^7.12.1", + "@babel/plugin-proposal-object-rest-spread": "^7.12.1", + "@babel/plugin-transform-runtime": "^7.12.10", + "@babel/preset-env": "^7.12.10", + "@babel/preset-flow": "^7.12.1", + "@babel/preset-react": "^7.12.10", + "@babel/preset-typescript": "^7.12.7", + "@babel/runtime": "^7.12.5", + "@emotion/babel-plugin": "11.3.0", + "@loadable/babel-plugin": "^5.13.2", + "@loadable/webpack-plugin": "^5.14.0", + "@storybook/addon-a11y": "^6.2.9", + "@storybook/addon-actions": "^6.3.0", + "@storybook/addon-backgrounds": "^6.2.9", + "@storybook/addon-essentials": "^6.3.0", + "@storybook/addon-knobs": "^6.2.9", + "@storybook/addon-links": "^6.3.0", + "@storybook/addon-notes": "^5.3.18", + "@storybook/addon-options": "^5.3.18", + "@storybook/addon-viewport": "^6.2.9", + "@storybook/addons": "^6.2.9", + "@storybook/cli": "^6.2.9", + "@storybook/react": "^6.3.0", + "@testing-library/jest-dom": "^5.12.0", + "@testing-library/react": "^11.2.7", + "@types/jest": "^24.9.1", + "@types/node": "^11.15.35", + "@types/react": "^16.9.55", + "@types/react-dom": "^16.9.9", + "@types/react-redux": "^7.1.9", + "@typescript-eslint/eslint-plugin": "^1.13.0", + "@typescript-eslint/parser": "^1.13.0", + "babel-eslint": "^10.1.0", + "babel-loader": "^8.2.2", + "babel-plugin-const-enum": "^1.1.0", + "babel-plugin-transform-remove-console": "^6.9.4", + "browserslist": "^4.16.6", + "clean-webpack-plugin": "^3.0.0", + "compression-webpack-plugin": "^6.1.1", + "copy-webpack-plugin": "^6.2.1", + "cors": "^2.8.5", + "cross-env": "^5.2.0", + "css-loader": "^5.2.0", + "dotenv-webpack": "^7.0.2", + "enzyme": "^3.11.0", + "enzyme-adapter-react-16": "^1.15.2", + "eslint": "^5.16.0", + "eslint-config-airbnb": "^17.1.1", + "eslint-config-airbnb-base": "^13.2.0", + "eslint-config-airbnb-typescript": "^4.0.1", + "eslint-config-prettier": "^6.15.0", + "eslint-import-resolver-webpack": "^0.10.1", + "eslint-plugin-babel": "^5.3.1", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-jest": "^22.17.0", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-prettier": "^3.1.4", + "eslint-plugin-react": "^7.21.5", + "eslint-plugin-react-hooks": "4.3.0", + "execa": "^4.0.1", + "express": "^4.17.1", + "file-loader": "^6.2.0", + "html-webpack-plugin": "^4.5.0", + "imagemin-gifsicle": "^7.0.0", + "imagemin-jpegtran": "^7.0.0", + "imagemin-mozjpeg": "^9.0.0", + "imagemin-optipng": "^8.0.0", + "imagemin-pngquant": "^9.0.1", + "imagemin-svgo": "^8.0.0", + "imagemin-webpack-plugin": "^2.4.2", + "inquirer-directory": "^2.2.0", + "jest": "^27.0.5", + "listr": "^0.14.3", + "mini-css-extract-plugin": "^1.6.0", + "optimize-css-assets-webpack-plugin": "^6.0.0", + "plop": "^2.6.0", + "postcss-loader": "^4.1.0", + "postcss-preset-env": "^6.7.0", + "prettier": "^1.18.2", + "prettier-stylelint": "^0.4.2", + "raw-loader": "^4.0.2", + "react-snap": "^1.23.0", + "redbox-react": "^1.6.0", + "redux-devtools-extension": "^2.13.8", + "redux-saga-test-plan": "^3.7.0", + "sass": "^1.34.1", + "sass-loader": "^10.1.1", + "serverless": "^2.29.0", + "serverless-offline": "^6.1.5", + "serverless-webpack": "^5.3.5", + "source-map-loader": "^2.0.1", + "storybook-react-router": "^1.0.8", + "style-loader": "^2.0.0", + "stylelint": "^13.13.1", + "stylelint-scss": "^3.17.2", + "terser-webpack-plugin": "^4.2.3", + "ts-jest": "^27.0.3", + "typescript": "^3.9.5", + "url-loader": "^4.1.1", + "webpack": "^4.44.2", + "webpack-bundle-analyzer": "^4.4.2", + "webpack-cli": "3.3.12", + "webpack-dev-middleware": "^3.7.2", + "webpack-dev-server": "3.11.2", + "webpack-hot-middleware": "2.25.1", + "webpack-manifest-plugin": "^2.2.0", + "webpack-merge": "^4.2.2", + "webpack-node-externals": "^2.5.2", + "webpackbar": "^4.0.0", + "write-file-webpack-plugin": "^4.5.1" + }, + "resolutions": { + "immer": "9.0.7" + } +} diff --git a/seed/plop-templates/component/component.hbs b/seed/plop-templates/component/component.hbs new file mode 100644 index 0000000..570b97e --- /dev/null +++ b/seed/plop-templates/component/component.hbs @@ -0,0 +1,26 @@ +import React, { Component } from 'react'; +import classNames from 'classnames/bind'; +import styleIdentifiers from './{{pascalCase name}}.module.scss'; +import TextItem from 'components/items/TextItem'; + +const styles = classNames.bind(styleIdentifiers); + +export interface StateProps {}; + +export interface DispatchProps {}; + +export interface OwnProps {}; + +export type {{pascalCase name}}Props = StateProps & DispatchProps & OwnProps; + +interface {{pascalCase name}}State {}; + +export default class {{pascalCase name}} extends Component<{{pascalCase name}}Props, {{pascalCase name}}State> { + render() { + return ( +
+ +
+ ); + } +} diff --git a/seed/plop-templates/component/componentDumb.hbs b/seed/plop-templates/component/componentDumb.hbs new file mode 100644 index 0000000..b5843c1 --- /dev/null +++ b/seed/plop-templates/component/componentDumb.hbs @@ -0,0 +1,20 @@ +import React, { Component } from 'react'; +import classNames from 'classnames/bind'; +import styleIdentifiers from './{{pascalCase name}}.module.scss'; +import TextItem from 'components/items/TextItem'; + +const styles = classNames.bind(styleIdentifiers); + +interface {{pascalCase name}}Props {}; + +interface {{pascalCase name}}State {}; + +export default class {{pascalCase name}} extends Component<{{pascalCase name}}Props, {{pascalCase name}}State> { + render() { + return ( +
+ +
+ ); + } +} diff --git a/seed/plop-templates/component/componentForm.hbs b/seed/plop-templates/component/componentForm.hbs new file mode 100644 index 0000000..1c9d11e --- /dev/null +++ b/seed/plop-templates/component/componentForm.hbs @@ -0,0 +1,51 @@ +import React from 'react'; +import { Field } from 'react-final-form'; + +// Redux part +import { useSelector, useDispatch } from 'react-redux'; +import { StoreState } from 'store/rootReducer'; + +// Import actions here + +import classNames from 'classnames/bind'; +import Input from 'components/formItems/Input'; +import Button from 'components/items/Button'; +import FieldAdapter from 'components/formItems/FieldAdapter'; + +import { required, email, composeValidators } from 'store/utils/validation'; +import styleIdentifiers from './{{pascalCase name}}.module.scss'; + +const styles = classNames.bind(styleIdentifiers); + +export interface StateProps {} + +export interface DispatchProps {} + +export interface OwnProps { + onSubmit: Function; +} + +export type {{pascalCase name}}Props = StateProps & DispatchProps & OwnProps; + +const {{pascalCase name}} = (props: {{pascalCase name}}Props) => { + const { handleSubmit, submitting, valid } = props; + + // mapStateToProps + const content = useSelector((state: StoreState) => state.content.raw); + + return ( +
+ +