├── .env ├── public ├── index.css ├── logo.ico ├── robots.txt ├── logo192.png ├── logo512.png ├── fonts │ ├── CircularStd-Bold.otf │ ├── CircularStd-Book.otf │ ├── CircularStd-Medium.otf │ └── index.css ├── manifest.json └── index.html ├── src ├── pages │ ├── Task │ │ ├── index.js │ │ └── Task.js │ └── ToDo │ │ ├── index.js │ │ └── ToDo.js ├── containers │ ├── Layout │ │ ├── index.js │ │ └── Layout.js │ └── AppContainer.js ├── redux │ ├── api │ │ ├── request.js │ │ └── apiCall.js │ ├── modules │ │ ├── todo │ │ │ ├── constants.js │ │ │ ├── selectors.js │ │ │ ├── actions.js │ │ │ └── reducers.js │ │ └── task │ │ │ ├── constants.js │ │ │ ├── selectors.js │ │ │ ├── actions.js │ │ │ └── reducers.js │ ├── reducers.js │ ├── sagas │ │ ├── index.js │ │ ├── todo.js │ │ └── task.js │ └── store.js ├── index.js ├── routes │ └── index.js └── serviceWorker.js ├── .eslintrc ├── config ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── pnpTs.js ├── getHttpsConfig.js ├── paths.js ├── env.js ├── modules.js ├── webpackDevServer.config.js └── webpack.config.js ├── .gitignore ├── README.md ├── scripts ├── test.js ├── start.js └── build.js └── package.json /.env: -------------------------------------------------------------------------------- 1 | NODE_PATH = "src/" 2 | -------------------------------------------------------------------------------- /public/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: lightgray; 3 | } -------------------------------------------------------------------------------- /src/pages/Task/index.js: -------------------------------------------------------------------------------- 1 | import Task from './Task' 2 | 3 | export default Task 4 | -------------------------------------------------------------------------------- /src/pages/ToDo/index.js: -------------------------------------------------------------------------------- 1 | import ToDo from './ToDo' 2 | 3 | export default ToDo 4 | -------------------------------------------------------------------------------- /public/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelaltao/Cohen-Company-Test/HEAD/public/logo.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/containers/Layout/index.js: -------------------------------------------------------------------------------- 1 | import Layout from './Layout' 2 | 3 | export default Layout 4 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelaltao/Cohen-Company-Test/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelaltao/Cohen-Company-Test/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/fonts/CircularStd-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelaltao/Cohen-Company-Test/HEAD/public/fonts/CircularStd-Bold.otf -------------------------------------------------------------------------------- /public/fonts/CircularStd-Book.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelaltao/Cohen-Company-Test/HEAD/public/fonts/CircularStd-Book.otf -------------------------------------------------------------------------------- /public/fonts/CircularStd-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelaltao/Cohen-Company-Test/HEAD/public/fonts/CircularStd-Medium.otf -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app", 3 | "rules": { 4 | "semi" : [2, "never"], 5 | "max-len": [2, 140, 2], 6 | "react/no-unused-prop-types": 0 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/redux/api/request.js: -------------------------------------------------------------------------------- 1 | export const requestPending = type => `${type}/pending` 2 | 3 | export const requestSuccess = type => `${type}/success` 4 | 5 | export const requestFail = type => `${type}/fail` 6 | -------------------------------------------------------------------------------- /src/redux/modules/todo/constants.js: -------------------------------------------------------------------------------- 1 | export const GET_TODO_LIST = 'GET_TODO_LIST' 2 | export const ADD_TODO = 'ADD_TODO' 3 | export const DELETE_TODO = 'DELETE_TODO' 4 | export const SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE' 5 | -------------------------------------------------------------------------------- /src/redux/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import todo from './modules/todo/reducers' 3 | import task from './modules/task/reducers' 4 | 5 | export default combineReducers({ 6 | todo, 7 | task, 8 | }) 9 | -------------------------------------------------------------------------------- /src/redux/sagas/index.js: -------------------------------------------------------------------------------- 1 | import { all } from 'redux-saga/effects' 2 | import todo from './todo' 3 | import task from './task' 4 | 5 | export default function* rootSaga() { 6 | yield all([ 7 | todo(), 8 | task() 9 | ]) 10 | } 11 | -------------------------------------------------------------------------------- /src/containers/AppContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Provider } from 'react-redux' 3 | import store from 'redux/store' 4 | import Routes from 'routes' 5 | 6 | export default () => ( 7 | 8 | 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /src/containers/Layout/Layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Box } from '@mui/material' 3 | 4 | const Layout = ({ children }) => { 5 | return ( 6 | 7 | {children} 8 | 9 | ) 10 | } 11 | 12 | export default Layout 13 | -------------------------------------------------------------------------------- /src/redux/modules/todo/selectors.js: -------------------------------------------------------------------------------- 1 | import { get } from 'lodash' 2 | 3 | export const todoStateSelector = (state) => 4 | get(state, 'todo') 5 | 6 | export const todoListSelector = (state) => 7 | get(state, 'todo.todos') 8 | 9 | export const errorMessageSelector = (state) => 10 | get(state, 'todo.error') 11 | -------------------------------------------------------------------------------- /src/redux/modules/task/constants.js: -------------------------------------------------------------------------------- 1 | export const GET_TASK_LIST = 'GET_TASK_LIST' 2 | export const DELETE_TASK = 'DELETE_TASK' 3 | export const ADD_TASK = 'ADD_TASK' 4 | export const GET_TASK = 'GET_TASK' 5 | export const UPDATE_TASK = 'UPDATE_TASK' 6 | export const GET_ALL_TASKS = 'GET_ALL_TASKS' 7 | export const SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE' 8 | -------------------------------------------------------------------------------- /src/redux/modules/todo/actions.js: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | import * as CONSTANTS from './constants' 3 | 4 | export const getTodoList = createAction(CONSTANTS.GET_TODO_LIST) 5 | export const addTodo = createAction(CONSTANTS.ADD_TODO) 6 | export const deleteTodo = createAction(CONSTANTS.DELETE_TODO) 7 | export const setErrorMessage = createAction(CONSTANTS.SET_ERROR_MESSAGE) 8 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/redux/modules/task/selectors.js: -------------------------------------------------------------------------------- 1 | import { get } from 'lodash' 2 | 3 | export const taskStateSelector = (state) => 4 | get(state, 'task') 5 | 6 | export const taskListSelector = (state) => 7 | get(state, 'task.tasks') 8 | 9 | export const taskSelector = (state) => 10 | get(state, 'task.task') 11 | 12 | export const allTasksSelector = (state) => 13 | get(state, 'task.allTasks') 14 | 15 | export const errorMessageSelector = (state) => 16 | get(state, 'task.error') 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | #IDE 4 | .idea 5 | 6 | # dependencies 7 | /node_modules 8 | /.pnp 9 | .pnp.js 10 | 11 | # testing 12 | /coverage 13 | 14 | # production 15 | /build 16 | 17 | # misc 18 | .DS_Store 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | package-lock.json 29 | yarn.lock 30 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './containers/AppContainer' 4 | import * as serviceWorker from './serviceWorker' 5 | 6 | ReactDOM.render( 7 | 8 | , 9 | document.getElementById('root')) 10 | 11 | // If you want your app to work offline and load faster, you can change 12 | // unregister() to register() below. Note this comes with some pitfalls. 13 | // Learn more about service workers: https://bit.ly/CRA-PWA 14 | serviceWorker.unregister() 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Cohen

