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

[Challenge #1] Update NavBar link behavior #1

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
18 changes: 10 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Route, BrowserRouter, Routes } from 'react-router-dom';
import TableDemoView from './components/TableDemoView';
import { useState } from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import HomeView from './components/HomeView';
import RedirectView from './components/RedirectView';
import YouCanDoItView from './components/YouCanDoItView';
import Instructions from './components/Instructions';
import Layout from './components/Layout';
import { useState } from 'react';
import Modal from './components/Modal';
import Instructions from './components/Instructions';
import PolicyholdersView from './components/PolicyholdersView';
import RedirectView from './components/RedirectView';
import TableDemoView from './components/TableDemoView';
import YouCanDoItView from './components/YouCanDoItView';

function App() {
const [isModalOpen, setIsModalOpen] = useState(false);
Expand All @@ -15,15 +16,16 @@ function App() {
<BrowserRouter>
<Layout onFooterClick={() => setIsModalOpen(true)}>
<Routes>
<Route path="*" element={<RedirectView />} />
<Route path="/" element={<HomeView />} />
<Route path="/policyholders" element={<PolicyholdersView />} />
<Route path="/table" element={<TableDemoView />} />
<Route path="/you-can-do-it" element={<YouCanDoItView />} />
<Route path="*" element={<RedirectView />} />
</Routes>
</Layout>
<Modal
isOpen={isModalOpen}
handleClose={() => setIsModalOpen(false)}
isOpen={isModalOpen}
title="Sure Technical Challenge"
>
<Instructions />
Expand Down
10 changes: 7 additions & 3 deletions src/components/InstructionsBar/InstructionsBar.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import InstructionsBar from './InstructionsBar';
import { fireEvent } from '@testing-library/react';
import { renderWithProviders } from '../../utils/test';
import InstructionsBar from './InstructionsBar';

describe('InstructionsBar', () => {
const defaultProps = {
Expand All @@ -11,6 +12,9 @@ describe('InstructionsBar', () => {
expect(getByText('View challenges')).toBeInTheDocument();
});

// TODO: Challenge 3
it('should call the onClick prop when the button is clicked', () => {});
it('should call the onClick prop when the button is clicked', () => {
const { getByRole } = renderWithProviders(<InstructionsBar {...defaultProps} />);
fireEvent.click(getByRole('button'));
expect(defaultProps.onClick).toHaveBeenCalledTimes(1);
});
});
9 changes: 7 additions & 2 deletions src/components/NavBar/NavBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ describe('NavBar', () => {
expect(getByText('Link3')).toBeInTheDocument();
});

// TODO: Challenge 2
it('should render an `href` attribute for each link', () => {});
it('should render an `href` attribute for each link', () => {
const { getByText } = renderWithProviders(<NavBar {...defaultProps} />);

expect(getByText(defaultProps.links[0].text).closest('a')).toHaveAttribute('href', defaultProps.links[0].href);
expect(getByText(defaultProps.links[1].text).closest('a')).toHaveAttribute('href', defaultProps.links[1].href);
expect(getByText(defaultProps.links[2].text).closest('a')).toHaveAttribute('href', defaultProps.links[2].href);
});
});
77 changes: 48 additions & 29 deletions src/components/NavBar/NavBar.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,71 @@
import { Link, Box } from '@mui/material';
import { Link as RouterLink } from 'react-router-dom';
import { Link as RouterLink, useLocation } from 'react-router-dom';
import { Box, Link, List, ListItem, ListItemText } from '@mui/material';

type TNavBar = {
links: {
text: string;
href: string;
'data-testid'?: string;
href: string;
text: string;
}[];
};

const listStyle = {
width: '100%',
} as const;
const listItemStyle = {
color: '#fff',
cursor: 'pointer',
padding: 0,
textAlign: 'center',
'&:hover': {
textDecoration: 'underline',
},
'&:not(:last-of-type)': {
marginBottom: '4px',
},
'&.Mui-selected': {
backgroundColor: '#f00',
},
} as const;

function NavBar({ links }: TNavBar) {
const location = useLocation()

return (
<Box
component="aside"
component='aside'
sx={{
alignItems: 'center',
background: '#0c2975',
padding: '16px',
width: '200px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '16px',
width: '200px',
}}
>
<Link
component={RouterLink}
to="/"
sx={{ cursor: 'pointer', marginBottom: '80px', marginTop: '40px' }}
to='/'
>
<img src="/surelogo.svg" alt="logo"></img>
<img alt="logo" src="/surelogo.svg" />
</Link>

{links.map(({ text, href, 'data-testid': dataTestId }) => (
<Link
component={RouterLink}
key={href}
to={href}
color="#fff"
underline="hover"
sx={{
cursor: 'pointer',
'&:not(:last-of-type)': {
marginBottom: '16px',
},
}}
data-testid={dataTestId}
>
{text}
</Link>
))}
<List role='menu' sx={listStyle}>
{links.map(({ text, href, 'data-testid': dataTestId }) => (
<ListItem
aria-label={href === location.pathname ? 'page' : ''} // TODO: Figure out aria-current fix
component={RouterLink}
data-testid={dataTestId}
key={href}
role='menuitem'
selected={href === location.pathname}
sx={listItemStyle}
to={href}
>
<ListItemText>{text}</ListItemText>
</ListItem>
))}
</List>
</Box>
);
}
Expand Down
156 changes: 156 additions & 0 deletions src/components/PolicyholdersView/PolicyholdersView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { useEffect, useState } from 'react';
import { Box, Button, List, ListItem, ListItemText } from '@mui/material';
import { mockPolicyHoldersList, mockSinglePolicyHolder } from '../../constants/mockPolicyHoldersData';
import InfoTable from '../InfoTable';

type TPolicyHoldersList = {
policyHolders: { key: string; value: string | number }[][];
};

function PolicyholdersView() {
const apiUrl = 'https://fe-interview-technical-challenge-api-git-main-sure.vercel.app/api/policyholders';
const defaultPolicyHoldersList: TPolicyHoldersList = { policyHolders: [[]] };
const toDoList: string[] = [
'Add actual form to add policy holders',
'Add error messaging in UI',
'Add loading icon/screen in UI during GET/POST requests',
'Add localization support',
'Add zero-state messaging in UI',
'Clear console.logs',
'Consider data storage and caching',
'Figure out aria-current fix',
'Verify accessibility',
];

const [policyHoldersList, setPolicyHoldersList] = useState(defaultPolicyHoldersList);

const formatData = (dataArray: []) => {
const formattedData = dataArray.map((
{
name = '',
age = 0,
address = {
line1: '',
line2: undefined,
city: '',
state: '',
postalCode: '',
},
phoneNumber = '',
isPrimary = true,
}) => ([
{
key: 'Name',
value: `${name}`,
},
{
key: 'Age',
value: Number(`${age}`),
},
{
key: 'Address',
value: `${address.line1}, ${address.line2 ? address.line2 + ', ' : ''}${address.city}, ${address.state}, ${address.postalCode}`,
},
{
key: 'Phone number',
value: `${phoneNumber}`,
},
{
key: 'Primary policy holder?',
value: `${isPrimary ? 'Yes' : 'No'}`,
},
])
);

return formattedData;
}

const handleAddPolicyHolder = () => {
fetch(apiUrl, { body: JSON.stringify(mockSinglePolicyHolder), method: 'POST' })
.then(async (response) => {
const rawData = await response.json();

if (!response.ok) {
const error = (rawData && rawData.message) || response.statusText;
throw new Error(error);
};

return rawData;
})
.then((rawData) => {
const displayData: TPolicyHoldersList = { policyHolders: [[]] };
const formattedData = formatData(rawData.policyHolders);

displayData.policyHolders = formattedData;

console.log(20, 'fetched raw data:', rawData);
console.log(20, 'formatted data:', formattedData);
console.log(20, 'display data:', displayData);

setPolicyHoldersList(displayData);
})
.catch((error) => {
console.error(20, error);
});
};

useEffect(() => {
fetch(apiUrl)
.then(async (response) => {
const rawData = await response.json();

if (!response.ok) {
const error = (rawData && rawData.message) || response.statusText;
throw new Error(error);
};

return rawData;
})
.then((rawData) => {
const displayData: TPolicyHoldersList = { policyHolders: [[]] };
const formattedData = formatData(rawData.policyHolders);

displayData.policyHolders = formattedData;

console.log(0, 'fetched raw data:', rawData);
console.log(0, 'formatted data:', formattedData);
console.log(0, 'display data:', displayData);

setPolicyHoldersList(displayData);
})
.catch((error) => {
console.error(0, error);
});
}, []);

console.log(10, 'formatted data mockPolicyHoldersList:', mockPolicyHoldersList);
console.log(10, 'formatted data policyHoldersList:', policyHoldersList);

return (
<Box sx={{ textAlign: 'center' }}>
{policyHoldersList.policyHolders.map((_, i) => (
<InfoTable key={i} header={(i === 0) ? "Policy Holders" : ""} rows={policyHoldersList.policyHolders[i]} />
))}
<Button
color="primary"
onClick={handleAddPolicyHolder}
size="medium"
sx={{ marginTop: '16px' }}
variant="contained"
>
Add a policy holder
</Button>
<List>
{toDoList.map((value, i) => {
return (
<ListItem key={i} disablePadding>
<ListItemText id={`label-${i}`} primary={`${value}`} />
</ListItem>
);
})}
</List>
</Box>
);
};

export default PolicyholdersView;
1 change: 1 addition & 0 deletions src/components/PolicyholdersView/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './PolicyholdersView';
12 changes: 6 additions & 6 deletions src/constants/links.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
const links = [
{
text: 'Policyholders',
href: '/policyholders',
'data-testid': 'policyholders_link',
href: '/policyholders',
text: 'Policy Holders',
},
{
text: 'Table Example',
href: '/table',
'data-testid': 'table_link',
href: '/table',
text: 'Table Example',
},
{
text: 'You Can Do It',
href: '/you-can-do-it',
'data-testid': 'you_go_link',
href: '/you-can-do-it',
text: 'You Can Do It',
},
];

Expand Down
Loading