Skip to content

Commit

Permalink
feat(auth): add ChooseOfferForm
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristiaanScheermeijer committed Jul 22, 2021
1 parent 9b92e2d commit 2f75031
Show file tree
Hide file tree
Showing 27 changed files with 892 additions and 34 deletions.
6 changes: 5 additions & 1 deletion public/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,9 @@
"player": "7xMa4gNw",
"recommendationsPlaylist": "fuD6TWcf",
"searchPlaylist": "tQ832H1H",
"siteName": "Blender"
"siteName": "Blender",
"json": {
"cleengMonthlyOffer": "S916977979_NL",
"cleengYearlyOffer": "S345569153_NL"
}
}
102 changes: 102 additions & 0 deletions src/components/ChooseOfferForm/ChooseOfferForm.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
@use '../../styles/variables';
@use '../../styles/theme';

.title {
margin-bottom: 8px;
font-family: theme.$body-alt-font-family;
font-weight: 700;
font-size: 24px;
}

.subtitle {
margin-bottom: 24px;
font-family: theme.$body-alt-font-family;
font-weight: 700;
font-size: 18px;
}

.offers {
display: flex;
width: 100%;
margin: 0 -4px 24px;
}

.offer {
flex: 1;
margin: 0 4px;
}

.radio {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
white-space: nowrap;
clip: rect(0 0 0 0);
clip-path: inset(50%);

:focus,
:active {
+ .label {
border-color: variables.$white;
}
}

&:checked + .label {
color: variables.$black;
background-color: variables.$white;
border-color: variables.$white;
}
}

.label {
display: block;
padding: 16px;
font-family: theme.$body-font-family;
background-color: rgba(variables.$black, 0.34);
border: 1px solid rgba(variables.$white, 0.34);
border-radius: 4px;
cursor: pointer;
transition: border 0.2s ease, background 0.2s ease;
}

.offerTitle {
font-weight: 700;
font-size: 20px;
text-align: center;
}

.offerDivider {
border: none;
border-bottom: 1px solid currentColor;
opacity: 0.54;
}

.offerBenefits {
margin-bottom: 16px;
padding: 0;

> li {
display: flex;
align-items: center;
margin-bottom: 4px;
padding: 4px 0;

> svg {
margin-right: 4px;
fill: variables.$green;
}
}
}

.offerPrice {
display: flex;
justify-content: center;
align-items: baseline;
font-size: 32px;

> small {
margin-left: 4px;
font-size: 12px;
}
}
96 changes: 96 additions & 0 deletions src/components/ChooseOfferForm/ChooseOfferForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React from 'react';
import { fireEvent, render } from '@testing-library/react';

import monthlyOffer from '../../fixtures/monthlyOffer.json';
import yearlyOffer from '../../fixtures/yearlyOffer.json';
import type { Offer } from '../../../types/checkout';

import ChooseOfferForm from './ChooseOfferForm';

