-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a04b15e
commit a1075d6
Showing
20 changed files
with
730 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
@use '../../styles/variables'; | ||
@use '../../styles/theme'; | ||
|
||
.backButton { | ||
position: absolute; | ||
right: 0; | ||
left: 0; | ||
} | ||
|
||
.title { | ||
margin-bottom: 24px; | ||
font-family: theme.$body-alt-font-family; | ||
font-weight: 700; | ||
font-size: 24px; | ||
} | ||
|
||
.order { | ||
display: flex; | ||
align-items: center; | ||
margin-bottom: 8px; | ||
padding: 16px; | ||
font-family: theme.$body-font-family; | ||
background-color: rgba(variables.$white, 0.08); | ||
border-radius: 4px; | ||
} | ||
|
||
.orderTitle { | ||
margin: 0 0 4px; | ||
font-weight: 700; | ||
font-size: 14px; | ||
} | ||
|
||
.orderBillingDate { | ||
margin: 0; | ||
font-size: 14px; | ||
} | ||
|
||
.orderInfo { | ||
flex: 1; | ||
} | ||
|
||
.orderPrice { | ||
display: flex; | ||
align-items: baseline; | ||
|
||
> span { | ||
font-weight: 700; | ||
font-size: 24px; | ||
} | ||
|
||
> small { | ||
margin-left: 4px; | ||
font-size: 14px; | ||
} | ||
} | ||
|
||
.couponForm { | ||
margin-bottom: 24px; | ||
text-align: right; | ||
} | ||
|
||
.redeemCoupon { | ||
display: flex; | ||
align-items: center; | ||
width: 100%; | ||
margin-bottom: 8px; | ||
} | ||
|
||
.couponInput { | ||
flex: 1; | ||
width: 100%; | ||
height: 36px; | ||
margin-right: 4px; | ||
padding: 14px; | ||
|
||
color: theme.$text-field-resting-color; | ||
|
||
font-size: 16px; | ||
|
||
background-color: theme.$text-field-bg-color; | ||
border: 1px solid theme.$text-field-resting-border-color; | ||
border-radius: 4px; | ||
transition: border 0.2s ease; | ||
|
||
&:focus-within { | ||
color: theme.$text-field-active-color; | ||
border-color: theme.$text-field-active-border-color; | ||
} | ||
} | ||
|
||
.orderTotals { | ||
width: 100%; | ||
margin-bottom: 24px; | ||
font-family: theme.$body-font-family; | ||
|
||
td:first-child { | ||
text-align: right; | ||
} | ||
|
||
td:last-child { | ||
width: 100px; | ||
text-align: right; | ||
} | ||
|
||
tfoot { | ||
td { | ||
padding-top: 8px; | ||
font-weight: 700; | ||
font-size: 24px; | ||
} | ||
} | ||
} | ||
|
||
.divider { | ||
border: none; | ||
border-top: 1px solid rgba(variables.$white, 0.34); | ||
} | ||
|
||
.paymentMethods { | ||
display: flex; | ||
margin: 0 -4px 24px; | ||
} | ||
|
||
.paymentMethod { | ||
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 { | ||
+ .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-family: theme.$body-font-family; | ||
font-weight: 700; | ||
font-size: 20px; | ||
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import React from 'react'; | ||
import { render } from '@testing-library/react'; | ||
|
||
import CheckoutForm from './CheckoutForm'; | ||
|
||
describe('<CheckoutForm>', () => { | ||
test('renders and matches snapshot', () => { | ||
const { container } = render(<CheckoutForm />); | ||
|
||
expect(container).toMatchSnapshot(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
import React from 'react'; | ||
import { useTranslation } from 'react-i18next'; | ||
|
||
import Button from '../Button/Button'; | ||
import type { Offer, Order, PaymentMethod } from '../../../types/checkout'; | ||
import IconButton from '../IconButton/IconButton'; | ||
import Exit from '../../icons/Exit'; | ||
import FormFeedback from '../FormFeedback/FormFeedback'; | ||
import { formatPrice } from '../../utils/formatting'; | ||
import Close from '../../icons/Close'; | ||
|
||
import styles from './CheckoutForm.module.scss'; | ||
|
||
type Props = { | ||
paymentMethodId?: number; | ||
onBackButtonClick: () => void; | ||
paymentMethods?: PaymentMethod[]; | ||
onPaymentMethodChange: React.ChangeEventHandler<HTMLInputElement>; | ||
onCouponFormSubmit: React.FormEventHandler<HTMLFormElement>; | ||
onCouponInputChange: React.ChangeEventHandler<HTMLInputElement>; | ||
onRedeemCouponButtonClick: () => void; | ||
onCloseCouponFormClick: () => void; | ||
couponFormOpen: boolean; | ||
couponFormError?: boolean; | ||
couponFormApplied?: boolean; | ||
couponFormSubmitting?: boolean; | ||
couponInputValue: string; | ||
order: Order | null; | ||
offer: Offer | null; | ||
}; | ||
|
||
const CheckoutForm: React.FC<Props> = ({ | ||
paymentMethodId, | ||
paymentMethods, | ||
order, | ||
offer, | ||
onBackButtonClick, | ||
onPaymentMethodChange, | ||
couponFormOpen, | ||
couponInputValue, | ||
couponFormError, | ||
couponFormApplied, | ||
couponFormSubmitting, | ||
onCouponInputChange, | ||
onCloseCouponFormClick, | ||
onCouponFormSubmit, | ||
onRedeemCouponButtonClick, | ||
}) => { | ||
const { t } = useTranslation('account'); | ||
|
||
const getOfferPeriod = () => { | ||
// t('periods.day') | ||
// t('periods.week') | ||
// t('periods.month') | ||
// t('periods.year') | ||
return offer ? t(`periods.${offer.period}`) : ''; | ||
}; | ||
|
||
const cardPaymentMethod = paymentMethods?.find((method) => method.methodName === 'card'); | ||
const paypalPaymentMethod = paymentMethods?.find((method) => method.methodName === 'paypal'); | ||
|
||
return ( | ||
<div> | ||
<IconButton onClick={onBackButtonClick} className={styles.backButton} aria-label="Go back"> | ||
<Exit /> | ||
</IconButton> | ||
{!order || !offer ? ( | ||
<FormFeedback variant="error">No order!</FormFeedback> | ||
) : ( | ||
<React.Fragment> | ||
<h2 className={styles.title}>{t('checkout.payment_method')}</h2> | ||
<div className={styles.order}> | ||
<div className={styles.orderInfo}> | ||
<p className={styles.orderTitle}>{offer.period === 'month' ? t('checkout.monthly') : t('checkout.yearly')}</p> | ||
{/*<p className={styles.orderBillingDate}>*/} | ||
{/* Billing date is on <time>05-12-2021</time>*/} | ||
{/*</p>*/} | ||
</div> | ||
<div className={styles.orderPrice}> | ||
<span>{formatPrice(order.priceBreakdown.offerPrice, order.currency, offer.customerCountry)}</span> | ||
<small>/{getOfferPeriod()}</small> | ||
</div> | ||
</div> | ||
<div className={styles.couponForm}> | ||
{couponFormOpen ? ( | ||
<form onSubmit={onCouponFormSubmit} noValidate> | ||
<div className={styles.redeemCoupon}> | ||
<IconButton aria-label="Close coupon form" onClick={onCloseCouponFormClick}> | ||
<Close /> | ||
</IconButton> | ||
<input | ||
className={styles.couponInput} | ||
name="couponCode" | ||
type="text" | ||
placeholder="Coupon code" | ||
value={couponInputValue} | ||
onChange={onCouponInputChange} | ||
/> | ||
<Button variant="outlined" label="Apply" type="submit" disabled={couponFormSubmitting} /> | ||
</div> | ||
{couponFormError ? <FormFeedback variant="error">Coupon code error! :-(</FormFeedback> : null} | ||
{couponFormApplied ? <FormFeedback variant="success">Coupon code applied!</FormFeedback> : null} | ||
</form> | ||
) : ( | ||
<Button variant="outlined" label={t('checkout.redeem_coupon')} onClick={onRedeemCouponButtonClick} /> | ||
)} | ||
</div> | ||
<div> | ||
<table className={styles.orderTotals}> | ||
<tbody> | ||
<tr> | ||
<td>{t('checkout.applicable_tax', { taxRate: Math.round(order.taxRate * 100) })}</td> | ||
<td>{formatPrice(order.priceBreakdown.taxValue, order.currency, offer.customerCountry)}</td> | ||
</tr> | ||
<tr> | ||
<td>{t('checkout.payment_method_fee')}</td> | ||
<td>{formatPrice(order.priceBreakdown.paymentMethodFee, order.currency, offer.customerCountry)}</td> | ||
</tr> | ||
{order.discount.applied && order.discount.type === 'coupon' ? ( | ||
<tr> | ||
<td> | ||
<div>{t('checkout.coupon_discount')}</div> | ||
<small> | ||
({t('checkout.discount_period', { | ||
count: order.discount.periods, | ||
period: t(`periods.${offer.period}`, { count: order.discount.periods }), | ||
})}) | ||
</small> | ||
</td> | ||
<td>{formatPrice(order.priceBreakdown.discountAmount, order.currency, offer.customerCountry)}</td> | ||
</tr> | ||
) : null} | ||
</tbody> | ||
<tfoot> | ||
<tr> | ||
<td>{t('checkout.total_price')}</td> | ||
<td>{formatPrice(order.totalPrice, order.currency, offer.customerCountry)}</td> | ||
</tr> | ||
</tfoot> | ||
</table> | ||
</div> | ||
<hr className={styles.divider} /> | ||
{order.requiredPaymentDetails ? ( | ||
<div className={styles.paymentMethods}> | ||
{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"> | ||
<Exit /> {t('checkout.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"> | ||
<Exit /> {t('checkout.paypal')} | ||
</label> | ||
</div> | ||
) : null} | ||
</div> | ||
) : null} | ||
<Button label={t('checkout.continue')} variant="contained" color="primary" size="large" fullWidth /> | ||
</React.Fragment> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
export default CheckoutForm; |
Oops, something went wrong.