Skip to content

Commit

Permalink
feat(auth): implement renew subscription flow
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristiaanScheermeijer committed Jul 30, 2021
1 parent eb38820 commit ccd6e4d
Show file tree
Hide file tree
Showing 18 changed files with 358 additions and 9 deletions.
8 changes: 6 additions & 2 deletions src/components/Payment/Payment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Button from '../Button/Button';
import styles from './Payment.module.scss';

type Props = {
activeSubscription?: Subscription;
activeSubscription?: Subscription | null;
activePaymentDetail?: PaymentDetail;
transactions: Transaction[];
customer: Customer;
Expand All @@ -20,11 +20,13 @@ type Props = {
panelHeaderClassName?: string;
onCompleteSubscriptionClick?: () => void;
onCancelSubscriptionClick?: () => void;
onRenewSubscriptionClick?: () => void;
};

const Payment = ({
onCompleteSubscriptionClick,
onCancelSubscriptionClick,
onRenewSubscriptionClick,
activePaymentDetail,
activeSubscription,
transactions,
Expand Down Expand Up @@ -57,7 +59,9 @@ const Payment = ({
</div>
{activeSubscription.status === 'active' ? (
<Button label={t('user:payment.cancel_subscription')} onClick={onCancelSubscriptionClick} />
) : null}
) : (
<Button label={t('user:payment.renew_subscription')} onClick={onRenewSubscriptionClick} />
)}
</React.Fragment>
) : (
<React.Fragment>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
@use '../../styles/variables';
@use '../../styles/theme';

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

.paragraph {
font-family: theme.$body-font-family;
}

.infoBox {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: variables.$base-spacing;
padding: variables.$base-spacing / 2 variables.$base-spacing;

font-family: theme.$body-font-family;
font-size: 14px;
line-height: 18px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.14), 0 3px 4px rgba(0, 0, 0, 0.12), 0 1px 5px rgba(0, 0, 0, 0.2);
background: theme.$panel-bg;
border-radius: 4px;

> strong {
line-height: 16px;
letter-spacing: 0.25px;
}
}

.price {
font-size: 14px;
line-height: 18px;

> strong {
font-weight: bold;
font-size: 24px;
line-height: 26px;
}
}

.confirmButton {
margin-bottom: 8px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import { render } from '@testing-library/react';

import customer from '../../fixtures/customer.json';
import subscription from '../../fixtures/subscription.json';
import type { Subscription } from '../../../types/subscription';
import type { Customer } from '../../../types/account';

import RenewSubscriptionForm from './RenewSubscriptionForm';

describe('<RenewSubscriptionForm>', () => {
test('renders and matches snapshot', () => {
const { container } = render(
<RenewSubscriptionForm customer={customer as Customer} subscription={subscription as Subscription} onConfirm={jest.fn()} onClose={jest.fn()} error={null}/>,
);

expect(container).toMatchSnapshot();
});
});
51 changes: 51 additions & 0 deletions src/components/RenewSubscriptionForm/RenewSubscriptionForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import { useTranslation } from 'react-i18next';

import type { Subscription } from '../../../types/subscription';
import Button from '../Button/Button';
import { formatDate, formatPrice } from '../../utils/formatting';
import type { Customer } from '../../../types/account';
import FormFeedback from '../FormFeedback/FormFeedback';

import styles from './RenewSubscriptionForm.module.scss';

type Props = {
subscription: Subscription;
customer: Customer;
error: string | null;
onConfirm: () => void;
onClose: () => void;
};

const RenewSubscriptionForm: React.FC<Props> = ({ subscription, customer, error, onConfirm, onClose }: Props) => {
const { t } = useTranslation('account');

return (
<div>
{error ? <FormFeedback variant="error">{error}</FormFeedback> : null}
<h2 className={styles.title}>{t('renew_subscription.renew_your_subscription')}</h2>
<p className={styles.paragraph}>{t('renew_subscription.explanation')}</p>
<div className={styles.infoBox}>
<p>
<strong>{subscription.offerTitle}</strong> <br />
{t('renew_subscription.next_billing_date_on', { date: formatDate(subscription.expiresAt) })}
</p>
<p className={styles.price}>
<strong>{formatPrice(subscription.nextPaymentPrice, subscription.nextPaymentCurrency, customer.country)}</strong>
<small>/{t(`periods.${subscription.period}`)}</small>
</p>
</div>
<Button
className={styles.confirmButton}
color="primary"
variant="contained"
label={t('renew_subscription.renew_subscription')}
onClick={onConfirm}
fullWidth
/>
<Button label={t('renew_subscription.no_thanks')} onClick={onClose} fullWidth />
</div>
);
};

export default RenewSubscriptionForm;
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<RenewSubscriptionForm> renders and matches snapshot 1`] = `
<div>
<div>
<h2
class="title"
>
renew_subscription.renew_your_subscription
</h2>
<p
class="paragraph"
>
renew_subscription.explanation
</p>
<div
class="infoBox"
>
<p>
<strong>
Annual subscription (recurring)
</strong>
<br />
renew_subscription.next_billing_date_on
</p>
<p
class="price"
>
<strong>
€ 22,15
</strong>
<small>
/
periods.year
</small>
</p>
</div>
<button
class="button confirmButton primary fullWidth"
>
<span>
renew_subscription.renew_subscription
</span>
</button>
<button
class="button default outlined fullWidth"
>
<span>
renew_subscription.no_thanks
</span>
</button>
</div>
</div>
`;
13 changes: 13 additions & 0 deletions src/components/SubscriptionRenewed/SubscriptionRenewed.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@use '../../styles/variables';
@use '../../styles/theme';

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

.paragraph {
font-family: theme.$body-font-family;
}
17 changes: 17 additions & 0 deletions src/components/SubscriptionRenewed/SubscriptionRenewed.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import { render } from '@testing-library/react';

import customer from '../../fixtures/customer.json';
import subscription from '../../fixtures/subscription.json';
import type { Subscription } from '../../../types/subscription';
import type { Customer } from '../../../types/account';

import SubscriptionRenewed from './SubscriptionRenewed';

describe('<SubscriptionRenewed>', () => {
test('renders and matches snapshot', () => {
const { container } = render(<SubscriptionRenewed customer={customer as Customer} subscription={subscription as Subscription} onClose={jest.fn()} />);

expect(container).toMatchSnapshot();
});
});
31 changes: 31 additions & 0 deletions src/components/SubscriptionRenewed/SubscriptionRenewed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { useTranslation } from 'react-i18next';

import Button from '../Button/Button';
import type { Subscription } from '../../../types/subscription';
import { formatDate, formatPrice } from '../../utils/formatting';
import type { Customer } from '../../../types/account';

import styles from './SubscriptionRenewed.module.scss';

type Props = {
customer: Customer;
subscription: Subscription;
onClose: () => void;
};

const SubscriptionRenewed: React.FC<Props> = ({ onClose, customer, subscription }: Props) => {
const { t } = useTranslation('account');
const date = formatDate(subscription.expiresAt);
const price = formatPrice(subscription.nextPaymentPrice, subscription.nextPaymentCurrency, customer.country);

return (
<div>
<h2 className={styles.title}>{t('subscription_renewed.title')}</h2>
<p className={styles.paragraph}>{t('subscription_renewed.message', { date, price })}</p>
<Button label={t('subscription_renewed.back_to_profile')} onClick={onClose} fullWidth />
</div>
);
};

export default SubscriptionRenewed;
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<SubscriptionRenewed> renders and matches snapshot 1`] = `
<div>
<div>
<h2
class="title"
>
subscription_renewed.title
</h2>
<p
class="paragraph"
>
subscription_renewed.message
</p>
<button
class="button default outlined fullWidth"
>
<span>
subscription_renewed.back_to_profile
</span>
</button>
</div>
</div>
`;
3 changes: 3 additions & 0 deletions src/containers/AccountModal/AccountModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import ChooseOffer from './forms/ChooseOffer';
import Checkout from './forms/Checkout';
import ResetPassword from './forms/ResetPassword';
import CancelSubscription from './forms/CancelSubscription';
import RenewSubscription from './forms/RenewSubscription';

const PUBLIC_VIEWS = ['login', 'create-account', 'forgot-password', 'reset-password'];

Expand Down Expand Up @@ -78,6 +79,8 @@ const AccountModal = () => {
return <Welcome onCloseButtonClick={closeHandler} onCountdownCompleted={closeHandler} />;
case 'unsubscribe':
return <CancelSubscription />;
case 'renew-subscription':
return <RenewSubscription />;
}
};

Expand Down
4 changes: 2 additions & 2 deletions src/containers/AccountModal/forms/CancelSubscription.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useHistory } from 'react-router';
import CancelSubscriptionForm from '../../../components/CancelSubscriptionForm/CancelSubscriptionForm';
import { removeQueryParam } from '../../../utils/history';
import LoadingOverlay from '../../../components/LoadingOverlay/LoadingOverlay';
import { AccountStore, cancelSubscription } from '../../../stores/AccountStore';
import { AccountStore, updateSubscription } from '../../../stores/AccountStore';
import SubscriptionCancelled from '../../../components/SubscriptionCancelled/SubscriptionCancelled';
import { formatDate } from '../../../utils/formatting';

