From 722750b2be9f7eaa49dbdfd3fcac0e7e6d5f7c34 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Mon, 15 Aug 2022 15:32:52 +1000 Subject: [PATCH 01/10] Add Brazilian Portuguese to the language picker --- crabfit-frontend/src/res/dayjs_locales.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crabfit-frontend/src/res/dayjs_locales.ts b/crabfit-frontend/src/res/dayjs_locales.ts index ce5b019..a324171 100644 --- a/crabfit-frontend/src/res/dayjs_locales.ts +++ b/crabfit-frontend/src/res/dayjs_locales.ts @@ -60,12 +60,24 @@ const locales = { weekStart: 1, timeFormat: '12h', }, + 'pt-BR': { // Portuguese (Brazil) + name: 'Português (do Brasil)', + import: () => import('dayjs/locale/pt-br'), + weekStart: 0, + timeFormat: '24h', + }, 'ru': { // Russian name: 'Pусский', import: () => import('dayjs/locale/ru'), weekStart: 1, timeFormat: '24h', }, + // 'zh-CN': { // Chinese + // name: '中文', + // import: () => import('dayjs/locale/zh-cn'), + // weekStart: 1, + // timeFormat: '12h', + // }, }; export default locales; From 4382f559f38e788a40e3143193a951440df68f78 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Mon, 15 Aug 2022 20:55:02 +1000 Subject: [PATCH 02/10] Setup frontend deployment github action --- .github/workflows/deploy_frontend.yml | 39 +++++++++++++++++++ .../{example-app.yaml => app.yaml} | 0 crabfit-frontend/deploy.sh | 23 ----------- 3 files changed, 39 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/deploy_frontend.yml rename crabfit-frontend/{example-app.yaml => app.yaml} (100%) delete mode 100755 crabfit-frontend/deploy.sh diff --git a/.github/workflows/deploy_frontend.yml b/.github/workflows/deploy_frontend.yml new file mode 100644 index 0000000..944efb2 --- /dev/null +++ b/.github/workflows/deploy_frontend.yml @@ -0,0 +1,39 @@ +name: Deploy Frontend + +on: + push: + branches: ['main'] + paths: ['crabfit-frontend/**'] + +jobs: + deploy: + runs-on: ubuntu-latest + + defaults: + run: + working-directory: crabfit-frontend + + permissions: + contents: read + id-token: write + + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 17 + cache: yarn + cache-dependency-path: '**/yarn.lock' + - run: yarn install --immutable + - run: yarn build + - name: Copy app.yaml to build + run: 'cp app.yaml ./build/app.yaml' + - id: auth + uses: google-github-actions/auth@v0 + with: + credentials_json: '${{ secrets.GCP_SA_KEY }}' + - id: deploy + uses: google-github-actions/deploy-appengine@v0 + with: + working_directory: crabfit-frontend/build + version: v1 diff --git a/crabfit-frontend/example-app.yaml b/crabfit-frontend/app.yaml similarity index 100% rename from crabfit-frontend/example-app.yaml rename to crabfit-frontend/app.yaml diff --git a/crabfit-frontend/deploy.sh b/crabfit-frontend/deploy.sh deleted file mode 100755 index 7fdae75..0000000 --- a/crabfit-frontend/deploy.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash - -yarn build - -cd build - -cat > app.yaml << EOF -runtime: nodejs12 -handlers: -- url: /(.*\..+)$ - static_files: \1 - upload: (.*\..+)$ - secure: always - redirect_http_response_code: 301 - -- url: /.* - static_files: index.html - upload: index.html - secure: always - redirect_http_response_code: 301 -EOF - -gcloud app deploy --project=crabfit --version=v1 From a67aee24dc78045c5304323df434fd04fba067fd Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Tue, 16 Aug 2022 14:29:41 +1000 Subject: [PATCH 03/10] Update components --- .github/workflows/deploy_frontend.yml | 4 +- crabfit-frontend/.eslintrc.js | 73 + crabfit-frontend/.gcloudignore | 10 + crabfit-frontend/.gitignore | 22 +- crabfit-frontend/README.md | 70 - crabfit-frontend/app.yaml | 8 +- crabfit-frontend/{public => }/index.html | 17 +- crabfit-frontend/jsconfig.json | 13 + crabfit-frontend/package.json | 93 +- crabfit-frontend/public/index.css | 134 +- crabfit-frontend/src/App.jsx | 85 + crabfit-frontend/src/App.test.ts | 8 - crabfit-frontend/src/App.tsx | 178 - ...ilityEditor.tsx => AvailabilityEditor.jsx} | 116 +- ...rStyle.ts => AvailabilityEditor.styles.js} | 14 +- ...ilityViewer.tsx => AvailabilityViewer.jsx} | 101 +- ...rStyle.ts => AvailabilityViewer.styles.js} | 128 +- .../src/components/Button/Button.jsx | 31 + .../{buttonStyle.ts => Button.styles.js} | 36 +- .../src/components/Button/Button.tsx | 15 - .../{CalendarField.tsx => CalendarField.jsx} | 192 +- ...rFieldStyle.ts => CalendarField.styles.js} | 48 +- .../src/components/Center/Center.js | 9 + .../src/components/Center/Center.ts | 9 - .../Donate/{Donate.tsx => Donate.jsx} | 117 +- .../{donateStyle.ts => Donate.styles.js} | 23 +- .../src/components/Egg/{Egg.tsx => Egg.jsx} | 12 +- .../Egg/{eggStyle.ts => Egg.styles.js} | 10 +- .../components/Error/{Error.tsx => Error.jsx} | 6 +- .../Error/{errorStyle.ts => Error.styles.js} | 12 +- .../src/components/Footer/Footer.jsx | 17 + .../{footerStyle.ts => Footer.styles.js} | 6 +- .../src/components/Footer/Footer.tsx | 17 - ...{GoogleCalendar.tsx => GoogleCalendar.jsx} | 92 +- ...endarStyle.ts => GoogleCalendar.styles.js} | 50 +- .../Legend/{Legend.tsx => Legend.jsx} | 24 +- .../{legendStyle.ts => Legend.styles.js} | 28 +- .../src/components/Loading/Loading.jsx | 5 + .../{loadingStyle.ts => Loading.styles.js} | 12 +- .../src/components/Loading/Loading.tsx | 12 - .../components/Logo/{Logo.tsx => Logo.jsx} | 16 +- .../Logo/{logoStyle.ts => Logo.styles.js} | 30 +- ...utlookCalendar.tsx => OutlookCalendar.jsx} | 136 +- .../Recents/{Recents.tsx => Recents.jsx} | 27 +- .../{recentsStyle.ts => Recents.styles.js} | 14 +- .../{SelectField.tsx => SelectField.jsx} | 15 +- .../SelectField/SelectField.styles.js | 63 + .../SelectField/selectFieldStyle.ts | 60 - .../Settings/{Settings.tsx => Settings.jsx} | 92 +- .../{settingsStyle.ts => Settings.styles.js} | 34 +- .../{TextField.tsx => TextField.jsx} | 9 +- .../components/TextField/TextField.styles.js | 47 + .../components/TextField/textFieldStyle.ts | 46 - ...{TimeRangeField.tsx => TimeRangeField.jsx} | 116 +- ...FieldStyle.ts => TimeRangeField.styles.js} | 44 +- .../{ToggleField.tsx => ToggleField.jsx} | 8 +- ...gleFieldStyle.ts => ToggleField.styles.js} | 35 +- ...ranslateDialog.tsx => TranslateDialog.jsx} | 16 +- ...alogStyle.ts => TranslateDialog.styles.js} | 16 +- .../{UpdateDialog.tsx => UpdateDialog.jsx} | 13 +- ...eDialogStyle.ts => UpdateDialog.styles.js} | 16 +- crabfit-frontend/src/components/index.js | 25 + crabfit-frontend/src/components/index.ts | 25 - .../src/i18n/{index.ts => index.js} | 17 +- .../{res/dayjs_locales.ts => i18n/locales.js} | 4 +- crabfit-frontend/src/index.jsx | 24 + crabfit-frontend/src/index.tsx | 11 - crabfit-frontend/src/pages/Help/Help.jsx | 101 + .../Help/{helpStyle.ts => Help.styles.js} | 46 +- crabfit-frontend/src/pages/Help/Help.tsx | 120 - .../Home/{homeStyle.ts => Home.styles.js} | 78 +- .../src/pages/Privacy/Privacy.jsx | 103 + .../src/pages/Privacy/Privacy.styles.js | 22 + .../src/pages/Privacy/Privacy.tsx | 114 - .../src/pages/Privacy/privacyStyle.ts | 22 - crabfit-frontend/src/pages/index.js | 7 + crabfit-frontend/src/pages/index.ts | 5 - crabfit-frontend/src/react-app-env.d.ts | 1 - crabfit-frontend/src/reportWebVitals.js | 13 - crabfit-frontend/src/res/dayjs_locales.json | 1 - crabfit-frontend/src/res/logo.svg | 21 +- crabfit-frontend/src/services/index.js | 22 +- crabfit-frontend/src/setupTests.js | 5 - crabfit-frontend/src/stores/index.js | 5 + crabfit-frontend/src/stores/index.ts | 64 - .../src/stores/localeUpdateStore.js | 8 + crabfit-frontend/src/stores/recentsStore.js | 23 + crabfit-frontend/src/stores/settingsStore.js | 19 + crabfit-frontend/src/stores/translateStore.js | 23 + crabfit-frontend/src/stores/twaStore.js | 8 + crabfit-frontend/src/utils/index.js | 32 + crabfit-frontend/src/utils/index.ts | 31 - crabfit-frontend/tsconfig.json | 27 - crabfit-frontend/vite.config.js | 6 + crabfit-frontend/yarn.lock | 12190 +++------------- 95 files changed, 3901 insertions(+), 12032 deletions(-) create mode 100644 crabfit-frontend/.eslintrc.js create mode 100644 crabfit-frontend/.gcloudignore delete mode 100644 crabfit-frontend/README.md rename crabfit-frontend/{public => }/index.html (79%) create mode 100644 crabfit-frontend/jsconfig.json create mode 100644 crabfit-frontend/src/App.jsx delete mode 100644 crabfit-frontend/src/App.test.ts delete mode 100644 crabfit-frontend/src/App.tsx rename crabfit-frontend/src/components/AvailabilityEditor/{AvailabilityEditor.tsx => AvailabilityEditor.jsx} (69%) rename crabfit-frontend/src/components/AvailabilityEditor/{availabilityEditorStyle.ts => AvailabilityEditor.styles.js} (62%) rename crabfit-frontend/src/components/AvailabilityViewer/{AvailabilityViewer.tsx => AvailabilityViewer.jsx} (77%) rename crabfit-frontend/src/components/AvailabilityViewer/{availabilityViewerStyle.ts => AvailabilityViewer.styles.js} (60%) create mode 100644 crabfit-frontend/src/components/Button/Button.jsx rename crabfit-frontend/src/components/Button/{buttonStyle.ts => Button.styles.js} (67%) delete mode 100644 crabfit-frontend/src/components/Button/Button.tsx rename crabfit-frontend/src/components/CalendarField/{CalendarField.tsx => CalendarField.jsx} (66%) rename crabfit-frontend/src/components/CalendarField/{calendarFieldStyle.ts => CalendarField.styles.js} (63%) create mode 100644 crabfit-frontend/src/components/Center/Center.js delete mode 100644 crabfit-frontend/src/components/Center/Center.ts rename crabfit-frontend/src/components/Donate/{Donate.tsx => Donate.jsx} (64%) rename crabfit-frontend/src/components/Donate/{donateStyle.ts => Donate.styles.js} (73%) rename crabfit-frontend/src/components/Egg/{Egg.tsx => Egg.jsx} (66%) rename crabfit-frontend/src/components/Egg/{eggStyle.ts => Egg.styles.js} (72%) rename crabfit-frontend/src/components/Error/{Error.tsx => Error.jsx} (86%) rename crabfit-frontend/src/components/Error/{errorStyle.ts => Error.styles.js} (81%) create mode 100644 crabfit-frontend/src/components/Footer/Footer.jsx rename crabfit-frontend/src/components/Footer/{footerStyle.ts => Footer.styles.js} (82%) delete mode 100644 crabfit-frontend/src/components/Footer/Footer.tsx rename crabfit-frontend/src/components/GoogleCalendar/{GoogleCalendar.tsx => GoogleCalendar.jsx} (74%) rename crabfit-frontend/src/components/GoogleCalendar/{googleCalendarStyle.ts => GoogleCalendar.styles.js} (75%) rename crabfit-frontend/src/components/Legend/{Legend.tsx => Legend.jsx} (64%) rename crabfit-frontend/src/components/Legend/{legendStyle.ts => Legend.styles.js} (61%) create mode 100644 crabfit-frontend/src/components/Loading/Loading.jsx rename crabfit-frontend/src/components/Loading/{loadingStyle.ts => Loading.styles.js} (74%) delete mode 100644 crabfit-frontend/src/components/Loading/Loading.tsx rename crabfit-frontend/src/components/Logo/{Logo.tsx => Logo.jsx} (59%) rename crabfit-frontend/src/components/Logo/{logoStyle.ts => Logo.styles.js} (70%) rename crabfit-frontend/src/components/OutlookCalendar/{OutlookCalendar.tsx => OutlookCalendar.jsx} (67%) rename crabfit-frontend/src/components/Recents/{Recents.tsx => Recents.jsx} (53%) rename crabfit-frontend/src/components/Recents/{recentsStyle.ts => Recents.styles.js} (66%) rename crabfit-frontend/src/components/SelectField/{SelectField.tsx => SelectField.jsx} (74%) create mode 100644 crabfit-frontend/src/components/SelectField/SelectField.styles.js delete mode 100644 crabfit-frontend/src/components/SelectField/selectFieldStyle.ts rename crabfit-frontend/src/components/Settings/{Settings.tsx => Settings.jsx} (55%) rename crabfit-frontend/src/components/Settings/{settingsStyle.ts => Settings.styles.js} (72%) rename crabfit-frontend/src/components/TextField/{TextField.tsx => TextField.jsx} (81%) create mode 100644 crabfit-frontend/src/components/TextField/TextField.styles.js delete mode 100644 crabfit-frontend/src/components/TextField/textFieldStyle.ts rename crabfit-frontend/src/components/TimeRangeField/{TimeRangeField.tsx => TimeRangeField.jsx} (61%) rename crabfit-frontend/src/components/TimeRangeField/{timeRangeFieldStyle.ts => TimeRangeField.styles.js} (58%) rename crabfit-frontend/src/components/ToggleField/{ToggleField.tsx => ToggleField.jsx} (93%) rename crabfit-frontend/src/components/ToggleField/{toggleFieldStyle.ts => ToggleField.styles.js} (63%) rename crabfit-frontend/src/components/TranslateDialog/{TranslateDialog.tsx => TranslateDialog.jsx} (78%) rename crabfit-frontend/src/components/TranslateDialog/{translateDialogStyle.ts => TranslateDialog.styles.js} (76%) rename crabfit-frontend/src/components/UpdateDialog/{UpdateDialog.tsx => UpdateDialog.jsx} (69%) rename crabfit-frontend/src/components/UpdateDialog/{updateDialogStyle.ts => UpdateDialog.styles.js} (68%) create mode 100644 crabfit-frontend/src/components/index.js delete mode 100644 crabfit-frontend/src/components/index.ts rename crabfit-frontend/src/i18n/{index.ts => index.js} (54%) rename crabfit-frontend/src/{res/dayjs_locales.ts => i18n/locales.js} (98%) create mode 100644 crabfit-frontend/src/index.jsx delete mode 100644 crabfit-frontend/src/index.tsx create mode 100644 crabfit-frontend/src/pages/Help/Help.jsx rename crabfit-frontend/src/pages/Help/{helpStyle.ts => Help.styles.js} (63%) delete mode 100644 crabfit-frontend/src/pages/Help/Help.tsx rename crabfit-frontend/src/pages/Home/{homeStyle.ts => Home.styles.js} (73%) create mode 100644 crabfit-frontend/src/pages/Privacy/Privacy.jsx create mode 100644 crabfit-frontend/src/pages/Privacy/Privacy.styles.js delete mode 100644 crabfit-frontend/src/pages/Privacy/Privacy.tsx delete mode 100644 crabfit-frontend/src/pages/Privacy/privacyStyle.ts create mode 100644 crabfit-frontend/src/pages/index.js delete mode 100644 crabfit-frontend/src/pages/index.ts delete mode 100644 crabfit-frontend/src/react-app-env.d.ts delete mode 100644 crabfit-frontend/src/reportWebVitals.js delete mode 100644 crabfit-frontend/src/res/dayjs_locales.json delete mode 100644 crabfit-frontend/src/setupTests.js create mode 100644 crabfit-frontend/src/stores/index.js delete mode 100644 crabfit-frontend/src/stores/index.ts create mode 100644 crabfit-frontend/src/stores/localeUpdateStore.js create mode 100644 crabfit-frontend/src/stores/recentsStore.js create mode 100644 crabfit-frontend/src/stores/settingsStore.js create mode 100644 crabfit-frontend/src/stores/translateStore.js create mode 100644 crabfit-frontend/src/stores/twaStore.js create mode 100644 crabfit-frontend/src/utils/index.js delete mode 100644 crabfit-frontend/src/utils/index.ts delete mode 100644 crabfit-frontend/tsconfig.json create mode 100644 crabfit-frontend/vite.config.js diff --git a/.github/workflows/deploy_frontend.yml b/.github/workflows/deploy_frontend.yml index 944efb2..5af7c6a 100644 --- a/.github/workflows/deploy_frontend.yml +++ b/.github/workflows/deploy_frontend.yml @@ -27,7 +27,7 @@ jobs: - run: yarn install --immutable - run: yarn build - name: Copy app.yaml to build - run: 'cp app.yaml ./build/app.yaml' + run: 'cp app.yaml ./dist/app.yaml' - id: auth uses: google-github-actions/auth@v0 with: @@ -35,5 +35,5 @@ jobs: - id: deploy uses: google-github-actions/deploy-appengine@v0 with: - working_directory: crabfit-frontend/build + working_directory: crabfit-frontend version: v1 diff --git a/crabfit-frontend/.eslintrc.js b/crabfit-frontend/.eslintrc.js new file mode 100644 index 0000000..0dfd57e --- /dev/null +++ b/crabfit-frontend/.eslintrc.js @@ -0,0 +1,73 @@ +/* eslint-env node */ +module.exports = { + 'settings': { + 'react': { + 'version': 'detect' + } + }, + 'env': { + 'browser': true, + 'es2021': true + }, + 'globals': { + 'process': true, + 'require': true, + 'gtag': true, + }, + 'extends': [ + 'eslint:recommended', + 'plugin:react/recommended' + ], + 'parserOptions': { + 'ecmaFeatures': { + 'jsx': true + }, + 'ecmaVersion': 12, + 'sourceType': 'module' + }, + 'plugins': [ + 'react' + ], + 'rules': { + 'react/display-name': 'off', + 'react/prop-types': 'off', + 'react/no-unescaped-entities': 'off', + 'react/react-in-jsx-scope': 'off', + 'eqeqeq': 2, + 'no-return-await': 1, + 'no-var': 2, + 'prefer-const': 1, + 'yoda': 2, + 'no-trailing-spaces': 1, + 'eol-last': [1, 'always'], + 'no-unused-vars': [ + 1, + { + 'args': 'all', + 'argsIgnorePattern': '^_', + 'ignoreRestSiblings': true + } + ], + 'indent': [ + 'error', + 2 + ], + 'linebreak-style': [ + 'error', + 'unix' + ], + 'quotes': [ + 'error', + 'single' + ], + 'semi': [ + 'error', + 'never' + ], + 'arrow-parens': [ + 'error', + 'as-needed' + ], + 'jsx-quotes': [1, 'prefer-double'], + } +} diff --git a/crabfit-frontend/.gcloudignore b/crabfit-frontend/.gcloudignore new file mode 100644 index 0000000..f970c16 --- /dev/null +++ b/crabfit-frontend/.gcloudignore @@ -0,0 +1,10 @@ +node_modules +.DS_Store +.git +.gitignore +.gcloudignore +src +public +.eslintrc.js +yarn.lock +package.json diff --git a/crabfit-frontend/.gitignore b/crabfit-frontend/.gitignore index 4d29575..6565811 100644 --- a/crabfit-frontend/.gitignore +++ b/crabfit-frontend/.gitignore @@ -1,22 +1,6 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local +node_modules +dist +build npm-debug.log* yarn-debug.log* diff --git a/crabfit-frontend/README.md b/crabfit-frontend/README.md deleted file mode 100644 index 02aac3f..0000000 --- a/crabfit-frontend/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# Getting Started with Create React App - -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). - -## Available Scripts - -In the project directory, you can run: - -### `yarn start` - -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. - -### `yarn test` - -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. - -### `yarn build` - -Builds the app for production to the `build` folder.\ -It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.\ -Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `yarn eject` - -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** - -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. - -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. - -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. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). - -### Code Splitting - -This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) - -### Analyzing the Bundle Size - -This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) - -### Making a Progressive Web App - -This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) - -### Advanced Configuration - -This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) - -### Deployment - -This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) - -### `yarn build` fails to minify - -This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) diff --git a/crabfit-frontend/app.yaml b/crabfit-frontend/app.yaml index 10ca879..b3ee472 100644 --- a/crabfit-frontend/app.yaml +++ b/crabfit-frontend/app.yaml @@ -1,15 +1,15 @@ -runtime: nodejs12 +runtime: nodejs16 handlers: # Serve all static files with url ending with a file extension - url: /(.*\..+)$ - static_files: \1 + static_files: dist/\1 upload: (.*\..+)$ secure: always redirect_http_response_code: 301 # Catch all handler to index.html - url: /.* - static_files: index.html - upload: index.html + static_files: dist/index.html + upload: dist/index.html secure: always redirect_http_response_code: 301 diff --git a/crabfit-frontend/public/index.html b/crabfit-frontend/index.html similarity index 79% rename from crabfit-frontend/public/index.html rename to crabfit-frontend/index.html index 5bd4e36..b0f99ac 100644 --- a/crabfit-frontend/public/index.html +++ b/crabfit-frontend/index.html @@ -2,7 +2,7 @@ - + - - + + - + Crab Fit @@ -38,9 +38,14 @@ +
+ + -
diff --git a/crabfit-frontend/jsconfig.json b/crabfit-frontend/jsconfig.json new file mode 100644 index 0000000..064fef1 --- /dev/null +++ b/crabfit-frontend/jsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "baseUrl": "./", + "paths": { + "/*": ["./*"] + } + }, + "exclude": [ + "**/node_modules/*", + "**/dist/*", + "**/.git/*" + ] +} diff --git a/crabfit-frontend/package.json b/crabfit-frontend/package.json index 597ca0c..a5b208d 100644 --- a/crabfit-frontend/package.json +++ b/crabfit-frontend/package.json @@ -3,58 +3,47 @@ "version": "1.0.0", "private": true, "license": "GPL-3.0-only", - "dependencies": { - "@azure/msal-browser": "^2.14.2", - "@emotion/react": "^11.1.5", - "@emotion/styled": "^11.1.5", - "@microsoft/microsoft-graph-client": "^2.2.1", - "@testing-library/jest-dom": "^5.11.4", - "@testing-library/react": "^11.1.0", - "@testing-library/user-event": "^12.1.10", - "@types/jest": "^26.0.20", - "@types/node": "^14.14.31", - "@types/react": "^17.0.2", - "@types/react-dom": "^17.0.1", - "axios": "^0.21.1", - "dayjs": "^1.10.4", - "gapi-script": "^1.2.0", - "i18next": "^20.2.4", - "i18next-browser-languagedetector": "^6.1.1", - "i18next-http-backend": "^1.2.4", - "react": "^17.0.2", - "react-dom": "^17.0.2", - "react-hook-form": "^7.8.1", - "react-i18next": "^11.8.15", - "react-router-dom": "^5.2.0", - "react-scripts": "4.0.3", - "typescript": "^4.2.2", - "web-vitals": "^1.0.1", - "workbox-background-sync": "^5.1.3", - "workbox-broadcast-update": "^5.1.3", - "workbox-cacheable-response": "^5.1.3", - "workbox-core": "^5.1.3", - "workbox-expiration": "^5.1.3", - "workbox-google-analytics": "^5.1.3", - "workbox-navigation-preload": "^5.1.3", - "workbox-precaching": "^5.1.3", - "workbox-range-requests": "^5.1.3", - "workbox-routing": "^5.1.3", - "workbox-strategies": "^5.1.3", - "workbox-streams": "^5.1.3", - "workbox-window": "^6.1.5", - "zustand": "^3.3.2" - }, "scripts": { - "start": "react-app-rewired start", - "build": "react-app-rewired build", - "test": "react-app-rewired test", - "eject": "react-scripts eject" + "dev": "vite", + "build": "vite build", + "lint": "eslint --ext .js,.jsx ./src" }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] + "dependencies": { + "@azure/msal-browser": "^2.28.1", + "@microsoft/microsoft-graph-client": "^3.0.2", + "dayjs": "^1.11.5", + "gapi-script": "^1.2.0", + "goober": "^2.1.10", + "i18next": "^21.9.0", + "i18next-browser-languagedetector": "^6.1.5", + "i18next-http-backend": "^1.4.1", + "lucide-react": "^0.84.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-hook-form": "^7.34.1", + "react-i18next": "^11.18.4", + "react-router-dom": "^6.3.0", + "workbox-background-sync": "^6.5.4", + "workbox-broadcast-update": "^6.5.4", + "workbox-cacheable-response": "^6.5.4", + "workbox-core": "^6.5.4", + "workbox-expiration": "^6.5.4", + "workbox-google-analytics": "^6.5.4", + "workbox-navigation-preload": "^6.5.4", + "workbox-precaching": "^6.5.4", + "workbox-range-requests": "^6.5.4", + "workbox-routing": "^6.5.4", + "workbox-strategies": "^6.5.4", + "workbox-streams": "^6.5.4", + "workbox-window": "^6.5.4", + "zustand": "^4.0.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^2.0.1", + "eslint": "^8.22.0", + "eslint-plugin-react": "^7.30.1", + "vite": "^3.0.7", + "workbox-webpack-plugin": "^6.5.4" }, "browserslist": { "production": [ @@ -67,9 +56,5 @@ "last 1 firefox version", "last 1 safari version" ] - }, - "devDependencies": { - "react-app-rewired": "^2.1.8", - "workbox-webpack-plugin": "^5.1.3" } } diff --git a/crabfit-frontend/public/index.css b/crabfit-frontend/public/index.css index 80d01b8..abe65c8 100644 --- a/crabfit-frontend/public/index.css +++ b/crabfit-frontend/public/index.css @@ -1,5 +1,5 @@ @font-face { - font-family: Karla; + font-family: 'Karla'; src: url('fonts/karla-variable.ttf') format('truetype'); font-weight: 1 999; } @@ -20,22 +20,146 @@ font-style: normal; } +:root { + color-scheme: light dark; + + /* LIGHT */ + --background-light: #FFFFFF; + --text-light: #000000; + --primary-light: #F79E00; + --secondary-light: #F48600; + --tertiary-light: #F4BB60; + --surface-light: #FEF2DD; + --error-light: #D32F2F; + --loading-light: #DDDDDD; + --font-weight-light: 600; + + /* DARK */ + --background-dark: #111111; + --text-dark: #DDDDDD; + --primary-dark: #F79E00; + --secondary-dark: #F4BB60; + --tertiary-dark: #CC7313; + --surface-dark: #30240F; + --error-dark: #E53935; + --loading-dark: #444444; + --font-weight-dark: 500; + + /* Define light defaults */ + --background: var(--background-light); + --text: var(--text-light); + --primary: var(--primary-light); + --secondary: var(--secondary-light); + --tertiary: var(--tertiary-light); + --surface: var(--surface-light); + --error: var(--error-light); + --loading: var(--loading-light); + --font-weight: var(--font-weight-light); +} + +@media (prefers-color-scheme: dark) { + :root { + --background: var(--background-dark); + --text: var(--text-dark); + --primary: var(--primary-dark); + --secondary: var(--secondary-dark); + --tertiary: var(--tertiary-dark); + --surface: var(--surface-dark); + --error: var(--error-dark); + --loading: var(--loading-dark); + --font-weight: var(--font-weight-dark); + } +} + +html { + scroll-behavior: smooth; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; +} + +body { + margin: 0; + font-family: 'Karla', sans-serif; + background: var(--background); + color: var(--text); + font-weight: var(--font-weight); +} + +.light { + color-scheme: light; + + --background: var(--background-light); + --text: var(--text-light); + --primary: var(--primary-light); + --secondary: var(--secondary-light); + --tertiary: var(--tertiary-light); + --surface: var(--surface-light); + --error: var(--error-light); + --loading: var(--loading-light); + --font-weight: var(--font-weight-light); +} + +@media not print { + .dark { + color-scheme: dark; + + --background: var(--background-dark); + --text: var(--text-dark); + --primary: var(--primary-dark); + --secondary: var(--secondary-dark); + --tertiary: var(--tertiary-dark); + --surface: var(--surface-dark); + --error: var(--error-dark); + --loading: var(--loading-dark); + --font-weight: var(--font-weight-dark); + } +} + +@media print { + #app, .light, .dark { + --background: white; + } +} + +a { + color: var(--primary); +} + +*::-webkit-scrollbar { + width: 16px; + height: 16px; +} +*::-webkit-scrollbar-track { + background: var(--surface); +} +*::-webkit-scrollbar-thumb { + border-radius: 100px; + border: 4px solid var(--surface); + width: 12px; + background: var(--secondary); +} +*::-webkit-scrollbar-thumb:hover { + background: var(--tertiary); +} +*::-webkit-scrollbar-thumb:active { + background: red; +} + /* IE 10+ */ @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { - #root { - font-family: Karla, sans-serif; + #app { text-align: center; margin: 20vh auto; font-size: 1.3em; font-weight: 600; } - #root::before { + #app::before { content: '🦀'; font-size: 1.5em; display: block; padding: 20px; } - #root::after { + #app::after { display: block; content: 'Crab Fit doesn\'t work in Internet Explorer. Please try using a modern browser.'; } diff --git a/crabfit-frontend/src/App.jsx b/crabfit-frontend/src/App.jsx new file mode 100644 index 0000000..d11a67a --- /dev/null +++ b/crabfit-frontend/src/App.jsx @@ -0,0 +1,85 @@ +import { useState, useEffect, useCallback, Suspense } from 'react' +import { Route, Routes } from 'react-router-dom' +import { Workbox } from 'workbox-window' + +import * as Pages from '/src/pages' +import { Settings, Loading, Egg, UpdateDialog, TranslateDialog } from '/src/components' + +import { useSettingsStore, useTranslateStore } from '/src/stores' + +const EGG_PATTERN = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a'] + +const wb = new Workbox('sw.js') + +const App = () => { + const [eggCount, setEggCount] = useState(0) + const [eggVisible, setEggVisible] = useState(false) + const [eggKey, setEggKey] = useState(0) + + const [updateAvailable, setUpdateAvailable] = useState(false) + const languageSupported = useTranslateStore(state => state.navigatorSupported) + const translateDialogDismissed = useTranslateStore(state => state.translateDialogDismissed) + + const eggHandler = useCallback(e => { + if (EGG_PATTERN.indexOf(e.key) < 0 || e.key !== EGG_PATTERN[eggCount]) return setEggCount(0) + setEggCount(eggCount+1) + if (EGG_PATTERN.length === eggCount+1) { + setEggKey(eggKey+1) + setEggCount(0) + setEggVisible(true) + } + }, [eggCount, eggKey]) + + useEffect(() => { + // Register service worker + if ('serviceWorker' in navigator && process.env.NODE_ENV === 'production') { + wb.addEventListener('installed', event => { + if (event.isUpdate) { + setUpdateAvailable(true) + } + }) + + wb.register() + } + }, []) + + useEffect(() => { + document.addEventListener('keyup', eggHandler, false) + return () => document.removeEventListener('keyup', eggHandler, false) + }, [eggHandler]) + + // Use user theme preference + const theme = useSettingsStore(state => state.theme) + useEffect(() => { + document.body.classList.toggle('light', theme === 'Light') + document.body.classList.toggle('dark', theme === 'Dark') + }, [theme]) + + return ( + <> + {!languageSupported && !translateDialogDismissed && } + + }> + + + + {/* } /> */} + {/* } /> */} + } /> + {/* } /> + } /> */} + + + + {updateAvailable && ( + }> + setUpdateAvailable(false)} /> + + )} + + {eggVisible && setEggVisible(false)} />} + + ) +} + +export default App diff --git a/crabfit-frontend/src/App.test.ts b/crabfit-frontend/src/App.test.ts deleted file mode 100644 index 1f03afe..0000000 --- a/crabfit-frontend/src/App.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/crabfit-frontend/src/App.tsx b/crabfit-frontend/src/App.tsx deleted file mode 100644 index 1b20075..0000000 --- a/crabfit-frontend/src/App.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { useState, useEffect, useCallback, Suspense, lazy } from 'react'; -import { BrowserRouter, Switch, Route } from 'react-router-dom'; -import { ThemeProvider, Global } from '@emotion/react'; -import { Workbox } from 'workbox-window'; - -import { Settings, Loading, Egg, UpdateDialog, TranslateDialog } from 'components'; - -import { useSettingsStore, useTranslateStore } from 'stores'; -import theme from 'theme'; - -const EGG_PATTERN = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a']; - -const Home = lazy(() => import('pages/Home/Home')); -const Event = lazy(() => import('pages/Event/Event')); -const Create = lazy(() => import('pages/Create/Create')); -const Help = lazy(() => import('pages/Help/Help')); -const Privacy = lazy(() => import('pages/Privacy/Privacy')); - -const wb = new Workbox('sw.js'); - -const App = () => { - const colortheme = useSettingsStore(state => state.theme); - const darkQuery = window.matchMedia('(prefers-color-scheme: dark)'); - const [isDark, setIsDark] = useState(darkQuery.matches); - const [offline, setOffline] = useState(!window.navigator.onLine); - - const [eggCount, setEggCount] = useState(0); - const [eggVisible, setEggVisible] = useState(false); - const [eggKey, setEggKey] = useState(0); - - const [updateAvailable, setUpdateAvailable] = useState(false); - const languageSupported = useTranslateStore(state => state.navigatorSupported); - const translateDialogDismissed = useTranslateStore(state => state.translateDialogDismissed); - - const eggHandler = useCallback( - event => { - if (EGG_PATTERN.indexOf(event.key) < 0 || event.key !== EGG_PATTERN[eggCount]) { - setEggCount(0); - return; - } - setEggCount(eggCount+1); - if (EGG_PATTERN.length === eggCount+1) { - setEggKey(eggKey+1); - setEggCount(0); - setEggVisible(true); - } - }, - [eggCount, eggKey] - ); - - darkQuery.addListener(e => colortheme === 'System' && setIsDark(e.matches)); - - useEffect(() => { - const onOffline = () => setOffline(true); - const onOnline = () => setOffline(false); - - window.addEventListener('offline', onOffline, false); - window.addEventListener('online', onOnline, false); - - return () => { - window.removeEventListener('offline', onOffline, false); - window.removeEventListener('online', onOnline, false); - }; - }, []); - - useEffect(() => { - // Register service worker - if ('serviceWorker' in navigator && process.env.NODE_ENV === 'production') { - wb.addEventListener('installed', event => { - if (event.isUpdate) { - setUpdateAvailable(true); - } - }); - - wb.register(); - } - }, []); - - useEffect(() => { - document.addEventListener('keyup', eggHandler, false); - - return () => { - document.removeEventListener('keyup', eggHandler, false); - }; - }, [eggHandler]); - - useEffect(() => { - setIsDark(colortheme === 'System' ? darkQuery.matches : colortheme === 'Dark'); - }, [colortheme, darkQuery.matches]); - - return ( - - - ({ - html: { - scrollBehavior: 'smooth', - WebkitPrintColorAdjust: 'exact', - }, - body: { - backgroundColor: theme.background, - color: theme.text, - fontFamily: `'Karla', sans-serif`, - fontWeight: theme.mode === 'dark' ? 500 : 600, - margin: 0, - }, - a: { - color: theme.primary, - }, - '*::-webkit-scrollbar': { - width: 16, - height: 16, - }, - '*::-webkit-scrollbar-track': { - background: `${theme.primaryBackground}`, - }, - '*::-webkit-scrollbar-thumb': { - borderRadius: 100, - border: `4px solid ${theme.primaryBackground}`, - width: 12, - background: `${theme.mode === 'light' ? theme.primaryLight : theme.primaryDark}AA`, - }, - '*::-webkit-scrollbar-thumb:hover': { - background: `${theme.mode === 'light' ? theme.primaryLight : theme.primaryDark}CC`, - }, - '*::-webkit-scrollbar-thumb:active': { - background: `${theme.mode === 'light' ? theme.primaryLight : theme.primaryDark}`, - }, - })} - /> - - {!languageSupported && !translateDialogDismissed && } - - }> - - - - - ( - }> - - - )} /> - ( - }> - - - )} /> - ( - }> - - - )} /> - ( - }> - - - )} /> - ( - }> - - - )} /> - - - {updateAvailable && ( - }> - setUpdateAvailable(false)} /> - - )} - - {eggVisible && setEggVisible(false)} />} - - - ); -} - -export default App; diff --git a/crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.tsx b/crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.jsx similarity index 69% rename from crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.tsx rename to crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.jsx index 165ed5b..f25ee25 100644 --- a/crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.tsx +++ b/crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.jsx @@ -1,12 +1,13 @@ -import { useState, useRef, Fragment, Suspense, lazy } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useLocaleUpdateStore } from 'stores'; -import dayjs from 'dayjs'; -import localeData from 'dayjs/plugin/localeData'; -import customParseFormat from 'dayjs/plugin/customParseFormat'; -import isBetween from 'dayjs/plugin/isBetween'; -import dayjs_timezone from 'dayjs/plugin/timezone'; -import utc from 'dayjs/plugin/utc'; +import { useState, useRef, Fragment, Suspense, lazy } from 'react' +import { useTranslation } from 'react-i18next' +import dayjs from 'dayjs' +import localeData from 'dayjs/plugin/localeData' +import customParseFormat from 'dayjs/plugin/customParseFormat' +import isBetween from 'dayjs/plugin/isBetween' +import dayjs_timezone from 'dayjs/plugin/timezone' +import utc from 'dayjs/plugin/utc' + +import { useLocaleUpdateStore } from '/src/stores' import { Wrapper, @@ -21,20 +22,20 @@ import { TimeLabel, TimeSpace, StyledMain, -} from 'components/AvailabilityViewer/availabilityViewerStyle'; -import { Time } from './availabilityEditorStyle'; +} from '/src/components/AvailabilityViewer/AvailabilityViewer.styles' +import { Time } from './AvailabilityEditor.styles' -import { _GoogleCalendar, _OutlookCalendar, Center } from 'components'; -import { Loader } from '../Loading/loadingStyle'; +import { _GoogleCalendar, _OutlookCalendar, Center } from '/src/components' +import { Loader } from '../Loading/Loading.styles' -const GoogleCalendar = lazy(() => _GoogleCalendar()); -const OutlookCalendar = lazy(() => _OutlookCalendar()); +const GoogleCalendar = lazy(() => _GoogleCalendar()) +const OutlookCalendar = lazy(() => _OutlookCalendar()) -dayjs.extend(localeData); -dayjs.extend(customParseFormat); -dayjs.extend(isBetween); -dayjs.extend(utc); -dayjs.extend(dayjs_timezone); +dayjs.extend(localeData) +dayjs.extend(customParseFormat) +dayjs.extend(isBetween) +dayjs.extend(utc) +dayjs.extend(dayjs_timezone) const AvailabilityEditor = ({ times, @@ -44,25 +45,24 @@ const AvailabilityEditor = ({ isSpecificDates, value = [], onChange, - ...props }) => { - const { t } = useTranslation('event'); - const locale = useLocaleUpdateStore(state => state.locale); + const { t } = useTranslation('event') + const locale = useLocaleUpdateStore(state => state.locale) - const [selectingTimes, _setSelectingTimes] = useState([]); - const staticSelectingTimes = useRef([]); + const [selectingTimes, _setSelectingTimes] = useState([]) + const staticSelectingTimes = useRef([]) const setSelectingTimes = newTimes => { - staticSelectingTimes.current = newTimes; - _setSelectingTimes(newTimes); - }; + staticSelectingTimes.current = newTimes + _setSelectingTimes(newTimes) + } - const startPos = useRef({}); - const staticMode = useRef(null); - const [mode, _setMode] = useState(staticMode.current); + const startPos = useRef({}) + const staticMode = useRef(null) + const [mode, _setMode] = useState(staticMode.current) const setMode = newMode => { - staticMode.current = newMode; - _setMode(newMode); - }; + staticMode.current = newMode + _setMode(newMode) + } return ( <> @@ -109,8 +109,8 @@ const AvailabilityEditor = ({ )} {dates.map((date, x) => { - const parsedDate = isSpecificDates ? dayjs(date, 'DDMMYYYY') : dayjs().day(date); - const last = dates.length === x+1 || (isSpecificDates ? dayjs(dates[x+1], 'DDMMYYYY') : dayjs().day(dates[x+1])).diff(parsedDate, 'day') > 1; + const parsedDate = isSpecificDates ? dayjs(date, 'DDMMYYYY') : dayjs().day(date) + const last = dates.length === x+1 || (isSpecificDates ? dayjs(dates[x+1], 'DDMMYYYY') : dayjs().day(dates[x+1])).diff(parsedDate, 'day') > 1 return ( @@ -122,13 +122,13 @@ const AvailabilityEditor = ({ borderLeft={x === 0 || (parsedDate).diff(isSpecificDates ? dayjs(dates[x-1], 'DDMMYYYY') : dayjs().day(dates[x-1]), 'day') > 1} > {timeLabels.map((timeLabel, y) => { - if (!timeLabel.time) return null; + if (!timeLabel.time) return null if (!times.includes(`${timeLabel.time}-${date}`)) { return ( - - ); + + ) } - const time = `${timeLabel.time}-${date}`; + const time = `${timeLabel.time}-${date}` return ( @@ -174,13 +174,13 @@ const AvailabilityEditor = ({ )} - ); + ) })} - ); -}; + ) +} -export default AvailabilityEditor; +export default AvailabilityEditor diff --git a/crabfit-frontend/src/components/AvailabilityEditor/availabilityEditorStyle.ts b/crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.styles.js similarity index 62% rename from crabfit-frontend/src/components/AvailabilityEditor/availabilityEditorStyle.ts rename to crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.styles.js index 65cb48e..a10940b 100644 --- a/crabfit-frontend/src/components/AvailabilityEditor/availabilityEditorStyle.ts +++ b/crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.styles.js @@ -1,24 +1,24 @@ -import styled from '@emotion/styled'; +import { styled } from 'goober' -export const Time = styled.div` +export const Time = styled('div')` height: 10px; touch-action: none; transition: background-color .1s; ${props => props.time.slice(2, 4) === '00' && ` - border-top: 2px solid ${props.theme.text}; + border-top: 2px solid var(--text); `} ${props => props.time.slice(2, 4) !== '00' && ` border-top: 2px solid transparent; `} ${props => props.time.slice(2, 4) === '30' && ` - border-top: 2px dotted ${props.theme.text}; + border-top: 2px dotted var(--text); `} ${props => (props.selected || (props.mode === 'add' && props.selecting)) && ` - background-color: ${props.theme.primary}; + background-color: var(--primary); `}; ${props => props.mode === 'remove' && props.selecting && ` - background-color: ${props.theme.background}; + background-color: var(--background); `}; -`; +` diff --git a/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx b/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.jsx similarity index 77% rename from crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx rename to crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.jsx index 579a074..bed8848 100644 --- a/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.tsx +++ b/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.jsx @@ -1,13 +1,13 @@ -import { useState, useEffect, useRef, useMemo, Fragment } from 'react'; -import { useTranslation } from 'react-i18next'; -import dayjs from 'dayjs'; -import localeData from 'dayjs/plugin/localeData'; -import customParseFormat from 'dayjs/plugin/customParseFormat'; -import relativeTime from 'dayjs/plugin/relativeTime'; +import { useState, useEffect, useRef, useMemo, Fragment } from 'react' +import { useTranslation } from 'react-i18next' +import dayjs from 'dayjs' +import localeData from 'dayjs/plugin/localeData' +import customParseFormat from 'dayjs/plugin/customParseFormat' +import relativeTime from 'dayjs/plugin/relativeTime' -import { useSettingsStore, useLocaleUpdateStore } from 'stores'; +import { useSettingsStore, useLocaleUpdateStore } from '/src/stores' -import { Legend } from 'components'; +import { Legend } from '/src/components' import { Wrapper, ScrollWrapper, @@ -30,13 +30,13 @@ import { Person, StyledMain, Info, -} from './availabilityViewerStyle'; +} from './AvailabilityViewer.styles' -import locales from 'res/dayjs_locales'; +import locales from '/src/i18n/locales' -dayjs.extend(localeData); -dayjs.extend(customParseFormat); -dayjs.extend(relativeTime); +dayjs.extend(localeData) +dayjs.extend(customParseFormat) +dayjs.extend(relativeTime) const AvailabilityViewer = ({ times, @@ -46,25 +46,24 @@ const AvailabilityViewer = ({ people = [], min = 0, max = 0, - ...props }) => { - const [tooltip, setTooltip] = useState(null); - const timeFormat = useSettingsStore(state => state.timeFormat); - const highlight = useSettingsStore(state => state.highlight); - const [filteredPeople, setFilteredPeople] = useState([]); - const [touched, setTouched] = useState(false); - const [tempFocus, setTempFocus] = useState(null); - const [focusCount, setFocusCount] = useState(null); + const [tooltip, setTooltip] = useState(null) + const timeFormat = useSettingsStore(state => state.timeFormat) + const highlight = useSettingsStore(state => state.highlight) + const [filteredPeople, setFilteredPeople] = useState([]) + const [touched, setTouched] = useState(false) + const [tempFocus, setTempFocus] = useState(null) + const [focusCount, setFocusCount] = useState(null) - const { t } = useTranslation('event'); - const locale = useLocaleUpdateStore(state => state.locale); + const { t } = useTranslation('event') + const locale = useLocaleUpdateStore(state => state.locale) - const wrapper = useRef(); + const wrapper = useRef() useEffect(() => { - setFilteredPeople(people.map(p => p.name)); - setTouched(people.length <= 1); - }, [people]); + setFilteredPeople(people.map(p => p.name)) + setTouched(people.length <= 1) + }, [people]) const heatmap = useMemo(() => ( @@ -76,8 +75,8 @@ const AvailabilityViewer = ({ )} {dates.map((date, i) => { - const parsedDate = isSpecificDates ? dayjs(date, 'DDMMYYYY') : dayjs().day(date); - const last = dates.length === i+1 || (isSpecificDates ? dayjs(dates[i+1], 'DDMMYYYY') : dayjs().day(dates[i+1])).diff(parsedDate, 'day') > 1; + const parsedDate = isSpecificDates ? dayjs(date, 'DDMMYYYY') : dayjs().day(date) + const last = dates.length === i+1 || (isSpecificDates ? dayjs(dates[i+1], 'DDMMYYYY') : dayjs().day(dates[i+1])).diff(parsedDate, 'day') > 1 return ( @@ -89,16 +88,16 @@ const AvailabilityViewer = ({ borderLeft={i === 0 || (parsedDate).diff(isSpecificDates ? dayjs(dates[i-1], 'DDMMYYYY') : dayjs().day(dates[i-1]), 'day') > 1} > {timeLabels.map((timeLabel, i) => { - if (!timeLabel.time) return null; + if (!timeLabel.time) return null if (!times.includes(`${timeLabel.time}-${date}`)) { return ( - - ); + + ) } - const time = `${timeLabel.time}-${date}`; + const time = `${timeLabel.time}-${date}` const peopleHere = tempFocus !== null ? people.filter(person => person.availability.includes(time) && tempFocus === person.name).map(person => person.name) - : people.filter(person => person.availability.includes(time) && filteredPeople.includes(person.name)).map(person => person.name); + : people.filter(person => person.availability.includes(time) && filteredPeople.includes(person.name)).map(person => person.name) return ( @@ -134,7 +133,7 @@ const AvailabilityViewer = ({ )} - ); + ) })} ), [ @@ -152,7 +151,7 @@ const AvailabilityViewer = ({ timeFormat, timeLabels, times, - ]); + ]) return ( <> @@ -173,16 +172,16 @@ const AvailabilityViewer = ({ key={i} filtered={filteredPeople.includes(person.name)} onClick={() => { - setTempFocus(null); + setTempFocus(null) if (filteredPeople.includes(person.name)) { if (!touched) { - setTouched(true); - setFilteredPeople([person.name]); + setTouched(true) + setFilteredPeople([person.name]) } else { - setFilteredPeople(filteredPeople.filter(n => n !== person.name)); + setFilteredPeople(filteredPeople.filter(n => n !== person.name)) } } else { - setFilteredPeople([...filteredPeople, person.name]); + setFilteredPeople([...filteredPeople, person.name]) } }} onMouseOver={() => setTempFocus(person.name)} @@ -221,7 +220,7 @@ const AvailabilityViewer = ({ - ); -}; + ) +} -export default AvailabilityViewer; +export default AvailabilityViewer diff --git a/crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts b/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.styles.js similarity index 60% rename from crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts rename to crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.styles.js index c4a98b0..a9b5627 100644 --- a/crabfit-frontend/src/components/AvailabilityViewer/availabilityViewerStyle.ts +++ b/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.styles.js @@ -1,16 +1,16 @@ -import styled from '@emotion/styled'; +import { styled } from 'goober' -export const Wrapper = styled.div` +export const Wrapper = styled('div')` overflow-y: visible; margin: 20px 0; position: relative; -`; +` -export const ScrollWrapper = styled.div` +export const ScrollWrapper = styled('div')` overflow-x: auto; -`; +` -export const Container = styled.div` +export const Container = styled('div')` display: inline-flex; box-sizing: border-box; min-width: 100%; @@ -21,147 +21,147 @@ export const Container = styled.div` @media (max-width: 660px) { padding: 0 30px; } -`; +` -export const Date = styled.div` +export const Date = styled('div')` flex-shrink: 0; display: flex; flex-direction: column; width: 60px; min-width: 60px; margin-bottom: 10px; -`; +` -export const Times = styled.div` +export const Times = styled('div')` display: flex; flex-direction: column; - border-bottom: 2px solid ${props => props.theme.text}; - border-left: 1px solid ${props => props.theme.text}; - border-right: 1px solid ${props => props.theme.text}; + border-bottom: 2px solid var(--text); + border-left: 1px solid var(--text); + border-right: 1px solid var(--text); ${props => props.borderLeft && ` - border-left: 2px solid ${props.theme.text}; + border-left: 2px solid var(--text); border-top-left-radius: 3px; border-bottom-left-radius: 3px; `} ${props => props.borderRight && ` - border-right: 2px solid ${props.theme.text}; + border-right: 2px solid var(--text); border-top-right-radius: 3px; border-bottom-right-radius: 3px; `} & .time + .timespace, & .timespace:first-of-type { - border-top: 2px solid ${props => props.theme.text}; + border-top: 2px solid var(--text); } -`; +` -export const DateLabel = styled.label` +export const DateLabel = styled('label')` display: block; font-size: 12px; text-align: center; user-select: none; -`; +` -export const DayLabel = styled.label` +export const DayLabel = styled('label')` display: block; font-size: 15px; text-align: center; user-select: none; -`; +` -export const Time = styled.div` +export const Time = styled('div')` height: 10px; background-origin: border-box; transition: background-color .1s; ${props => props.time.slice(2, 4) === '00' && ` - border-top: 2px solid ${props.theme.text}; + border-top: 2px solid var(--text); `} ${props => props.time.slice(2, 4) !== '00' && ` border-top: 2px solid transparent; `} ${props => props.time.slice(2, 4) === '30' && ` - border-top: 2px dotted ${props.theme.text}; + border-top: 2px dotted var(--text); `} - background-color: ${props => `${props.theme.primary}${Math.round((props.peopleCount/props.maxPeople)*255).toString(16)}`}; + background-color: ${props => `#FF0000${Math.round((props.peopleCount/props.maxPeople)*255).toString(16)}`}; ${props => props.highlight && props.peopleCount === props.maxPeople && props.peopleCount > 0 && ` background-image: repeating-linear-gradient( 45deg, transparent, transparent 4.3px, - ${props.theme.primaryDark} 4.3px, - ${props.theme.primaryDark} 8.6px + var(--secondary) 4.3px, + var(--secondary) 8.6px ); `} @media (prefers-reduced-motion: reduce) { transition: none; } -`; +` -export const Spacer = styled.div` +export const Spacer = styled('div')` width: 12px; flex-shrink: 0; -`; +` -export const Tooltip = styled.div` +export const Tooltip = styled('div')` position: absolute; top: ${props => props.y}px; left: ${props => props.x}px; transform: translateX(-50%); - border: 1px solid ${props => props.theme.text}; + border: 1px solid var(--text); border-radius: 3px; padding: 4px 8px; - background-color: ${props => props.theme.background}${props => props.theme.mode === 'light' ? 'EE' : 'DD'}; + background-color: var(--background); max-width: 200px; pointer-events: none; z-index: 100; user-select: none; -`; +` -export const TooltipTitle = styled.span` +export const TooltipTitle = styled('span')` font-size: 15px; display: block; font-weight: 700; -`; +` -export const TooltipDate = styled.span` +export const TooltipDate = styled('span')` font-size: 13px; display: block; opacity: .8; font-weight: 600; -`; +` -export const TooltipContent = styled.div` +export const TooltipContent = styled('div')` font-size: 13px; padding: 4px 0; -`; +` -export const TooltipPerson = styled.span` +export const TooltipPerson = styled('span')` display: inline-block; margin: 2px; padding: 1px 4px; - border: 1px solid ${props => props.theme.primary}; + border: 1px solid var(--primary); border-radius: 3px; ${props => props.disabled && ` opacity: .5; - border-color: ${props.theme.text} + border-color: var(--text); `} -`; +` -export const TimeLabels = styled.div` +export const TimeLabels = styled('div')` flex-shrink: 0; display: flex; flex-direction: column; width: 40px; padding-right: 6px; -`; +` -export const TimeSpace = styled.div` +export const TimeSpace = styled('div')` height: 10px; position: relative; border-top: 2px solid transparent; @@ -172,13 +172,13 @@ export const TimeSpace = styled.div` 45deg, transparent, transparent 4.3px, - ${props => props.theme.loading} 4.3px, - ${props => props.theme.loading} 8.6px + var(--loading) 4.3px, + var(--loading) 8.6px ); } -`; +` -export const TimeLabel = styled.label` +export const TimeLabel = styled('label')` display: block; position: absolute; top: -.7em; @@ -186,28 +186,28 @@ export const TimeLabel = styled.label` text-align: right; user-select: none; width: 100%; -`; +` -export const StyledMain = styled.div` +export const StyledMain = styled('div')` width: 600px; margin: 20px auto; max-width: calc(100% - 60px); -`; +` -export const People = styled.div` +export const People = styled('div')` display: flex; flex-wrap: wrap; gap: 5px; justify-content: center; margin: 14px auto; -`; +` -export const Person = styled.button` +export const Person = styled('button')` font: inherit; font-size: 15px; border-radius: 3px; - border: 1px solid ${props => props.theme.text}; - color: ${props => props.theme.text}; + border: 1px solid var(--text); + color: var(--text); font-weight: 500; background: transparent; cursor: pointer; @@ -215,17 +215,17 @@ export const Person = styled.button` user-select: none; ${props => props.filtered && ` - background: ${props.theme.primary}; + background: var(--primary); color: #FFFFFF; - border-color: ${props.theme.primary}; + border-color: var(--primary); `} -`; +` -export const Info = styled.span` +export const Info = styled('span')` display: block; text-align: center; @media print { display: none; } -`; +` diff --git a/crabfit-frontend/src/components/Button/Button.jsx b/crabfit-frontend/src/components/Button/Button.jsx new file mode 100644 index 0000000..3de45b9 --- /dev/null +++ b/crabfit-frontend/src/components/Button/Button.jsx @@ -0,0 +1,31 @@ +import { Pressable } from './Button.styles' + +const Button = ({ + href, + type = 'button', + icon, + children, + secondary, + primaryColor, + secondaryColor, + small, + size, + ...props +}) => ( + + {icon} + {children} + +) + +export default Button diff --git a/crabfit-frontend/src/components/Button/buttonStyle.ts b/crabfit-frontend/src/components/Button/Button.styles.js similarity index 67% rename from crabfit-frontend/src/components/Button/buttonStyle.ts rename to crabfit-frontend/src/components/Button/Button.styles.js index 5526187..e9721cd 100644 --- a/crabfit-frontend/src/components/Button/buttonStyle.ts +++ b/crabfit-frontend/src/components/Button/Button.styles.js @@ -1,6 +1,6 @@ -import styled from '@emotion/styled'; +import { styled } from 'goober' -export const Pressable = styled.button` +export const Pressable = styled('button')` position: relative; display: inline-flex; align-items: center; @@ -11,12 +11,12 @@ export const Pressable = styled.button` text-decoration: none; font: inherit; box-sizing: border-box; - background: ${props => props.primaryColor || props.theme.primary}; - color: ${props => props.primaryColor ? '#FFF' : props.theme.background}; + background: ${props => props.$primaryColor || 'var(--primary)'}; + color: ${props => props.$primaryColor ? '#FFF' : 'var(--background)'}; font-weight: 600; transition: transform 150ms cubic-bezier(0, 0, 0.58, 1); border-radius: 3px; - padding: ${props => props.small ? '.4em 1.3em' : '.6em 1.5em'}; + padding: ${props => props.$small ? '.4em 1.3em' : '.6em 1.5em'}; transform-style: preserve-3d; margin-bottom: 5px; @@ -26,10 +26,10 @@ export const Pressable = styled.button` margin-right: .5em; } - ${props => props.size && ` + ${props => props.$size && ` padding: 0; - height: ${props.size}; - width: ${props.size}; + height: ${props.$size}; + width: ${props.$size}; `} &::before { @@ -39,7 +39,7 @@ export const Pressable = styled.button` width: 100%; top: 0; left: 0; - background: ${props => props.secondaryColor || props.theme.primaryDark}; + background: ${props => props.$secondaryColor || 'var(--secondary)'}; border-radius: inherit; transform: translate3d(0, 5px, -1em); transition: transform 150ms cubic-bezier(0, 0, 0.58, 1), box-shadow 150ms cubic-bezier(0, 0, 0.58, 1); @@ -59,7 +59,7 @@ export const Pressable = styled.button` } } - ${props => props.isLoading && ` + ${props => props.$isLoading && ` color: transparent; cursor: wait; @@ -83,7 +83,7 @@ export const Pressable = styled.button` left: calc(50% - 12px); height: 18px; width: 18px; - border: 3px solid ${props.primaryColor ? '#FFF' : props.theme.background}; + border: 3px solid ${props.$primaryColor ? '#FFF' : 'var(--background)'}; border-left-color: transparent; border-radius: 100px; animation: load .5s linear infinite; @@ -92,7 +92,7 @@ export const Pressable = styled.button` @media (prefers-reduced-motion: reduce) { &:after { content: 'loading...'; - color: ${props.primaryColor ? '#FFF' : props.theme.background}; + color: ${props.$primaryColor ? '#FFF' : 'var(--background)'}; animation: none; width: initial; height: initial; @@ -108,10 +108,10 @@ export const Pressable = styled.button` } `} - ${props => props.secondary && ` + ${props => props.$secondary && ` background: transparent; - border: 1px solid ${props.primaryColor || props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight}; - color: ${props.primaryColor || props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight}; + border: 1px solid ${props.$primaryColor || 'var(--secondary)'}; + color: ${props.$primaryColor || 'var(--secondary)'}; margin-bottom: 0; &::before { @@ -123,12 +123,12 @@ export const Pressable = styled.button` `} @media print { - ${props => !props.secondary && ` - box-shadow: 0 4px 0 0 ${props.secondaryColor || props.theme.primaryDark}; + ${props => !props.$secondary && ` + box-shadow: 0 4px 0 0 ${props.$secondaryColor || 'var(--secondary)'}; `} &::before { display: none; } } -`; +` diff --git a/crabfit-frontend/src/components/Button/Button.tsx b/crabfit-frontend/src/components/Button/Button.tsx deleted file mode 100644 index e7a067b..0000000 --- a/crabfit-frontend/src/components/Button/Button.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Pressable } from './buttonStyle'; - -const Button = ({ href, type = 'button', icon, children, ...props }) => ( - - {icon} - {children} - -); - -export default Button; diff --git a/crabfit-frontend/src/components/CalendarField/CalendarField.tsx b/crabfit-frontend/src/components/CalendarField/CalendarField.jsx similarity index 66% rename from crabfit-frontend/src/components/CalendarField/CalendarField.tsx rename to crabfit-frontend/src/components/CalendarField/CalendarField.jsx index e90012c..9eea45b 100644 --- a/crabfit-frontend/src/components/CalendarField/CalendarField.tsx +++ b/crabfit-frontend/src/components/CalendarField/CalendarField.jsx @@ -1,12 +1,12 @@ -import { useState, useEffect, useRef, forwardRef } from 'react'; -import { useTranslation } from 'react-i18next'; -import dayjs from 'dayjs'; -import isToday from 'dayjs/plugin/isToday'; -import localeData from 'dayjs/plugin/localeData'; -import updateLocale from 'dayjs/plugin/updateLocale'; +import { useState, useEffect, useRef, forwardRef } from 'react' +import { useTranslation } from 'react-i18next' +import dayjs from 'dayjs' +import isToday from 'dayjs/plugin/isToday' +import localeData from 'dayjs/plugin/localeData' +import updateLocale from 'dayjs/plugin/updateLocale' -import { Button, ToggleField } from 'components'; -import { useSettingsStore, useLocaleUpdateStore } from 'stores'; +import { Button, ToggleField } from '/src/components' +import { useSettingsStore, useLocaleUpdateStore } from '/src/stores' import { Wrapper, @@ -17,35 +17,35 @@ import { CalendarBody, Date, Day, -} from './calendarFieldStyle'; +} from './CalendarField.styles' -dayjs.extend(isToday); -dayjs.extend(localeData); -dayjs.extend(updateLocale); +dayjs.extend(isToday) +dayjs.extend(localeData) +dayjs.extend(updateLocale) const calculateMonth = (month, year, weekStart) => { - const date = dayjs().month(month).year(year); - const daysInMonth = date.daysInMonth(); - const daysBefore = date.date(1).day() - weekStart; - const daysAfter = 6 - date.date(daysInMonth).day() + weekStart; + const date = dayjs().month(month).year(year) + const daysInMonth = date.daysInMonth() + const daysBefore = date.date(1).day() - weekStart + const daysAfter = 6 - date.date(daysInMonth).day() + weekStart - let dates = []; - let curDate = date.date(1).subtract(daysBefore, 'day'); - let y = 0; - let x = 0; + const dates = [] + let curDate = date.date(1).subtract(daysBefore, 'day') + let y = 0 + let x = 0 for (let i = 0; i < daysBefore + daysInMonth + daysAfter; i++) { - if (x === 0) dates[y] = []; - dates[y][x] = curDate.clone(); - curDate = curDate.add(1, 'day'); - x++; + if (x === 0) dates[y] = [] + dates[y][x] = curDate.clone() + curDate = curDate.add(1, 'day') + x++ if (x > 6) { - x = 0; - y++; + x = 0 + y++ } } - return dates; -}; + return dates +} const CalendarField = forwardRef(({ label, @@ -54,48 +54,48 @@ const CalendarField = forwardRef(({ setValue, ...props }, ref) => { - const weekStart = useSettingsStore(state => state.weekStart); - const locale = useLocaleUpdateStore(state => state.locale); - const { t } = useTranslation('home'); + const weekStart = useSettingsStore(state => state.weekStart) + const locale = useLocaleUpdateStore(state => state.locale) + const { t } = useTranslation('home') - const [type, setType] = useState(0); + const [type, setType] = useState(0) - const [dates, setDates] = useState(calculateMonth(dayjs().month(), dayjs().year(), weekStart)); - const [month, setMonth] = useState(dayjs().month()); - const [year, setYear] = useState(dayjs().year()); + const [dates, setDates] = useState(calculateMonth(dayjs().month(), dayjs().year(), weekStart)) + const [month, setMonth] = useState(dayjs().month()) + const [year, setYear] = useState(dayjs().year()) - const [selectedDates, setSelectedDates] = useState([]); - const [selectingDates, _setSelectingDates] = useState([]); - const staticSelectingDates = useRef([]); + const [selectedDates, setSelectedDates] = useState([]) + const [selectingDates, _setSelectingDates] = useState([]) + const staticSelectingDates = useRef([]) const setSelectingDates = newDates => { - staticSelectingDates.current = newDates; - _setSelectingDates(newDates); - }; + staticSelectingDates.current = newDates + _setSelectingDates(newDates) + } - const [selectedDays, setSelectedDays] = useState([]); - const [selectingDays, _setSelectingDays] = useState([]); - const staticSelectingDays = useRef([]); + const [selectedDays, setSelectedDays] = useState([]) + const [selectingDays, _setSelectingDays] = useState([]) + const staticSelectingDays = useRef([]) const setSelectingDays = newDays => { - staticSelectingDays.current = newDays; - _setSelectingDays(newDays); - }; + staticSelectingDays.current = newDays + _setSelectingDays(newDays) + } - const startPos = useRef({}); - const staticMode = useRef(null); - const [mode, _setMode] = useState(staticMode.current); + const startPos = useRef({}) + const staticMode = useRef(null) + const [mode, _setMode] = useState(staticMode.current) const setMode = newMode => { - staticMode.current = newMode; - _setMode(newMode); - }; + staticMode.current = newMode + _setMode(newMode) + } - useEffect(() => setValue(props.name, type ? JSON.stringify(selectedDays) : JSON.stringify(selectedDates)), [type, selectedDays, selectedDates, setValue, props.name]); + useEffect(() => setValue(props.name, type ? JSON.stringify(selectedDays) : JSON.stringify(selectedDates)), [type, selectedDays, selectedDates, setValue, props.name]) useEffect(() => { - if (dayjs.Ls.hasOwnProperty(locale) && weekStart !== dayjs.Ls[locale].weekStart) { - dayjs.updateLocale(locale, { weekStart }); + if (dayjs.Ls?.[locale] && weekStart !== dayjs.Ls[locale].weekStart) { + dayjs.updateLocale(locale, { weekStart }) } - setDates(calculateMonth(month, year, weekStart)); - }, [weekStart, month, year, locale]); + setDates(calculateMonth(month, year, weekStart)) + }, [weekStart, month, year, locale]) return ( @@ -128,10 +128,10 @@ const CalendarField = forwardRef(({ title={t('form.dates.tooltips.previous')} onClick={() => { if (month-1 < 0) { - setYear(year-1); - setMonth(11); + setYear(year-1) + setMonth(11) } else { - setMonth(month-1); + setMonth(month-1) } }} >< @@ -141,10 +141,10 @@ const CalendarField = forwardRef(({ title={t('form.dates.tooltips.next')} onClick={() => { if (month+1 > 11) { - setYear(year+1); - setMonth(0); + setYear(year+1) + setMonth(0) } else { - setMonth(month+1); + setMonth(month+1) } }} >> @@ -170,37 +170,37 @@ const CalendarField = forwardRef(({ onKeyPress={e => { if (e.key === ' ' || e.key === 'Enter') { if (selectedDates.includes(date.format('DDMMYYYY'))) { - setSelectedDates(selectedDates.filter(d => d !== date.format('DDMMYYYY'))); + setSelectedDates(selectedDates.filter(d => d !== date.format('DDMMYYYY'))) } else { - setSelectedDates([...selectedDates, date.format('DDMMYYYY')]); + setSelectedDates([...selectedDates, date.format('DDMMYYYY')]) } } }} onPointerDown={e => { - startPos.current = {x, y}; - setMode(selectedDates.includes(date.format('DDMMYYYY')) ? 'remove' : 'add'); - setSelectingDates([date]); - e.currentTarget.releasePointerCapture(e.pointerId); + startPos.current = {x, y} + setMode(selectedDates.includes(date.format('DDMMYYYY')) ? 'remove' : 'add') + setSelectingDates([date]) + e.currentTarget.releasePointerCapture(e.pointerId) document.addEventListener('pointerup', () => { if (staticMode.current === 'add') { - setSelectedDates([...selectedDates, ...staticSelectingDates.current.map(d => d.format('DDMMYYYY'))]); + setSelectedDates([...selectedDates, ...staticSelectingDates.current.map(d => d.format('DDMMYYYY'))]) } else if (staticMode.current === 'remove') { - const toRemove = staticSelectingDates.current.map(d => d.format('DDMMYYYY')); - setSelectedDates(selectedDates.filter(d => !toRemove.includes(d))); + const toRemove = staticSelectingDates.current.map(d => d.format('DDMMYYYY')) + setSelectedDates(selectedDates.filter(d => !toRemove.includes(d))) } - setMode(null); - }, { once: true }); + setMode(null) + }, { once: true }) }} onPointerEnter={() => { if (staticMode.current) { - let found = []; + const found = [] for (let cy = Math.min(startPos.current.y, y); cy < Math.max(startPos.current.y, y)+1; cy++) { for (let cx = Math.min(startPos.current.x, x); cx < Math.max(startPos.current.x, x)+1; cx++) { - found.push({y: cy, x: cx}); + found.push({y: cy, x: cx}) } } - setSelectingDates(found.map(d => dates[d.y][d.x])); + setSelectingDates(found.map(d => dates[d.y][d.x])) } }} >{date.date()} @@ -222,35 +222,35 @@ const CalendarField = forwardRef(({ onKeyPress={e => { if (e.key === ' ' || e.key === 'Enter') { if (selectedDays.includes(((i + weekStart) % 7 + 7) % 7)) { - setSelectedDays(selectedDays.filter(d => d !== ((i + weekStart) % 7 + 7) % 7)); + setSelectedDays(selectedDays.filter(d => d !== ((i + weekStart) % 7 + 7) % 7)) } else { - setSelectedDays([...selectedDays, ((i + weekStart) % 7 + 7) % 7]); + setSelectedDays([...selectedDays, ((i + weekStart) % 7 + 7) % 7]) } } }} - onPointerDown={(e) => { - startPos.current = i; - setMode(selectedDays.includes(((i + weekStart) % 7 + 7) % 7) ? 'remove' : 'add'); - setSelectingDays([((i + weekStart) % 7 + 7) % 7]); - e.currentTarget.releasePointerCapture(e.pointerId); + onPointerDown={e => { + startPos.current = i + setMode(selectedDays.includes(((i + weekStart) % 7 + 7) % 7) ? 'remove' : 'add') + setSelectingDays([((i + weekStart) % 7 + 7) % 7]) + e.currentTarget.releasePointerCapture(e.pointerId) document.addEventListener('pointerup', () => { if (staticMode.current === 'add') { - setSelectedDays([...selectedDays, ...staticSelectingDays.current]); + setSelectedDays([...selectedDays, ...staticSelectingDays.current]) } else if (staticMode.current === 'remove') { - const toRemove = staticSelectingDays.current; - setSelectedDays(selectedDays.filter(d => !toRemove.includes(d))); + const toRemove = staticSelectingDays.current + setSelectedDays(selectedDays.filter(d => !toRemove.includes(d))) } - setMode(null); - }, { once: true }); + setMode(null) + }, { once: true }) }} onPointerEnter={() => { if (staticMode.current) { - let found = []; + const found = [] for (let ci = Math.min(startPos.current, i); ci < Math.max(startPos.current, i)+1; ci++) { - found.push(((ci + weekStart) % 7 + 7) % 7); + found.push(((ci + weekStart) % 7 + 7) % 7) } - setSelectingDays(found); + setSelectingDays(found) } }} >{name} @@ -258,7 +258,7 @@ const CalendarField = forwardRef(({ )} - ); -}); + ) +}) -export default CalendarField; +export default CalendarField diff --git a/crabfit-frontend/src/components/CalendarField/calendarFieldStyle.ts b/crabfit-frontend/src/components/CalendarField/CalendarField.styles.js similarity index 63% rename from crabfit-frontend/src/components/CalendarField/calendarFieldStyle.ts rename to crabfit-frontend/src/components/CalendarField/CalendarField.styles.js index fe6c4b8..5f6ad5b 100644 --- a/crabfit-frontend/src/components/CalendarField/calendarFieldStyle.ts +++ b/crabfit-frontend/src/components/CalendarField/CalendarField.styles.js @@ -1,22 +1,22 @@ -import styled from '@emotion/styled'; +import { styled } from 'goober' -export const Wrapper = styled.div` +export const Wrapper = styled('div')` margin: 30px 0; -`; +` -export const StyledLabel = styled.label` +export const StyledLabel = styled('label')` display: block; padding-bottom: 4px; font-size: 18px; -`; +` -export const StyledSubLabel = styled.label` +export const StyledSubLabel = styled('label')` display: block; font-size: 13px; opacity: .6; -`; +` -export const CalendarHeader = styled.div` +export const CalendarHeader = styled('div')` display: flex; align-items: center; justify-content: space-between; @@ -24,15 +24,15 @@ export const CalendarHeader = styled.div` padding: 6px 0; font-size: 1.2em; font-weight: bold; -`; +` -export const CalendarDays = styled.div` +export const CalendarDays = styled('div')` display: grid; grid-template-columns: repeat(7, 1fr); grid-gap: 2px; -`; +` -export const Day = styled.div` +export const Day = styled('div')` display: flex; align-items: center; justify-content: center; @@ -44,9 +44,9 @@ export const Day = styled.div` @media (max-width: 350px) { font-size: 12px; } -`; +` -export const CalendarBody = styled.div` +export const CalendarBody = styled('div')` display: grid; grid-template-columns: repeat(7, 1fr); grid-gap: 2px; @@ -63,9 +63,9 @@ export const CalendarBody = styled.div` & button:last-of-type { border-bottom-right-radius: 3px; } -`; +` -export const Date = styled.button` +export const Date = styled('button')` font: inherit; color: inherit; background: none; @@ -77,8 +77,8 @@ export const Date = styled.button` transition: none; } - background-color: ${props => props.theme.primaryBackground}; - border: 1px solid ${props => props.theme.primary}; + background-color: var(--surface); + border: 1px solid var(--primary); display: flex; align-items: center; justify-content: center; @@ -87,18 +87,18 @@ export const Date = styled.button` touch-action: none; ${props => props.otherMonth && ` - color: ${props.theme.mode === 'light' ? props.theme.primaryLight : props.theme.primaryDark}; + color: var(--tertiary); `} ${props => props.isToday && ` font-weight: 900; - color: ${props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight}; + color: var(--secondary); `} ${props => (props.selected || (props.mode === 'add' && props.selecting)) && ` color: ${props.otherMonth ? 'rgba(255,255,255,.5)' : '#FFF'}; - background-color: ${props.theme.primary}; + background-color: var(--primary); `} ${props => props.mode === 'remove' && props.selecting && ` - background-color: ${props.theme.primaryBackground}; - color: ${props.isToday ? props.theme.primaryDark : (props.otherMonth ? props.theme.primaryLight : 'inherit')}; + background-color: var(--surface); + color: ${props.isToday ? 'var(--secondary)' : (props.otherMonth ? 'var(--tertiary)' : 'inherit')}; `} -`; +` diff --git a/crabfit-frontend/src/components/Center/Center.js b/crabfit-frontend/src/components/Center/Center.js new file mode 100644 index 0000000..a696734 --- /dev/null +++ b/crabfit-frontend/src/components/Center/Center.js @@ -0,0 +1,9 @@ +import { styled } from 'goober' + +const Center = styled('div')` + display: flex; + align-items: center; + justify-content: center; +` + +export default Center diff --git a/crabfit-frontend/src/components/Center/Center.ts b/crabfit-frontend/src/components/Center/Center.ts deleted file mode 100644 index bd722ff..0000000 --- a/crabfit-frontend/src/components/Center/Center.ts +++ /dev/null @@ -1,9 +0,0 @@ -import styled from '@emotion/styled'; - -const Center = styled.div` - display: flex; - align-items: center; - justify-content: center; -`; - -export default Center; diff --git a/crabfit-frontend/src/components/Donate/Donate.tsx b/crabfit-frontend/src/components/Donate/Donate.jsx similarity index 64% rename from crabfit-frontend/src/components/Donate/Donate.tsx rename to crabfit-frontend/src/components/Donate/Donate.jsx index ac46a0f..a40f9d6 100644 --- a/crabfit-frontend/src/components/Donate/Donate.tsx +++ b/crabfit-frontend/src/components/Donate/Donate.jsx @@ -1,106 +1,107 @@ -import { useState, useEffect, useRef } from 'react'; -import { Button } from 'components'; -import { useTWAStore } from 'stores'; -import { useTranslation } from 'react-i18next'; +import { useState, useEffect, useRef } from 'react' +import { useTranslation } from 'react-i18next' + +import { Button } from '/src/components' +import { useTWAStore } from '/src/stores' import { Wrapper, Options, -} from './donateStyle'; +} from './Donate.styles' -import paypal_logo from 'res/paypal.svg'; +import paypal_logo from '/src/res/paypal.svg' -const PAYMENT_METHOD = 'https://play.google.com/billing'; -const SKU = 'crab_donation'; +const PAYMENT_METHOD = 'https://play.google.com/billing' +const SKU = 'crab_donation' const Donate = () => { - const store = useTWAStore(); - const { t } = useTranslation('common'); + const store = useTWAStore() + const { t } = useTranslation('common') - const firstLinkRef = useRef(); - const modalRef = useRef(); - const [isOpen, _setIsOpen] = useState(false); - const [closed, setClosed] = useState(false); + const firstLinkRef = useRef() + const modalRef = useRef() + const [isOpen, _setIsOpen] = useState(false) + const [closed, setClosed] = useState(false) const setIsOpen = open => { - _setIsOpen(open); + _setIsOpen(open) if (open) { - window.setTimeout(() => firstLinkRef.current.focus(), 150); + window.setTimeout(() => firstLinkRef.current.focus(), 150) } - }; + } const linkPressed = () => { - setIsOpen(false); - gtag('event', 'donate', { 'event_category': 'donate' }); - }; + setIsOpen(false) + gtag('event', 'donate', { 'event_category': 'donate' }) + } useEffect(() => { if (store.TWA === undefined) { - store.setTWA(document.referrer.includes('android-app://fit.crab')); + store.setTWA(document.referrer.includes('android-app://fit.crab')) } - }, [store]); + }, [store]) const acknowledge = async (token, type='repeatable', onComplete = () => {}) => { try { - let service = await window.getDigitalGoodsService(PAYMENT_METHOD); - await service.acknowledge(token, type); + const service = await window.getDigitalGoodsService(PAYMENT_METHOD) + await service.acknowledge(token, type) if ('acknowledge' in service) { // DGAPI 1.0 - service.acknowledge(token, type); + service.acknowledge(token, type) } else { // DGAPI 2.0 - service.consume(token); + service.consume(token) } - onComplete(); + onComplete() } catch (error) { - console.error(error); + console.error(error) } } const purchase = () => { - if (!window.PaymentRequest) return false; - if (!window.getDigitalGoodsService) return false; + if (!window.PaymentRequest) return false + if (!window.getDigitalGoodsService) return false const supportedInstruments = [{ supportedMethods: PAYMENT_METHOD, data: { sku: SKU } - }]; + }] const details = { total: { label: 'Total', amount: { currency: 'AUD', value: '0' } }, - }; + } - const request = new PaymentRequest(supportedInstruments, details); + const request = new PaymentRequest(supportedInstruments, details) request.show() .then(response => { response .complete('success') .then(() => { - console.log(`Payment done: ${JSON.stringify(response, undefined, 2)}`); + console.log(`Payment done: ${JSON.stringify(response, undefined, 2)}`) if (response.details && response.details.token) { - const token = response.details.token; - console.log(`Read Token: ${token.substring(0, 6)}...`); - alert(t('donate.messages.success')); - acknowledge(token); + const token = response.details.token + console.log(`Read Token: ${token.substring(0, 6)}...`) + alert(t('donate.messages.success')) + acknowledge(token) } }) .catch(e => { - console.error(e.message); - alert(t('donate.messages.error')); - }); + console.error(e.message) + alert(t('donate.messages.error')) + }) }) .catch(e => { - console.error(e); - alert(t('donate.messages.error')); - }); - }; + console.error(e) + alert(t('donate.messages.error')) + }) + } return ( @@ -109,20 +110,20 @@ const Donate = () => { title={t('donate.title')} onClick={event => { if (closed) { - event.preventDefault(); - return setClosed(false); + event.preventDefault() + return setClosed(false) } if (store.TWA) { - gtag('event', 'donate', { 'event_category': 'donate' }); - event.preventDefault(); + gtag('event', 'donate', { 'event_category': 'donate' }) + event.preventDefault() if (window.confirm(t('donate.messages.about'))) { if (purchase() === false) { - alert(t('donate.messages.error')); + alert(t('donate.messages.error')) } } } else { - event.preventDefault(); - setIsOpen(true); + event.preventDefault() + setIsOpen(true) } }} href="https://www.paypal.com/donate?business=N89X6YXRT5HKW&item_name=Crab+Fit+Donation¤cy_code=AUD&amount=5" @@ -135,13 +136,13 @@ const Donate = () => { >{t('donate.button')} { - if (modalRef.current.contains(e.relatedTarget)) return; - setIsOpen(false); + if (modalRef.current?.contains(e.relatedTarget)) return + setIsOpen(false) if (e.relatedTarget && e.relatedTarget.id === 'donate_button') { - setClosed(true); + setClosed(true) } }} > @@ -152,7 +153,7 @@ const Donate = () => { {t('donate.options.choose')} - ); + ) } -export default Donate; +export default Donate diff --git a/crabfit-frontend/src/components/Donate/donateStyle.ts b/crabfit-frontend/src/components/Donate/Donate.styles.js similarity index 73% rename from crabfit-frontend/src/components/Donate/donateStyle.ts rename to crabfit-frontend/src/components/Donate/Donate.styles.js index 0353637..5b008b6 100644 --- a/crabfit-frontend/src/components/Donate/donateStyle.ts +++ b/crabfit-frontend/src/components/Donate/Donate.styles.js @@ -1,19 +1,20 @@ -import styled from '@emotion/styled'; +import { styled } from 'goober' +import { forwardRef } from 'react' -export const Wrapper = styled.div` +export const Wrapper = styled('div')` margin-top: 6px; margin-left: 12px; position: relative; -`; +` -export const Options = styled.div` +export const Options = styled('div', forwardRef)` position: absolute; bottom: calc(100% + 20px); right: 0; - background-color: ${props => props.theme.background}; - ${props => props.theme.mode === 'dark' && ` + background-color: var(--background); + ${/* FIXME: ${props => props.theme.mode === 'dark' && ` border: 1px solid ${props.theme.primaryBackground}; - `} + `} */''} z-index: 60; padding: 4px 10px; border-radius: 14px; @@ -27,7 +28,7 @@ export const Options = styled.div` transform: translateY(5px); transition: opacity .15s, transform .15s, visibility .15s; - ${props => props.isOpen && ` + ${props => props.$isOpen && ` pointer-events: all; opacity: 1; transform: translateY(0); @@ -48,8 +49,8 @@ export const Options = styled.div` margin: 6px 0; text-decoration: none; border-radius: 100px; - background-color: ${props => props.theme.primary}; - color: ${props => props.theme.background}; + background-color: var(--primary); + color: var(--background); &:hover { text-decoration: underline; @@ -62,4 +63,4 @@ export const Options = styled.div` @media (prefers-reduced-motion: reduce) { transition: none; } -`; +` diff --git a/crabfit-frontend/src/components/Egg/Egg.tsx b/crabfit-frontend/src/components/Egg/Egg.jsx similarity index 66% rename from crabfit-frontend/src/components/Egg/Egg.tsx rename to crabfit-frontend/src/components/Egg/Egg.jsx index 0d65643..b6ed7db 100644 --- a/crabfit-frontend/src/components/Egg/Egg.tsx +++ b/crabfit-frontend/src/components/Egg/Egg.jsx @@ -1,10 +1,10 @@ -import { useState } from 'react'; +import { useState } from 'react' -import { Loading } from 'components'; -import { Image, Wrapper } from './eggStyle'; +import { Loading } from '/src/components' +import { Image, Wrapper } from './Egg.styles' const Egg = ({ eggKey, onClose }) => { - const [isLoading, setIsLoading] = useState(true); + const [isLoading, setIsLoading] = useState(true) return ( onClose()}> @@ -15,7 +15,7 @@ const Egg = ({ eggKey, onClose }) => { /> {isLoading && } - ); + ) } -export default Egg; +export default Egg diff --git a/crabfit-frontend/src/components/Egg/eggStyle.ts b/crabfit-frontend/src/components/Egg/Egg.styles.js similarity index 72% rename from crabfit-frontend/src/components/Egg/eggStyle.ts rename to crabfit-frontend/src/components/Egg/Egg.styles.js index 2aebe29..715dbed 100644 --- a/crabfit-frontend/src/components/Egg/eggStyle.ts +++ b/crabfit-frontend/src/components/Egg/Egg.styles.js @@ -1,6 +1,6 @@ -import styled from '@emotion/styled'; +import { styled } from 'goober' -export const Wrapper = styled.div` +export const Wrapper = styled('div')` position: fixed; background: rgba(0,0,0,.6); top: 0; @@ -14,10 +14,10 @@ export const Wrapper = styled.div` align-items: center; z-index: 1000; cursor: pointer; -`; +` -export const Image = styled.img` +export const Image = styled('img')` max-width: 80%; max-height: 80%; position: absolute; -`; +` diff --git a/crabfit-frontend/src/components/Error/Error.tsx b/crabfit-frontend/src/components/Error/Error.jsx similarity index 86% rename from crabfit-frontend/src/components/Error/Error.tsx rename to crabfit-frontend/src/components/Error/Error.jsx index 0d103a0..f9fbae9 100644 --- a/crabfit-frontend/src/components/Error/Error.tsx +++ b/crabfit-frontend/src/components/Error/Error.jsx @@ -1,4 +1,4 @@ -import { Wrapper, CloseButton } from './errorStyle'; +import { Wrapper, CloseButton } from './Error.styles' const Error = ({ children, @@ -12,6 +12,6 @@ const Error = ({ -); +) -export default Error; +export default Error diff --git a/crabfit-frontend/src/components/Error/errorStyle.ts b/crabfit-frontend/src/components/Error/Error.styles.js similarity index 81% rename from crabfit-frontend/src/components/Error/errorStyle.ts rename to crabfit-frontend/src/components/Error/Error.styles.js index 201001e..a3c804a 100644 --- a/crabfit-frontend/src/components/Error/errorStyle.ts +++ b/crabfit-frontend/src/components/Error/Error.styles.js @@ -1,8 +1,8 @@ -import styled from '@emotion/styled'; +import { styled } from 'goober' -export const Wrapper = styled.div` +export const Wrapper = styled('div')` border-radius: 3px; - background-color: ${props => props.theme.error}; + background-color: var(--error); color: #FFFFFF; padding: 0 16px; display: flex; @@ -27,9 +27,9 @@ export const Wrapper = styled.div` @media (prefers-reduced-motion: reduce) { transition: none; } -`; +` -export const CloseButton = styled.button` +export const CloseButton = styled('button')` border: 0; background: none; height: 30px; @@ -40,4 +40,4 @@ export const CloseButton = styled.button` align-items: center; justify-content: center; margin-left: 16px; -`; +` diff --git a/crabfit-frontend/src/components/Footer/Footer.jsx b/crabfit-frontend/src/components/Footer/Footer.jsx new file mode 100644 index 0000000..4666fe7 --- /dev/null +++ b/crabfit-frontend/src/components/Footer/Footer.jsx @@ -0,0 +1,17 @@ +import { useTranslation } from 'react-i18next' + +import { Donate } from '/src/components' +import { Wrapper } from './Footer.styles' + +const Footer = props => { + const { t } = useTranslation('common') + + return ( + + ) +} + +export default Footer diff --git a/crabfit-frontend/src/components/Footer/footerStyle.ts b/crabfit-frontend/src/components/Footer/Footer.styles.js similarity index 82% rename from crabfit-frontend/src/components/Footer/footerStyle.ts rename to crabfit-frontend/src/components/Footer/Footer.styles.js index 009cd12..b9b9884 100644 --- a/crabfit-frontend/src/components/Footer/footerStyle.ts +++ b/crabfit-frontend/src/components/Footer/Footer.styles.js @@ -1,6 +1,6 @@ -import styled from '@emotion/styled'; +import { styled } from 'goober' -export const Wrapper = styled.footer` +export const Wrapper = styled('footer')` width: 600px; margin: 20px auto; max-width: calc(100% - 60px); @@ -23,4 +23,4 @@ export const Wrapper = styled.footer` @media print { display: none; } -`; +` diff --git a/crabfit-frontend/src/components/Footer/Footer.tsx b/crabfit-frontend/src/components/Footer/Footer.tsx deleted file mode 100644 index 8603aaf..0000000 --- a/crabfit-frontend/src/components/Footer/Footer.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useTranslation } from 'react-i18next'; - -import { Donate } from 'components'; -import { Wrapper } from './footerStyle'; - -const Footer = (props) => { - const { t } = useTranslation('common'); - - return ( - - ); -}; - -export default Footer; diff --git a/crabfit-frontend/src/components/GoogleCalendar/GoogleCalendar.tsx b/crabfit-frontend/src/components/GoogleCalendar/GoogleCalendar.jsx similarity index 74% rename from crabfit-frontend/src/components/GoogleCalendar/GoogleCalendar.tsx rename to crabfit-frontend/src/components/GoogleCalendar/GoogleCalendar.jsx index 5b6f871..05a5492 100644 --- a/crabfit-frontend/src/components/GoogleCalendar/GoogleCalendar.tsx +++ b/crabfit-frontend/src/components/GoogleCalendar/GoogleCalendar.jsx @@ -1,9 +1,9 @@ -import { useState, useEffect } from 'react'; -import { loadGapiInsideDOM } from 'gapi-script'; -import { useTranslation } from 'react-i18next'; +import { useState, useEffect } from 'react' +import { loadGapiInsideDOM } from 'gapi-script' +import { useTranslation } from 'react-i18next' -import { Button, Center } from 'components'; -import { Loader } from '../Loading/loadingStyle'; +import { Button, Center } from '/src/components' +import { Loader } from '../Loading/Loading.styles' import { CalendarList, CheckboxInput, @@ -14,22 +14,22 @@ import { Title, Icon, LinkButton, -} from './googleCalendarStyle'; +} from './GoogleCalendar.styles' -import googleLogo from 'res/google.svg'; +import googleLogo from '/src/res/google.svg' -const signIn = () => window.gapi.auth2.getAuthInstance().signIn(); +const signIn = () => window.gapi.auth2.getAuthInstance().signIn() -const signOut = () => window.gapi.auth2.getAuthInstance().signOut(); +const signOut = () => window.gapi.auth2.getAuthInstance().signOut() const GoogleCalendar = ({ timeZone, timeMin, timeMax, onImport }) => { - const [signedIn, setSignedIn] = useState(undefined); - const [calendars, setCalendars] = useState(undefined); - const [freeBusyLoading, setFreeBusyLoading] = useState(false); - const { t } = useTranslation('event'); + const [signedIn, setSignedIn] = useState(undefined) + const [calendars, setCalendars] = useState(undefined) + const [freeBusyLoading, setFreeBusyLoading] = useState(false) + const { t } = useTranslation('event') const calendarLogin = async () => { - const gapi = await loadGapiInsideDOM(); + const gapi = await loadGapiInsideDOM() gapi.load('client:auth2', () => { window.gapi.client.init({ clientId: '276505195333-9kjl7e48m272dljbspkobctqrpet0n8m.apps.googleusercontent.com', @@ -38,23 +38,23 @@ const GoogleCalendar = ({ timeZone, timeMin, timeMax, onImport }) => { }) .then(() => { // Listen for state changes - window.gapi.auth2.getAuthInstance().isSignedIn.listen(isSignedIn => setSignedIn(isSignedIn)); + window.gapi.auth2.getAuthInstance().isSignedIn.listen(isSignedIn => setSignedIn(isSignedIn)) // Handle initial sign-in state - setSignedIn(window.gapi.auth2.getAuthInstance().isSignedIn.get()); + setSignedIn(window.gapi.auth2.getAuthInstance().isSignedIn.get()) }) .catch(e => { - console.error(e); - setSignedIn(false); - }); - }); - }; + console.error(e) + setSignedIn(false) + }) + }) + } const importAvailability = () => { - setFreeBusyLoading(true); + setFreeBusyLoading(true) gtag('event', 'google_cal_sync', { 'event_category': 'event', - }); + }) window.gapi.client.calendar.freebusy.query({ timeMin, timeMax, @@ -62,15 +62,15 @@ const GoogleCalendar = ({ timeZone, timeMin, timeMax, onImport }) => { items: calendars.filter(c => c.checked).map(c => ({id: c.id})), }) .then(response => { - onImport(response.result.calendars ? Object.values(response.result.calendars).reduce((busy, c) => [...busy, ...c.busy], []) : []); - setFreeBusyLoading(false); + onImport(response.result.calendars ? Object.values(response.result.calendars).reduce((busy, c) => [...busy, ...c.busy], []) : []) + setFreeBusyLoading(false) }, e => { - console.error(e); - setFreeBusyLoading(false); - }); - }; + console.error(e) + setFreeBusyLoading(false) + }) + } - useEffect(() => calendarLogin(), []); + useEffect(() => calendarLogin(), []) useEffect(() => { if (signedIn) { @@ -83,15 +83,15 @@ const GoogleCalendar = ({ timeZone, timeMin, timeMax, onImport }) => { 'description': item.description, 'id': item.id, 'color': item.backgroundColor, - 'checked': item.hasOwnProperty('primary') && item.primary === true, - }))); + 'checked': item.primary === true, + }))) }) .catch(e => { - console.error(e); - signOut(); - }); + console.error(e) + signOut() + }) } - }, [signedIn]); + }, [signedIn]) return ( <> @@ -113,21 +113,21 @@ const GoogleCalendar = ({ timeZone, timeMin, timeMax, onImport }) => { {t('event:you.google_cal.login')} ( { - e.preventDefault(); - signOut(); + e.preventDefault() + signOut() }}>{t('event:you.google_cal.logout')}) {calendars !== undefined && !calendars.every(c => c.checked) && ( { - e.preventDefault(); - setCalendars(calendars.map(c => ({...c, checked: true}))); + e.preventDefault() + setCalendars(calendars.map(c => ({...c, checked: true}))) }}>{t('event:you.google_cal.select_all')} )} {calendars !== undefined && calendars.every(c => c.checked) && ( { - e.preventDefault(); - setCalendars(calendars.map(c => ({...c, checked: false}))); + e.preventDefault() + setCalendars(calendars.map(c => ({...c, checked: false}))) }}>{t('event:you.google_cal.select_none')} )} @@ -139,7 +139,7 @@ const GoogleCalendar = ({ timeZone, timeMin, timeMax, onImport }) => { id={calendar.id} color={calendar.color} checked={calendar.checked} - onChange={e => setCalendars(calendars.map(c => c.id === calendar.id ? {...c, checked: !c.checked} : c))} + onChange={() => setCalendars(calendars.map(c => c.id === calendar.id ? {...c, checked: !c.checked} : c))} /> {calendar.name} @@ -161,7 +161,7 @@ const GoogleCalendar = ({ timeZone, timeMin, timeMax, onImport }) => { )} - ); -}; + ) +} -export default GoogleCalendar; +export default GoogleCalendar diff --git a/crabfit-frontend/src/components/GoogleCalendar/googleCalendarStyle.ts b/crabfit-frontend/src/components/GoogleCalendar/GoogleCalendar.styles.js similarity index 75% rename from crabfit-frontend/src/components/GoogleCalendar/googleCalendarStyle.ts rename to crabfit-frontend/src/components/GoogleCalendar/GoogleCalendar.styles.js index 00fd0f8..acc3a37 100644 --- a/crabfit-frontend/src/components/GoogleCalendar/googleCalendarStyle.ts +++ b/crabfit-frontend/src/components/GoogleCalendar/GoogleCalendar.styles.js @@ -1,14 +1,14 @@ -import styled from '@emotion/styled'; +import { styled } from 'goober' -export const CalendarList = styled.div` +export const CalendarList = styled('div')` width: 100%; & > div { display: flex; margin: 2px 0; } -`; +` -export const CheckboxInput = styled.input` +export const CheckboxInput = styled('input')` height: 0px; width: 0px; margin: 0; @@ -27,8 +27,8 @@ export const CheckboxInput = styled.input` opacity: .6; } &[disabled] + label:after { - border: 2px solid ${props => props.theme.text}; - background-color: ${props => props.theme.text}; + border: 2px solid var(--text); + background-color: var(--text); } &:focus + label { box-shadow: 0 0 0 2px ${props => props.theme.text}44; @@ -39,9 +39,9 @@ export const CheckboxInput = styled.input` box-shadow: 0 0 0 2px ${props => props.color || props.theme.primary}44; background-color: ${props => props.color || props.theme.primary}44; } -`; +` -export const CheckboxLabel = styled.label` +export const CheckboxLabel = styled('label')` display: inline-block; height: 24px; width: 24px; @@ -55,7 +55,7 @@ export const CheckboxLabel = styled.label` display: inline-block; height: 14px; width: 14px; - border: 2px solid ${props => props.theme.text}; + border: 2px solid var(--text); border-radius: 2px; position: absolute; top: 3px; @@ -66,8 +66,8 @@ export const CheckboxLabel = styled.label` display: inline-block; height: 14px; width: 14px; - border: 2px solid ${props => props.color || props.theme.primary}; - background-color: ${props => props.color || props.theme.primary}; + border: 2px solid ${props => props.color || 'var(--primary)'}; + background-color: ${props => props.color || 'var(--primary)'}; border-radius: 2px; position: absolute; top: 3px; @@ -80,37 +80,37 @@ export const CheckboxLabel = styled.label` transform: scale(.5); transition: opacity 0.15s, transform 0.15s; } -`; +` -export const CalendarLabel = styled.label` +export const CalendarLabel = styled('label')` margin-left: .6em; font-size: 15px; font-weight: 500; line-height: 24px; -`; +` -export const Info = styled.div` +export const Info = styled('div')` font-size: 14px; opacity: .6; font-weight: 500; padding: 14px 0 10px; -`; +` -export const Options = styled.div` +export const Options = styled('div')` font-size: 14px; padding: 0 0 5px; -`; +` -export const Title = styled.p` +export const Title = styled('p')` display: flex; align-items: center; & strong { margin-right: 1ex; } -`; +` -export const Icon = styled.img` +export const Icon = styled('img')` height: 24px; width: 24px; margin-right: 12px; @@ -118,11 +118,11 @@ export const Icon = styled.img` ${props => props.theme.mode === 'light' && ` filter: invert(1); `} -`; +` -export const LinkButton = styled.button` +export const LinkButton = styled('button')` font: inherit; - color: ${props => props.theme.primary}; + color: var(--primary); border: 0; background: none; text-decoration: underline; @@ -131,4 +131,4 @@ export const LinkButton = styled.button` display: inline; cursor: pointer; appearance: none; -`; +` diff --git a/crabfit-frontend/src/components/Legend/Legend.tsx b/crabfit-frontend/src/components/Legend/Legend.jsx similarity index 64% rename from crabfit-frontend/src/components/Legend/Legend.tsx rename to crabfit-frontend/src/components/Legend/Legend.jsx index 69eae02..175ac1b 100644 --- a/crabfit-frontend/src/components/Legend/Legend.tsx +++ b/crabfit-frontend/src/components/Legend/Legend.jsx @@ -1,25 +1,23 @@ -import { useTheme } from '@emotion/react'; -import { useSettingsStore } from 'stores'; -import { useTranslation } from 'react-i18next'; +import { useTranslation } from 'react-i18next' + +import { useSettingsStore } from '/src/stores' import { Wrapper, Label, Bar, Grade, -} from './legendStyle'; +} from './Legend.styles' const Legend = ({ min, max, total, onSegmentFocus, - ...props }) => { - const theme = useTheme(); - const { t } = useTranslation('event'); - const highlight = useSettingsStore(state => state.highlight); - const setHighlight = useSettingsStore(state => state.setHighlight); + const { t } = useTranslation('event') + const highlight = useSettingsStore(state => state.highlight) + const setHighlight = useSettingsStore(state => state.setHighlight) return ( @@ -33,7 +31,7 @@ const Legend = ({ {[...Array(max+1-min).keys()].map(i => i+min).map(i => 0} onMouseOver={() => onSegmentFocus(i)} /> @@ -42,7 +40,7 @@ const Legend = ({ - ); -}; + ) +} -export default Legend; +export default Legend diff --git a/crabfit-frontend/src/components/Legend/legendStyle.ts b/crabfit-frontend/src/components/Legend/Legend.styles.js similarity index 61% rename from crabfit-frontend/src/components/Legend/legendStyle.ts rename to crabfit-frontend/src/components/Legend/Legend.styles.js index ce5ba62..d8a37f0 100644 --- a/crabfit-frontend/src/components/Legend/legendStyle.ts +++ b/crabfit-frontend/src/components/Legend/Legend.styles.js @@ -1,6 +1,6 @@ -import styled from '@emotion/styled'; +import { styled } from 'goober' -export const Wrapper = styled.div` +export const Wrapper = styled('div')` margin: 10px 0; display: flex; align-items: center; @@ -13,40 +13,40 @@ export const Wrapper = styled.div` @media (max-width: 400px) { display: block; } -`; +` -export const Label = styled.label` +export const Label = styled('label')` display: block; font-size: 14px; text-align: left; -`; +` -export const Bar = styled.div` +export const Bar = styled('div')` display: flex; width: 40%; height: 20px; border-radius: 3px; overflow: hidden; margin: 0 8px; - border: 1px solid ${props => props.theme.text}; + border: 1px solid var(--text); @media (max-width: 400px) { width: 100%; margin: 8px 0; } -`; +` -export const Grade = styled.div` +export const Grade = styled('div')` flex: 1; background-color: ${props => props.color}; ${props => props.highlight && ` background-image: repeating-linear-gradient( 45deg, - ${props.theme.primary}, - ${props.theme.primary} 4.5px, - ${props.theme.primaryDark} 4.5px, - ${props.theme.primaryDark} 9px + var(--primary), + var(--primary) 4.5px, + var(--secondary) 4.5px, + var(--secondary) 9px ); `} -`; +` diff --git a/crabfit-frontend/src/components/Loading/Loading.jsx b/crabfit-frontend/src/components/Loading/Loading.jsx new file mode 100644 index 0000000..ccb2ece --- /dev/null +++ b/crabfit-frontend/src/components/Loading/Loading.jsx @@ -0,0 +1,5 @@ +import { Wrapper, Loader } from './Loading.styles' + +const Loading = () => + +export default Loading diff --git a/crabfit-frontend/src/components/Loading/loadingStyle.ts b/crabfit-frontend/src/components/Loading/Loading.styles.js similarity index 74% rename from crabfit-frontend/src/components/Loading/loadingStyle.ts rename to crabfit-frontend/src/components/Loading/Loading.styles.js index 84665ff..ff7d1fb 100644 --- a/crabfit-frontend/src/components/Loading/loadingStyle.ts +++ b/crabfit-frontend/src/components/Loading/Loading.styles.js @@ -1,13 +1,13 @@ -import styled from '@emotion/styled'; +import { styled } from 'goober' -export const Wrapper = styled.main` +export const Wrapper = styled('main')` display: flex; justify-content: center; align-items: center; min-height: 100vh; -`; +` -export const Loader = styled.div` +export const Loader = styled('div')` @keyframes load { from { transform: rotate(0deg); @@ -19,7 +19,7 @@ export const Loader = styled.div` height: 24px; width: 24px; - border: 3px solid ${props => props.theme.primary}; + border: 3px solid var(--primary); border-left-color: transparent; border-radius: 100px; animation: load .5s linear infinite; @@ -32,4 +32,4 @@ export const Loader = styled.div` content: 'loading...'; } } -`; +` diff --git a/crabfit-frontend/src/components/Loading/Loading.tsx b/crabfit-frontend/src/components/Loading/Loading.tsx deleted file mode 100644 index 23606b7..0000000 --- a/crabfit-frontend/src/components/Loading/Loading.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { - Wrapper, - Loader, -} from './loadingStyle'; - -const Loading = () => ( - - - -); - -export default Loading; diff --git a/crabfit-frontend/src/components/Logo/Logo.tsx b/crabfit-frontend/src/components/Logo/Logo.jsx similarity index 59% rename from crabfit-frontend/src/components/Logo/Logo.tsx rename to crabfit-frontend/src/components/Logo/Logo.jsx index 181bf0e..88c8c12 100644 --- a/crabfit-frontend/src/components/Logo/Logo.tsx +++ b/crabfit-frontend/src/components/Logo/Logo.jsx @@ -1,5 +1,5 @@ -import { useTranslation } from 'react-i18next'; -import { Link } from 'react-router-dom'; +import { useTranslation } from 'react-i18next' +import { Link } from 'react-router-dom' import { Wrapper, @@ -8,12 +8,12 @@ import { Image, Title, Tagline, -} from './logoStyle'; +} from './Logo.styles' -import image from 'res/logo.svg'; +import image from '/src/res/logo.svg' const Logo = () => { - const { t } = useTranslation('common'); + const { t } = useTranslation('common') return ( @@ -25,7 +25,7 @@ const Logo = () => { {t('common:tagline')} - ); -}; + ) +} -export default Logo; +export default Logo diff --git a/crabfit-frontend/src/components/Logo/logoStyle.ts b/crabfit-frontend/src/components/Logo/Logo.styles.js similarity index 70% rename from crabfit-frontend/src/components/Logo/logoStyle.ts rename to crabfit-frontend/src/components/Logo/Logo.styles.js index e6c3bdd..09424b6 100644 --- a/crabfit-frontend/src/components/Logo/logoStyle.ts +++ b/crabfit-frontend/src/components/Logo/Logo.styles.js @@ -1,12 +1,12 @@ -import styled from '@emotion/styled'; +import { styled } from 'goober' -export const Wrapper = styled.div` +export const Wrapper = styled('div')` display: flex; align-items: center; justify-content: center; -`; +` -export const A = styled.a` +export const A = styled('a')` text-decoration: none; @keyframes jelly { @@ -32,30 +32,30 @@ export const A = styled.a` animation: none; } } -`; +` -export const Top = styled.div` +export const Top = styled('div')` display: inline-flex; justify-content: center; align-items: center; -`; +` -export const Image = styled.img` +export const Image = styled('img')` width: 2.5rem; margin-right: 16px; -`; +` -export const Title = styled.span` +export const Title = styled('span')` display: block; font-size: 2rem; - color: ${props => props.theme.primary}; + color: var(--primary); font-family: 'Molot', sans-serif; font-weight: 400; - text-shadow: 0 2px 0 ${props => props.theme.primaryDark}; + text-shadow: 0 2px 0 var(--secondary); line-height: 1em; -`; +` -export const Tagline = styled.span` +export const Tagline = styled('span')` text-decoration: underline; font-size: 14px; padding-top: 2px; @@ -66,4 +66,4 @@ export const Tagline = styled.span` @media print { display: none; } -`; +` diff --git a/crabfit-frontend/src/components/OutlookCalendar/OutlookCalendar.tsx b/crabfit-frontend/src/components/OutlookCalendar/OutlookCalendar.jsx similarity index 67% rename from crabfit-frontend/src/components/OutlookCalendar/OutlookCalendar.tsx rename to crabfit-frontend/src/components/OutlookCalendar/OutlookCalendar.jsx index 17b3f2a..1a1b0b5 100644 --- a/crabfit-frontend/src/components/OutlookCalendar/OutlookCalendar.tsx +++ b/crabfit-frontend/src/components/OutlookCalendar/OutlookCalendar.jsx @@ -1,10 +1,10 @@ -import { useState, useEffect } from 'react'; -import { PublicClientApplication } from "@azure/msal-browser"; -import { Client } from "@microsoft/microsoft-graph-client"; -import { useTranslation } from 'react-i18next'; +import { useState, useEffect } from 'react' +import { PublicClientApplication } from '@azure/msal-browser' +import { Client } from '@microsoft/microsoft-graph-client' +import { useTranslation } from 'react-i18next' -import { Button, Center } from 'components'; -import { Loader } from '../Loading/loadingStyle'; +import { Button, Center } from '/src/components' +import { Loader } from '../Loading/Loading.styles' import { CalendarList, CheckboxInput, @@ -15,11 +15,11 @@ import { Title, Icon, LinkButton, -} from '../GoogleCalendar/googleCalendarStyle'; +} from '../GoogleCalendar/GoogleCalendar.styles' -import outlookLogo from 'res/outlook.svg'; +import outlookLogo from '/src/res/outlook.svg' -const scopes = ['Calendars.Read', 'Calendars.Read.Shared']; +const scopes = ['Calendars.Read', 'Calendars.Read.Shared'] // Initialise the MSAL object const publicClientApplication = new PublicClientApplication({ @@ -31,69 +31,69 @@ const publicClientApplication = new PublicClientApplication({ cacheLocation: 'sessionStorage', storeAuthStateInCookie: true, }, -}); +}) const getAuthenticatedClient = accessToken => { const client = Client.init({ authProvider: done => done(null, accessToken), - }); - return client; -}; + }) + return client +} const OutlookCalendar = ({ timeZone, timeMin, timeMax, onImport }) => { - const [client, setClient] = useState(undefined); - const [calendars, setCalendars] = useState(undefined); - const [freeBusyLoading, setFreeBusyLoading] = useState(false); - const { t } = useTranslation('event'); + const [client, setClient] = useState(undefined) + const [calendars, setCalendars] = useState(undefined) + const [freeBusyLoading, setFreeBusyLoading] = useState(false) + const { t } = useTranslation('event') const checkLogin = async () => { - const accounts = publicClientApplication.getAllAccounts(); + const accounts = publicClientApplication.getAllAccounts() if (accounts && accounts.length > 0) { try { - const accessToken = await getAccessToken(); - setClient(getAuthenticatedClient(accessToken)); + const accessToken = await getAccessToken() + setClient(getAuthenticatedClient(accessToken)) } catch (e) { - console.error(e); - signOut(); + console.error(e) + signOut() } } else { - setClient(null); + setClient(null) } - }; + } const signIn = async () => { try { - await publicClientApplication.loginPopup({ scopes }); + await publicClientApplication.loginPopup({ scopes }) } catch (e) { - console.error(e); + console.error(e) } finally { - checkLogin(); + checkLogin() } - }; + } const signOut = async () => { try { await publicClientApplication.logoutRedirect({ onRedirectNavigate: () => false, - }); + }) } catch (e) { - console.error(e); + console.error(e) } finally { - checkLogin(); + checkLogin() } - }; + } const getAccessToken = async () => { try { - const accounts = publicClientApplication.getAllAccounts(); - if (accounts.length <= 0) throw new Error('login_required'); + const accounts = publicClientApplication.getAllAccounts() + if (accounts.length <= 0) throw new Error('login_required') // Try to get silently const result = await publicClientApplication.acquireTokenSilent({ scopes, account: accounts[0], - }); - return result.accessToken; + }) + return result.accessToken } catch (e) { if ([ 'consent_required', @@ -102,19 +102,19 @@ const OutlookCalendar = ({ timeZone, timeMin, timeMax, onImport }) => { 'no_account_in_silent_request' ].includes(e.message)) { // Try to get with popup - const result = await publicClientApplication.acquireTokenPopup({ scopes }); - return result.accessToken; + const result = await publicClientApplication.acquireTokenPopup({ scopes }) + return result.accessToken } else { - throw e; + throw e } } - }; + } const importAvailability = () => { - setFreeBusyLoading(true); + setFreeBusyLoading(true) gtag('event', 'outlook_cal_sync', { 'event_category': 'event', - }); + }) client.api('/me/calendar/getSchedule').post({ schedules: calendars.filter(c => c.checked).map(c => c.id), startTime: { @@ -128,17 +128,16 @@ const OutlookCalendar = ({ timeZone, timeMin, timeMax, onImport }) => { availabilityViewInterval: 30, }) .then(response => { - onImport(response.value.reduce((busy, c) => c.hasOwnProperty('error') ? busy : [...busy, ...c.scheduleItems.filter(item => item.status === 'busy' || item.status === 'tentative')], [])); + onImport(response.value.reduce((busy, c) => c.error ? busy : [...busy, ...c.scheduleItems.filter(item => item.status === 'busy' || item.status === 'tentative')], [])) }) .catch(e => { - console.error(e); - signOut(); + console.error(e) + signOut() }) - .finally(() => setFreeBusyLoading(false)); - }; + .finally(() => setFreeBusyLoading(false)) + } - // eslint-disable-next-line - useEffect(() => checkLogin(), []); + useEffect(() => checkLogin(), []) useEffect(() => { if (client) { @@ -150,15 +149,14 @@ const OutlookCalendar = ({ timeZone, timeMin, timeMax, onImport }) => { 'id': item.owner.address, 'color': item.hexColor, 'checked': item.isDefaultCalendar === true, - }))); + }))) }) .catch(e => { - console.error(e); - signOut(); - }); + console.error(e) + signOut() + }) } - // eslint-disable-next-line - }, [client]); + }, [client]) return ( <> @@ -170,9 +168,7 @@ const OutlookCalendar = ({ timeZone, timeMin, timeMax, onImport }) => { primaryColor="#0364B9" secondaryColor="#02437D" icon={} - > - {t('event:you.outlook_cal')} - + >{t('event:you.outlook_cal')} ) : ( @@ -180,21 +176,21 @@ const OutlookCalendar = ({ timeZone, timeMin, timeMax, onImport }) => { {t('event:you.outlook_cal')} ( { - e.preventDefault(); - signOut(); + e.preventDefault() + signOut() }}>{t('event:you.google_cal.logout')}) {calendars !== undefined && !calendars.every(c => c.checked) && ( { - e.preventDefault(); - setCalendars(calendars.map(c => ({...c, checked: true}))); + e.preventDefault() + setCalendars(calendars.map(c => ({...c, checked: true}))) }}>{t('event:you.google_cal.select_all')} )} {calendars !== undefined && calendars.every(c => c.checked) && ( { - e.preventDefault(); - setCalendars(calendars.map(c => ({...c, checked: false}))); + e.preventDefault() + setCalendars(calendars.map(c => ({...c, checked: false}))) }}>{t('event:you.google_cal.select_none')} )} @@ -206,14 +202,12 @@ const OutlookCalendar = ({ timeZone, timeMin, timeMax, onImport }) => { id={calendar.id} color={calendar.color} checked={calendar.checked} - onChange={e => setCalendars(calendars.map(c => c.id === calendar.id ? {...c, checked: !c.checked} : c))} + onChange={() => setCalendars(calendars.map(c => c.id === calendar.id ? {...c, checked: !c.checked} : c))} /> {calendar.name} - )) : ( - - )} + )) : } {calendars !== undefined && ( <> {t('event:you.google_cal.info')} @@ -228,7 +222,7 @@ const OutlookCalendar = ({ timeZone, timeMin, timeMax, onImport }) => { )} - ); -}; + ) +} -export default OutlookCalendar; +export default OutlookCalendar diff --git a/crabfit-frontend/src/components/Recents/Recents.tsx b/crabfit-frontend/src/components/Recents/Recents.jsx similarity index 53% rename from crabfit-frontend/src/components/Recents/Recents.tsx rename to crabfit-frontend/src/components/Recents/Recents.jsx index 451e620..9d4a9fe 100644 --- a/crabfit-frontend/src/components/Recents/Recents.tsx +++ b/crabfit-frontend/src/components/Recents/Recents.jsx @@ -1,17 +1,18 @@ -import { useTranslation } from 'react-i18next'; -import { useRecentsStore, useLocaleUpdateStore } from 'stores'; -import dayjs from 'dayjs'; -import relativeTime from 'dayjs/plugin/relativeTime'; +import { useTranslation } from 'react-i18next' +import dayjs from 'dayjs' +import relativeTime from 'dayjs/plugin/relativeTime' -import { AboutSection, StyledMain } from '../../pages/Home/homeStyle'; -import { Wrapper, Recent } from './recentsStyle'; +import { useRecentsStore, useLocaleUpdateStore } from '/src/stores' -dayjs.extend(relativeTime); +import { AboutSection, StyledMain } from '../../pages/Home/Home.styles' +import { Wrapper, Recent } from './Recents.styles' + +dayjs.extend(relativeTime) const Recents = ({ target }) => { - const recents = useRecentsStore(state => state.recents); - const locale = useLocaleUpdateStore(state => state.locale); - const { t } = useTranslation(['home', 'common']); + const recents = useRecentsStore(state => state.recents) + const locale = useLocaleUpdateStore(state => state.locale) + const { t } = useTranslation(['home', 'common']) return !!recents.length && ( @@ -27,7 +28,7 @@ const Recents = ({ target }) => { - ); -}; + ) +} -export default Recents; +export default Recents diff --git a/crabfit-frontend/src/components/Recents/recentsStyle.ts b/crabfit-frontend/src/components/Recents/Recents.styles.js similarity index 66% rename from crabfit-frontend/src/components/Recents/recentsStyle.ts rename to crabfit-frontend/src/components/Recents/Recents.styles.js index dca0b6c..fd0eec8 100644 --- a/crabfit-frontend/src/components/Recents/recentsStyle.ts +++ b/crabfit-frontend/src/components/Recents/Recents.styles.js @@ -1,12 +1,12 @@ -import styled from '@emotion/styled'; +import { styled } from 'goober' -export const Wrapper = styled.div` +export const Wrapper = styled('div')` @media print { display: none; } -`; +` -export const Recent = styled.a` +export const Recent = styled('a')` text-decoration: none; display: flex; align-items: center; @@ -17,7 +17,7 @@ export const Recent = styled.a` & .name { font-weight: 700; font-size: 1.1em; - color: ${props => props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight}; + color: var(--secondary); flex: 1; display: block; } @@ -25,7 +25,7 @@ export const Recent = styled.a` font-weight: 400; opacity: .8; white-space: nowrap; - color: ${props => props.theme.text}; + color: var(--text); } &:hover .name { @@ -39,4 +39,4 @@ export const Recent = styled.a` white-space: normal; } } -`; +` diff --git a/crabfit-frontend/src/components/SelectField/SelectField.tsx b/crabfit-frontend/src/components/SelectField/SelectField.jsx similarity index 74% rename from crabfit-frontend/src/components/SelectField/SelectField.tsx rename to crabfit-frontend/src/components/SelectField/SelectField.jsx index a036e71..d07c4a0 100644 --- a/crabfit-frontend/src/components/SelectField/SelectField.tsx +++ b/crabfit-frontend/src/components/SelectField/SelectField.jsx @@ -1,10 +1,11 @@ -import { forwardRef } from 'react'; +import { forwardRef } from 'react' + import { Wrapper, StyledLabel, StyledSubLabel, StyledSelect, -} from './selectFieldStyle'; +} from './SelectField.styles' const SelectField = forwardRef(({ label, @@ -16,13 +17,13 @@ const SelectField = forwardRef(({ defaultOption, ...props }, ref) => ( - - {label && {label}} + + {label && {label}} {subLabel && {subLabel}} @@ -38,6 +39,6 @@ const SelectField = forwardRef(({ )} -)); +)) -export default SelectField; +export default SelectField diff --git a/crabfit-frontend/src/components/SelectField/SelectField.styles.js b/crabfit-frontend/src/components/SelectField/SelectField.styles.js new file mode 100644 index 0000000..19d1c98 --- /dev/null +++ b/crabfit-frontend/src/components/SelectField/SelectField.styles.js @@ -0,0 +1,63 @@ +import { styled } from 'goober' +import { forwardRef } from 'react' + +export const Wrapper = styled('div')` + margin: 30px 0; + + ${props => props.$inline && ` + margin: 0; + `} + ${props => props.$small && ` + margin: 10px 0; + `} +` + +export const StyledLabel = styled('label')` + display: block; + padding-bottom: 4px; + font-size: 18px; + + ${props => props.$inline && ` + font-size: 16px; + `} + ${props => props.$small && ` + font-size: .9rem; + `} +` + +export const StyledSubLabel = styled('label')` + display: block; + padding-bottom: 6px; + font-size: 13px; + opacity: .6; +` + +export const StyledSelect = styled('select', forwardRef)` + width: 100%; + box-sizing: border-box; + font: inherit; + background: var(--surface); + color: inherit; + padding: 10px 14px; + border: 1px solid var(--primary); + box-shadow: inset 0 0 0 0 var(--primary); + border-radius: 3px; + outline: none; + transition: border-color .15s, box-shadow .15s; + /* FIXME: + appearance: none; + background-image: url("data:image/svg+xml,
"); + background-repeat: no-repeat; + background-position: right 10px center; + background-size: 1em; + */ + + &:focus { + border: 1px solid var(--secondary); + box-shadow: inset 0 -3px 0 0 var(--secondary); + } + + ${props => props.$small && ` + padding: 6px 8px; + `} +` diff --git a/crabfit-frontend/src/components/SelectField/selectFieldStyle.ts b/crabfit-frontend/src/components/SelectField/selectFieldStyle.ts deleted file mode 100644 index c921f06..0000000 --- a/crabfit-frontend/src/components/SelectField/selectFieldStyle.ts +++ /dev/null @@ -1,60 +0,0 @@ -import styled from '@emotion/styled'; - -export const Wrapper = styled.div` - margin: 30px 0; - - ${props => props.inline && ` - margin: 0; - `} - ${props => props.small && ` - margin: 10px 0; - `} -`; - -export const StyledLabel = styled.label` - display: block; - padding-bottom: 4px; - font-size: 18px; - - ${props => props.inline && ` - font-size: 16px; - `} - ${props => props.small && ` - font-size: .9rem; - `} -`; - -export const StyledSubLabel = styled.label` - display: block; - padding-bottom: 6px; - font-size: 13px; - opacity: .6; -`; - -export const StyledSelect = styled.select` - width: 100%; - box-sizing: border-box; - font: inherit; - background: ${props => props.theme.primaryBackground}; - color: inherit; - padding: 10px 14px; - border: 1px solid ${props => props.theme.primary}; - box-shadow: inset 0 0 0 0 ${props => props.theme.primary}; - border-radius: 3px; - outline: none; - transition: border-color .15s, box-shadow .15s; - appearance: none; - background-image: url("data:image/svg+xml,
encodeURIComponent(props.theme.primary)};font-size:60px;display:flex;align-items:center;justify-content:center;height:100%25;width:100%25%22>▼
"); - background-repeat: no-repeat; - background-position: right 10px center; - background-size: 1em; - - &:focus { - border: 1px solid ${props => props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight}; - box-shadow: inset 0 -3px 0 0 ${props => props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight}; - } - - ${props => props.small && ` - padding: 6px 8px; - `} -`; diff --git a/crabfit-frontend/src/components/Settings/Settings.tsx b/crabfit-frontend/src/components/Settings/Settings.jsx similarity index 55% rename from crabfit-frontend/src/components/Settings/Settings.tsx rename to crabfit-frontend/src/components/Settings/Settings.jsx index 74ecb30..75020da 100644 --- a/crabfit-frontend/src/components/Settings/Settings.tsx +++ b/crabfit-frontend/src/components/Settings/Settings.jsx @@ -1,93 +1,91 @@ -import { useState, useEffect, useRef } from 'react'; -import { useTheme } from '@emotion/react'; -import { useLocation } from 'react-router-dom'; -import { useTranslation } from 'react-i18next'; -import dayjs from 'dayjs'; +import { useState, useEffect, useRef } from 'react' +import { useLocation } from 'react-router-dom' +import { useTranslation } from 'react-i18next' +import dayjs from 'dayjs' -import { ToggleField, SelectField } from 'components'; +import { ToggleField, SelectField } from '/src/components' -import { useSettingsStore, useLocaleUpdateStore } from 'stores'; +import { useSettingsStore, useLocaleUpdateStore } from '/src/stores' import { OpenButton, Modal, Heading, Cover, -} from './settingsStyle'; +} from './Settings.styles' -import locales from 'res/dayjs_locales'; +import locales from '/src/i18n/locales' // Language specific options const setDefaults = (lang, store) => { - if (locales.hasOwnProperty(lang)) { - store.setWeekStart(locales[lang].weekStart); - store.setTimeFormat(locales[lang].timeFormat); + if (locales[lang]) { + store.setWeekStart(locales[lang].weekStart) + store.setTimeFormat(locales[lang].timeFormat) } -}; +} const Settings = () => { - const { pathname } = useLocation(); - const theme = useTheme(); - const store = useSettingsStore(); - const [isOpen, _setIsOpen] = useState(false); - const { t, i18n } = useTranslation('common'); - const setLocale = useLocaleUpdateStore(state => state.setLocale); - const firstControlRef = useRef(); + const { pathname } = useLocation() + const store = useSettingsStore() + const [isOpen, _setIsOpen] = useState(false) + const { t, i18n } = useTranslation('common') + const setLocale = useLocaleUpdateStore(state => state.setLocale) + const firstControlRef = useRef() const onEsc = e => { if (e.key === 'Escape') { - setIsOpen(false); + setIsOpen(false) } - }; + } const setIsOpen = open => { - _setIsOpen(open); + _setIsOpen(open) if (open) { - window.setTimeout(() => firstControlRef.current.focus(), 150); - document.addEventListener('keyup', onEsc, true); + window.setTimeout(() => firstControlRef.current?.focus(), 150) + document.addEventListener('keyup', onEsc, true) } else { - document.removeEventListener('keyup', onEsc); + document.removeEventListener('keyup', onEsc) } - }; + } useEffect(() => { if (Object.keys(locales).includes(i18n.language)) { locales[i18n.language].import().then(() => { - dayjs.locale(i18n.language); - setLocale(dayjs.locale()); - document.documentElement.setAttribute('lang', i18n.language); - }); + dayjs.locale(i18n.language) + setLocale(dayjs.locale()) + document.documentElement.setAttribute('lang', i18n.language) + }) } else { - setLocale('en'); + setLocale('en') document.documentElement.setAttribute('lang', 'en') } - }, [i18n.language, setLocale]); + }, [i18n.language, setLocale]) if (!i18n.options.storedLang) { - setDefaults(i18n.language, store); - i18n.options.storedLang = i18n.language; + setDefaults(i18n.language, store) + i18n.options.storedLang = i18n.language } i18n.on('languageChanged', lang => { - setDefaults(lang, store); - }); + setDefaults(lang, store) + }) // Reset scroll on navigation - useEffect(() => window.scrollTo(0, 0), [pathname]); + useEffect(() => window.scrollTo(0, 0), [pathname]) return ( <> setIsOpen(!isOpen)} title={t('options.name')} > - + - setIsOpen(false)} /> - + setIsOpen(false)} /> + {t('options.name')} { id="language" options={{ ...Object.keys(locales).reduce((ls, l) => { - ls[l] = locales[l].name; - return ls; + ls[l] = locales[l].name + return ls }, {}), ...process.env.NODE_ENV !== 'production' && { 'cimode': 'DEV' }, }} @@ -158,7 +156,7 @@ const Settings = () => { /> - ); -}; + ) +} -export default Settings; +export default Settings diff --git a/crabfit-frontend/src/components/Settings/settingsStyle.ts b/crabfit-frontend/src/components/Settings/Settings.styles.js similarity index 72% rename from crabfit-frontend/src/components/Settings/settingsStyle.ts rename to crabfit-frontend/src/components/Settings/Settings.styles.js index 952a6c0..d692b23 100644 --- a/crabfit-frontend/src/components/Settings/settingsStyle.ts +++ b/crabfit-frontend/src/components/Settings/Settings.styles.js @@ -1,6 +1,6 @@ -import styled from '@emotion/styled'; +import { styled } from 'goober' -export const OpenButton = styled.button` +export const OpenButton = styled('button')` border: 0; background: none; height: 50px; @@ -23,10 +23,10 @@ export const OpenButton = styled.button` outline: 0; } &:focus-visible { - background-color: ${props => props.theme.text}22; + /* FIXME: background-color: props => props.theme.text22; */ } - ${props => props.isOpen && ` + ${props => props.$isOpen && ` transform: rotate(-45deg); `} @@ -36,9 +36,9 @@ export const OpenButton = styled.button` @media print { display: none; } -`; +` -export const Cover = styled.div` +export const Cover = styled('div')` position: fixed; top: 0; left: 0; @@ -47,19 +47,19 @@ export const Cover = styled.div` z-index: 100; display: none; - ${props => props.isOpen && ` + ${props => props.$isOpen && ` display: block; `} -`; +` -export const Modal = styled.div` +export const Modal = styled('div')` position: absolute; top: 70px; right: 12px; - background-color: ${props => props.theme.background}; - ${props => props.theme.mode === 'dark' && ` - border: 1px solid ${props.theme.primaryBackground}; - `} + background-color: var(--background); + ${/* FIXME: props => props.theme.mode === 'dark' && ` + border: 1px solid props.theme.primaryBackground; + ` */''} z-index: 150; padding: 10px 18px; border-radius: 3px; @@ -74,7 +74,7 @@ export const Modal = styled.div` visibility: hidden; transition: opacity .15s, transform .15s, visibility .15s; - ${props => props.isOpen && ` + ${props => props.$isOpen && ` pointer-events: all; opacity: 1; transform: translateY(0); @@ -87,11 +87,11 @@ export const Modal = styled.div` @media print { display: none; } -`; +` -export const Heading = styled.span` +export const Heading = styled('span')` font-size: 1.5rem; display: block; margin: 6px 0; line-height: 1em; -`; +` diff --git a/crabfit-frontend/src/components/TextField/TextField.tsx b/crabfit-frontend/src/components/TextField/TextField.jsx similarity index 81% rename from crabfit-frontend/src/components/TextField/TextField.tsx rename to crabfit-frontend/src/components/TextField/TextField.jsx index 68dbc2b..8abd3c2 100644 --- a/crabfit-frontend/src/components/TextField/TextField.tsx +++ b/crabfit-frontend/src/components/TextField/TextField.jsx @@ -1,10 +1,11 @@ -import { forwardRef } from 'react'; +import { forwardRef } from 'react' + import { Wrapper, StyledLabel, StyledSubLabel, StyledInput, -} from './textFieldStyle'; +} from './TextField.styles' const TextField = forwardRef(({ label, @@ -18,6 +19,6 @@ const TextField = forwardRef(({ {subLabel && {subLabel}}
-)); +)) -export default TextField; +export default TextField diff --git a/crabfit-frontend/src/components/TextField/TextField.styles.js b/crabfit-frontend/src/components/TextField/TextField.styles.js new file mode 100644 index 0000000..f469330 --- /dev/null +++ b/crabfit-frontend/src/components/TextField/TextField.styles.js @@ -0,0 +1,47 @@ +import { styled } from 'goober' +import { forwardRef } from 'react' + +export const Wrapper = styled('div')` + margin: 30px 0; + + ${props => props.inline && ` + margin: 0; + `} +` + +export const StyledLabel = styled('label')` + display: block; + padding-bottom: 4px; + font-size: 18px; + + ${props => props.inline && ` + font-size: 16px; + `} +` + +export const StyledSubLabel = styled('label')` + display: block; + padding-bottom: 6px; + font-size: 13px; + opacity: .6; +` + +export const StyledInput = styled('input', forwardRef)` + width: 100%; + box-sizing: border-box; + font: inherit; + background: var(--surface); + color: inherit; + padding: 10px 14px; + border: 1px solid var(--primary); + box-shadow: inset 0 0 0 0 var(--primary); + border-radius: 3px; + font-size: 18px; + outline: none; + transition: border-color .15s, box-shadow .15s; + + &:focus { + border: 1px solid var(--secondary); + box-shadow: inset 0 -3px 0 0 var(--secondary); + } +` diff --git a/crabfit-frontend/src/components/TextField/textFieldStyle.ts b/crabfit-frontend/src/components/TextField/textFieldStyle.ts deleted file mode 100644 index 9fe8315..0000000 --- a/crabfit-frontend/src/components/TextField/textFieldStyle.ts +++ /dev/null @@ -1,46 +0,0 @@ -import styled from '@emotion/styled'; - -export const Wrapper = styled.div` - margin: 30px 0; - - ${props => props.inline && ` - margin: 0; - `} -`; - -export const StyledLabel = styled.label` - display: block; - padding-bottom: 4px; - font-size: 18px; - - ${props => props.inline && ` - font-size: 16px; - `} -`; - -export const StyledSubLabel = styled.label` - display: block; - padding-bottom: 6px; - font-size: 13px; - opacity: .6; -`; - -export const StyledInput = styled.input` - width: 100%; - box-sizing: border-box; - font: inherit; - background: ${props => props.theme.primaryBackground}; - color: inherit; - padding: 10px 14px; - border: 1px solid ${props => props.theme.primary}; - box-shadow: inset 0 0 0 0 ${props => props.theme.primary}; - border-radius: 3px; - font-size: 18px; - outline: none; - transition: border-color .15s, box-shadow .15s; - - &:focus { - border: 1px solid ${props => props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight}; - box-shadow: inset 0 -3px 0 0 ${props => props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight}; - } -`; diff --git a/crabfit-frontend/src/components/TimeRangeField/TimeRangeField.tsx b/crabfit-frontend/src/components/TimeRangeField/TimeRangeField.jsx similarity index 61% rename from crabfit-frontend/src/components/TimeRangeField/TimeRangeField.tsx rename to crabfit-frontend/src/components/TimeRangeField/TimeRangeField.jsx index b176cd6..34e2a3a 100644 --- a/crabfit-frontend/src/components/TimeRangeField/TimeRangeField.tsx +++ b/crabfit-frontend/src/components/TimeRangeField/TimeRangeField.jsx @@ -1,7 +1,7 @@ -import { useState, useEffect, useRef, forwardRef } from 'react'; -import dayjs from 'dayjs'; +import { useState, useEffect, useRef, forwardRef } from 'react' +import dayjs from 'dayjs' -import { useSettingsStore, useLocaleUpdateStore } from 'stores'; +import { useSettingsStore, useLocaleUpdateStore } from '/src/stores' import { Wrapper, @@ -10,9 +10,9 @@ import { Range, Handle, Selected, -} from './timeRangeFieldStyle'; +} from './TimeRangeField.styles' -const times = ['00','01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24']; +const times = ['00','01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24'] const TimeRangeField = forwardRef(({ label, @@ -21,39 +21,39 @@ const TimeRangeField = forwardRef(({ setValue, ...props }, ref) => { - const timeFormat = useSettingsStore(state => state.timeFormat); - const locale = useLocaleUpdateStore(state => state.locale); + const timeFormat = useSettingsStore(state => state.timeFormat) + const locale = useLocaleUpdateStore(state => state.locale) - const [start, setStart] = useState(9); - const [end, setEnd] = useState(17); + const [start, setStart] = useState(9) + const [end, setEnd] = useState(17) - const isStartMoving = useRef(false); - const isEndMoving = useRef(false); - const rangeRef = useRef(); - const rangeRect = useRef(); + const isStartMoving = useRef(false) + const isEndMoving = useRef(false) + const rangeRef = useRef() + const rangeRect = useRef() useEffect(() => { if (rangeRef.current) { - rangeRect.current = rangeRef.current.getBoundingClientRect(); + rangeRect.current = rangeRef.current.getBoundingClientRect() } - }, [rangeRef]); + }, [rangeRef]) - useEffect(() => setValue(props.name, JSON.stringify({start, end})), [start, end, setValue, props.name]); + useEffect(() => setValue(props.name, JSON.stringify({start, end})), [start, end, setValue, props.name]) const handleMouseMove = e => { if (isStartMoving.current || isEndMoving.current) { - let step = Math.round(((e.pageX - rangeRect.current.left) / rangeRect.current.width) * 24); - if (step < 0) step = 0; - if (step > 24) step = 24; - step = Math.abs(step); + let step = Math.round(((e.pageX - rangeRect.current.left) / rangeRect.current.width) * 24) + if (step < 0) step = 0 + if (step > 24) step = 24 + step = Math.abs(step) if (isStartMoving.current) { - setStart(step); + setStart(step) } else if (isEndMoving.current) { - setEnd(step); + setEnd(step) } } - }; + } return ( @@ -75,32 +75,32 @@ const TimeRangeField = forwardRef(({ label={timeFormat === '24h' ? times[start] : dayjs().hour(times[start]).format('ha')} extraPadding={end - start === 1 ? 'padding-right: 20px;' : (start - end === 1 ? 'padding-left: 20px;' : '')} onMouseDown={() => { - document.addEventListener('mousemove', handleMouseMove); - isStartMoving.current = true; + document.addEventListener('mousemove', handleMouseMove) + isStartMoving.current = true document.addEventListener('mouseup', () => { - isStartMoving.current = false; - document.removeEventListener('mousemove', handleMouseMove); - }, { once: true }); + isStartMoving.current = false + document.removeEventListener('mousemove', handleMouseMove) + }, { once: true }) }} - onTouchMove={(e) => { - const touch = e.targetTouches[0]; + onTouchMove={e => { + const touch = e.targetTouches[0] - let step = Math.round(((touch.pageX - rangeRect.current.left) / rangeRect.current.width) * 24); - if (step < 0) step = 0; - if (step > 24) step = 24; - step = Math.abs(step); - setStart(step); + let step = Math.round(((touch.pageX - rangeRect.current.left) / rangeRect.current.width) * 24) + if (step < 0) step = 0 + if (step > 24) step = 24 + step = Math.abs(step) + setStart(step) }} tabIndex="0" onKeyDown={e => { if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { - e.preventDefault(); - setStart(Math.max(start-1, 0)); + e.preventDefault() + setStart(Math.max(start-1, 0)) } if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { - e.preventDefault(); - setStart(Math.min(start+1, 24)); + e.preventDefault() + setStart(Math.min(start+1, 24)) } }} /> @@ -109,38 +109,38 @@ const TimeRangeField = forwardRef(({ label={timeFormat === '24h' ? times[end] : dayjs().hour(times[end]).format('ha')} extraPadding={end - start === 1 ? 'padding-left: 20px;' : (start - end === 1 ? 'padding-right: 20px;' : '')} onMouseDown={() => { - document.addEventListener('mousemove', handleMouseMove); - isEndMoving.current = true; + document.addEventListener('mousemove', handleMouseMove) + isEndMoving.current = true document.addEventListener('mouseup', () => { - isEndMoving.current = false; - document.removeEventListener('mousemove', handleMouseMove); - }, { once: true }); + isEndMoving.current = false + document.removeEventListener('mousemove', handleMouseMove) + }, { once: true }) }} - onTouchMove={(e) => { - const touch = e.targetTouches[0]; + onTouchMove={e => { + const touch = e.targetTouches[0] - let step = Math.round(((touch.pageX - rangeRect.current.left) / rangeRect.current.width) * 24); - if (step < 0) step = 0; - if (step > 24) step = 24; - step = Math.abs(step); - setEnd(step); + let step = Math.round(((touch.pageX - rangeRect.current.left) / rangeRect.current.width) * 24) + if (step < 0) step = 0 + if (step > 24) step = 24 + step = Math.abs(step) + setEnd(step) }} tabIndex="0" onKeyDown={e => { if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { - e.preventDefault(); - setEnd(Math.max(end-1, 0)); + e.preventDefault() + setEnd(Math.max(end-1, 0)) } if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { - e.preventDefault(); - setEnd(Math.min(end+1, 24)); + e.preventDefault() + setEnd(Math.min(end+1, 24)) } }} /> - ); -}); + ) +}) -export default TimeRangeField; +export default TimeRangeField diff --git a/crabfit-frontend/src/components/TimeRangeField/timeRangeFieldStyle.ts b/crabfit-frontend/src/components/TimeRangeField/TimeRangeField.styles.js similarity index 58% rename from crabfit-frontend/src/components/TimeRangeField/timeRangeFieldStyle.ts rename to crabfit-frontend/src/components/TimeRangeField/TimeRangeField.styles.js index 03869f7..09b479c 100644 --- a/crabfit-frontend/src/components/TimeRangeField/timeRangeFieldStyle.ts +++ b/crabfit-frontend/src/components/TimeRangeField/TimeRangeField.styles.js @@ -1,41 +1,41 @@ -import styled from '@emotion/styled'; +import { styled } from 'goober' -export const Wrapper = styled.div` +export const Wrapper = styled('div')` margin: 30px 0; -`; +` -export const StyledLabel = styled.label` +export const StyledLabel = styled('label')` display: block; padding-bottom: 4px; font-size: 18px; -`; +` -export const StyledSubLabel = styled.label` +export const StyledSubLabel = styled('label')` display: block; padding-bottom: 6px; font-size: 13px; opacity: .6; -`; +` -export const Range = styled.div` +export const Range = styled('div')` user-select: none; - background-color: ${props => props.theme.primaryBackground}; - border: 1px solid ${props => props.theme.primary}; + background-color: var(--surface); + border: 1px solid var(--primary); border-radius: 3px; height: 50px; position: relative; margin: 38px 6px 18px; -`; +` -export const Handle = styled.div` +export const Handle = styled('div')` height: calc(100% + 20px); width: 20px; - border: 1px solid ${props => props.theme.primary}; - background-color: ${props => props.theme.primaryLight}; + border: 1px solid var(--primary); + background-color: var(--tertiary); border-radius: 3px; position: absolute; top: -10px; - left: calc(${props => props.value * 4.1666666666666666}% - 11px); + left: calc(${props => props.value * 4.166}% - 11px); cursor: ew-resize; touch-action: none; transition: left .1s; @@ -54,7 +54,7 @@ export const Handle = styled.div` display: flex; align-items: center; justify-content: center; - color: ${props => props.theme.primaryDark}; + color: var(--secondary); } &:before { @@ -67,18 +67,18 @@ export const Handle = styled.div` white-space: nowrap; ${props => props.extraPadding} } -`; +` -export const Selected = styled.div` +export const Selected = styled('div')` position: absolute; height: 100%; - left: ${props => props.start * 4.1666666666666666}%; - right: calc(100% - ${props => props.end * 4.1666666666666666}%); + left: ${props => props.start * 4.166}%; + right: calc(100% - ${props => props.end * 4.166}%); top: 0; - background-color: ${props => props.theme.primary}; + background-color: var(--primary); border-radius: 2px; transition: left .1s, right .1s; @media (prefers-reduced-motion: reduce) { transition: none; } -`; +` diff --git a/crabfit-frontend/src/components/ToggleField/ToggleField.tsx b/crabfit-frontend/src/components/ToggleField/ToggleField.jsx similarity index 93% rename from crabfit-frontend/src/components/ToggleField/ToggleField.tsx rename to crabfit-frontend/src/components/ToggleField/ToggleField.jsx index 2894a0d..c35e674 100644 --- a/crabfit-frontend/src/components/ToggleField/ToggleField.tsx +++ b/crabfit-frontend/src/components/ToggleField/ToggleField.jsx @@ -5,18 +5,16 @@ import { Option, HiddenInput, LabelButton, -} from './toggleFieldStyle'; +} from './ToggleField.styles' const ToggleField = ({ label, - id, name, title = '', options = [], value, onChange, inputRef, - ...props }) => ( {label && {label} {title !== '' && }} @@ -38,6 +36,6 @@ const ToggleField = ({ )} -); +) -export default ToggleField; +export default ToggleField diff --git a/crabfit-frontend/src/components/ToggleField/toggleFieldStyle.ts b/crabfit-frontend/src/components/ToggleField/ToggleField.styles.js similarity index 63% rename from crabfit-frontend/src/components/ToggleField/toggleFieldStyle.ts rename to crabfit-frontend/src/components/ToggleField/ToggleField.styles.js index e3e7398..39bfc78 100644 --- a/crabfit-frontend/src/components/ToggleField/toggleFieldStyle.ts +++ b/crabfit-frontend/src/components/ToggleField/ToggleField.styles.js @@ -1,19 +1,20 @@ -import styled from '@emotion/styled'; +import { styled } from 'goober' +import { forwardRef } from 'react' -export const Wrapper = styled.div` +export const Wrapper = styled('div')` margin: 10px 0; -`; +` -export const ToggleContainer = styled.div` +export const ToggleContainer = styled('div')` display: flex; - border: 1px solid ${props => props.theme.primary}; + border: 1px solid var(--primary); border-radius: 3px; overflow: hidden; - --focus-color: ${props => props.theme.primary}; + --focus-color: var(--primary); transition: border .15s; &:focus-within { - --focus-color: ${props => props.theme.mode === 'light' ? props.theme.primaryDark : props.theme.primaryLight}; + --focus-color: var(--secondary); border: 1px solid var(--focus-color); & label { box-shadow: inset 0 -3px 0 0 var(--focus-color); @@ -26,9 +27,9 @@ export const ToggleContainer = styled.div` & > div:last-of-type label { border-end-end-radius: 2px; } -`; +` -export const StyledLabel = styled.label` +export const StyledLabel = styled('label')` display: block; padding-bottom: 4px; font-size: .9rem; @@ -38,14 +39,14 @@ export const StyledLabel = styled.label` width: 1em; vertical-align: middle; } -`; +` -export const Option = styled.div` +export const Option = styled('div')` flex: 1; position: relative; -`; +` -export const HiddenInput = styled.input` +export const HiddenInput = styled('input', forwardRef)` height: 0; width: 0; position: absolute; @@ -55,12 +56,12 @@ export const HiddenInput = styled.input` appearance: none; &:checked + label { - color: ${props => props.theme.background}; + color: var(--background); background-color: var(--focus-color); } -`; +` -export const LabelButton = styled.label` +export const LabelButton = styled('label')` padding: 6px; display: flex; text-align: center; @@ -71,4 +72,4 @@ export const LabelButton = styled.label` align-items: center; justify-content: center; transition: box-shadow .15s, background-color .15s; -`; +` diff --git a/crabfit-frontend/src/components/TranslateDialog/TranslateDialog.tsx b/crabfit-frontend/src/components/TranslateDialog/TranslateDialog.jsx similarity index 78% rename from crabfit-frontend/src/components/TranslateDialog/TranslateDialog.tsx rename to crabfit-frontend/src/components/TranslateDialog/TranslateDialog.jsx index 391c1af..780d529 100644 --- a/crabfit-frontend/src/components/TranslateDialog/TranslateDialog.tsx +++ b/crabfit-frontend/src/components/TranslateDialog/TranslateDialog.jsx @@ -1,15 +1,15 @@ -import { Button } from 'components'; +import { Button } from '/src/components' -import { useTranslateStore } from 'stores'; +import { useTranslateStore } from '/src/stores' import { Wrapper, ButtonWrapper, -} from './translateDialogStyle'; +} from './TranslateDialog.styles' -const TranslateDialog = ({ onClose }) => { - const navigatorLang = useTranslateStore(state => state.navigatorLang); - const setDialogDismissed = useTranslateStore(state => state.setDialogDismissed); +const TranslateDialog = () => { + const navigatorLang = useTranslateStore(state => state.navigatorLang) + const setDialogDismissed = useTranslateStore(state => state.setDialogDismissed) return ( @@ -26,7 +26,7 @@ const TranslateDialog = ({ onClose }) => { - ); + ) } -export default TranslateDialog; +export default TranslateDialog diff --git a/crabfit-frontend/src/components/TranslateDialog/translateDialogStyle.ts b/crabfit-frontend/src/components/TranslateDialog/TranslateDialog.styles.js similarity index 76% rename from crabfit-frontend/src/components/TranslateDialog/translateDialogStyle.ts rename to crabfit-frontend/src/components/TranslateDialog/TranslateDialog.styles.js index 4a31995..2366080 100644 --- a/crabfit-frontend/src/components/TranslateDialog/translateDialogStyle.ts +++ b/crabfit-frontend/src/components/TranslateDialog/TranslateDialog.styles.js @@ -1,13 +1,13 @@ -import styled from '@emotion/styled'; +import { styled } from 'goober' -export const Wrapper = styled.div` +export const Wrapper = styled('div')` position: fixed; top: 20px; left: 20px; - background-color: ${props => props.theme.background}; - ${props => props.theme.mode === 'dark' && ` + background-color: var(--background); + /* FIXME: ${props => props.theme.mode === 'dark' && ` border: 1px solid ${props.theme.primaryBackground}; - `} + `} */ z-index: 900; padding: 20px; border-radius: 3px; @@ -30,9 +30,9 @@ export const Wrapper = styled.div` @media (max-width: 400px) { display: block; } -`; +` -export const ButtonWrapper = styled.div` +export const ButtonWrapper = styled('div')` display: flex; flex-direction: column; align-items: stretch; @@ -46,4 +46,4 @@ export const ButtonWrapper = styled.div` margin: 20px 0 0; white-space: normal; } -`; +` diff --git a/crabfit-frontend/src/components/UpdateDialog/UpdateDialog.tsx b/crabfit-frontend/src/components/UpdateDialog/UpdateDialog.jsx similarity index 69% rename from crabfit-frontend/src/components/UpdateDialog/UpdateDialog.tsx rename to crabfit-frontend/src/components/UpdateDialog/UpdateDialog.jsx index 4f594af..20f0f31 100644 --- a/crabfit-frontend/src/components/UpdateDialog/UpdateDialog.tsx +++ b/crabfit-frontend/src/components/UpdateDialog/UpdateDialog.jsx @@ -1,13 +1,14 @@ -import { Button } from 'components'; -import { useTranslation } from 'react-i18next'; +import { useTranslation } from 'react-i18next' + +import { Button } from '/src/components' import { Wrapper, ButtonWrapper, -} from './updateDialogStyle'; +} from './UpdateDialog.styles' const UpdateDialog = ({ onClose }) => { - const { t } = useTranslation('common'); + const { t } = useTranslation('common') return ( @@ -18,7 +19,7 @@ const UpdateDialog = ({ onClose }) => { - ); + ) } -export default UpdateDialog; +export default UpdateDialog diff --git a/crabfit-frontend/src/components/UpdateDialog/updateDialogStyle.ts b/crabfit-frontend/src/components/UpdateDialog/UpdateDialog.styles.js similarity index 68% rename from crabfit-frontend/src/components/UpdateDialog/updateDialogStyle.ts rename to crabfit-frontend/src/components/UpdateDialog/UpdateDialog.styles.js index 1e22753..2becb85 100644 --- a/crabfit-frontend/src/components/UpdateDialog/updateDialogStyle.ts +++ b/crabfit-frontend/src/components/UpdateDialog/UpdateDialog.styles.js @@ -1,13 +1,13 @@ -import styled from '@emotion/styled'; +import { styled } from 'goober' -export const Wrapper = styled.div` +export const Wrapper = styled('div')` position: fixed; bottom: 20px; right: 20px; - background-color: ${props => props.theme.background}; - ${props => props.theme.mode === 'dark' && ` + background-color: var(--background); + /* FIXME: ${props => props.theme.mode === 'dark' && ` border: 1px solid ${props.theme.primaryBackground}; - `} + `} */ z-index: 900; padding: 20px 26px; border-radius: 3px; @@ -24,12 +24,12 @@ export const Wrapper = styled.div` margin: 16px 0 24px; font-size: 1rem; } -`; +` -export const ButtonWrapper = styled.div` +export const ButtonWrapper = styled('div')` display: flex; align-items: center; justify-content: flex-end; gap: 16px; flex-wrap: wrap; -`; +` diff --git a/crabfit-frontend/src/components/index.js b/crabfit-frontend/src/components/index.js new file mode 100644 index 0000000..6254806 --- /dev/null +++ b/crabfit-frontend/src/components/index.js @@ -0,0 +1,25 @@ +export { default as TextField } from './TextField/TextField' +export { default as SelectField } from './SelectField/SelectField' +export { default as CalendarField } from './CalendarField/CalendarField' +export { default as TimeRangeField } from './TimeRangeField/TimeRangeField' +export { default as ToggleField } from './ToggleField/ToggleField' + +export { default as Button } from './Button/Button' +export { default as Legend } from './Legend/Legend' +export { default as AvailabilityViewer } from './AvailabilityViewer/AvailabilityViewer' +export { default as AvailabilityEditor } from './AvailabilityEditor/AvailabilityEditor' +export { default as Error } from './Error/Error' +export { default as Loading } from './Loading/Loading' + +export { default as Center } from './Center/Center' +export { default as Donate } from './Donate/Donate' +export { default as Settings } from './Settings/Settings' +export { default as Egg } from './Egg/Egg' +export { default as Footer } from './Footer/Footer' +export { default as Recents } from './Recents/Recents' +export { default as Logo } from './Logo/Logo' +export { default as UpdateDialog } from './UpdateDialog/UpdateDialog' +export { default as TranslateDialog } from './TranslateDialog/TranslateDialog' + +export const _GoogleCalendar = () => import('./GoogleCalendar/GoogleCalendar') +export const _OutlookCalendar = () => import('./OutlookCalendar/OutlookCalendar') diff --git a/crabfit-frontend/src/components/index.ts b/crabfit-frontend/src/components/index.ts deleted file mode 100644 index 9147137..0000000 --- a/crabfit-frontend/src/components/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -export { default as TextField } from './TextField/TextField'; -export { default as SelectField } from './SelectField/SelectField'; -export { default as CalendarField } from './CalendarField/CalendarField'; -export { default as TimeRangeField } from './TimeRangeField/TimeRangeField'; -export { default as ToggleField } from './ToggleField/ToggleField'; - -export { default as Button } from './Button/Button'; -export { default as Legend } from './Legend/Legend'; -export { default as AvailabilityViewer } from './AvailabilityViewer/AvailabilityViewer'; -export { default as AvailabilityEditor } from './AvailabilityEditor/AvailabilityEditor'; -export { default as Error } from './Error/Error'; -export { default as Loading } from './Loading/Loading'; - -export { default as Center } from './Center/Center'; -export { default as Donate } from './Donate/Donate'; -export { default as Settings } from './Settings/Settings'; -export { default as Egg } from './Egg/Egg'; -export { default as Footer } from './Footer/Footer'; -export { default as Recents } from './Recents/Recents'; -export { default as Logo } from './Logo/Logo'; -export { default as UpdateDialog } from './UpdateDialog/UpdateDialog'; -export { default as TranslateDialog } from './TranslateDialog/TranslateDialog'; - -export const _GoogleCalendar = () => import('./GoogleCalendar/GoogleCalendar'); -export const _OutlookCalendar = () => import('./OutlookCalendar/OutlookCalendar'); diff --git a/crabfit-frontend/src/i18n/index.ts b/crabfit-frontend/src/i18n/index.js similarity index 54% rename from crabfit-frontend/src/i18n/index.ts rename to crabfit-frontend/src/i18n/index.js index 433fedf..e18ebe1 100644 --- a/crabfit-frontend/src/i18n/index.ts +++ b/crabfit-frontend/src/i18n/index.js @@ -1,11 +1,11 @@ -import i18n from 'i18next'; -import { initReactI18next } from 'react-i18next'; -import LanguageDetector from 'i18next-browser-languagedetector'; -import Backend from 'i18next-http-backend'; +import i18n from 'i18next' +import { initReactI18next } from 'react-i18next' +import LanguageDetector from 'i18next-browser-languagedetector' +import Backend from 'i18next-http-backend' -import locales from 'res/dayjs_locales'; +import locales from './locales' -const storedLang = localStorage.getItem('i18nextLng'); +const storedLang = localStorage.getItem('i18nextLng') i18n .use(LanguageDetector) @@ -15,7 +15,6 @@ i18n fallbackLng: 'en', supportedLngs: Object.keys(locales), ns: 'common', - defaultNS: 'common', debug: process.env.NODE_ENV !== 'production', interpolation: { escapeValue: false, @@ -24,6 +23,6 @@ i18n loadPath: '/i18n/{{lng}}/{{ns}}.json', }, storedLang, - }).then(() => document.documentElement.setAttribute('lang', i18n.language)); + }).then(() => document.documentElement.setAttribute('lang', i18n.language)) -export default i18n; +export default i18n diff --git a/crabfit-frontend/src/res/dayjs_locales.ts b/crabfit-frontend/src/i18n/locales.js similarity index 98% rename from crabfit-frontend/src/res/dayjs_locales.ts rename to crabfit-frontend/src/i18n/locales.js index a324171..651e7e6 100644 --- a/crabfit-frontend/src/res/dayjs_locales.ts +++ b/crabfit-frontend/src/i18n/locales.js @@ -78,6 +78,6 @@ const locales = { // weekStart: 1, // timeFormat: '12h', // }, -}; +} -export default locales; +export default locales diff --git a/crabfit-frontend/src/index.jsx b/crabfit-frontend/src/index.jsx new file mode 100644 index 0000000..daef4a1 --- /dev/null +++ b/crabfit-frontend/src/index.jsx @@ -0,0 +1,24 @@ +import { StrictMode, createElement } from 'react' +import { createRoot } from 'react-dom/client' +import { setup } from 'goober' +import { shouldForwardProp } from 'goober/should-forward-prop' +import { BrowserRouter } from 'react-router-dom' +import '/src/i18n' + +import App from './App' + +setup( + createElement, + undefined, undefined, + shouldForwardProp(prop => !prop.startsWith('$')) +) + +const root = createRoot(document.getElementById('app')) + +root.render( + + + + + +) diff --git a/crabfit-frontend/src/index.tsx b/crabfit-frontend/src/index.tsx deleted file mode 100644 index ed39b03..0000000 --- a/crabfit-frontend/src/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import App from './App'; -import 'i18n'; - -ReactDOM.render( - - - , - document.getElementById('root') -); diff --git a/crabfit-frontend/src/pages/Help/Help.jsx b/crabfit-frontend/src/pages/Help/Help.jsx new file mode 100644 index 0000000..198fef4 --- /dev/null +++ b/crabfit-frontend/src/pages/Help/Help.jsx @@ -0,0 +1,101 @@ +import { useEffect, useState } from 'react' +import { Link, useNavigate } from 'react-router-dom' +import { useTranslation, Trans } from 'react-i18next' + +import { Button, Center, Footer, AvailabilityViewer, Logo } from '/src/components' + +import { StyledMain, AboutSection, P, VideoWrapper, VideoLink } from '../Home/Home.styles' + +import { Step, FakeCalendar, FakeTimeRange, ButtonArea } from './Help.styles' + +import video_thumb from '/src/res/video_thumb.jpg' + +const Help = () => { + const navigate = useNavigate() + const { t } = useTranslation(['common', 'help']) + const [videoPlay, setVideoPlay] = useState(false) + + useEffect(() => { + document.title = t('help:name') + }, [t]) + + return <> + + + + + +

{t('help:name')}

+ {videoPlay ? ( + + + + ) : ( + { + e.preventDefault() + setVideoPlay(true) + }} + > + {t('common:video.button')} + {t('common:video.button')} + + )} +

{t('help:p1')}

+

{t('help:p2')}

+ + {t('help:s1')} +

Use the form at crab.fit to make a new event. You only need to put in the rough time period for when your event occurs here, not your availability.

+

{t('help:p4')}

+ +
SunMonTueWedThuFriSat
+
11121314151617
+
+

{t('help:p5')}

+ +
+
+
+ + {t('help:s2')} +

{t('help:p6')}

+

{t('help:p7')}

+ + + {t('help:s3')} +

{t('help:p8')}

+

{t('help:p9')}

+

{t('help:p10')}

+ +
+ + + + +
+
+
+
+ +