Skip to content

Commit

Permalink
Merge pull request #57 from manh-t/release/0.3.0
Browse files Browse the repository at this point in the history
Release - 0.3.0
  • Loading branch information
manh-t authored Jul 28, 2023
2 parents ef6fed1 + a84b657 commit c87513a
Show file tree
Hide file tree
Showing 36 changed files with 776 additions and 69 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Team
* @manh-t @nvminhtue @tyrro
* @manh-t @nvminhtue @tyrro @hoangmirs
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "manh-react-survey",
"version": "0.2.0",
"version": "0.3.0",
"private": true,
"dependencies": {
"@reduxjs/toolkit": "1.9.5",
Expand Down
4 changes: 2 additions & 2 deletions src/adapters/Base/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { AxiosRequestConfig } from 'axios';
import { JSONObject, keysToSnakeCase } from 'helpers/json';
import requestManager from 'lib/request/v1/requestManager';

export function get(path: string, params?: JSONObject) {
export const get = (path: string, params?: JSONObject) => {
const requestOptions: AxiosRequestConfig = {};
if (params) {
requestOptions.params = keysToSnakeCase(params);
}

return requestManager('get', path, requestOptions);
}
};

export const post = (path: string, params: JSONObject) => {
const requestOptions: AxiosRequestConfig = { data: keysToSnakeCase(params) };
Expand Down
85 changes: 85 additions & 0 deletions src/adapters/BaseAuth/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/* eslint camelcase: ["error", {allow: ["camel_case_key"]}] */
import { getToken } from 'helpers/authentication';
import requestManager from 'lib/request/v1/requestManager';

import { authenticatedHeader, getAuth, postAuth } from '.';

jest.mock('lib/request/v1/requestManager');
jest.mock('helpers/authentication');

describe('BaseAuthAdapter', () => {
const path = '/path';
const params = {
camelCaseKey: 'value',
};

beforeEach(() => {
(requestManager as jest.Mock).mockImplementation(() => jest.fn());
});

afterEach(() => {
jest.clearAllMocks();
});

describe('authenticatedHeader', () => {
const mockAccessToken = 'access token';
const mockTokenType = 'token type';
const mockTokens = {
id: 'id',
resourceType: 'resource type',
accessToken: mockAccessToken,
refreshToken: 'refresh token',
tokenType: mockTokenType,
};

beforeEach(() => {
(getToken as jest.Mock).mockImplementation(() => mockTokens);
});

afterEach(() => {
jest.clearAllMocks();
});

it('returns the headers that includes the token', () => {
const expectedHeader = {
Authorization: `${mockTokenType} ${mockAccessToken}`,
};

expect(authenticatedHeader()).toEqual(expectedHeader);
});
});

describe('getAuth', () => {
describe('given only the path', () => {
it('calls the get method from request manager with the path and authenticated header', () => {
getAuth(path);

expect(requestManager).toHaveBeenCalledWith('get', path, { headers: authenticatedHeader() });
});
});

describe('given the path and url params', () => {
it('calls the get method from request manager with the path, params and authenticated header', () => {
getAuth(path, params);

expect(requestManager).toHaveBeenCalledWith('get', path, {
params: { camel_case_key: params.camelCaseKey },
headers: authenticatedHeader(),
});
});
});
});

describe('postAuth', () => {
describe('given the path and params', () => {
it('calls the post method from request manager with the path, params and authenticated header', () => {
postAuth(path, params);

expect(requestManager).toHaveBeenCalledWith('post', path, {
data: { camel_case_key: params.camelCaseKey },
headers: authenticatedHeader(),
});
});
});
});
});
26 changes: 26 additions & 0 deletions src/adapters/BaseAuth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { AxiosRequestConfig } from 'axios';

import { getToken } from 'helpers/authentication';
import { JSONObject, keysToSnakeCase } from 'helpers/json';
import requestManager from 'lib/request/v1/requestManager';

export const authenticatedHeader = (): { Authorization: string } => ({
Authorization: `${getToken()?.tokenType} ${getToken()?.accessToken}`,
});

const defaultRequestOptions = (): AxiosRequestConfig => ({ headers: authenticatedHeader() });

export const getAuth = (path: string, params?: JSONObject) => {
const requestOptions: AxiosRequestConfig = { ...defaultRequestOptions() };
if (params) {
requestOptions.params = keysToSnakeCase(params);
}

return requestManager('get', path, requestOptions);
};

export const postAuth = (path: string, params: JSONObject) => {
const requestOptions: AxiosRequestConfig = { ...defaultRequestOptions(), data: keysToSnakeCase(params) };

return requestManager('post', path, requestOptions);
};
25 changes: 25 additions & 0 deletions src/adapters/Survey/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { getAuth } from 'adapters/BaseAuth';

import { getSurveys } from '.';

jest.mock('adapters/BaseAuth');

describe('SurveyAdapter', () => {
beforeEach(() => {
(getAuth as jest.Mock).mockImplementation(() => jest.fn());
});

afterEach(() => {
jest.clearAllMocks();
});

describe('getSurveys', () => {
it('calls the get method from the base adapter', () => {
const expectedPath = 'surveys?page[number]=1&page[size]=10';

getSurveys();

expect(getAuth).toHaveBeenCalledWith(expectedPath);
});
});
});
3 changes: 3 additions & 0 deletions src/adapters/Survey/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { getAuth } from 'adapters/BaseAuth';

export const getSurveys = () => getAuth('surveys?page[number]=1&page[size]=10');
3 changes: 3 additions & 0 deletions src/assets/images/icons/arrow-right.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions src/components/BackgroundImage/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';

import { render, screen } from '@testing-library/react';

import BackgroundImage, { backgroundImageTestIds } from '.';

describe('BackgroundImage', () => {
it('renders BackgroundImage with the background image', () => {
const imgUrl = 'test url';

render(<BackgroundImage backgroundUrl={imgUrl}>The children</BackgroundImage>);

const backgroundImage = screen.getByTestId(backgroundImageTestIds.base);
const backgroundImgElement = screen.getByAltText('background');

expect(backgroundImage).toBeVisible();
expect(backgroundImgElement).toHaveAttribute('src', imgUrl);
});

it('renders BackgroundImage with the black as a background', () => {
render(<BackgroundImage>The children</BackgroundImage>);

const backgroundImage = screen.getByTestId(backgroundImageTestIds.base);

expect(backgroundImage).toBeVisible();
expect(backgroundImage).toHaveClass('bg-black');
});
});
25 changes: 25 additions & 0 deletions src/components/BackgroundImage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';

export const backgroundImageTestIds = {
base: 'background-image',
};

interface BackgroundImageProps {
children: React.ReactNode;
backgroundUrl?: string;
}

const BackgroundImage = ({ backgroundUrl, children }: BackgroundImageProps): JSX.Element => {
return backgroundUrl ? (
<div className="h-screen w-screen relative" data-test-id={backgroundImageTestIds.base}>
<img src={backgroundUrl} alt="background" className="absolute w-full h-full z-[-1]" />
<div className="h-full backdrop-blur-[50px] bg-gradient-to-b from-black/20 to-black">{children}</div>
</div>
) : (
<div className="absolute w-full h-full z-[-1] bg-black" data-test-id={backgroundImageTestIds.base}>
{children}
</div>
);
};

export default BackgroundImage;
49 changes: 49 additions & 0 deletions src/components/Dashboard/Content/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';

import { render, screen } from '@testing-library/react';

import DashboardContent, { dashboardDataTestIds } from '.';

describe('DashboardContent', () => {
const surveys = [
{
id: '1',
resourceType: 'survey',
coverImageUrl: 'https://dhdbhh0jsld0o.cloudfront.net/m/1ea51560991bcb7d00d0_',
title: 'Working from home Check-In',
description: 'We would like to know how you feel about our work from home.',
},
];

it('renders DashboardContent and its components', () => {
render(
<DashboardContent
surveys={surveys}
currentPosition={0}
shouldShowShimmer={false}
onNextSurvey={() => jest.fn()}
onIndicatorTapped={() => jest.fn()}
/>
);

const dashboardContent = screen.getByTestId(dashboardDataTestIds.content);

expect(dashboardContent).toBeVisible();
expect(dashboardContent).toHaveTextContent(surveys[0].title);
expect(dashboardContent).toHaveTextContent(surveys[0].description);
});

it('does NOT render the DashboardContent components', () => {
render(
<DashboardContent
surveys={surveys}
currentPosition={0}
shouldShowShimmer={true}
onNextSurvey={() => jest.fn()}
onIndicatorTapped={() => jest.fn()}
/>
);

expect(screen.queryByTestId(dashboardDataTestIds.content)).not.toBeInTheDocument();
});
});
87 changes: 87 additions & 0 deletions src/components/Dashboard/Content/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React, { useEffect } from 'react';

import classNames from 'classnames';

import { ReactComponent as ArrowRight } from 'assets/images/icons/arrow-right.svg';
import Shimmer from 'components/Shimmer';
import { getHighResolutionImage } from 'helpers/image';
import { Survey } from 'types/survey';

export const dashboardDataTestIds = {
content: 'dashboard__content',
};

interface DashboardContentProps {
surveys: Survey[];
currentPosition: number;
shouldShowShimmer: boolean;
onNextSurvey: () => void;
onIndicatorTapped: (position: number) => void;
}

const DashboardContent = ({
surveys,
currentPosition,
shouldShowShimmer = true,
onNextSurvey,
onIndicatorTapped,
}: DashboardContentProps): JSX.Element => {
useEffect(() => {
const interval = setInterval(() => {
onNextSurvey();
}, 3000);
return () => {
clearInterval(interval);
};
});

return shouldShowShimmer ? (
<div className="flex flex-col h-full">
<Shimmer classAttributes="h-[302px] rounded-[12px]" />
<div className="flex flex-row justify-between mt-[38px]">
<div className="flex flex-col justify-between">
<Shimmer classAttributes="w-[318px] h-[18px] rounded-[14px]" />
<Shimmer classAttributes="w-[212px] h-[18px] rounded-[14px]" />
</div>
<Shimmer classAttributes="w-[56px] h-[56px] rounded-full" />
</div>
</div>
) : (
<div className="flex flex-col h-full" data-test-id={dashboardDataTestIds.content}>
<div
style={{ backgroundImage: `url(${getHighResolutionImage(surveys[currentPosition].coverImageUrl)})` }}
className="w-full h-[302px] rounded-[12px] bg-cover duration-500 ease-in-out"
></div>
<div className="flex flex-row justify-between mt-[38px]">
<div className="flex flex-col justify-between">
<p className="text-white text-x-regular font-extrabold">{surveys[currentPosition].title}</p>
<p className="text-white text-regular tracking-survey-tight opacity-60 mt-2">{surveys[currentPosition].description}</p>
</div>
<button
type="button"
className="w-[56px] h-[56px] bg-white rounded-full inline-flex items-center justify-center text-black-chinese"
>
<ArrowRight />
</button>
</div>
{/* <!-- Slider indicators --> */}
<div className="flex-1 flex space-x-3 justify-center items-end mb-[42px]">
{surveys.map((surveyItem, index) => {
return (
<button
key={surveyItem.id}
type="button"
className={classNames('w-2 h-2 rounded-full bg-white', { 'bg-opacity-20': index !== currentPosition })}
aria-current={index === currentPosition ? 'true' : 'false'}
aria-label={`Slide ${surveyItem.id}`}
data-carousel-slide-to={index}
onClick={() => onIndicatorTapped(index)}
></button>
);
})}
</div>
</div>
);
};

export default DashboardContent;
Loading

0 comments on commit c87513a

Please sign in to comment.