Expand All @@ -22,7 +22,7 @@ const CancelSubscription = () => {
setError(null);

try {
await cancelSubscription();
await updateSubscription('cancelled');
setCancelled(true);
} catch (error: unknown) {
setError(t('cancel_subscription.unknown_error_occurred'));
Expand Down
50 changes: 50 additions & 0 deletions src/containers/AccountModal/forms/RenewSubscription.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router';

import { removeQueryParam } from '../../../utils/history';
import LoadingOverlay from '../../../components/LoadingOverlay/LoadingOverlay';
import { AccountStore, updateSubscription } from '../../../stores/AccountStore';
import RenewSubscriptionForm from '../../../components/RenewSubscriptionForm/RenewSubscriptionForm';
import SubscriptionRenewed from '../../../components/SubscriptionRenewed/SubscriptionRenewed';

const RenewSubscription = () => {
const { t } = useTranslation('account');
const history = useHistory();
const { subscription, user } = AccountStore.useState((s) => s);
const [renewed, setRenewed] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);

const renewSubscriptionConfirmHandler = async () => {
setLoading(true);
setError(null);

try {
await updateSubscription('active');
setRenewed(true);
} catch (error: unknown) {
setError(t('renew_subscription.unknown_error_occurred'));
}

setLoading(false);
};

const closeHandler = () => {
history.replace(removeQueryParam(history, 'u'));
};

if (!subscription || !user) return null;

return (
<React.Fragment>
{renewed ? (
<SubscriptionRenewed onClose={closeHandler} subscription={subscription} customer={user} />
) : (
<RenewSubscriptionForm subscription={subscription} customer={user} error={error} onConfirm={renewSubscriptionConfirmHandler} onClose={closeHandler} />
)}
{loading ? <LoadingOverlay inline /> : null}
</React.Fragment>
);
};
export default RenewSubscription;
Loading

0 comments on commit ccd6e4d

Please sign in to comment.