Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PR]: Add spontaneous assessment result #28

Open
wants to merge 45 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
b0430c0
fix: Add handler to call list component to set the number of records …
christianjtr Apr 4, 2024
cdbf2d2
feat: Add call types filter to call list page
christianjtr Apr 4, 2024
fac93f7
Add names identifiers to inputs
christianjtr Apr 4, 2024
82cf50f
feat: Add date sort filter and extract filter logic to custom hook
christianjtr Apr 5, 2024
b1a2925
Enhance formatDate util to allow receiving multiple format patterns
christianjtr Apr 5, 2024
ca4d54e
fix: Enable logout feature
christianjtr Apr 5, 2024
5f397a3
Add missing key to call list
christianjtr Apr 5, 2024
910e0a0
Add unit tests to date utils
christianjtr Apr 5, 2024
19dbb3e
Add unit tests to custom utils hook
christianjtr Apr 5, 2024
148a88f
Add unit tests to CallsListFilter component
christianjtr Apr 6, 2024
b1e82a1
Enable entire tests
christianjtr Apr 6, 2024
804ab6f
Change placeholder
christianjtr Apr 6, 2024
016a50c
feat(refresh-token): Implement auto refreshing token featute - first …
christianjtr Apr 6, 2024
3f55b7a
Add comment - suggestion o how to manage the check auth token freq va…
christianjtr Apr 6, 2024
9156edf
feat(refresh-token): Enhace logic throughout services
christianjtr Apr 6, 2024
dbc95ec
Add cypress and first test to the Login page
christianjtr Apr 6, 2024
f3c7da8
Add another e2e tests - logIn and logOut feat
christianjtr Apr 6, 2024
94e1764
feat(archive-call): Implement archive call in call detail page
christianjtr Apr 7, 2024
cf988de
Add dependencies to handle Websockets
christianjtr Apr 7, 2024
18ff606
feat(real-time-sync-call) : Add feature to allow syncing data across …
christianjtr Apr 7, 2024
d00cd33
Add required field to login form and add usrname to header component
christianjtr Apr 7, 2024
1265884
Add css rule to enlarge global height
christianjtr Apr 8, 2024
e7276ed
feat(loader): Include a loader spinner
christianjtr Apr 8, 2024
7d8cbc6
Parametrize apollo gql uris and auth keys to be stored
christianjtr Apr 8, 2024
01d9f57
Add cypress env variables
christianjtr Apr 8, 2024
1a5f1c4
Enhance login page
christianjtr Apr 8, 2024
0bc9bf5
Type useLocalStorage hook
christianjtr Apr 8, 2024
d5f5f0e
Navigate to login when path is '/'
christianjtr Apr 8, 2024
0b17ded
Add back button and styled component
christianjtr Apr 8, 2024
7441bb5
Enhance filters when navigating between details and list page
christianjtr Apr 8, 2024
408ddae
feat(redirect-no-logged-in-users) : Add automatic redirection when us…
christianjtr Apr 8, 2024
dbb125e
Add assestment result document, Jr SE contributions
christianjtr Apr 8, 2024
95f95b9
Update ASSESTMENT_RESULTS.md
christianjtr Apr 8, 2024
1e8fa75
Change assessment results filename
christianjtr Apr 8, 2024
54d8467
Add documentation about tests
christianjtr Apr 8, 2024
9251ad5
Update ASSESSMENT_RESULTS.md
christianjtr Apr 8, 2024
a9b4e52
Update ASSESSMENT_RESULTS.md
christianjtr Apr 8, 2024
3146541
Update ASSESSMENT_RESULTS.md
christianjtr Apr 8, 2024
aa2cedd
Add additional improvements section
christianjtr Apr 8, 2024
3a0b1a4
Add the archive call documentation along with the sync tab feature
christianjtr Apr 8, 2024
2e6d803
Update ASSESSMENT_RESULTS.md
christianjtr Apr 8, 2024
637158a
Add logout feature explanation
christianjtr Apr 8, 2024
18b8eec
Add token expiration explanation
christianjtr Apr 9, 2024
ccf9702
Add some comments regarding the release
christianjtr Apr 9, 2024
5ce6891
Update ASSESSMENT_RESULTS.md
christianjtr Apr 9, 2024
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
501 changes: 501 additions & 0 deletions ASSESSMENT_RESULTS.md

Large diffs are not rendered by default.

Binary file added assets/task001.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/task002.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/task002.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/task003.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/task006.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/task007.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/task007_cy_run.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/task008.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/task008.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions phone-test/.env.e2e
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#CYPRESS
APP_BASE_URL="http://localhost:3000"
AUTH_TOKEN_KEY="access_token"
REFRESH_TOKEN_KEY="refresh_token"
17 changes: 17 additions & 0 deletions phone-test/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { defineConfig } from "cypress";
import dotenv from 'dotenv';

