Skip to content

Commit

Permalink
feat(payment): update payment details
Browse files Browse the repository at this point in the history
  • Loading branch information
RCVZ authored and ChristiaanScheermeijer committed May 30, 2023
1 parent bb0f745 commit f978779
Show file tree
Hide file tree
Showing 23 changed files with 673 additions and 22 deletions.
10 changes: 10 additions & 0 deletions src/components/BackButton/BackButton.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@use '../../styles/variables';
@use '../../styles/theme';

.backButton {
position: absolute;
top: 16px;
left: 16px;
z-index: 1;
pointer-events: auto;
}
25 changes: 25 additions & 0 deletions src/components/BackButton/BackButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import classNames from 'classnames';

import IconButton from '../IconButton/IconButton';
import ArrowLeft from '../../icons/ArrowLeft';

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

type Props = {
className?: string;
onClick: () => void;
};

const BackButton: React.FC<Props> = ({ className, onClick }: Props) => {
const { t } = useTranslation('common');

return (
<IconButton onClick={onClick} className={classNames(styles.backButton, className)} aria-label={t('common:back')}>
<ArrowLeft />
</IconButton>
);
};

export default BackButton;
13 changes: 13 additions & 0 deletions src/components/Payment/Payment.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,16 @@
display: flex;
margin-top: variables.$base-spacing * 2;
}

