diff --git a/src/components/Modal/styles.css b/src/components/Modal/styles.css
index 496687c..683cae2 100644
--- a/src/components/Modal/styles.css
+++ b/src/components/Modal/styles.css
@@ -50,10 +50,10 @@
max-width: 100%;
min-height: 48px;
width: 100%;
- color: #121620;
}
.modal .main-btn:disabled {
+ color: #121620;
background: #c7c7c7;
cursor: not-allowed;
opacity: 100%;
@@ -61,5 +61,9 @@
.modal .main-btn svg {
margin-right: 12px;
+ fill: #fff;
+}
+
+.modal .main-btn:disabled svg {
fill: #121620;
}
diff --git a/src/components/PaymentPage/index.tsx b/src/components/PaymentPage/index.tsx
index c6df6ea..2bc536f 100644
--- a/src/components/PaymentPage/index.tsx
+++ b/src/components/PaymentPage/index.tsx
@@ -15,11 +15,13 @@ import Title from '../Title'
import secure from './secure.png'
import './styles.css'
import { useState } from 'react'
+import Loader from '../Loader'
function PaymentPage(): JSX.Element {
const { t } = useTranslation()
const { applePay } = usePayment()
const [open, setOpen] = useState(false)
+ const isLoading = applePay === null
const isApplePayAvailable = import.meta.env.PROD && applePay?.canMakePayments()
const email = useSelector(selectors.selectEmail)
@@ -27,18 +29,22 @@ function PaymentPage(): JSX.Element {
<>
-
- { isApplePayAvailable ?
:
}
-

-
- {t('choose_payment')}
- { isApplePayAvailable ? : }
- {t('or').toUpperCase()}
- setOpen(true)} />
-
- {t('will_be_charged', { strongText: {t('trial_price')} })}
-
- setOpen(false)} />
+ { isLoading ? : (
+ <>
+
+ { isApplePayAvailable ?
:
}
+

+
+ {t('choose_payment')}
+ { isApplePayAvailable ? : }
+ {t('or').toUpperCase()}
+ setOpen(true)} />
+
+ {t('will_be_charged', { strongText: {t('trial_price')} })}
+
+ setOpen(false)} />
+ >
+ )}
>
)
diff --git a/src/components/PaymentPage/methods/Card/Modal.tsx b/src/components/PaymentPage/methods/Card/Modal.tsx
index b9af53c..39e430c 100644
--- a/src/components/PaymentPage/methods/Card/Modal.tsx
+++ b/src/components/PaymentPage/methods/Card/Modal.tsx
@@ -1,58 +1,119 @@
-import { useRef } from 'react'
import { useTranslation } from 'react-i18next'
+import { useEffect, useRef, useState, ChangeEvent } from 'react'
import {
CardCVV, CardComponent, CardExpiry, CardNumber, Provider
} from '@chargebee/chargebee-js-react-wrapper'
+import ChargebeeComponents from '@chargebee/chargebee-js-react-wrapper/dist/components/ComponentGroup'
import { usePayment } from '../../../../payment'
import Modal from '../../../Modal'
import Title from '../../../Title'
import MainButton from '../../../MainButton'
+import Loader from '../../../Loader'
import visa from './visa.svg'
import mastercard from './mastercard.svg'
import amex from './amex.svg'
import diners from './diners.svg'
import discover from './discover.svg'
+import { cardStyles } from './styles'
interface CardModalProps {
open: boolean
onClose: () => void
}
+interface Field {
+ cardType: string | undefined
+ complete: boolean
+ empty: boolean
+ error: Error | undefined
+ field: 'number' | 'expiry' | 'cvv'
+ key: string | undefined
+ type: string
+}
+
+type Status = 'idle' | 'loading' | 'filling' | 'tokenizing' | 'ready' | 'success' | 'error'
+
+const initCompletedFields = {
+ number: false,
+ expiry: false,
+ cvv: false,
+}
+
+type CompletedFields = typeof initCompletedFields
+
+const isReady = (fields: CompletedFields) => Object.values(fields).every((complete: boolean) => complete)
+
export function CardModal({ open, onClose }: CardModalProps): JSX.Element {
- const cardRef = useRef(null)
+ const cardRef = useRef
(null)
+ const [status, setStatus] = useState('idle')
+ const [fields, setFields] = useState(initCompletedFields)
const { t, i18n } = useTranslation()
const locale = i18n.language
+ const isInit = status === 'idle'
+ const isLoading = status === 'loading'
+ const isTokenizing = status === 'tokenizing'
+ const isDisabled = status !== 'ready'
const { chargebee } = usePayment()
- const payWithCard = () => {
- // cardRef.current?.tokenize()
+ const handleReady = () => setStatus('filling')
+ const handleClose = () => {
+ setStatus('loading')
+ onClose()
}
+ const handleChange = ({ field, complete, error }: ChangeEvent & Field) => {
+ setFields((state) => ({ ...state, [field]: complete && !error }))
+ }
+ const payWithCard = () => {
+ setStatus('tokenizing')
+ cardRef.current?.tokenize({})
+ .then(console.log)
+ .catch(console.error)
+ .finally(() => setStatus('success'))
+ }
+
+ useEffect(() => {
+ setStatus(isReady(fields) ? 'ready' : 'filling')
+ }, [fields])
+
+ useEffect(() => {
+ if (isInit) setStatus('loading')
+ }, [isInit])
+
return (
-
-
-
{t('card')}
-
-

-

-

-

-

+
+ { isLoading ?
: null}
+
+
+
+
+
+
+
+
+
{t('charged_only')}
+
+ { isTokenizing ? : (
+ <>
+
+ {t('start_trial')}
+ >
+ ) }
+
-
-
-
-
-
-
-
- {t('charged_only')}
-
-
- Start 7-Day Trial
-
)
}
diff --git a/src/components/PaymentPage/methods/Card/styles.ts b/src/components/PaymentPage/methods/Card/styles.ts
new file mode 100644
index 0000000..6af1925
--- /dev/null
+++ b/src/components/PaymentPage/methods/Card/styles.ts
@@ -0,0 +1,18 @@
+export const cardStyles = {
+ base: {
+ color: '#121620',
+ lineHeight: '18px',
+ fontSize: '16px',
+ fontWeight: '400',
+ fontFamily: 'SF Pro Text, system-ui, sans-serif',
+ '::placeholder': {
+ color: '#8E8E93',
+ }
+ },
+ invalid: {
+ color: '#FF5758',
+ },
+ empty: {
+ fontWeight: '400',
+ },
+}
diff --git a/src/components/PaymentPage/styles.css b/src/components/PaymentPage/styles.css
index c5d5de6..33e7784 100644
--- a/src/components/PaymentPage/styles.css
+++ b/src/components/PaymentPage/styles.css
@@ -62,3 +62,28 @@
flex-direction: column;
margin-bottom: 16px;
}
+
+.payment-input {
+ padding-bottom: 4px;
+ border-radius: 10px;
+ border: 1px solid #c7c7c7 !important;
+ padding: 14px 12px;
+ width: 100%;
+ height: 48px;
+ text-rendering: optimizeLegibility;
+ margin-bottom: 12px;
+}
+
+.payment-group {
+ display: flex;
+}
+
+.payment-group > .payment-input:first-child {
+ margin-right: 12px;
+}
+
+.payment-loader {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
diff --git a/src/locales/dev.ts b/src/locales/dev.ts
index 7c2e62e..04144c1 100644
--- a/src/locales/dev.ts
+++ b/src/locales/dev.ts
@@ -42,5 +42,6 @@ export default {
contact_us: "Contact us",
will_be_charged: "You will be charged only
. We'll email your a reminder before your trial period ends.",
trial_price: "$1 for your 7-day trial",
+ start_trial: "Start 7-Day Trial",
},
}
diff --git a/src/payment/types.ts b/src/payment/types.ts
new file mode 100644
index 0000000..69888ac
--- /dev/null
+++ b/src/payment/types.ts
@@ -0,0 +1,19 @@
+import { PaymentIntent } from '@chargebee/chargebee-js-types'
+
+interface Handler {
+ mountPaymentButton: (id: string) => Promise
+ handlePayment: () => Promise
+}
+
+interface ApplePay extends Handler {
+ canMakePayments: () => boolean
+}
+
+interface GooglePay extends Handler {
+ getPaymentIntent: () => PaymentIntent
+ setPaymentIntent: (paymentIntent: PaymentIntent) => void
+ updatePaymentIntent: (paymentIntent: PaymentIntent) => void
+}
+
+export type ApplePayHandler = ApplePay | null
+export type GooglePayHandler = GooglePay | null
diff --git a/src/payment/usePayment.ts b/src/payment/usePayment.ts
index 87a83b2..c8af806 100644
--- a/src/payment/usePayment.ts
+++ b/src/payment/usePayment.ts
@@ -1,18 +1,11 @@
import { useContext, useEffect, useState } from 'react'
-import { PaymentIntent } from '@chargebee/chargebee-js-types'
import { PaymentContext } from './PaymentContext'
-
-interface ApplePayHandler {
- canMakePayments: () => boolean
- setPaymentIntent: (paymentIntent: PaymentIntent) => void
- mountPaymentButton: (id: string) => Promise
- handlePayment: () => Promise
-}
+import { ApplePayHandler, GooglePayHandler } from './types'
export const usePayment = () => {
const chargebee = useContext(PaymentContext)
- const [googlePay, setGooglePay] = useState(null)
- const [applePay, setApplePay] = useState(null)
+ const [googlePay, setGooglePay] = useState(null)
+ const [applePay, setApplePay] = useState(null)
useEffect(() => {
Promise.all([