const env_e2e = dotenv.config({ path: '.env.e2e' }).parsed;

export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
config.env = {
...config.env,
...env_e2e,
};

return config;
},
},
});
25 changes: 25 additions & 0 deletions phone-test/cypress/e2e/Login.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'cypress-wait-until';
import fixtures from '../fixtures/example.json';

describe('Login page', () => {
it('should visit the Login page', () => {
cy.visit(`${Cypress.env('APP_BASE_URL')}/login`);
});

it('should perform login and navigate to calls page', () => {

const { login } = fixtures;

cy.visit(`${Cypress.env('APP_BASE_URL')}/login`);

cy.get('[data-cy="email"]').should('exist');
cy.get('[data-cy="password"]').should('exist');
cy.get('[data-cy="btn-submit"]').should('exist');

cy.get('[data-cy="email"]').type(login.username);
cy.get('[data-cy="password"]').type(login.password);
cy.get('[data-cy="btn-submit"]').click().then(() => {
cy.url().should('eq', `${Cypress.env('APP_BASE_URL')}/calls`);
});
});
});
34 changes: 34 additions & 0 deletions phone-test/cypress/e2e/Logout.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* eslint-disable @typescript-eslint/no-unused-expressions */
import 'cypress-wait-until';
import fixtures from '../fixtures/example.json';

describe('Logout functionlaity', () => {
let AUTH_TOKEN_KEY: string, REFRESH_TOKEN_KEY: string;

before(() => {
AUTH_TOKEN_KEY = Cypress.env('AUTH_TOKEN_KEY');
REFRESH_TOKEN_KEY = Cypress.env('REFRESH_TOKEN_KEY');
})

it('should perform a success login and a correctly logout', () => {
const { login } = fixtures;

cy.visit(`${Cypress.env('APP_BASE_URL')}/login`);

cy.get('[data-cy="email"]').type(login.username);
cy.get('[data-cy="password"]').type(login.password);
cy.get('[data-cy="btn-submit"]').click();

cy.url().should('eq', `${Cypress.env('APP_BASE_URL')}/calls`).then(() => {
expect(localStorage.getItem(AUTH_TOKEN_KEY)).not.to.be.null;
expect(localStorage.getItem(REFRESH_TOKEN_KEY)).not.to.be.null;
});

cy.get('[data-cy="btn-logout"]').click();

cy.url().should('eq', `${Cypress.env('APP_BASE_URL')}/login`).then(() => {
expect(localStorage.getItem(AUTH_TOKEN_KEY)).to.be.null;
expect(localStorage.getItem(REFRESH_TOKEN_KEY)).to.be.null;
});
});
});
6 changes: 6 additions & 0 deletions phone-test/cypress/fixtures/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"login": {
"username": "[email protected]",
"password": "12345"
}
}
37 changes: 37 additions & 0 deletions phone-test/cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }
20 changes: 20 additions & 0 deletions phone-test/cypress/support/e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands'

// Alternatively you can use CommonJS syntax:
// require('./commands')
19 changes: 18 additions & 1 deletion phone-test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,36 @@
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/styled-components": "^5.1.26",
"cypress": "13.6.1",
"cypress-wait-until": "3.0.1",
"date-fns": "^2.29.3",
"graphql": "^16.6.0",
"graphql-ws": "^5.16.0",
"husky": "^8.0.2",
"lint-staged": "^13.0.3",
"prettier": "^2.7.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.4.3",
"react-scripts": "5.0.1",
"start-server-and-test": "2.0.3",
"styled-components": "^5.3.6",
"subscriptions-transport-ws": "^0.11.0",
"typescript": "^4.4.2",
"web-vitals": "^2.1.0"
},
"config": {
"host": "http://localhost:3000"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"cy:open": "cypress open",
"cy:run": "cypress run --e2e",
"cy:test": "start-server-and-test start $npm_package_config_host cy:run",
"cy:e2e": "start-server-and-test start $npm_package_config_host cy:open"
},
"eslintConfig": {
"extends": [
Expand Down Expand Up @@ -59,5 +71,10 @@
"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}": [
"prettier --write"
]
},
"jest": {
"transformIgnorePatterns": [
"/node_modules/?!(@aircall)"
]
}
}
38 changes: 10 additions & 28 deletions phone-test/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createBrowserRouter, createRoutesFromElements, Route } from 'react-router-dom';
import { createBrowserRouter, createRoutesFromElements, Route, Navigate } from 'react-router-dom';
import { LoginPage } from './pages/Login/Login';
import { CallsListPage } from './pages/CallsList';
import { CallsListPage } from './pages/CallsList/CallsList';
import { CallDetailsPage } from './pages/CallDetails';
import { Tractor } from '@aircall/tractor';