describe('<OffersForm>', () => {
test('renders and matches snapshot', () => {
const { container } = render(
<ChooseOfferForm
values={{ periodicity: 'monthly' }}
errors={{}}
onChange={jest.fn()}
onSubmit={jest.fn()}
submitting={false}
monthlyOffer={monthlyOffer as Offer}
yearlyOffer={yearlyOffer as Offer}
/>,
);

expect(container).toMatchSnapshot();
});

test('checks the monthly offer correctly', () => {
const { getByLabelText } = render(
<ChooseOfferForm
values={{ periodicity: 'monthly' }}
errors={{}}
onChange={jest.fn()}
onSubmit={jest.fn()}
submitting={false}
monthlyOffer={monthlyOffer as Offer}
yearlyOffer={yearlyOffer as Offer}
/>,
);

expect(getByLabelText('choose_offer.monthly_subscription')).toBeChecked();
});

test('checks the yearly offer correctly', () => {
const { getByLabelText } = render(
<ChooseOfferForm
values={{ periodicity: 'yearly' }}
errors={{}}
onChange={jest.fn()}
onSubmit={jest.fn()}
submitting={false}
monthlyOffer={monthlyOffer as Offer}
yearlyOffer={yearlyOffer as Offer}
/>,
);

expect(getByLabelText('choose_offer.yearly_subscription')).toBeChecked();
});

test('calls the onChange callback when changing the offer', () => {
const onChange = jest.fn();
const { getByLabelText } = render(
<ChooseOfferForm
values={{ periodicity: 'monthly' }}
errors={{}}
onChange={onChange}
onSubmit={jest.fn()}
submitting={false}
monthlyOffer={monthlyOffer as Offer}
yearlyOffer={yearlyOffer as Offer}
/>,
);

fireEvent.click(getByLabelText('choose_offer.yearly_subscription'));

expect(onChange).toBeCalled();
});

test('calls the onSubmit callback when submitting the form', () => {
const onSubmit = jest.fn();
const { getByTestId } = render(
<ChooseOfferForm
values={{ periodicity: 'monthly' }}
errors={{}}
onChange={jest.fn()}
onSubmit={onSubmit}
submitting={false}
monthlyOffer={monthlyOffer as Offer}
yearlyOffer={yearlyOffer as Offer}
/>,
);

fireEvent.submit(getByTestId('choose-offer-form'));

expect(onSubmit).toBeCalled();
});
});
124 changes: 124 additions & 0 deletions src/components/ChooseOfferForm/ChooseOfferForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import React, { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import type { ChooseOfferFormData } from 'types/account';

import Button from '../Button/Button';
import CheckCircle from '../../icons/CheckCircle';
import { ConfigContext } from '../../providers/ConfigProvider';
import type { FormErrors } from '../../hooks/useForm';
import type { Offer } from '../../../types/checkout';

import styles from './ChooseOfferForm.module.scss';
import FormFeedback from '../FormFeedback/FormFeedback';
import { getOfferPrice } from '../../utils/subscription';

type Props = {
values: ChooseOfferFormData;
errors: FormErrors<ChooseOfferFormData>;
onChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>;
onSubmit: React.FormEventHandler<HTMLFormElement>;
monthlyOffer?: Offer;
yearlyOffer?: Offer;
submitting: boolean;
};

const ChooseOfferForm: React.FC<Props> = ({ values, errors, onChange, onSubmit, submitting, yearlyOffer, monthlyOffer }: Props) => {
const { siteName } = useContext(ConfigContext);
const { t } = useTranslation('account');

const getFreeTrialText = (offer: Offer) => {
if (offer.freeDays > 0) {
return t('choose_offer.benefits.first_days_free', { count: offer.freeDays });
} else if (offer.freePeriods) {
// t('choose_offer.periods.day')
// t('choose_offer.periods.week')
// t('choose_offer.periods.month')
// t('choose_offer.periods.year')
const period = t(`choose_offer.periods.${offer.period}`, { count: offer.freePeriods });

return t('choose_offer.benefits.first_periods_free', { count: offer.freePeriods, period });
}

return null;
}

return (
<form onSubmit={onSubmit} data-testid="choose-offer-form" noValidate>
<h2 className={styles.title}>{t('choose_offer.subscription')}</h2>
<h3 className={styles.subtitle}>{t('choose_offer.all_movies_and_series_of_platform', { siteName })}</h3>
{errors.form ? <FormFeedback variant="error">{errors.form}</FormFeedback> : null}
<div className={styles.offers}>
{monthlyOffer ? (
<div className={styles.offer}>
<input
className={styles.radio}
onChange={onChange}
type="radio"
name="periodicity"
value="monthly"
id="monthly"
checked={values.periodicity === 'monthly'}
aria-label={t('choose_offer.monthly_subscription')}
/>
<label className={styles.label} htmlFor="monthly">
<h4 className={styles.offerTitle}>{t('choose_offer.monthly')}</h4>
<hr className={styles.offerDivider} />
<ul className={styles.offerBenefits}>
{monthlyOffer.freeDays > 0 || monthlyOffer.freePeriods > 0 ? (
<li>
<CheckCircle /> {getFreeTrialText(monthlyOffer)}
</li>
) : null}
<li>
<CheckCircle /> {t('choose_offer.benefits.cancel_anytime')}
</li>
<li>
<CheckCircle /> {t('choose_offer.benefits.watch_on_all_devices')}
</li>
</ul>
<div className={styles.offerPrice}>
{getOfferPrice(monthlyOffer)} <small>/{t('choose_offer.periods.month')}</small>
</div>
</label>
</div>
) : null}
{yearlyOffer ? (
<div className={styles.offer}>
<input
className={styles.radio}
onChange={onChange}
type="radio"
name="periodicity"
value="yearly"
id="yearly"
checked={values.periodicity === 'yearly'}
aria-label={t('choose_offer.yearly_subscription')}
/>
<label className={styles.label} htmlFor="yearly">
<h4 className={styles.offerTitle}>{t('choose_offer.yearly')}</h4>
<hr className={styles.offerDivider} />
<ul className={styles.offerBenefits}>
{yearlyOffer.freeDays > 0 || yearlyOffer.freePeriods > 0 ? (
<li>
<CheckCircle /> {getFreeTrialText(yearlyOffer)}
</li>
) : null}
<li>
<CheckCircle /> {t('choose_offer.benefits.cancel_anytime')}
</li>
<li>
<CheckCircle /> {t('choose_offer.benefits.watch_on_all_devices')}
</li>
</ul>
<div className={styles.offerPrice}>
{getOfferPrice(yearlyOffer)} <small>/{t('choose_offer.periods.year')}</small>
</div>
</label>
</div>
) : null}
</div>
<Button label={t('choose_offer.continue')} disabled={submitting} variant="contained" color="primary" type="submit" fullWidth />
</form>
);
};
export default ChooseOfferForm;
Loading

0 comments on commit 2f75031

Please sign in to comment.