Skip to content
This repository has been archived by the owner on Sep 1, 2022. It is now read-only.

Jeremy Scatigna #23

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
237e6d8
feat(typescript): create typescript config for project
jeremyscatigna Nov 18, 2019
a47fca1
Merge pull request #1 from jeremyscatigna/feature/init-typescript
jeremyscatigna Nov 18, 2019
21021ce
feat(prettier): init prettier
jeremyscatigna Nov 18, 2019
2885843
Merge branch 'master' of https://github.com/jeremyscatigna/frontend-test
jeremyscatigna Nov 18, 2019
b70e0d8
feat(networking): create networking setup and add endpoints to api file
jeremyscatigna Nov 18, 2019
a9b9dfc
Merge pull request #2 from jeremyscatigna/feature/api
jeremyscatigna Nov 18, 2019
e3fa873
feat(redux): init redux with needed actions and reducers
jeremyscatigna Nov 18, 2019
2ef4639
Merge pull request #3 from jeremyscatigna/feature/redux
jeremyscatigna Nov 18, 2019
6cdbb15
feat(typescript): fix typescript config and init components
jeremyscatigna Nov 19, 2019
58e4fa2
Merge pull request #4 from jeremyscatigna/feature/typescript-fix
jeremyscatigna Nov 19, 2019
7a2d2e8
feat(config): update config and dependencies versions
jeremyscatigna Nov 19, 2019
4f93f09
Merge pull request #5 from jeremyscatigna/feature/version-config-update
jeremyscatigna Nov 19, 2019
e9d0dfa
feat(activity-list): display a simple activity list
jeremyscatigna Nov 19, 2019
68e1a8b
feat(activity-list): add archive capabilities and styling
jeremyscatigna Nov 19, 2019
18df3b4
feat(activity-list): add icons to activity items
jeremyscatigna Nov 19, 2019
c6f68bc
feat(activity-list): update ui to archive calls
jeremyscatigna Nov 21, 2019
f70a18d
Merge pull request #6 from jeremyscatigna/feature/display_activity_list
jeremyscatigna Nov 21, 2019
8dc4d8e
feat(activity): use state in activity page
jeremyscatigna Nov 21, 2019
196cd77
feat(ci): add circleci config
jeremyscatigna Nov 21, 2019
0bea5fb
Merge pull request #7 from jeremyscatigna/feature/ci
jeremyscatigna Nov 21, 2019
25d405f
Update README.md
jeremyscatigna Nov 21, 2019
5a9b13b
Update README.md
jeremyscatigna Nov 21, 2019
f74b38b
feat(test): add test config and first blank test
jeremyscatigna Nov 21, 2019
fe1e708
Update README.md
jeremyscatigna Nov 21, 2019
ec0aed0
Merge pull request #8 from jeremyscatigna/feature/tests
jeremyscatigna Nov 21, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
orbs:
react: 'thefrontside/react@dev:alpha'
version: 2.1
workflows:
push:
jobs:
- react/install
- react/eslint:
requires:
- react/install
- react/stylelint:
requires:
- react/install
- react/test:
requires:
- react/install
- react/coverage:
requires:
- react/install
7 changes: 7 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"parser": "typescript",
"semi": false,
"singleQuote": true,
"arrowParens": "always",
"printWidth": 100
}
8 changes: 8 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"editor.tabSize": 2,
"editor.formatOnSave": true,
"git.ignoreLimitWarning": true,
"files.exclude": {
"dist": true
}
}
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# [aircall.io](https://aircall.io) - Frontend technical test