4 | 5 | ## Quick start 6 | 7 | ## 1.install 8 | 9 | ### Steps 10 | 11 | 1. Type following commands: 12 | ``` console 13 | npm install 14 | or 15 | yarn install 16 | ``` 17 | 18 | 2. Serve your app: 19 | ``` console 20 | npm start 21 | or 22 | yarn start 23 | ``` 24 | ## 2.Build 25 | ```sh 26 | npm run build or yarn build 27 | ``` 28 | 29 | Builds the app for production to the `build` folder.
30 | 31 | Your app is ready to be deployed. 32 | 33 | ## 3.User Guide 34 | 35 | soon.. -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Cohen", 3 | "name": "Cohen Company", 4 | "icons": [ 5 | { 6 | "src": "logo.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png" 13 | }, 14 | { 15 | "src": "logo512.png", 16 | "type": "image/png" 17 | } 18 | ], 19 | "start_url": ".", 20 | "display": "standalone", 21 | "theme_color": "#000000", 22 | "background_color": "#ffffff" 23 | } 24 | -------------------------------------------------------------------------------- /public/fonts/index.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'CircularStd'; 3 | font-weight: 400; 4 | font-style: normal; 5 | src: local('CircularStd'), url('CircularStd-Book.otf') format('opentype'); 6 | } 7 | @font-face { 8 | font-family: 'CircularStd'; 9 | font-weight: 500; 10 | font-style: normal; 11 | src: local('CircularStd'), url('CircularStd-Medium.otf') format('opentype'); 12 | } 13 | @font-face { 14 | font-family: 'CircularStd'; 15 | font-weight: 700; 16 | font-style: normal; 17 | src: local('CircularStd'), url('CircularStd-Bold.otf') format('opentype'); 18 | } 19 | -------------------------------------------------------------------------------- /src/redux/modules/task/actions.js: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | import * as CONSTANTS from './constants' 3 | 4 | export const getTasks = createAction(CONSTANTS.GET_TASK_LIST) 5 | export const deleteTask = createAction(CONSTANTS.DELETE_TASK) 6 | export const addTask = createAction(CONSTANTS.ADD_TASK) 7 | export const getTask = createAction(CONSTANTS.GET_TASK) 8 | export const updateTask = createAction(CONSTANTS.UPDATE_TASK) 9 | export const getAllTasks = createAction(CONSTANTS.GET_ALL_TASKS) 10 | export const setErrorMessage = createAction(CONSTANTS.SET_ERROR_MESSAGE) 11 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' 3 | import ToDo from 'pages/ToDo' 4 | import Task from 'pages/Task' 5 | import Layout from 'containers/Layout' 6 | import ScrollToTop from 'react-router-scroll-top' 7 | 8 | const routes = () => ( 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ) 20 | 21 | export default (routes) 22 | -------------------------------------------------------------------------------- /config/pnpTs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { resolveModuleName } = require('ts-pnp'); 4 | 5 | exports.resolveModuleName = ( 6 | typescript, 7 | moduleName, 8 | containingFile, 9 | compilerOptions, 10 | resolutionHost 11 | ) => { 12 | return resolveModuleName( 13 | moduleName, 14 | containingFile, 15 | compilerOptions, 16 | resolutionHost, 17 | typescript.resolveModuleName 18 | ); 19 | }; 20 | 21 | exports.resolveTypeReferenceDirective = ( 22 | typescript, 23 | moduleName, 24 | containingFile, 25 | compilerOptions, 26 | resolutionHost 27 | ) => { 28 | return resolveModuleName( 29 | moduleName, 30 | containingFile, 31 | compilerOptions, 32 | resolutionHost, 33 | typescript.resolveTypeReferenceDirective 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/redux/sagas/todo.js: -------------------------------------------------------------------------------- 1 | import { takeLatest } from 'redux-saga/effects' 2 | import * as CONSTANTS from '../modules/todo/constants' 3 | import apiCall from '../api/apiCall' 4 | 5 | const doGetTodoList = apiCall({ 6 | type: CONSTANTS.GET_TODO_LIST, 7 | method: 'get', 8 | path: 'todos', 9 | }) 10 | 11 | const doAddTodo = apiCall({ 12 | type: CONSTANTS.ADD_TODO, 13 | method: 'post', 14 | path: 'todos', 15 | }) 16 | 17 | const doDeleteTodo = apiCall({ 18 | type: CONSTANTS.DELETE_TODO, 19 | method: 'delete', 20 | path: ({ payload }) => `/todos/${payload.id}`, 21 | }) 22 | 23 | export default function* rootSaga() { 24 | yield takeLatest(CONSTANTS.GET_TODO_LIST, doGetTodoList) 25 | yield takeLatest(CONSTANTS.ADD_TODO, doAddTodo) 26 | yield takeLatest(CONSTANTS.DELETE_TODO, doDeleteTodo) 27 | } 28 | -------------------------------------------------------------------------------- /src/redux/modules/todo/reducers.js: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions' 2 | import { requestSuccess, requestFail } from 'redux/api/request' 3 | import * as CONSTANTS from './constants' 4 | 5 | const getInitialState = () => { 6 | return { 7 | todos: [], 8 | error: '', 9 | } 10 | } 11 | 12 | export default handleActions({ 13 | [requestSuccess(CONSTANTS.GET_TODO_LIST)]: (state, { payload }) => ({ 14 | ...state, 15 | todos: payload, 16 | }), 17 | [requestSuccess(CONSTANTS.ADD_TODO)]: (state, { payload }) => ({ 18 | ...state, 19 | todos: payload, 20 | }), 21 | [requestFail(CONSTANTS.ADD_TODO)]: (state, { payload }) => ({ 22 | ...state, 23 | error: payload.data.message, 24 | }), 25 | [requestSuccess(CONSTANTS.DELETE_TODO)]: (state, { payload }) => ({ 26 | ...state, 27 | todos: payload, 28 | }), 29 | [(CONSTANTS.SET_ERROR_MESSAGE)]: (state, { payload }) => ({ 30 | ...state, 31 | error: payload, 32 | }) 33 | 34 | }, getInitialState()) 35 | -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux' 2 | import { routerMiddleware } from 'react-router-redux' 3 | import createSagaMiddleware from 'redux-saga' 4 | import { createBrowserHistory as createHistory } from 'history' 5 | import rootReducer from './reducers' 6 | import sagas from './sagas' 7 | 8 | export const history = createHistory() 9 | 10 | const initialState = {} 11 | const enhancers = [] 12 | 13 | const sagaMiddleware = createSagaMiddleware() 14 | 15 | const middleware = [ 16 | sagaMiddleware, 17 | routerMiddleware(history) 18 | ] 19 | 20 | if (process.env.NODE_ENV === 'development') { 21 | const devToolsExtension = window.devToolsExtension 22 | 23 | if (typeof devToolsExtension === 'function') { 24 | enhancers.push(devToolsExtension()) 25 | } 26 | } 27 | 28 | const composedEnhancers = compose( 29 | applyMiddleware(...middleware), 30 | ...enhancers 31 | ) 32 | 33 | const store = createStore( 34 | rootReducer, 35 | initialState, 36 | composedEnhancers 37 | ) 38 | 39 | sagaMiddleware.run(sagas) 40 | 41 | export default store 42 | -------------------------------------------------------------------------------- /src/redux/modules/task/reducers.js: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions' 2 | import { requestSuccess, requestFail } from 'redux/api/request' 3 | import * as CONSTANTS from './constants' 4 | 5 | const getInitialState = () => { 6 | return { 7 | tasks: [], 8 | task: {}, 9 | allTasks: [], 10 | error: '', 11 | } 12 | } 13 | 14 | export default handleActions({ 15 | [requestSuccess(CONSTANTS.GET_TASK_LIST)]: (state, { payload }) => ({ 16 | ...state, 17 | tasks: payload, 18 | }), 19 | [requestSuccess(CONSTANTS.DELETE_TASK)]: (state, { payload }) => ({ 20 | ...state, 21 | tasks: payload, 22 | }), 23 | [requestSuccess(CONSTANTS.ADD_TASK)]: (state, { payload }) => ({ 24 | ...state, 25 | tasks: payload, 26 | }), 27 | [requestFail(CONSTANTS.ADD_TASK)]: (state, { payload }) => ({ 28 | ...state, 29 | error: payload.data.message, 30 | }), 31 | [requestSuccess(CONSTANTS.GET_TASK)]: (state, { payload }) => ({ 32 | ...state, 33 | task: payload, 34 | }), 35 | [requestSuccess(CONSTANTS.UPDATE_TASK)]: (state, { payload }) => ({ 36 | ...state, 37 | tasks: payload, 38 | }), 39 | [requestSuccess(CONSTANTS.GET_ALL_TASKS)]: (state, { payload }) => ({ 40 | ...state, 41 | allTasks: payload, 42 | }), 43 | [(CONSTANTS.SET_ERROR_MESSAGE)]: (state, { payload }) => ({ 44 | ...state, 45 | error: payload, 46 | }) 47 | 48 | }, getInitialState()) 49 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const camelcase = require('camelcase'); 5 | 6 | // This is a custom Jest transformer turning file imports into filenames. 7 | // http://facebook.github.io/jest/docs/en/webpack.html 8 | 9 | module.exports = { 10 | process(src, filename) { 11 | const assetFilename = JSON.stringify(path.basename(filename)); 12 | 13 | if (filename.match(/\.svg$/)) { 14 | // Based on how SVGR generates a component name: 15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 16 | const pascalCaseFilename = camelcase(path.parse(filename).name, { 17 | pascalCase: true, 18 | }); 19 | const componentName = `Svg${pascalCaseFilename}`; 20 | return `const React = require('react'); 21 | module.exports = { 22 | __esModule: true, 23 | default: ${assetFilename}, 24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 25 | return { 26 | $$typeof: Symbol.for('react.element'), 27 | type: 'svg', 28 | ref: ref, 29 | key: null, 30 | props: Object.assign({}, props, { 31 | children: ${assetFilename} 32 | }) 33 | }; 34 | }), 35 | };`; 36 | } 37 | 38 | return `module.exports = ${assetFilename};`; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /src/redux/sagas/task.js: -------------------------------------------------------------------------------- 1 | import { takeLatest } from 'redux-saga/effects' 2 | import * as CONSTANTS from '../modules/task/constants' 3 | import apiCall from '../api/apiCall' 4 | 5 | const doGetTaskList = apiCall({ 6 | type: CONSTANTS.GET_TASK_LIST, 7 | method: 'get', 8 | path: ({ payload }) => `task/${payload.id}`, 9 | }) 10 | 11 | const doDeleteTask = apiCall({ 12 | type: CONSTANTS.DELETE_TASK, 13 | method: 'delete', 14 | path: ({ payload }) => `task/${payload.id}/${payload.task_id}` 15 | }) 16 | 17 | const doAddTask = apiCall({ 18 | type: CONSTANTS.ADD_TASK, 19 | method: 'post', 20 | path: ({ payload }) => `task/${payload.id}`, 21 | }) 22 | 23 | const doGetTask = apiCall({ 24 | type: CONSTANTS.GET_TASK, 25 | method: 'get', 26 | path: ({ payload }) => `tasks/${payload.task_id}`, 27 | }) 28 | 29 | const doUpdateTask = apiCall({ 30 | type: CONSTANTS.UPDATE_TASK, 31 | method: 'put', 32 | path: ({ payload }) => `task/${payload.id}/${payload.task_id}`, 33 | }) 34 | 35 | const doGetAllTasks = apiCall({ 36 | type: CONSTANTS.GET_ALL_TASKS, 37 | method: 'get', 38 | path: 'task' 39 | }) 40 | 41 | export default function* rootSaga() { 42 | yield takeLatest(CONSTANTS.GET_TASK_LIST, doGetTaskList) 43 | yield takeLatest(CONSTANTS.DELETE_TASK, doDeleteTask) 44 | yield takeLatest(CONSTANTS.ADD_TASK, doAddTask) 45 | yield takeLatest(CONSTANTS.GET_TASK, doGetTask) 46 | yield takeLatest(CONSTANTS.UPDATE_TASK, doUpdateTask) 47 | yield takeLatest(CONSTANTS.GET_ALL_TASKS, doGetAllTasks) 48 | } 49 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI or explicitly running all tests 42 | if ( 43 | !process.env.CI && 44 | argv.indexOf('--watchAll') === -1 && 45 | argv.indexOf('--watchAll=false') === -1 46 | ) { 47 | // https://github.com/facebook/create-react-app/issues/5210 48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 49 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 50 | } 51 | 52 | 53 | jest.run(argv); 54 | -------------------------------------------------------------------------------- /src/redux/api/apiCall.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { call, put } from 'redux-saga/effects' 3 | import { get } from 'lodash' 4 | import { requestFail, requestPending, requestSuccess } from './request' 5 | 6 | export default ({ 7 | type, 8 | method, // one of 'get', 'post', 'put', 'delete' 9 | path, 10 | success, 11 | fail, 12 | payloadOnSuccess, 13 | payloadOnFail 14 | }) => function* (action) { 15 | const { 16 | body, 17 | params, 18 | success: successCallback, 19 | fail: failCallback 20 | } = (action.payload || {}) 21 | 22 | axios.defaults.baseURL = 'http://localhost:3000' 23 | 24 | let headers = { 25 | 'Accept': '*/*', 26 | 'Content-Type': 'application/json', 27 | } 28 | 29 | try { 30 | yield put({ 31 | type: requestPending(type) 32 | }) 33 | 34 | const res = yield call(axios.request, { 35 | url: typeof path === 'function' ? path(action) : path, 36 | method: method.toLowerCase(), 37 | headers: Object.assign({}, headers), 38 | data: body, 39 | params 40 | }) 41 | 42 | success && success(res, action) 43 | successCallback && successCallback(res) 44 | 45 | yield put({ 46 | type: requestSuccess(type), 47 | payload: payloadOnSuccess ? payloadOnSuccess(res.data, action) : res.data 48 | }) 49 | } catch (err) { 50 | const errRes = get(err, 'response', err) 51 | 52 | if (errRes.data && (errRes.data.code === 401 && errRes.data.message === 'Please authenticate')) { 53 | yield put({ 54 | type: 'IS_UNAUTHORIZED', 55 | }) 56 | } 57 | 58 | fail && fail(errRes) 59 | failCallback && failCallback(errRes) 60 | 61 | yield put({ 62 | type: requestFail(type), 63 | payload: payloadOnFail ? payloadOnFail(errRes, action) : errRes 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 26 | 27 | Cohen Company Assessment 28 | 29 | 30 | 31 | 32 |
33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /config/getHttpsConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const crypto = require('crypto'); 6 | const chalk = require('react-dev-utils/chalk'); 7 | const paths = require('./paths'); 8 | 9 | // Ensure the certificate and key provided are valid and if not 10 | // throw an easy to debug error 11 | function validateKeyAndCerts({ cert, key, keyFile, crtFile }) { 12 | let encrypted; 13 | try { 14 | // publicEncrypt will throw an error with an invalid cert 15 | encrypted = crypto.publicEncrypt(cert, Buffer.from('test')); 16 | } catch (err) { 17 | throw new Error( 18 | `The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}` 19 | ); 20 | } 21 | 22 | try { 23 | // privateDecrypt will throw an error with an invalid key 24 | crypto.privateDecrypt(key, encrypted); 25 | } catch (err) { 26 | throw new Error( 27 | `The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${ 28 | err.message 29 | }` 30 | ); 31 | } 32 | } 33 | 34 | // Read file and throw an error if it doesn't exist 35 | function readEnvFile(file, type) { 36 | if (!fs.existsSync(file)) { 37 | throw new Error( 38 | `You specified ${chalk.cyan( 39 | type 40 | )} in your env, but the file "${chalk.yellow(file)}" can't be found.` 41 | ); 42 | } 43 | return fs.readFileSync(file); 44 | } 45 | 46 | // Get the https config 47 | // Return cert files if provided in env, otherwise just true or false 48 | function getHttpsConfig() { 49 | const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env; 50 | const isHttps = HTTPS === 'true'; 51 | 52 | if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) { 53 | const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE); 54 | const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE); 55 | const config = { 56 | cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), 57 | key: readEnvFile(keyFile, 'SSL_KEY_FILE'), 58 | }; 59 | 60 | validateKeyAndCerts({ ...config, keyFile, crtFile }); 61 | return config; 62 | } 63 | return isHttps; 64 | } 65 | 66 | module.exports = getHttpsConfig; 67 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 13 | // "public path" at which the app is served. 14 | // webpack needs to know it to put the right