diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..1213c17 --- /dev/null +++ b/.babelrc @@ -0,0 +1,53 @@ +{ + "presets": ["next/babel", "@zeit/next-typescript/babel"], + "plugins": [ + "graphql-tag", + "lodash", + [ + "styled-components", + { "ssr": true, "displayName": true, "preprocess": false } + ], + + ["module-resolver", { + "root": ["./"], + "alias": { + "router": "./src/router", + "components": "./src/components", + "containers": "./src/containers", + "constants": "./src/constants", + "data": "./src/data", + "appRedux": "./src/redux", + "store": "./src/store", + "util": "./src/util", + "views": "./src/views", + } + }] + ], + "env": { + "production": { + "plugins": [ + "graphql-tag", + "lodash", + [ + "styled-components", + { "ssr": true, "displayName": false, "preprocess": false } + ], + + ["module-resolver", { + "root": ["./"], + "alias": { + "router": "./src/router", + "components": "./src/components", + "containers": "./src/containers", + "constants": "./src/constants", + "data": "./src/data", + "appRedux": "./src/redux", + "store": "./src/store", + "util": "./src/util", + "views": "./src/views", + } + }] + ] + } + } +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..85dcc16 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.git +node_modules diff --git a/.env.dist b/.env.dist new file mode 100644 index 0000000..2442aec --- /dev/null +++ b/.env.dist @@ -0,0 +1,2 @@ +SERVER_URI=http://localhost:3010 +GRAPHQL_PATH=/gql/v1 \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..f7517e4 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,10 @@ +.next/ +build/ +node_modules/ + +*.css +*.ico +*.jpg +*.png +*.txt +*.xml diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..b298b44 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,52 @@ +module.exports = { + extends: [ + "plugin:@typescript-eslint/recommended", + "plugin:import/errors", + "plugin:import/warnings", + "plugin:import/typescript", + "plugin:react/recommended", + "prettier", + "prettier/@typescript-eslint", + ], + parser: "@typescript-eslint/parser", + parserOptions: { + project: "./tsconfig.json", + }, + plugins: [ + "@typescript-eslint", + "graphql", + "import", + "json", + "react", + "react-hooks", + "simple-import-sort", + ], + rules: { + "@typescript-eslint/array-type": ["error", "array-simple"], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/explicit-function-return-type": "off", + + // waiting for full import support in TypeScript + "import/default": "off", + "import/order": "off", + "import/no-duplicates": "error", + "import/no-named-as-default": "off", + "import/no-named-as-default-member": "off", + "import/no-unresolved": "off", + "import/named": "off", + "import/namespace": "off", + + "no-unused-expressions": ["error", { allowTaggedTemplates: true }], + "no-unused-vars": "off", + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "error", + "simple-import-sort/sort": "error", + "sort-imports": "off", + }, + settings: { + react: { + version: "detect", // React version. "detect" automatically picks the version you have installed. + }, + }, +}; diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a7d337a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* linguist-vendored +*.ts linguist-vendored=false +*.tsx linguist-vendored=false \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2ad9707 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +.env +yarn-error.log + +/.next +/build +/lang/.messages +/node_modules +/locales/**/*.missing.json diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..1a5497e --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,33 @@ +stages: + - build-app + - build-image + +build app: + stage: build-app + image: node:10 + script: + - yarn + - yarn lint + - yarn build + cache: + paths: + - node_modules + artifacts: + paths: + - .next + - build + expire_in: 1 day + +build image: + stage: build-image + image: docker:latest + services: + - docker:dind + script: + - docker info + - echo $CI_REGISTRY_PASSWORD | docker login --username $CI_REGISTRY_USER $CI_REGISTRY --password-stdin + - docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG || docker pull $CI_REGISTRY_IMAGE:master || true + - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --cache-from=$CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME --cache-from=$CI_REGISTRY_IMAGE:master --shm-size 512M . + - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA + - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ae1d2f7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM node:10.15.1-alpine + +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app + +COPY package.json yarn.lock /usr/src/app/ +RUN YARN_CACHE_FOLDER=/dev/shm/yarn_cache yarn --production +ENV NODE_ICU_DATA=/usr/src/app/node_modules/full-icu + +COPY .next /usr/src/app/.next +COPY locales /usr/src/app/locales +COPY build /usr/src/app/build +COPY src/static /usr/src/app/build/static + +EXPOSE 3000 + +USER node + +CMD [ "yarn", "start" ] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4626c2e --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2019-present, Vuga (https://vuga.io) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4e59390 --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +#Alien Typer Shit: Frontend + +#####User-facing web app. this code produces a simple react js web app with SSR in TS. + + + +#### Key ingredients + +- **[TypeScript](https://www.typescriptlang.org/)** to ensure the highest code quality +- **[Next.js](https://github.com/zeit/next.js)** to bundle source files and render web pages both on the server and the client (SSR) + +- **[React Context](https://reactjs.org/docs/context.html)** for static react state +- ~~**[Redux](https://github.com/reduxjs/redux)** for static react state~~ +- **[Apollo GraphQL client](https://github.com/apollographql/apollo-client)** to get data from the backend +- **[Styled components](https://www.styled-components.com/)** to produce well-structured and collision-free CSS +- **[Lodash](https://lodash.com/)** to leverage common utility functions +- **[ESLint](https://eslint.org/)** and **[Prettier](https://prettier.io/)** to ensure that source files are error-free and easy to read +- **[Docker](https://www.docker.com/)** to make the production version of the microservice straightforward to deploy +- **[GitLab CI](https://about.gitlab.com/features/gitlab-ci-cd/)** to automatically check code quality and generate a new docker image on every push + +## Running a local copy + +1. Ensure you have the latest git, Node.js and Yarn installed on your machine + + ```bash + git --version + ## ≥ 2.14 + + node --version + ## ≥ 10.0 + + yarn --version + ## ≥ 1.10 + ``` + +1. Clone the repo + + ```bash + cd PATH/TO/MISC/PROJECTS + git clone https://github.com/vugga/ats-fe.git + cd ats-fe + ``` + +1. Install npm dependencies using Yarn + + ```bash + yarn + ``` + +1. Copy `.env.dist` to `.env`. You can have a look at [`src/config.ts`](src/config.ts) for hints on what is expected. + + +1. Start the server in development mode + + ```bash + yarn dev + ``` + + Modifying any of the files will refresh the app, thanks to Next.js hot module reloading. + To stop running the server, press `ctrl+c`. + +1. If you want to test a production copy of the microservice, build and run it like this: + + ```bash + yarn build + yarn start + ``` + diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..d8c868b --- /dev/null +++ b/nodemon.json @@ -0,0 +1,13 @@ +{ + "watch": [ + ".babelrc", + ".env", + "locales/**/*.json", + "src/next.config.js", + "src/*.ts" + ], + "ignore": ["locales/**/*.missing.json"], + "execMap": { + "ts": "ts-node --compiler-options '{\"module\":\"commonjs\"}'" + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ae5b8ea --- /dev/null +++ b/package.json @@ -0,0 +1,97 @@ +{ + "name": "ats-fe", + "version": "0.0.1", + "description": "Alien Typer Shit: Frontend", + "private": true, + "scripts": { + "analyze": "BUNDLE_ANALYZE=both yarn build", + "build": "next build src && rimraf build && tsc --project tsconfig.server.json && ln -s ../src/static build/static", + "export": "next export src", + "dev": "nodemon src/server.ts", + "fix": "yarn fix:eslint && yarn fix:prettier", + "fix:eslint": "eslint --fix \"src/**\"", + "fix:prettier": "prettier --write --ignore-path .gitignore \"**/*.{css,js,json,md,ts,tsx,yml}\"", + "lint": "yarn lint:eslint && yarn lint:prettier && yarn lint:tsc", + "lint:eslint": "eslint \"src/**\"", + "lint:prettier": "prettier --check --ignore-path .gitignore \"**/*.{css,js,json,md,ts,tsx,yml}\"", + "lint:tsc": "tsc --project .", + "start": "NODE_ENV=production node build/server.js" + }, + "engines": { + "node": ">=10.0.0", + "yarn": "^1.10.0" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "**/*.{css,js,json,md,ts,tsx,yml}": [ + "prettier --write", + "git add" + ] + }, + "dependencies": { + "apollo-boost": "^0.1.28", + "apollo-client-preset": "^1.0.8", + "apollo-link-persisted-queries": "^0.2.2", + "compression": "^1.7.3", + "envalid": "^4.2.2", + "express": "^4.16.4", + "formik": "^1.5.1", + "full-icu": "^1.2.1", + "glob": "^7.1.3", + "graphql": "^14.1.1", + "graphql-tag": "^2.10.1", + "isomorphic-fetch": "^2.2.1", + "isomorphic-unfetch": "^3.0.0", + "js-cookie": "^2.2.0", + "lodash": "^4.17.11", + "next": "^8.0.3", + "next-cookies": "^1.1.2", + "next-routes": "^1.4.2", + "normalize.css": "^8.0.1", + "nprogress": "^0.2.0", + "polished": "^3.2.0", + "react": "^16.8.3", + "react-apollo": "^2.4.1", + "react-dom": "^16.8.3", + "styled-components": "^4.1.3", + "styled-system": "^4.0.8" + }, + "devDependencies": { + "@types/lodash": "^4.14.121", + "@types/next": "^8.0.1", + "@types/nprogress": "^0.0.29", + "@types/react": "^16.8.4", + "@types/styled-components": "^4.1.10", + "@types/styled-system": "^4.0.0", + "@typescript-eslint/eslint-plugin": "^1.4.1", + "@zeit/next-bundle-analyzer": "^0.1.2", + "@zeit/next-typescript": "^1.1.1", + "babel-plugin-graphql-tag": "^2.0.0", + "babel-plugin-inline-import": "^3.0.0", + "babel-plugin-lodash": "^3.3.4", + "babel-plugin-module-resolver": "^3.2.0", + "babel-plugin-styled-components": "^1.10.0", + "eslint": "^5.14.1", + "eslint-config-prettier": "^4.0.0", + "eslint-plugin-graphql": "^3.0.3", + "eslint-plugin-import": "^2.16.0", + "eslint-plugin-json": "^1.4.0", + "eslint-plugin-react": "^7.12.4", + "eslint-plugin-react-hooks": "^1.2.0", + "eslint-plugin-simple-import-sort": "^3.0.0", + "husky": "^1.3.1", + "lint-staged": "^8.1.4", + "lodash-webpack-plugin": "^0.11.5", + "module-resolver": "^1.0.0", + "next-compose-plugins": "^2.1.1", + "nodemon": "^1.18.10", + "prettier": "^1.16.4", + "rimraf": "^2.6.3", + "ts-node": "^8.0.2", + "typescript": "^3.3.3333" + } +} \ No newline at end of file diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..3bdedb3 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,5 @@ +module.exports = { + arrowParens: "always", + endOfLine: "lf", + trailingComma: "all", +}; diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..84551bb --- /dev/null +++ b/src/config.ts @@ -0,0 +1,9 @@ +import * as envalid from "envalid"; + +export const env = envalid.cleanEnv(process.env, { + SERVER_URI: envalid.str(), + GRAPHQL_PATH: envalid.str(), + NODE_ENV: envalid.str({ default: "development" }), + PORT: envalid.port({ default: 3000 }), + ENFORCED_LOCALE: envalid.str({ default: "" }), +}); diff --git a/src/constants/env.ts b/src/constants/env.ts new file mode 100644 index 0000000..97e291f --- /dev/null +++ b/src/constants/env.ts @@ -0,0 +1,10 @@ +export const DEV = process.env.NODE_ENV !== "production"; + +export const GA_TRACKING_ID = ""; +export const FB_TRACKING_ID = ""; +export const SENTRY_TRACKING_ID = ""; + +export const SITE_NAME = ""; +export const SITE_TITLE = ""; +export const SITE_DESCRIPTION = ""; +export const SITE_IMAGE = ""; diff --git a/src/lib/initApollo.ts b/src/lib/initApollo.ts new file mode 100644 index 0000000..8fead4a --- /dev/null +++ b/src/lib/initApollo.ts @@ -0,0 +1,50 @@ +import { + ApolloClient, + HttpLink, + InMemoryCache, + ApolloLink, +} from "apollo-boost"; +import { createPersistedQueryLink } from "apollo-link-persisted-queries"; +import fetch from "isomorphic-unfetch"; +// import { LoggingLink, wrapPubSub, formatResponse } from "apollo-logger"; + +// const logOptions = { logger: console.log }; + +let apolloClient; + +const isBrowser = typeof window !== "undefined"; + +// Polyfill fetch() on the server (used by apollo-client) +if (!isBrowser) { + (global as any).fetch = fetch; +} + +function create({ uri, initialState }) { + return new ApolloClient({ + connectToDevTools: isBrowser, + ssrMode: !isBrowser, + link: createPersistedQueryLink().concat( + ApolloLink.from([ + // cleanTypenameLink, + // new LoggingLink(logOptions), + new HttpLink({ uri }), + ]), + ), + cache: new InMemoryCache().restore(initialState || {}), + }); +} + +export default function initApollo(options) { + // Make sure to create a new client for every server-side request so that data + // isn't shared between connections (which would be bad) + if (!isBrowser) { + return create(options); + } + + // Reuse client on the client-side + if (!apolloClient) { + apolloClient = create(options); + } + + return apolloClient; +} diff --git a/src/lib/initRedux.ts b/src/lib/initRedux.ts new file mode 100644 index 0000000..685a04b --- /dev/null +++ b/src/lib/initRedux.ts @@ -0,0 +1,49 @@ +import { createStore, Store } from "redux"; +import thunkMiddleware from "redux-thunk"; +import { reducer as rootReducer, rootInitState } from "appRedux"; + +import createMiddleware from "./middleware"; +// import persist from "./persist"; + +let reduxStore = null; +const middleware = createMiddleware(thunkMiddleware); + +interface Props { + initialState?: any; + token?: string; +} + +export default (props?: Props): Store => { + const initStore = props && props.initialState? props.initialState : rootInitState; + let store; + if (!process.browser || !reduxStore) { + store = createStore(rootReducer, initStore , middleware); + + /** + let tokenInStore = store.getState().auth.token; + + if (!tokenInStore) { + (async () => { + tokenInStore = + token || (await Promise.resolve(persist.willGetAccessToken())); + + if (typeof token === 'string' && !token.includes('Error')) { + if (token.length) { + store.dispatch(dispatchers.signIn(token)); + } else { + store.dispatch(dispatchers.signOut()); + } + } else { + store.dispatch(dispatchers.signOut()); + } + })(); + } + **/ + + if (!process.browser) { + return store; + } + reduxStore = store; + } + return reduxStore; +}; diff --git a/src/lib/middleware.ts b/src/lib/middleware.ts new file mode 100644 index 0000000..b1ae55f --- /dev/null +++ b/src/lib/middleware.ts @@ -0,0 +1,6 @@ +import { applyMiddleware } from "redux"; +import { composeWithDevTools } from "redux-devtools-extension"; + +export default function createMiddleware(clientMiddleware) { + return composeWithDevTools(applyMiddleware(clientMiddleware)); +} diff --git a/src/lib/pageEvents.ts b/src/lib/pageEvents.ts new file mode 100644 index 0000000..e88e7c7 --- /dev/null +++ b/src/lib/pageEvents.ts @@ -0,0 +1,24 @@ +import * as _ from "lodash"; +import Router from "next/router"; +import NProgress from "nprogress"; + +// import { logPageView } from "./gtag"; + +if (typeof window !== "undefined") { + // const debouncedLogPageView = _.debounce(logPageView, 500); + + NProgress.configure({ showSpinner: false }); + + Router.events.on("routeChangeStart", () => { + NProgress.start(); + }); + + Router.events.on("routeChangeComplete", () => { + NProgress.done(); + // debouncedLogPageView(); + }); + + Router.events.on("routeChangeError", () => { + NProgress.done(); + }); +} diff --git a/src/lib/persist.ts b/src/lib/persist.ts new file mode 100644 index 0000000..938110c --- /dev/null +++ b/src/lib/persist.ts @@ -0,0 +1,20 @@ +// @flow +import cookies from "js-cookie"; + +export default class persist { + static get ACCESS_TOKEN_KEY(): string { + return "accessToken"; + } + + static async willGetAccessToken() { + return cookies.get(persist.ACCESS_TOKEN_KEY); + } + + static async willSetAccessToken(value: string) { + return cookies.set(persist.ACCESS_TOKEN_KEY, value); + } + + static async willRemoveAccessToken() { + return cookies.remove(persist.ACCESS_TOKEN_KEY); + } +} diff --git a/src/lib/removeCommentsAndSpacing.ts b/src/lib/removeCommentsAndSpacing.ts new file mode 100644 index 0000000..8b70e47 --- /dev/null +++ b/src/lib/removeCommentsAndSpacing.ts @@ -0,0 +1,2 @@ +export default (str = "") => + str.replace(/\/\*.*\*\//g, " ").replace(/\s+/g, " "); diff --git a/src/lib/withData.tsx b/src/lib/withData.tsx new file mode 100644 index 0000000..2ef79e5 --- /dev/null +++ b/src/lib/withData.tsx @@ -0,0 +1,93 @@ +import Head from "next/head"; +import React from "react"; +import { getDataFromTree } from "react-apollo"; + +import initApollo from "./initApollo"; +// import initRedux from "./initRedux"; +// import { Store } from "redux"; + +const isBrowser = typeof window !== "undefined"; + +export default (App) => { + return class Apollo extends React.Component<{ + graphqlUrl: string; + apolloState: any; + // reduxState: any; + }> { + public static displayName = "withApollo(App)"; + public static async getInitialProps(ctx) { + const { Component, router } = ctx; + + let appProps = {}; + if (App.getInitialProps) { + appProps = await App.getInitialProps(ctx); + } + + const { graphqlUri, serverUri } = + ctx.ctx.req || (window as any).__NEXT_DATA__.props; // coming from the env + + // Run all GraphQL queries in the component tree + // and extract the resulting data + const apollo = initApollo({ uri: graphqlUri }); + // let reduxState: Store = initRedux(); + if (!isBrowser) { + // reduxState = initRedux(); + try { + // Run all GraphQL queries + await getDataFromTree( + , + ); + } catch (error) { + // Prevent Apollo Client GraphQL errors from crashing SSR. + // Handle them in components via the data.error prop: + // https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error + console.error("Error while running `getDataFromTree`", error); + } + + // getDataFromTree does not call componentWillUnmount + // head side effect therefore need to be cleared manually + Head.rewind(); + } + + // Extract query data from the Apollo store + const apolloState = apollo.cache.extract(); + // const serverState = reduxState.getState(); + + return { + ...appProps, + apolloState, + // reduxState: serverState, + graphqlUri, + serverUri, + }; + } + private apolloClient; + private reduxStore; + + public constructor(props) { + super(props); + this.apolloClient = initApollo({ + initialState: props.apolloState, + uri: props.graphqlUri, + }); + + // this.reduxStore = initRedux(props.reduxState); + } + + public render() { + return ( + + ); + } + }; +}; diff --git a/src/lngProvider/entries/en-US.ts b/src/lngProvider/entries/en-US.ts new file mode 100644 index 0000000..96dc447 --- /dev/null +++ b/src/lngProvider/entries/en-US.ts @@ -0,0 +1,13 @@ +import antdEn from "antd/lib/locale-provider/en_US"; +import appLocaleData from "react-intl/locale-data/en"; +import enMessages from "../locales/en_US.json"; + +const EnLang = { + messages: { + ...enMessages, + }, + antd: antdEn, + locale: "en-US", + data: appLocaleData, +}; +export default EnLang; diff --git a/src/lngProvider/entries/fr_FR.ts b/src/lngProvider/entries/fr_FR.ts new file mode 100644 index 0000000..02e5d8b --- /dev/null +++ b/src/lngProvider/entries/fr_FR.ts @@ -0,0 +1,13 @@ +import antdSA from "antd/lib/locale-provider/fr_FR"; +import appLocaleData from "react-intl/locale-data/fr"; +import saMessages from "../locales/fr_FR.json"; + +const saLang = { + messages: { + ...saMessages, + }, + antd: antdSA, + locale: "fr-FR", + data: appLocaleData, +}; +export default saLang; diff --git a/src/lngProvider/index.ts b/src/lngProvider/index.ts new file mode 100644 index 0000000..380638e --- /dev/null +++ b/src/lngProvider/index.ts @@ -0,0 +1,12 @@ +import { addLocaleData } from "react-intl"; +import enLang from "./entries/en-US"; +import frLang from "./entries/fr_FR"; + +const AppLocale = { + en: enLang, + fr: frLang, +}; +addLocaleData(AppLocale.en.data); +addLocaleData(AppLocale.fr.data); + +export default AppLocale; diff --git a/src/lngProvider/locales/en_US.json b/src/lngProvider/locales/en_US.json new file mode 100644 index 0000000..49318f0 --- /dev/null +++ b/src/lngProvider/locales/en_US.json @@ -0,0 +1,3 @@ +{ + "app-name": "ATS Frontend" +} diff --git a/src/lngProvider/locales/fr_FR.json b/src/lngProvider/locales/fr_FR.json new file mode 100644 index 0000000..49318f0 --- /dev/null +++ b/src/lngProvider/locales/fr_FR.json @@ -0,0 +1,3 @@ +{ + "app-name": "ATS Frontend" +} diff --git a/src/next.config.js b/src/next.config.js new file mode 100644 index 0000000..f2be8b1 --- /dev/null +++ b/src/next.config.js @@ -0,0 +1,40 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ + +const LodashModuleReplacementPlugin = require("lodash-webpack-plugin"); +const withBundleAnalyzer = require("@zeit/next-bundle-analyzer"); +// const withCss = require("@zeit/next-css"); +const withTypescript = require("@zeit/next-typescript"); +const withPlugins = require("next-compose-plugins"); + +module.exports = withPlugins( + [ + withTypescript, + // withCss, + [ + withBundleAnalyzer, + { + analyzeServer: ["server", "both"].includes(process.env.BUNDLE_ANALYZE), + analyzeBrowser: ["browser", "both"].includes( + process.env.BUNDLE_ANALYZE, + ), + }, + ], + ], + { + distDir: "../.next", + // exportPathMap: function() { + // return { + // '/': { page: '/' } + // } + // }, + webpack: (config, { isServer }) => { + config.plugins.push(new LodashModuleReplacementPlugin({ paths: true })); + if (!isServer) { + config.externals = { + "./config": "{}", + }; + } + return config; + }, + }, +); diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx new file mode 100644 index 0000000..975fa08 --- /dev/null +++ b/src/pages/_app.tsx @@ -0,0 +1,36 @@ +import "../lib/pageEvents"; + +import ApolloClient from "apollo-client"; +import App, { Container } from "next/app"; +import React from "react"; +import { ApolloProvider } from "react-apollo"; +// import { Provider } from 'react-redux'; +// import { Store } from "redux"; +import withData from "../lib/withData"; + +class MyApp extends App<{ apolloClient: ApolloClient<{}>, reduxStore: Store }> { + public static async getInitialProps({ Component, ctx }) { + let pageProps = {}; + + if (Component.getInitialProps) { + pageProps = await Component.getInitialProps(ctx); + } + + return { pageProps }; + } + + public render() { + const { Component, pageProps, apolloClient, /**reduxStore */ } = this.props; + return ( + + + {/* */} + + {/* */} + + + ); + } +} + +export default withData(MyApp); diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx new file mode 100644 index 0000000..0002e0e --- /dev/null +++ b/src/pages/_document.tsx @@ -0,0 +1,129 @@ +import { readFileSync } from "fs"; +import * as _ from "lodash"; +import Document, { Head, Main, NextScript } from "next/document"; +import { resolve } from "path"; +import React from "react"; +import { ServerStyleSheet } from "styled-components"; + +// import removeCommentsAndSpacing from "../lib/removeCommentsAndSpacing"; + +const nextDir = resolve(process.cwd(), ".next"); +const doGetContent = (file) => readFileSync(resolve(nextDir, file), "utf8"); +const getContent = + process.env.NODE_ENV === "production" + ? _.memoize(doGetContent) + : doGetContent; +class InlineStylesHead extends Head { + public getCssLinks() { + return this.__getInlineStyles(); + } + + public __getInlineStyles() { + const { assetPrefix, files } = (this as any).context._documentProps; + if (!files || files.length === 0) { + return null; + } + + return files + .filter((file) => /\.css$/.test(file)) + .map((file) => ( +