diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..9d54872 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,41 @@ +{ + "parser": "@typescript-eslint/parser", + "parserOptions": { + "jsx": true, + "useJSXTextNode": true + }, + "plugins": ["@typescript-eslint"], + "extends": [ + "plugin:@typescript-eslint/recommended", + "prettier", + "prettier/@typescript-eslint", + "plugin:react/recommended", + "plugin:react-hooks/recommended" + ], + "rules": { + "no-unused-vars": "off", + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", + "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "vars": "all", + "args": "after-used", + "ignoreRestSiblings": false, + "argsIgnorePattern": "^_" + } + ] + }, + "overrides": [ + { + "files": ["*.js"], + "rules": { + "@typescript-eslint/no-var-requires": "off" + } + } + ] +} diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..9f10b14 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "singleQuote": true, + "printWidth": 100, + "semi": true, + "trailingComma": "all", + "bracketSpacing": true, + "jsxBracketSameLine": false, + "arrowParens": "always" +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..23fd35f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.formatOnSave": true +} \ No newline at end of file diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..01c9725 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,36 @@ +module.exports = { + // The root of your source code, typically /src + // `` is a token Jest substitutes + roots: ['/src'], + + // Jest transformations -- this adds support for TypeScript + // using ts-jest + transform: { + '^.+\\.tsx?$': 'ts-jest', + '^.+\\.svg$': '/src/test-util/svgTransform.js', + }, + + // Runs special logic, such as cleaning up components + // when using React Testing Library and adds special + // extended assertions to Jest + setupFilesAfterEnv: ['./src/test-util/setupTest.ts'], + globalSetup: './src/test-util/globalSetup.js', + collectCoverageFrom: [ + '/**/*.ts', + '/**/*.tsx', + '!/__tests__/**/*.ts', + '!/__tests__/**/*.tsx', + '!/**/*.d.ts', + ], + + // The directory where Jest should output its coverage files + coverageDirectory: '/../coverage', + // Test spec file resolution pattern + // Matches parent folder `__tests__` and filename + // should contain `test` or `spec`. + testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', + + // Module file extensions for importing + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + testEnvironment: 'jest-environment-jsdom-sixteen', +}; diff --git a/package.json b/package.json index f09dd4d..4d1f748 100644 --- a/package.json +++ b/package.json @@ -2,28 +2,65 @@ "name": "frontend-test-react", "version": "1.0.0", "description": "Technical test for Frontend Engineer position at Aircall", - "main": "index.js", "repository": "https://github.com/aircall/frontend-test-react.git", "author": "Xavier Durand ", "license": "MIT", "private": false, "scripts": { "start": "webpack-dev-server --mode development --open", - "build": "webpack --mode production" + "build": "webpack --mode production", + "lint:fix": "yarn lint --fix", + "lint": "eslint . --ext .js --ext .ts --ext .tsx", + "prettier": "prettier --write 'src/**/*.ts' 'src/**/*.tsx'", + "test": "jest" }, "dependencies": { - "react": "^16.3.1", - "react-dom": "^16.3.1" + "@reduxjs/toolkit": "^1.4.0", + "axios": "^0.19.2", + "date-fns": "^2.14.0", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-redux": "^7.2.0", + "react-router-dom": "^5.2.0", + "redux": "^4.0.5", + "redux-devtools-extension": "^2.13.8", + "redux-saga": "^1.1.3", + "styled-components": "^5.1.1", + "ts-jest": "^26.1.3", + "url-loader": "^4.1.0" }, "devDependencies": { + "@testing-library/jest-dom": "^5.11.1", + "@testing-library/react": "^10.4.7", + "@types/jest": "^26.0.5", + "@types/react": "^16.9.43", + "@types/react-dom": "^16.9.8", + "@types/react-redux": "^7.1.9", + "@types/react-router-dom": "^5.1.5", + "@types/styled-components": "^5.1.1", + "@typescript-eslint/eslint-plugin": "^3.6.1", + "@typescript-eslint/parser": "^3.6.1", + "axios-mock-adapter": "^1.18.2", "babel-core": "6.26.*", + "babel-eslint": "^10.1.0", "babel-loader": "7.1.*", "babel-preset-env": "1.7.0", "babel-preset-react": "6.24.*", "css-loader": "2.1.*", + "eslint": "^7.5.0", + "eslint-config-prettier": "^6.11.0", + "eslint-plugin-react": "^7.20.3", + "eslint-plugin-react-hooks": "^4.0.8", "html-loader": "0.5.*", "html-webpack-plugin": "3.2.*", + "jest": "^26.1.0", + "jest-environment-jsdom-sixteen": "^1.0.3", + "jest-styled-components": "^7.0.2", + "prettier": "^2.0.5", + "source-map-loader": "^1.0.1", "style-loader": "0.23.*", + "ts-loader": "^8.0.0", + "typescript": "^3.9.6", "webpack": "4.29.*", "webpack-cli": "3.2.*", "webpack-dev-server": "3.1.*" diff --git a/src/@types/call.ts b/src/@types/call.ts new file mode 100644 index 0000000..3998f8a --- /dev/null +++ b/src/@types/call.ts @@ -0,0 +1,11 @@ +export interface Call { + id: string; + created_at: string; + direction: 'outbound' | 'inbound'; + from: string; + to: string | null; + via: string; + duration: number; + is_archived: boolean; + call_type: 'missed' | 'answered' | 'voicemail'; +} diff --git a/src/@types/custom.d.ts b/src/@types/custom.d.ts new file mode 100644 index 0000000..8f5ffa5 --- /dev/null +++ b/src/@types/custom.d.ts @@ -0,0 +1,7 @@ +// To be able to import svg in .ts file : +// https://webpack.js.org/guides/typescript/#importing-other-assets + +declare module '*.svg' { + const content: any; + export default content; +} diff --git a/src/@types/styled.d.ts b/src/@types/styled.d.ts new file mode 100644 index 0000000..645d8eb --- /dev/null +++ b/src/@types/styled.d.ts @@ -0,0 +1,6 @@ +import 'styled-components'; +import { Theme } from '../theme'; + +declare module 'styled-components' { + export interface DefaultTheme extends Theme {} +} diff --git a/src/App.jsx b/src/App.jsx deleted file mode 100644 index 6f44e55..0000000 --- a/src/App.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; - -import Header from './Header.jsx'; - -const App = () => { - return ( -
-
-
Some activities should be here
-
- ); -}; - -ReactDOM.render(, document.getElementById('app')); - -export default App; diff --git a/src/GlobalStyle.ts b/src/GlobalStyle.ts new file mode 100644 index 0000000..5da6e44 --- /dev/null +++ b/src/GlobalStyle.ts @@ -0,0 +1,35 @@ +import { createGlobalStyle } from 'styled-components'; + +const GlobalStyle = createGlobalStyle` + html, body, h1, h2, h3, ul, li { + margin: 0; + padding: 0; + border: 0; + font: inherit; + font-size: 13px; + } + + body { + background: #233142; + font-family: helvetica, arial, -serif; + color: #333333; + line-height: 1; + } + + #app{ + position: absolute; + display: flex; + align-items: center; + justify-content: center; + left: 0; + top: 0; + right: 0; + bottom: 0; + } + a { + color: inherit; + text-decoration: inherit; + } +`; + +export default GlobalStyle; diff --git a/src/Header.jsx b/src/Header.jsx deleted file mode 100644 index 46cbb90..0000000 --- a/src/Header.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; - -const Header = () => { - return ( -
- - - - - - - - - - - - - - - -
- ); -}; - -export default Header; diff --git a/src/app/components/Header.tsx b/src/app/components/Header.tsx new file mode 100644 index 0000000..b8f9258 --- /dev/null +++ b/src/app/components/Header.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { HeaderWrapper } from './styledComponents'; + +export const Header = () => { + return ( + + + + + + + + + + + + + + + + + + ); +}; + +export default Header; diff --git a/src/app/components/Nav.tsx b/src/app/components/Nav.tsx new file mode 100644 index 0000000..bf2e8fa --- /dev/null +++ b/src/app/components/Nav.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +import { NavWrapper, NavIcon } from './styledComponents'; +import phone from '../../asset/phone.svg'; +import profile from '../../asset/profile.svg'; +import setting from '../../asset/setting.svg'; + +const Nav = () => { + return ( + + + + + + + + ); +}; + +export default Nav; diff --git a/src/app/components/Routes.tsx b/src/app/components/Routes.tsx new file mode 100644 index 0000000..2364708 --- /dev/null +++ b/src/app/components/Routes.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { Switch, Route } from 'react-router-dom'; +import Feed from '../../feed'; +import Detail from '../../detail'; + +const Routes = () => { + return ( + + + + + ); +}; + +export default Routes; diff --git a/src/app/components/styledComponents.ts b/src/app/components/styledComponents.ts new file mode 100644 index 0000000..e0ce25f --- /dev/null +++ b/src/app/components/styledComponents.ts @@ -0,0 +1,32 @@ +import styled from 'styled-components'; + +export const HeaderWrapper = styled.header` + height: 40px; + margin: 0 auto; + padding: 20px 0; + text-align: center; + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1); + svg { + display: block; + width: 100%; + height: 100%; + } +`; + +export const NavWrapper = styled.div` + position: absolute; + width: 100%; + bottom: 0; + display: flex; + justify-content: space-around; + background: ${({ theme }) => theme.colors.white}; + border-top: 0.0625rem solid ${({ theme }) => theme.colors.muted}; +`; + +export const NavIcon = styled.img<{ active?: boolean }>` + padding: 1rem; + border-bottom: ${({ theme, active }) => + active ? `0.25rem solid ${theme.colors.primary}` : 'none'}; + height: 1.5rem; + display: block; +`; diff --git a/src/app/index.tsx b/src/app/index.tsx new file mode 100644 index 0000000..4c039c0 --- /dev/null +++ b/src/app/index.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { BrowserRouter as Router } from 'react-router-dom'; + +import Header from './components/Header'; +import Routes from './components/Routes'; +import Nav from './components/Nav'; +import { Container } from './styledComponents'; + +const App = () => { + return ( + + +
+ +