From c62e66a86aec6c9aa1d55fea1e947094c275eca1 Mon Sep 17 00:00:00 2001 From: Divlo Date: Sat, 8 May 2021 19:52:04 +0200 Subject: [PATCH] feat: add light mode + rewrite in Tailwind CSS (#15) --- .babelrc.json | 11 +- .env.example | 6 +- .eslintignore | 6 + .eslintrc.json | 15 + .github/dependabot.yml | 8 +- .gitpod.yml | 13 +- .husky/pre-commit | 5 +- .lintstagedrc.json | 11 + .prettierignore | 8 + .prettierrc.json | 6 + .vscode/extensions.json | 9 +- .vscode/settings.json | 13 +- CONTRIBUTING.md | 1 - Dockerfile | 15 +- __test__/pages/api/send-email.test.ts | 70 - components/Contact/FormResult.tsx | 31 - components/Contact/FormState.tsx | 39 - components/Contact/index.tsx | 89 - components/ErrorPage.tsx | 46 +- components/Footer.tsx | 26 +- components/Head.tsx | 6 +- components/Header/Language/Arrow.tsx | 2 +- components/Header/Language/LanguageFlag.tsx | 12 +- components/Header/Language/index.tsx | 91 +- components/Header/SwitchTheme.tsx | 127 + components/Header/index.tsx | 101 +- components/Interests/InterestParagraph.tsx | 6 +- .../Interests/InterestsList/InterestItem.tsx | 35 +- components/Interests/InterestsList/index.tsx | 49 +- components/Interests/index.tsx | 12 +- components/Portfolio/PortfolioItem.tsx | 115 +- components/Portfolio/index.tsx | 24 +- .../Profile/ProfileDescriptionBottom.tsx | 28 +- components/Profile/ProfileInfo.tsx | 11 +- .../Profile/ProfileList/ProfileItem.tsx | 15 +- components/Profile/ProfileList/index.tsx | 36 +- components/Profile/ProfileLogo.tsx | 28 +- .../SocialMediaIcons/EmailIcon.tsx | 10 + .../SocialMediaIcons/GitHubIcon.tsx | 10 + .../SocialMediaList/SocialMediaIcons/Icon.tsx | 14 + .../SocialMediaIcons/TwitchIcon.tsx | 10 + .../SocialMediaIcons/TwitterIcon.tsx | 10 + .../SocialMediaIcons/YouTubeIcon.tsx | 10 + .../SocialMediaList/SocialMediaItem.tsx | 56 +- components/Profile/SocialMediaList/index.tsx | 60 +- components/Profile/index.tsx | 30 +- components/Skills/Skill.tsx | 42 +- components/Skills/SkillsSection.tsx | 41 +- components/design/Button.tsx | 43 - components/design/Input.tsx | 75 - components/design/RevealFade.tsx | 31 +- components/design/Section/SectionHeading.tsx | 26 +- components/design/Section/index.tsx | 26 +- components/design/ShadowContainer.tsx | 14 +- components/design/Textarea.tsx | 39 - components/design/Tooltip.tsx | 49 - components/design/__test__/Button.test.tsx | 10 - components/design/__test__/Input.test.tsx | 11 - docker-compose.yml | 11 +- jest.config.js | 1 + locales/en/home.json | 15 - locales/fr/home.json | 15 - package-lock.json | 10508 ++++++++-------- package.json | 64 +- pages/404.tsx | 1 - pages/500.tsx | 1 - pages/_app.tsx | 21 +- pages/_document.tsx | 31 + pages/api/send-email.ts | 61 - pages/index.tsx | 13 +- postcss.config.js | 17 +- public/images/divlo_logo.png | Bin 83439 -> 116651 bytes public/images/portfolio/functiondivlofr.png | Bin 114122 -> 112504 bytes public/images/portfolio/threamdivlofr.png | Bin 70724 -> 67630 bytes public/images/web/Email.png | Bin 8783 -> 0 bytes public/images/web/GitHub.png | Bin 2011 -> 0 bytes public/images/web/Twitch.png | Bin 1613 -> 0 bytes public/images/web/Twitter.png | Bin 3271 -> 0 bytes public/images/web/YouTube.png | Bin 4974 -> 0 bytes styles/general.scss | 95 - styles/grid.scss | 710 -- styles/nprogress.scss | 85 - tailwind.config.js | 34 + 83 files changed, 5803 insertions(+), 7623 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc.json create mode 100644 .lintstagedrc.json create mode 100644 .prettierignore create mode 100644 .prettierrc.json delete mode 100644 __test__/pages/api/send-email.test.ts delete mode 100644 components/Contact/FormResult.tsx delete mode 100644 components/Contact/FormState.tsx delete mode 100644 components/Contact/index.tsx create mode 100644 components/Header/SwitchTheme.tsx create mode 100644 components/Profile/SocialMediaList/SocialMediaIcons/EmailIcon.tsx create mode 100644 components/Profile/SocialMediaList/SocialMediaIcons/GitHubIcon.tsx create mode 100644 components/Profile/SocialMediaList/SocialMediaIcons/Icon.tsx create mode 100644 components/Profile/SocialMediaList/SocialMediaIcons/TwitchIcon.tsx create mode 100644 components/Profile/SocialMediaList/SocialMediaIcons/TwitterIcon.tsx create mode 100644 components/Profile/SocialMediaList/SocialMediaIcons/YouTubeIcon.tsx delete mode 100644 components/design/Button.tsx delete mode 100644 components/design/Input.tsx delete mode 100644 components/design/Textarea.tsx delete mode 100644 components/design/Tooltip.tsx delete mode 100644 components/design/__test__/Button.test.tsx delete mode 100644 components/design/__test__/Input.test.tsx create mode 100644 pages/_document.tsx delete mode 100644 pages/api/send-email.ts delete mode 100644 public/images/web/Email.png delete mode 100644 public/images/web/GitHub.png delete mode 100644 public/images/web/Twitch.png delete mode 100644 public/images/web/Twitter.png delete mode 100644 public/images/web/YouTube.png delete mode 100644 styles/general.scss delete mode 100644 styles/grid.scss delete mode 100644 styles/nprogress.scss create mode 100644 tailwind.config.js diff --git a/.babelrc.json b/.babelrc.json index 02d0f1b..1ff94f7 100644 --- a/.babelrc.json +++ b/.babelrc.json @@ -1,12 +1,3 @@ { - "presets": [ - [ - "next/babel", - { - "styled-jsx": { - "plugins": ["@styled-jsx/plugin-sass"] - } - } - ] - ] + "presets": ["next/babel"] } diff --git a/.env.example b/.env.example index 05c02d5..f46d98e 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,2 @@ -COMPOSE_PROJECT_NAME=divlo.fr-website +COMPOSE_PROJECT_NAME=divlo.fr PORT=3000 -EMAIL_HOST=divlo.fr-maildev -EMAIL_USER=reply@divlo-website.fr -EMAIL_PASSWORD=password -EMAIL_PORT=25 diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..7e9eaac --- /dev/null +++ b/.eslintignore @@ -0,0 +1,6 @@ +.next +.lighthouseci +node_modules +next-env.d.ts +**/workbox-*.js +**/sw.js diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..b426668 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,15 @@ +{ + "extends": ["standard-with-typescript", "eslint-config-prettier"], + "plugins": ["eslint-plugin-prettier"], + "parserOptions": { + "project": "./tsconfig.json" + }, + "env": { + "node": true, + "browser": true, + "jest": true + }, + "rules": { + "prettier/prettier": "error" + } +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c50b9bd..768da71 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,7 +10,7 @@ updates: schedule: interval: 'daily' - # - package-ecosystem: 'npm' - # directory: '/' - # schedule: - # interval: 'daily' + - package-ecosystem: 'npm' + directory: '/' + schedule: + interval: 'daily' diff --git a/.gitpod.yml b/.gitpod.yml index 75064c8..572ad93 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,21 +1,14 @@ image: 'gitpod/workspace-full' tasks: - - name: 'docker-daemon' - init: 'cp .env.example .env && npm install --global npm@7 && npm ci' - command: 'sudo docker-up' - - name: 'docker-container' - init: 'echo "Waiting for docker daemon to start" && - until docker info &> /dev/null; do sleep 1; done;' - command: 'docker-compose up' + - before: 'cp .env.example .env && npm install --global npm@7' + init: 'npm clean-install' + command: 'npm run dev' ports: - port: 3000 onOpen: 'open-preview' - - port: 1080 - onOpen: 'notify' - github: prebuilds: master: true diff --git a/.husky/pre-commit b/.husky/pre-commit index 2b2fddb..3199e8e 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,7 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -npm run lint:docker -npm run lint:editorconfig -npm run lint:markdown -npm run lint:typescript +npm run lint:staged diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 0000000..6f5ee7c --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,11 @@ +{ + "*": ["editorconfig-checker"], + "*.{js,ts,jsx,tsx}": [ + "prettier --write", + "eslint --fix", + "jest --findRelatedTests" + ], + "*.{css,yml,json}": ["prettier --write"], + "*.{md}": ["prettier --write", "markdownlint --dot --fix"], + "./Dockerfile": ["dockerfilelint"] +} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..6d4bdb9 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,8 @@ +.next +.lighthouseci +node_modules +next-env.d.ts +package.json +package-lock.json +**/workbox-*.js +**/sw.js diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..83ef1ce --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "jsxSingleQuote": true, + "semi": false, + "trailingComma": "none" +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 42a93ef..68345f7 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,12 +1,13 @@ { "recommendations": [ + "editorconfig.editorconfig", + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", "divlo.vscode-styled-jsx-syntax", "divlo.vscode-styled-jsx-languageserver", - "standard.vscode-standard", + "bradlc.vscode-tailwindcss", "mikestead.dotenv", - "editorconfig.editorconfig", "coenraads.bracket-pair-colorizer", - "davidanson.vscode-markdownlint", - "syler.sass-indented" + "davidanson.vscode-markdownlint" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 320a6bb..f7a833f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,9 @@ { - "standard.enable": true, - "standard.engine": "ts-standard", - "standard.treatErrorsAsWarnings": true, - "standard.usePackageJson": true, - "standard.autoFixOnSave": true, - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "editor.defaultFormatter": "esbenp.prettier-vscode", + "prettier.configPath": ".prettierrc.json", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll": true + } } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8c9dc3a..ad9def7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,4 +72,3 @@ docker-compose up --build ### Services started - website : `http://localhost:3000` -- [MailDev](https://maildev.github.io/maildev/) : `http://localhost:1080` diff --git a/Dockerfile b/Dockerfile index f0f9a0e..39f1886 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,11 @@ -FROM node:14.16.1 -RUN npm install --global npm@7 +FROM node:16.1.0 -WORKDIR /app - -COPY ./package*.json ./ +WORKDIR /usr/src/app +RUN chown --recursive node:node /usr/src/app +COPY --chown=node:node ./package*.json ./ RUN npm install -COPY ./ ./ +COPY --chown=node:node ./ ./ -CMD ["npm", "run", "dev", "--", "--port", "${PORT}"] +USER node +RUN npm run build +CMD ["npm", "run", "start", "--", "--port", "${PORT}"] diff --git a/__test__/pages/api/send-email.test.ts b/__test__/pages/api/send-email.test.ts deleted file mode 100644 index b78e7af..0000000 --- a/__test__/pages/api/send-email.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { createMocks } from 'node-mocks-http' - -import handleSendEmail from 'pages/api/send-email' - -jest.mock('nodemailer', () => ({ - createTransport: () => { - return { - sendMail: jest.fn(async () => {}) - } - } -})) - -describe('POST /api/send-email', () => { - it('succeeds and send the email', async () => { - const { req, res } = createMocks({ - method: 'POST', - body: { - name: 'Divlo', - email: 'contact@divlo.fr', - subject: 'Subject', - message: 'Hello world!' - } - }) - await handleSendEmail(req, res) - expect(res._getStatusCode()).toBe(201) - expect(JSON.parse(res._getData())).toEqual( - expect.objectContaining({ - type: 'success' - }) - ) - }) - - it('fails with empty values', async () => { - const { req, res } = createMocks({ - method: 'POST', - body: { - name: '', - email: '', - subject: '', - message: '' - } - }) - await handleSendEmail(req, res) - expect(res._getStatusCode()).toBe(400) - expect(JSON.parse(res._getData())).toEqual( - expect.objectContaining({ - type: 'requiredFields' - }) - ) - }) - - it('fails with invalid email', async () => { - const { req, res } = createMocks({ - method: 'POST', - body: { - name: 'Name', - email: 'random wrong email', - subject: 'Subject', - message: 'Message' - } - }) - await handleSendEmail(req, res) - expect(res._getStatusCode()).toBe(400) - expect(JSON.parse(res._getData())).toEqual( - expect.objectContaining({ - type: 'invalidEmail' - }) - ) - }) -}) diff --git a/components/Contact/FormResult.tsx b/components/Contact/FormResult.tsx deleted file mode 100644 index 27c6a6d..0000000 --- a/components/Contact/FormResult.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import useTranslation from 'next-translate/useTranslation' - -import { FormState } from './FormState' -import { ResultState } from './index' - -export interface FormResultProps { - state: ResultState -} - -export const FormResult: React.FC = (props) => { - const { state } = props - const { t } = useTranslation() - - if (state === 'idle') { - return null - } - - if (state === 'loading' || state === 'success') { - return ( - - {t(`home:contact.result.${state}`)} - - ) - } - - return ( - - {t(`home:contact.result.${state}`)} - - ) -} diff --git a/components/Contact/FormState.tsx b/components/Contact/FormState.tsx deleted file mode 100644 index cd7c90f..0000000 --- a/components/Contact/FormState.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import useTranslation from 'next-translate/useTranslation' - -export interface FormStateProps extends React.ComponentPropsWithRef<'p'> { - state: 'success' | 'error' | 'loading' - children: string -} - -export const FormState: React.FC = props => { - const { state, children, ...rest } = props - const { t } = useTranslation() - - return ( - <> -
-

- {['error', 'success'].includes(state) && ( - - {state === 'error' ? t('home:contact.error') : t('home:contact.success')}: - - )}{' '} - {children} -

-
- - - - ) -} diff --git a/components/Contact/index.tsx b/components/Contact/index.tsx deleted file mode 100644 index 0de6746..0000000 --- a/components/Contact/index.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import useTranslation from 'next-translate/useTranslation' -import { useState } from 'react' -import Form, { HandleForm } from 'react-component-form' -import axios from 'axios' - -import { Input } from 'components/design/Input' -import { Button } from 'components/design/Button' -import { Textarea } from 'components/design/Textarea' -import { FormResult } from './FormResult' - -export const resultState = [ - 'idle', - 'success', - 'loading', - 'requiredFields', - 'invalidEmail', - 'serverError' -] as const - -export type ResultState = typeof resultState[number] - -export const Contact: React.FC = () => { - const { t } = useTranslation() - const [state, setState] = useState('idle') - - const handleSubmit: HandleForm = async (formData, formElement) => { - setState('loading') - try { - const { data } = await axios.post<{ type: ResultState }>( - '/api/send-email', - formData - ) - if (data.type === 'success') { - setState('success') - return formElement.reset() - } - return setState('serverError') - } catch (error) { - const type = error.response.data.type - if (resultState.includes(type)) { - return setState(type) - } - return setState('serverError') - } - } - - return ( - <> -
-
- - - - -