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 ( - <> -
-
- - - - -