Expand All @@ -9,33 +9,10 @@ import { ProtectedLayout } from './components/routing/ProtectedLayout';
import { darkTheme } from './style/theme/darkTheme';
import { RouterProvider } from 'react-router-dom';
import { GlobalAppStyle } from './style/global';
import { ApolloClient, InMemoryCache, ApolloProvider, createHttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { ApolloProvider } from '@apollo/client';
import useApolloClient from './hooks/useApolloClient';
import { AuthProvider } from './hooks/useAuth';

const httpLink = createHttpLink({
uri: 'https://frontend-test-api.aircall.dev/graphql'
});

const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const accessToken = localStorage.getItem('access_token');
const parsedToken = accessToken ? JSON.parse(accessToken) : undefined;

// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: accessToken ? `Bearer ${parsedToken}` : ''
}
};
});

const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});

export const router = createBrowserRouter(
createRoutesFromElements(
<Route element={<AuthProvider />}>
Expand All @@ -44,14 +21,19 @@ export const router = createBrowserRouter(
<Route path="/calls" element={<CallsListPage />} />
<Route path="/calls/:callId" element={<CallDetailsPage />} />
</Route>
<Route path="/" element={<Navigate to="/login" replace />} />
</Route>
)
);

function App() {
const { apolloClient } = useApolloClient();

if (!apolloClient) return null;

return (
<Tractor injectStyle theme={darkTheme}>
<ApolloProvider client={client}>
<ApolloProvider client={apolloClient}>
<RouterProvider router={router} />
<GlobalAppStyle />
</ApolloProvider>
Expand Down
55 changes: 55 additions & 0 deletions phone-test/src/components/Layout/Loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import styled from 'styled-components';

export const LoaderWrapper = styled.div`
display: flex;
height: 100vh;
flex-direction: column;
justify-content: center;
align-items: center;

> span.loader {
display: block;
width: 48px;
height: 48px;
border: 5px solid #fff;
border-bottom-color: #29b388;
border-radius: 50%;
display: inline-block;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}

> span.loader-message {
display: block;
width: 100%;
text-align: center;
padding: 20px;
}

@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
`;

export interface LoaderProps {
message?: string;
}

const Loader: React.FC<LoaderProps> = (props: LoaderProps): React.ReactElement => {
const { message = 'Loading...' } = props;

return (
<LoaderWrapper>
<span className="loader"></span>
<span className="loader-message">{message}</span>
</LoaderWrapper>
);
};

export default Loader;
20 changes: 16 additions & 4 deletions phone-test/src/components/routing/ProtectedLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
import { Outlet, Link } from 'react-router-dom';
import { Box, Flex, Spacer, Grid } from '@aircall/tractor';
import { Outlet, Link, Navigate } from 'react-router-dom';
import { Box, Flex, Spacer, Grid, Button } from '@aircall/tractor';
import { AUTH_CONFIG } from '../../services/auth/authConfig';
import { useLocalStorage } from '../../hooks/useLocalStorage';
import { useAuth } from '../../hooks/useAuth';
import logo from '../../logo.png';

export const ProtectedLayout = () => {
const { logout, checkIsLoggedIn } = useAuth();
const [user] = useLocalStorage(AUTH_CONFIG.USER, undefined);

const isLoggedIn = checkIsLoggedIn();

if (!isLoggedIn) return <Navigate to="/login" />;

return (
<Box minWidth="100vh" p={4}>
<Flex justifyContent="space-between" alignItems="center">
<Link to="/calls">
<img src={logo} alt="Aircall" width="32px" height="32px" />
</Link>
<Spacer space="m" alignItems="center">
<span>{`Welcome {username}!`}</span>
<Link to="/login">logout</Link>
<span>{`Welcome ${user?.username}!`}</span>
<Button name="btn-logout" mode="link" onClick={logout} data-cy="btn-logout">
Logout
</Button>
</Spacer>
</Flex>
<Grid w="500px" mx="auto" rowGap={2}>
Expand Down
11 changes: 8 additions & 3 deletions phone-test/src/components/routing/ProtectedRoute.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
// TODO check that the user is authenticated before displaying the route
return <>{children}</>;
import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from '../../hooks/useAuth';

export const ProtectedRoute = () => {
const { checkIsLoggedIn } = useAuth();
const isLoggedIn = checkIsLoggedIn();

return isLoggedIn ? <Outlet /> : <Navigate to="/login" />;
};
12 changes: 12 additions & 0 deletions phone-test/src/gql/mutations/archiveCall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { gql } from '@apollo/client';
import { CALL_FIELDS } from '../fragments/call';

export const ARCHIVE_CALL = gql`
${CALL_FIELDS}

mutation ArchiveCall($id: ID!) {
archiveCall(id: $id) {
...CallFields
}
}
`;
2 changes: 2 additions & 0 deletions phone-test/src/gql/mutations/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from './login';
export * from './refreshTokenV2';
export * from './archiveCall';
Loading