Skip to content

Commit

Permalink
Merge pull request #3 from cupton15/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
cupton15 authored Jun 7, 2021
2 parents a4b5656 + 77c56d1 commit d323e40
Show file tree
Hide file tree
Showing 12 changed files with 446 additions and 102 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "reality-check",
"version": "1.0.1",
"version": "1.1.0",
"private": true,
"dependencies": {
"@craco/craco": "^6.1.2",
Expand Down
20 changes: 12 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,19 @@ function App(): JSX.Element {
<div className="App flex flex-col self-center items-center justify-items-center">
{!submitted && (
<>
<div className="mb-8 px-4">
<p>
Sometimes you need a reality check when it comes to your
earnings.
</p>
<p>Spoiler alert: You&apos;ll be fine</p>
</div>

<SalaryCheckForm onSubmit={onSubmit} />
<p>
All data sourced{' '}
<a
href="https://www.ons.gov.uk/employmentandlabourmarket/peopleinwork/earningsandworkinghours/bulletins/annualsurveyofhoursandearnings/2020/relateddata"
target="_blank"
rel="noreferrer"
aria-label="Visit the government employment and labour website for data sources"
className="underline text-blue-400"
>
here
</a>
</p>
</>
)}
{submitted && jobInfo && <SalaryResults jobInfo={jobInfo} />}
Expand Down
1 change: 1 addition & 0 deletions src/chevron-down-solid.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion src/components/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export default function Header(): JSX.Element {
return (
<header className="p-5 bg-blue-400 text-white shadow-lg leading-loose">
<h1 className="font-bold text-3xl md:text-5xl">Reality Check</h1>
<p>Chances are you&apos;re doing fine</p>
</header>
);
}
45 changes: 43 additions & 2 deletions src/components/salary check form/SalaryCheckForm.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import {
render,
screen,
fireEvent,
getAllByRole,
} from '@testing-library/react';
import SalaryCheckForm from './SalaryCheckForm';

const mockSubmit = jest.fn(({ data: JobInformation }) =>
Expand All @@ -12,6 +17,15 @@ describe('SalaryCheckForm', () => {
});

it('should display required error when no salary entered', async () => {
fireEvent.change(
await screen.getByRole('combobox', { name: 'Select an age range' }),
{
target: {
value: 'all',
},
}
);

fireEvent.submit(screen.getByRole('button'));

expect(await screen.findAllByRole('alert')).toHaveLength(1);
Expand All @@ -21,15 +35,42 @@ describe('SalaryCheckForm', () => {
expect(mockSubmit).not.toBeCalled();
});

it('should display required error when no age range entered', async () => {
fireEvent.input(
await screen.getByRole('textbox', { name: 'Enter Salary (Gross)' }),
{
target: {
value: '1000',
},
}
);

fireEvent.submit(screen.getByRole('button'));
expect(await screen.findAllByRole('alert')).toHaveLength(1);
expect(await screen.findByRole('alert')).toHaveTextContent(
'Must select an age range'
);
expect(mockSubmit).not.toBeCalled();
});

it('should display positive error when a negative salary is entered', async () => {
fireEvent.input(
await screen.getByRole('textbox', { name: 'Enter Salary' }),
await screen.getByRole('textbox', { name: 'Enter Salary (Gross)' }),
{
target: {
value: '-1000',
},
}
);
fireEvent.change(
await screen.getByRole('combobox', { name: 'Select an age range' }),
{
target: {
value: 'all',
},
}
);

fireEvent.submit(screen.getByRole('button'));
expect(await screen.findAllByRole('alert')).toHaveLength(1);
expect(await screen.findByRole('alert')).toHaveTextContent(
Expand Down
143 changes: 82 additions & 61 deletions src/components/salary check form/SalaryCheckForm.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/* eslint-disable react/jsx-props-no-spreading */
import React from 'react';
import { useForm } from 'react-hook-form';
import { useForm, FormProvider } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
import ValidationMessage from './validation message/ValidationMessage';
import JobInformation from '../../types/JobInformation';
import { ReactComponent as PoundSign } from '../../solid_pound_sign.svg';
import SubmitButton from '../shared/SubmitButton';
import Select from '../shared/Select';

type FormProps = {
onSubmit: (data: JobInformation) => void;
Expand All @@ -18,79 +19,99 @@ const schema = yup.object().shape({
.transform((x) => (Number.isNaN(x) ? undefined : x))
.positive('Must provide a positive salary')
.required('Must provide a salary'),
ageRange: yup.string().required('Must select an age range'),
});

export default function SalaryCheckForm({ onSubmit }: FormProps): JSX.Element {
const formMethods = useForm<JobInformation>({
resolver: yupResolver(schema),
});
const {
register,
handleSubmit,
getValues,
formState: { errors },
} = useForm<JobInformation>({ resolver: yupResolver(schema) });
} = formMethods;
const submitHandler = handleSubmit((data) => onSubmit(data));

return (
<form
onSubmit={submitHandler}
className="flex flex-col w-screen md:w-1/2 p-5 space-y-8 max-w-2xl text-gray-400 "
>
<div className="relative focus-within:text-gray-600 ">
<label htmlFor="salary">
<PoundSign
className="w-6 h-6 absolute left-2 top-2"
fill="currentColor"
/>
<input
id="salary"
type="text"
placeholder="Enter Salary"
aria-label="Enter Salary"
aria-invalid={errors.salary ? 'true' : 'false'}
className="focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none w-full border border-gray-200 rounded-md text-sm placeholder-gray-400 py-2 pl-10"
defaultValue=""
{...register('salary', { required: true })}
/>
</label>
{errors.salary && (
<ValidationMessage>{errors.salary.message}</ValidationMessage>
)}
</div>
<div className="flex justify-around">
<div>
<input
id="full-time"
aria-label="Full Time"
type="radio"
value="FullTime"
{...register('type')}
className="hidden"
defaultChecked
/>
<label
htmlFor="full-time"
className="label-checked:bg-blue-400 label-checked:text-white label-checked:font-bold label-checked:border-blue-400 border border-gray-200 rounded-md p-3 cursor-pointer"
>
Full-Time
<FormProvider {...formMethods}>
<form
onSubmit={submitHandler}
className="flex flex-col w-screen md:w-1/2 p-5 space-y-8 max-w-2xl text-gray-400 "
>
<div className="relative focus-within:text-gray-600 ">
<label htmlFor="salary">
<PoundSign
className="w-6 h-6 absolute left-2 top-2"
fill="currentColor"
/>
<input
id="salary"
type="text"
placeholder="Enter Salary (Gross)"
aria-label="Enter Salary (Gross)"
aria-invalid={errors.salary ? 'true' : 'false'}
className="focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none w-full border border-gray-200 rounded-md text-sm placeholder-gray-400 py-2 pl-10"
defaultValue=""
{...register('salary', { required: true })}
/>
</label>
{errors.salary && (
<ValidationMessage>{errors.salary.message}</ValidationMessage>
)}
</div>
<div>
<input
id="part-time"
aria-label="Part Time"
type="radio"
value="PartTime"
{...register('type')}
className="hidden"
/>
<label
htmlFor="part-time"
className="label-checked:bg-blue-400 label-checked:text-white label-checked:font-bold label-checked:border-blue-400 border border-gray-200 rounded-md p-3 cursor-pointer"
>
Part-Time
</label>
<Select id="age-select" name="ageRange">
<option value="">Select an age range</option>
<option value="all">All</option>
<option value="18-21">18-21</option>
<option value="22-29">22-29</option>
<option value="30-39">30-39</option>
<option value="40-49">40-49</option>
<option value="50-59">50-59</option>
<option value="60+">60+</option>
</Select>
{errors.ageRange && (
<ValidationMessage>{errors.ageRange.message}</ValidationMessage>
)}
</div>
<div className="flex justify-around">
<div>
<input
id="full-time"
aria-label="Full Time"
type="radio"
value="FullTime"
{...register('type')}
className="hidden"
defaultChecked
/>
<label
htmlFor="full-time"
className="label-checked:bg-blue-400 label-checked:text-white label-checked:font-bold label-checked:border-blue-400 border border-gray-200 rounded-md p-3 cursor-pointer"
>
Full-Time
</label>
</div>
<div>
<input
id="part-time"
aria-label="Part Time"
type="radio"
value="PartTime"
{...register('type')}
className="hidden"
/>
<label
htmlFor="part-time"
className="label-checked:bg-blue-400 label-checked:text-white label-checked:font-bold label-checked:border-blue-400 border border-gray-200 rounded-md p-3 cursor-pointer"
>
Part-Time
</label>
</div>
</div>
</div>
<SubmitButton text="Compare Salary" />
</form>
<SubmitButton text="Compare Salary" />
</form>
</FormProvider>
);
}
19 changes: 16 additions & 3 deletions src/components/salary results/SalaryResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,19 @@ type ResultProps = {
};

export default function SalaryResults({ jobInfo }: ResultProps): JSX.Element {
const salaryData =
jobInfo.type === 'FullTime' ? ukJobData.fullTime[0] : ukJobData.partTime[0];
const ageRanges =
jobInfo.type === 'FullTime'
? ukJobData.fullTime[0].ageRanges
: ukJobData.partTime[0].ageRanges;

const salaryData = ageRanges.find(
(x) => x.range === jobInfo.ageRange
)?.salaryData;

if (salaryData === undefined) {
return <></>;
}

return (
<div className="flex flex-col items-center text-xl md:text-4xl p-5 gap-y-2">
<MedianText
Expand All @@ -23,7 +34,9 @@ export default function SalaryResults({ jobInfo }: ResultProps): JSX.Element {
salary={jobInfo.salary}
/>
<SalaryChart
title="UK National median salaries"
title={`UK National median salaries ${
jobInfo.ageRange !== 'all' ? `(${jobInfo.ageRange} age range)` : ''
}`}
salary={jobInfo.salary}
percentiles={salaryData.percentiles}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/components/salary results/median text/MedianText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default function MedianText({

return (
<p>
Your salary is {resultText} the national median of&nbsp;
Your salary is {resultText} the median of&nbsp;
{Intl.NumberFormat('en-GB', {
style: 'currency',
currency: 'GBP',
Expand Down
34 changes: 34 additions & 0 deletions src/components/shared/Select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* eslint-disable react/jsx-props-no-spreading */
import React from 'react';
import { useFormContext } from 'react-hook-form';
import { ReactComponent as Chevron } from '../../chevron-down-solid.svg';

type SelectProps = {
id: string;
name: string;
children: React.ReactNode;
};

export default function Select({
id,
name,
children,
}: SelectProps): JSX.Element {
const { register } = useFormContext();
const formattedName = name.replace(/([A-Z])/g, ' $1').toLowerCase();
return (
<div className="relative">
<label htmlFor={id} className="sr-only">
{`Select an ${formattedName}`}
</label>
<Chevron className="w-6 h-6 absolute right-2 top-3" fill="currentColor" />
<select
id={id}
{...register(name)}
className="focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none w-full border border-gray-200 rounded-md p-2 bg-white appearance-none"
>
{children}
</select>
</div>
);
}
Loading

0 comments on commit d323e40

Please sign in to comment.