[![CircleCI](https://circleci.com/gh/jeremyscatigna/frontend-test.svg?style=svg)](https://circleci.com/gh/jeremyscatigna/frontend-test) [![style: styled-components](https://img.shields.io/badge/style-%F0%9F%92%85%20styled--components-orange.svg?colorB=daa357&colorA=db748e)](https://github.com/styled-components/styled-components) [![tested with jest](https://img.shields.io/badge/tested_with-jest-99424f.svg)](https://github.com/facebook/jest) [![jest](https://jestjs.io/img/jest-badge.svg)](https://github.com/facebook/jest)

This test is a part of our hiring process at Aircall for [the Frontend Developer position](https://jobs.lever.co/aircall/c0fdd41e-a2a1-408d-ad08-890153518587). It should take you between 3 and 6 hours depending on your experience.

**Feel free to apply! Drop us a line with your LinkedIn/GitHub/Twitter at [email protected]**
Expand Down
1 change: 1 addition & 0 deletions globals.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '@testing-library/jest-dom/extend-expect'
27 changes: 27 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module.exports = {
// The root of your source code, typically /src
// `<rootDir>` is a token Jest substitutes
roots: ['<rootDir>/src'],

// Jest transformations -- this adds support for TypeScript
// using ts-jest
transform: {
'^.+\\.tsx?$': 'ts-jest'
},

// Runs special logic, such as cleaning up components
// when using React Testing Library and adds special
// extended assertions to Jest
setupFilesAfterEnv: [
'@testing-library/react/cleanup-after-each',
'@testing-library/jest-dom/extend-expect'
],

// 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']
}
31 changes: 27 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,44 @@
"private": false,
"scripts": {
"start": "webpack-dev-server --mode development --open",
"build": "webpack --mode production"
"build": "webpack --mode production",
"test": "jest"
},
"dependencies": {
"react": "^16.3.1",
"react-dom": "^16.3.1"
"@types/node": "^12.12.8",
"@types/react": "^16.9.11",
"@types/react-dom": "^16.9.4",
"axios": "^0.19.0",
"moment": "^2.24.0",
"react": "^16.12.0",
"react-collapsible": "^2.6.0",
"react-dom": "^16.12.0",
"react-redux": "^7.1.3",
"redux": "^4.0.4",
"redux-axios-middleware": "^4.0.1",
"redux-devtools-extension": "^2.13.8",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0",
"styled-components": "^4.4.1"
},
"devDependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@types/jest": "^24.0.23",
"awesome-typescript-loader": "^5.2.1",
"babel-core": "6.26.*",
"babel-loader": "7.1.*",
"babel-loader": "8.0.*",
"babel-preset-env": "1.7.0",
"babel-preset-react": "6.24.*",
"css-loader": "2.1.*",
"html-loader": "0.5.*",
"html-webpack-plugin": "3.2.*",
"jest": "^24.9.0",
"react-svg-loader": "^3.0.3",
"source-map-loader": "^0.2.4",
"style-loader": "0.23.*",
"ts-jest": "^24.1.0",
"typescript": "^3.7.2",
"webpack": "4.29.*",
"webpack-cli": "3.2.*",
"webpack-dev-server": "3.1.*"
Expand Down
1 change: 1 addition & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
</head>
<body>
<div id="app"></div>
<script src="dist/bundle.js"></script>
</body>
</html>
17 changes: 0 additions & 17 deletions src/App.jsx

This file was deleted.

17 changes: 17 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react'

import Header from './Header'
import Activity from './pages/Activity'

const App = () => {
return (
<div className="container">
<Header />
<div className="container-view">
<Activity />
</div>
</div>
)
}

export default App
File renamed without changes.
5 changes: 5 additions & 0 deletions src/IStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { IActivityState } from './reducers/activity'

export default interface IStore {
activity: IActivityState
}
10 changes: 10 additions & 0 deletions src/__tests__/Activity.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react'
import { render, fireEvent, waitForElement } from '@testing-library/react'

import Activity from '../pages/Activity'

describe('<Activity />', () => {
test('should display Activity page', async () => {
// ???
})
})
170 changes: 170 additions & 0 deletions src/actions/activity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import api from '../api'
import { IActivity } from '../shared/api-types'
import { KThunkAction } from './helpers'

export enum ActivityTypeKeys {
ACTIVITIES_LIST_FETCHING = 'ACTIVITIES_LIST_FETCHING',
ACTIVITIES_LIST_SUCCESS = 'ACTIVITIES_LIST_SUCCESS',
ACTIVITIES_LIST_ERROR = 'ACTIVITIES_LIST_ERROR',

ACTIVITY_FETCHING = 'ACTIVITY_FETCHING',
ACTIVITY_SUCCESS = 'ACTIVITY_SUCCESS',
ACTIVITY_ERROR = 'ACTIVITY_ERROR',

UPDATE_ACTIVITY_FETCHING = 'UPDATE_ACTIVITY_FETCHING',
UPDATE_ACTIVITY_SUCCESS = 'UPDATE_ACTIVITY_SUCCESS',
UPDATE_ACTIVITY_ERROR = 'UPDATE_ACTIVITY_ERROR'
}

export type ActivityActions =
| IActivitiesListFetchingAction
| IActivitiesListSuccessAction
| IActivitiesListErrorAction
| IActivityFetchingAction
| IActivitySuccessAction
| IActivityErrorAction
| IUpdateActivityFetchingAction
| IUpdateActivitySuccessAction
| IUpdateActivityErrorAction

// GET ACTIVITIES LIST
export interface IActivitiesListFetchingAction {
type: ActivityTypeKeys.ACTIVITIES_LIST_FETCHING
}

export interface IActivitiesListSuccessAction {
type: ActivityTypeKeys.ACTIVITIES_LIST_SUCCESS
activities: IActivity[]
}

export interface IActivitiesListErrorAction {
type: ActivityTypeKeys.ACTIVITIES_LIST_ERROR
error: string
}

export function activitiesListFetching(): IActivitiesListFetchingAction {
return {
type: ActivityTypeKeys.ACTIVITIES_LIST_FETCHING
}
}

export function activitiesListSuccess(activities: IActivity[]): IActivitiesListSuccessAction {
return {
type: ActivityTypeKeys.ACTIVITIES_LIST_SUCCESS,
activities
}
}

export function activitiesListError(error: string): IActivitiesListErrorAction {
return {
type: ActivityTypeKeys.ACTIVITIES_LIST_ERROR,
error
}
}

export function doGetActivitiesList(): KThunkAction {
return async (dispatch) => {
dispatch(activitiesListFetching())
try {
const activitiesResponse = await api.activity.getActivitiesList()
dispatch(activitiesListSuccess(activitiesResponse))
} catch (err) {
dispatch(activitiesListError(err))
}
}
}

// GET ACTIVITY BY ID
export interface IActivityFetchingAction {
type: ActivityTypeKeys.ACTIVITY_FETCHING
}

export interface IActivitySuccessAction {
type: ActivityTypeKeys.ACTIVITY_SUCCESS
activity: IActivity
}

export interface IActivityErrorAction {
type: ActivityTypeKeys.ACTIVITY_ERROR
error: string
}

export function activityFetching(): IActivityFetchingAction {
return {
type: ActivityTypeKeys.ACTIVITY_FETCHING
}
}

export function activitySuccess(activity: IActivity): IActivitySuccessAction {
return {
type: ActivityTypeKeys.ACTIVITY_SUCCESS,
activity
}
}

export function activityError(error: string): IActivityErrorAction {
return {
type: ActivityTypeKeys.ACTIVITY_ERROR,
error
}
}

export function doGetActivity(id: string): KThunkAction {
return async (dispatch) => {
dispatch(activityFetching())
try {
const activityResponse = await api.activity.getActivityById(id)
dispatch(activitySuccess(activityResponse))
} catch (err) {
dispatch(activityError(err))
}
}
}

// UPDATE ACTIVITY
export interface IUpdateActivityFetchingAction {
type: ActivityTypeKeys.UPDATE_ACTIVITY_FETCHING
}

export interface IUpdateActivitySuccessAction {
type: ActivityTypeKeys.UPDATE_ACTIVITY_SUCCESS
activity: IActivity
}

export interface IUpdateActivityErrorAction {
type: ActivityTypeKeys.UPDATE_ACTIVITY_ERROR
error: string
}

export function updateActivityFetching(): IUpdateActivityFetchingAction {
return {
type: ActivityTypeKeys.UPDATE_ACTIVITY_FETCHING
}
}

export function updateActivitySuccess(activity: IActivity): IUpdateActivitySuccessAction {
return {
type: ActivityTypeKeys.UPDATE_ACTIVITY_SUCCESS,
activity
}
}

export function updateActivityError(error: string): IUpdateActivityErrorAction {
return {
type: ActivityTypeKeys.UPDATE_ACTIVITY_ERROR,
error
}
}

export function doUpdateActivity(id: string, is_archived: boolean): KThunkAction {
return async (dispatch) => {
dispatch(updateActivityFetching())
try {
const activityResponse = await api.activity.updateActivity(id, is_archived)
await doGetActivitiesList()
dispatch(updateActivitySuccess(activityResponse))
} catch (err) {
dispatch(updateActivityError(err))
}
}
}
5 changes: 5 additions & 0 deletions src/actions/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ThunkAction } from 'redux-thunk'
import IStore from '../IStore'
import { AnyAction } from 'redux'

export type KThunkAction = ThunkAction<Promise<void>, IStore, undefined, AnyAction>
28 changes: 28 additions & 0 deletions src/api/ActivityNetworking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Networking } from './Networking'
import {
IActivityResponse,
IActivitiesListResponse,
IActivityUpdateRequestBody,
IActivity
} from '../shared/api-types'

export class ActivityNetworking {
private networking: Networking

constructor(networking: Networking) {
this.networking = networking
}

public async getActivitiesList(): Promise<IActivity[]> {
return this.networking.get<IActivity[]>(`/activities`)
}

public async getActivityById(id: string): Promise<IActivity> {
return this.networking.get<IActivity>(`/activities/${id}`)
}

public async updateActivity(id: string, is_archived: boolean): Promise<IActivity> {
const body: IActivityUpdateRequestBody = { is_archived }
return this.networking.post<IActivity>(`/activities/${id}`, body)
}
}
Loading