.paypal {
display: flex;
align-items: center;
margin-bottom: variables.$base-spacing;
font-weight: var(--body-font-weight-bold);
font-size: 24px;
font-style: italic;

> svg {
margin-right: 4px;
}
}
1 change: 1 addition & 0 deletions src/components/Payment/Payment.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('<Payment>', () => {
transactions={transactions as Transaction[]}
activeSubscription={subscription as Subscription}
activePaymentDetail={paymentDetail as PaymentDetail}
canUpdatePaymentMethod={false}
showAllTransactions={false}
isLoading={false}
/>,
Expand Down
32 changes: 22 additions & 10 deletions src/components/Payment/Payment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { formatDate, formatPrice } from '#src/utils/formatting';
import { addQueryParam } from '#src/utils/location';
import type { PaymentDetail, Subscription, Transaction } from '#types/subscription';
import type { AccessModel } from '#types/Config';
import PayPal from '#src/icons/PayPal';

const VISIBLE_TRANSACTIONS = 4;

Expand All @@ -26,6 +27,7 @@ type Props = {
panelHeaderClassName?: string;
onShowAllTransactionsClick?: () => void;
showAllTransactions: boolean;
canUpdatePaymentMethod: boolean;
canRenewSubscription?: boolean;
};

Expand All @@ -41,6 +43,7 @@ const Payment = ({
onShowAllTransactionsClick,
showAllTransactions,
canRenewSubscription = false,
canUpdatePaymentMethod,
}: Props): JSX.Element => {
const { t } = useTranslation(['user', 'account']);
const hiddenTransactionsCount = transactions ? transactions?.length - VISIBLE_TRANSACTIONS : 0;
Expand Down Expand Up @@ -117,22 +120,31 @@ const Payment = ({
<h3>{t('user:payment.payment_method')}</h3>
</div>
{activePaymentDetail ? (
<div key={activePaymentDetail.id}>
<TextField
label={t('user:payment.card_number')}
value={`•••• •••• •••• ${activePaymentDetail.paymentMethodSpecificParams.lastCardFourDigits || ''}`}
editing={false}
/>
<div className={styles.cardDetails}>
<TextField label={t('user:payment.expiry_date')} value={activePaymentDetail.paymentMethodSpecificParams.cardExpirationDate} editing={false} />
<TextField label={t('user:payment.security_code')} value={'******'} editing={false} />
activePaymentDetail.paymentMethod === 'paypal' ? (
<div className={styles.paypal}>
<PayPal /> {t('account:payment.paypal')}
</div>
</div>
) : (
<div key={activePaymentDetail.id}>
<TextField
label={t('user:payment.card_number')}
value={`•••• •••• •••• ${activePaymentDetail.paymentMethodSpecificParams.lastCardFourDigits || ''}`}
editing={false}
/>
<div className={styles.cardDetails}>
<TextField label={t('user:payment.expiry_date')} value={activePaymentDetail.paymentMethodSpecificParams.cardExpirationDate} editing={false} />
<TextField label={t('user:payment.security_code')} value={'******'} editing={false} />
</div>
</div>
)
) : (
<div>
<p>{!isLoading && t('user:payment.no_payment_methods')}</p>
</div>
)}
{canUpdatePaymentMethod && (
<Button label={t('user:payment.update_payment_details')} type="button" onClick={() => navigate(addQueryParam(location, 'u', 'payment-method'))} />
)}
</div>
<div className={panelClassName}>
<div className={panelHeaderClassName}>
Expand Down
75 changes: 75 additions & 0 deletions src/components/PaymentMethodForm/PaymentMethodForm.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
@use '../../styles/variables';
@use '../../styles/theme';
@use '../../styles/mixins/responsive';


.title {
margin-bottom: 24px;
font-weight: var(--body-font-weight-bold);
font-size: 24px;
}

.success {
margin-bottom: 12px;
}

.paymentMethodsInputs {
display: flex;
flex-direction: column;
margin: 0 -4px 24px;
}

.paymentMethod {
flex: 1;
margin: 6px 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 {
+ .paymentMethodLabel {
border-color: variables.$white;
}
}

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

svg {
fill: variables.$black;
}
}
}

.paymentMethodLabel {
display: flex;
justify-content: center;
align-items: center;
padding: 16px;
font-weight: var(--body-font-weight-bold);
font-size: 24px;
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;

> svg {
margin-right: 4px;
}
}

.backButton {
margin-top: 4px;
margin-left: 4px;
}
94 changes: 94 additions & 0 deletions src/components/PaymentMethodForm/PaymentMethodForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React from 'react';
import { useTranslation } from 'react-i18next';

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

import CreditCard from '#src/icons/CreditCard';
import PayPal from '#src/icons/PayPal';
import BackButton from '#components/BackButton/BackButton';
import Button from '#components/Button/Button';
import LoadingOverlay from '#components/LoadingOverlay/LoadingOverlay';
import type { PaymentMethod } from '#types/checkout';

type Props = {
paymentMethodId?: number;
onBackButtonClick: () => void;
onCloseButtonClick: () => void;
paymentMethods?: PaymentMethod[];
onPaymentMethodChange: React.ChangeEventHandler<HTMLInputElement>;
renderPaymentMethod?: () => JSX.Element | null;
submitting: boolean;
updateSuccess: boolean;
};

const PaymentMethodForm: React.FC<Props> = ({
paymentMethodId,
paymentMethods,
onBackButtonClick,
onCloseButtonClick,
onPaymentMethodChange,
renderPaymentMethod,
submitting,
updateSuccess,
}) => {
const { t } = useTranslation('account');

const cardPaymentMethod = paymentMethods?.find((method) => method.methodName === 'card');
const paypalPaymentMethod = paymentMethods?.find((method) => method.methodName === 'paypal');

return (
<>
<h1 className={styles.title}>{t('payment.update_payment_details')}</h1>
{updateSuccess ? (
<>
<p className={styles.success}>{t('payment.update_payment_success')}</p>
<Button label={t('payment.back_to_profile')} onClick={onCloseButtonClick} color="primary" fullWidth />
</>
) : (
<>
<div className={styles.paymentMethodsInputs}>
{cardPaymentMethod ? (
<div className={styles.paymentMethod}>
<input
className={styles.radio}
type="radio"
name="paymentMethod"
value={cardPaymentMethod.id}
id="card"
checked={paymentMethodId === cardPaymentMethod.id}
onChange={onPaymentMethodChange}
/>
<label className={styles.paymentMethodLabel} htmlFor="card">
<CreditCard />
{t('payment.credit_card')}
</label>
</div>
) : null}
{paypalPaymentMethod ? (
<div className={styles.paymentMethod}>
<input
className={styles.radio}
type="radio"
name="paymentMethod"
value={paypalPaymentMethod.id}
id="paypal"
checked={paymentMethodId === paypalPaymentMethod.id}
onChange={onPaymentMethodChange}
/>
<label className={styles.paymentMethodLabel} htmlFor="paypal">
<PayPal /> {t('payment.paypal')}
</label>
</div>
) : null}
</div>
<div>{renderPaymentMethod ? renderPaymentMethod() : null}</div>
{submitting && <LoadingOverlay transparentBackground inline />}

<BackButton className={styles.backButton} onClick={onBackButtonClick} />
</>
)}
</>
);
};

export default PaymentMethodForm;
4 changes: 4 additions & 0 deletions src/containers/AccountModal/AccountModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Dialog from '#components/Dialog/Dialog';
import { addQueryParam, removeQueryParam } from '#src/utils/location';
import FinalizePayment from '#components/FinalizePayment/FinalizePayment';
import WaitingForPayment from '#components/WaitingForPayment/WaitingForPayment';
import UpdatePaymentMethod from '#src/containers/UpdatePaymentMethod/UpdatePaymentMethod';

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

Expand Down Expand Up @@ -92,6 +93,9 @@ const AccountModal = () => {
return <CancelSubscription />;
case 'renew-subscription':
return <RenewSubscription />;
case 'payment-method':
case 'payment-method-success':
return <UpdatePaymentMethod onCloseButtonClick={closeHandler} />;
case 'waiting-for-payment':
return <WaitingForPayment />;
case 'finalize-payment':
Expand Down
Loading

0 comments on commit f978779

Please sign in to comment.