feat: Chargebee integration init
@ -14,6 +14,7 @@
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<script src="https://js.chargebee.com/v2/chargebee.js"></script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
29
package-lock.json
generated
@ -8,6 +8,7 @@
|
||||
"name": "aurawebapp",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@chargebee/chargebee-js-react-wrapper": "^0.6.3",
|
||||
"@reduxjs/toolkit": "^1.9.5",
|
||||
"html-react-parser": "^3.0.16",
|
||||
"i18next": "^22.5.0",
|
||||
@ -20,6 +21,7 @@
|
||||
"react-router-dom": "^6.11.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chargebee/chargebee-js-types": "^1.0.1",
|
||||
"@types/react": "^18.2.6",
|
||||
"@types/react-dom": "^18.2.4",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
@ -404,6 +406,21 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@chargebee/chargebee-js-react-wrapper": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@chargebee/chargebee-js-react-wrapper/-/chargebee-js-react-wrapper-0.6.3.tgz",
|
||||
"integrity": "sha512-U3KJLZZiUxxkW4HAkAZr2bs6r4HqL1S59zo3AAp4UYtJZbxmBpfX5e/MlsSENvsRxd2GvOpumcHr8rR4V0D5vw==",
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0 || ^17.0.0",
|
||||
"react-dom": "^18.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@chargebee/chargebee-js-types": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@chargebee/chargebee-js-types/-/chargebee-js-types-1.0.1.tgz",
|
||||
"integrity": "sha512-JsHlIAjZDwX2Q/vDGN4xzKRC8n1K4xCwzKl7wZOOwUH9ow030CRspVRkP3OWHrY5gLmpbmICc/iK2aptF3t/Ow==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.17.18",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.18.tgz",
|
||||
@ -3646,6 +3663,18 @@
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@chargebee/chargebee-js-react-wrapper": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@chargebee/chargebee-js-react-wrapper/-/chargebee-js-react-wrapper-0.6.3.tgz",
|
||||
"integrity": "sha512-U3KJLZZiUxxkW4HAkAZr2bs6r4HqL1S59zo3AAp4UYtJZbxmBpfX5e/MlsSENvsRxd2GvOpumcHr8rR4V0D5vw==",
|
||||
"requires": {}
|
||||
},
|
||||
"@chargebee/chargebee-js-types": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@chargebee/chargebee-js-types/-/chargebee-js-types-1.0.1.tgz",
|
||||
"integrity": "sha512-JsHlIAjZDwX2Q/vDGN4xzKRC8n1K4xCwzKl7wZOOwUH9ow030CRspVRkP3OWHrY5gLmpbmICc/iK2aptF3t/Ow==",
|
||||
"dev": true
|
||||
},
|
||||
"@esbuild/android-arm": {
|
||||
"version": "0.17.18",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.18.tgz",
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chargebee/chargebee-js-react-wrapper": "^0.6.3",
|
||||
"@reduxjs/toolkit": "^1.9.5",
|
||||
"html-react-parser": "^3.0.16",
|
||||
"i18next": "^22.5.0",
|
||||
@ -22,6 +23,7 @@
|
||||
"react-router-dom": "^6.11.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chargebee/chargebee-js-types": "^1.0.1",
|
||||
"@types/react": "^18.2.6",
|
||||
"@types/react-dom": "^18.2.4",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import './styles.css'
|
||||
import './BackButton.css'
|
||||
|
||||
function BackButton({ className, ...props }: React.ButtonHTMLAttributes<HTMLDivElement>): JSX.Element {
|
||||
const combinedClassNames = ['back-btn', className].filter(Boolean).join(' ')
|
||||
@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'
|
||||
import { useNavigate, useLocation } from 'react-router-dom'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import routes, { hasNavigation, isNotEntrypoint } from '../../routes'
|
||||
import BackButton from '../BackButton'
|
||||
import BackButton from './BackButton'
|
||||
import iconUrl from './icon.png'
|
||||
import menuUrl from './menu.png'
|
||||
import './styles.css'
|
||||
|
||||
30
src/components/Modal/index.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { ReactNode } from 'react'
|
||||
import './styles.css'
|
||||
|
||||
interface ModalProps {
|
||||
children: ReactNode
|
||||
open?: boolean
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
function Modal({ open, children, onClose }: ModalProps): JSX.Element {
|
||||
const handleClose = () => {
|
||||
onClose?.()
|
||||
}
|
||||
if (!open) return <></>
|
||||
return (
|
||||
<div className='modal'>
|
||||
<div className='modal-content'>
|
||||
<button className='modal-close-btn' onClick={handleClose}>
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M12.7071 12.7071C12.3166 13.0976 11.6834 13.0976 11.2929 12.7071L6.29289 7.70711C5.90237 7.31658 5.90237 6.68342 6.29289 6.29289L11.2929 1.29289C11.6834 0.902369 12.3166 0.902369 12.7071 1.29289C13.0976 1.68342 13.0976 2.31658 12.7071 2.70711L8.41421 7L12.7071 11.2929C13.0976 11.6834 13.0976 12.3166 12.7071 12.7071Z" fill="#858DA5" stroke="#858DA5" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M1.29289 12.7071C1.68342 13.0976 2.31658 13.0976 2.70711 12.7071L7.70711 7.70711C8.09763 7.31658 8.09763 6.68342 7.70711 6.29289L2.70711 1.29289C2.31658 0.902369 1.68342 0.902369 1.29289 1.29289C0.902369 1.68342 0.902369 2.31658 1.29289 2.70711L5.58579 7L1.29289 11.2929C0.902369 11.6834 0.902369 12.3166 1.29289 12.7071Z" fill="#858DA5" stroke="#858DA5" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||
</svg>
|
||||
</button>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Modal
|
||||
65
src/components/Modal/styles.css
Normal file
@ -0,0 +1,65 @@
|
||||
.modal {
|
||||
background: rgba(85,84,85,.8);
|
||||
height: calc(100% + 76px);
|
||||
left: 0;
|
||||
margin-inline: 50%;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
top: -76px;
|
||||
-webkit-transform: translate(-50%);
|
||||
transform: translate(-50%);
|
||||
width: 100vw;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
max-width: 375px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
z-index: 3;
|
||||
border-radius: 16px;
|
||||
padding: 64px 24px;
|
||||
transform: translate(-50%,-50%);
|
||||
}
|
||||
|
||||
.modal-close-btn {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #eff2fd;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
right: 20px;
|
||||
top: 16px;
|
||||
width: 32px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal .main-btn {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
height: 48px;
|
||||
line-height: 18px;
|
||||
margin-top: 16px;
|
||||
max-width: 100%;
|
||||
min-height: 48px;
|
||||
width: 100%;
|
||||
color: #121620;
|
||||
}
|
||||
|
||||
.modal .main-btn:disabled {
|
||||
background: #c7c7c7;
|
||||
cursor: not-allowed;
|
||||
opacity: 100%;
|
||||
}
|
||||
|
||||
.modal .main-btn svg {
|
||||
margin-right: 12px;
|
||||
fill: #121620;
|
||||
}
|
||||
@ -1,66 +1,44 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { selectors } from '../../store'
|
||||
import { useAuth } from '../../auth'
|
||||
import { useApi, useApiCall, SubscriptionCheckout } from '../../api'
|
||||
import { usePayment } from '../../payment'
|
||||
import {
|
||||
ApplePayBanner,
|
||||
ApplePayButton,
|
||||
GooglePayBanner,
|
||||
GooglePayButton,
|
||||
CardButton,
|
||||
CardModal
|
||||
} from './methods'
|
||||
import UserHeader from '../UserHeader'
|
||||
import Title from '../Title'
|
||||
import Loader from '../Loader'
|
||||
import MainButton from '../MainButton'
|
||||
import applePaySafeCheckout from './Apple-Pay.png'
|
||||
import gPaySafeCheckout from './Google-Pay.png'
|
||||
import secure from './Secure.png'
|
||||
import ApplePay from './Apple-Pay.svg'
|
||||
import GooglePay from './G-Pay.svg'
|
||||
import card from './card.svg'
|
||||
import routes from '../../routes'
|
||||
import secure from './secure.png'
|
||||
import './styles.css'
|
||||
|
||||
const isAndroid = () => /Android/i.test(navigator.userAgent)
|
||||
const isApple = () => /Macintosh|iPhone|iPad|iPod/i.test(navigator.userAgent)
|
||||
import { useState } from 'react'
|
||||
|
||||
function PaymentPage(): JSX.Element {
|
||||
const api = useApi()
|
||||
const { token } = useAuth()
|
||||
const { t, i18n } = useTranslation()
|
||||
const locale = i18n.language
|
||||
const navigate = useNavigate()
|
||||
const { t } = useTranslation()
|
||||
const { applePay } = usePayment()
|
||||
const [open, setOpen] = useState(false)
|
||||
const isApplePayAvailable = import.meta.env.PROD && applePay?.canMakePayments()
|
||||
const email = useSelector(selectors.selectEmail)
|
||||
const itemPriceId = 'aura-membership-2-week-USD'
|
||||
const handleClick = () => navigate(routes.client.wallpaper())
|
||||
const loadData = useCallback(() => {
|
||||
return api.getSubscriptionCheckout({ locale, token, itemPriceId })
|
||||
}, [api, itemPriceId, locale, token])
|
||||
const { data, isPending } = useApiCall<SubscriptionCheckout.Response>(loadData)
|
||||
console.log(data, isPending)
|
||||
|
||||
return (
|
||||
<>
|
||||
<UserHeader email={email} />
|
||||
<section className='page'>
|
||||
<div className='page-header'>
|
||||
{isAndroid() && <img src={gPaySafeCheckout} alt='Guaranteed safe checkout' />}
|
||||
{isApple() && <img src={applePaySafeCheckout} alt='Guaranteed safe checkout' />}
|
||||
{ isApplePayAvailable ? <ApplePayBanner /> : <GooglePayBanner /> }
|
||||
<img src={secure} alt='100% Secure' />
|
||||
</div>
|
||||
<Title variant='h1' className='mb-45'>{t('choose_payment')}</Title>
|
||||
{isPending ? <Loader /> : (
|
||||
<>
|
||||
<MainButton onClick={handleClick}>
|
||||
{isAndroid() && <img className='payment-btn' src={GooglePay} alt='Google Pay' />}
|
||||
{isApple() && <img className='payment-btn' src={ApplePay} alt='Apple Pay' />}
|
||||
</MainButton>
|
||||
<div className='payment-divider'>{t('or').toUpperCase()}</div>
|
||||
<MainButton color='blue' onClick={handleClick}>
|
||||
<img className='payment-card' src={card} alt='Credit / Debit Card' />
|
||||
{t('card')}
|
||||
</MainButton>
|
||||
<p className='payment-warining'>
|
||||
{t('will_be_charged', { strongText: <strong>{t('trial_price')}</strong> })}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
{ isApplePayAvailable ? <ApplePayButton /> : <GooglePayButton /> }
|
||||
<div className='payment-divider'>{t('or').toUpperCase()}</div>
|
||||
<CardButton onClick={() => setOpen(true)} />
|
||||
<p className='payment-warining'>
|
||||
{t('will_be_charged', { strongText: <strong>{t('trial_price')}</strong> })}
|
||||
</p>
|
||||
<CardModal open={open} onClose={() => setOpen(false)} />
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
|
||||
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
5
src/components/PaymentPage/methods/ApplePay/Banner.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import applePaySafeCheckout from './Apple-Pay.png'
|
||||
|
||||
export function ApplePayBanner (): JSX.Element {
|
||||
return <img src={applePaySafeCheckout} alt='Guaranteed safe checkout' />
|
||||
}
|
||||
14
src/components/PaymentPage/methods/ApplePay/Button.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import MainButton from '../../../MainButton'
|
||||
import routes from '../../../../routes'
|
||||
import ApplePay from './Apple-Pay.svg'
|
||||
|
||||
export function ApplePayButton(): JSX.Element {
|
||||
const navigate = useNavigate()
|
||||
const handleClick = () => navigate(routes.client.wallpaper())
|
||||
return (
|
||||
<MainButton onClick={handleClick}>
|
||||
<img className='payment-btn' src={ApplePay} alt='Apple Pay' />
|
||||
</MainButton>
|
||||
)
|
||||
}
|
||||
2
src/components/PaymentPage/methods/ApplePay/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './Banner'
|
||||
export * from './Button'
|
||||
18
src/components/PaymentPage/methods/Card/Button.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import MainButton from '../../../MainButton'
|
||||
import card from './card.svg'
|
||||
|
||||
interface CardButtonProps {
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
export function CardButton({ onClick }: CardButtonProps): JSX.Element {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<MainButton color='blue' onClick={onClick}>
|
||||
<img className='payment-card' src={card} alt='Credit / Debit Card' />
|
||||
{t('card')}
|
||||
</MainButton>
|
||||
)
|
||||
}
|
||||
58
src/components/PaymentPage/methods/Card/Modal.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import { useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
CardCVV, CardComponent, CardExpiry, CardNumber, Provider
|
||||
} from '@chargebee/chargebee-js-react-wrapper'
|
||||
import { usePayment } from '../../../../payment'
|
||||
import Modal from '../../../Modal'
|
||||
import Title from '../../../Title'
|
||||
import MainButton from '../../../MainButton'
|
||||
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'
|
||||
|
||||
interface CardModalProps {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export function CardModal({ open, onClose }: CardModalProps): JSX.Element {
|
||||
const cardRef = useRef(null)
|
||||
const { t, i18n } = useTranslation()
|
||||
const locale = i18n.language
|
||||
const { chargebee } = usePayment()
|
||||
const payWithCard = () => {
|
||||
// cardRef.current?.tokenize()
|
||||
}
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<div className='payment-modal-header'>
|
||||
<Title variant='h3' className='mb-0'>{t('card')}</Title>
|
||||
<div className='payment-card-list'>
|
||||
<img src={visa} alt='Visa Card' />
|
||||
<img src={mastercard} alt='Mastercard Card' />
|
||||
<img src={amex} alt='Amex Card' />
|
||||
<img src={diners} alt='Diners Card' />
|
||||
<img src={discover} alt='Discover Card' />
|
||||
</div>
|
||||
</div>
|
||||
<Provider cbInstance={chargebee}>
|
||||
<CardComponent ref={cardRef} locale={locale} icon={false} className='payment-chargebee'>
|
||||
<CardNumber className="payment-input" />
|
||||
<CardExpiry className="payment-input"/>
|
||||
<CardCVV className="payment-input"/>
|
||||
</CardComponent>
|
||||
</Provider>
|
||||
<p className='payment-inforamtion'>{t('charged_only')}</p>
|
||||
<MainButton onClick={payWithCard} disabled>
|
||||
<svg width="13" height="16" viewBox="0 0 13 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.5556 6.24219H1.44444C0.6467 6.24219 0 6.97481 0 7.87855V13.6058C0 14.5096 0.6467 15.2422 1.44444 15.2422H11.5556C12.3533 15.2422 13 14.5096 13 13.6058V7.87855C13 6.97481 12.3533 6.24219 11.5556 6.24219Z"></path>
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M6.5 0.242188C4.29086 0.242188 2.5 2.03305 2.5 4.24219V8.24219H10.5V4.24219C10.5 2.03305 8.70914 0.242188 6.5 0.242188ZM6.5 1.24219C4.84315 1.24219 3.5 2.58533 3.5 4.24219V7.24219H9.5V4.24219C9.5 2.58533 8.15685 1.24219 6.5 1.24219Z"></path>
|
||||
</svg>
|
||||
Start 7-Day Trial
|
||||
</MainButton>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
4
src/components/PaymentPage/methods/Card/amex.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="40" height="26" viewBox="0 0 40 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1.14258" y="0.50708" width="38.2857" height="24" rx="3.5" fill="#1F72CD" stroke="#C7C7C7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.31366 8.86157L6 16.4103H9.96691L10.4587 15.2067H11.5828L12.0746 16.4103H16.441V15.4917L16.8301 16.4103H19.0888L19.4779 15.4722V16.4103H28.5589L29.6631 15.2379L30.6971 16.4103L35.3613 16.42L32.0371 12.657L35.3613 8.86157H30.7694L29.6945 10.0122L28.6931 8.86157H18.8141L17.9658 10.81L17.0976 8.86157H13.139V9.74892L12.6986 8.86157H9.31366ZM10.0812 9.93314H12.0149L14.2128 15.0519V9.93314H16.3311L18.0287 13.6033L19.5933 9.93314H21.701V15.3498H20.4185L20.408 11.1053L18.5383 15.3498H17.3911L15.5109 11.1053V15.3498H12.8726L12.3724 14.1355H9.67018L9.17103 15.3488H7.75745L10.0812 9.93314ZM28.0891 9.93314H22.8743V15.3466H28.0084L29.6631 13.5525L31.2581 15.3466H32.9254L30.502 12.6556L32.9254 9.93314H31.3304L29.6841 11.7067L28.0891 9.93314ZM11.0218 10.8504L10.1315 13.0137H11.911L11.0218 10.8504ZM24.1622 12.0433V11.0545V11.0536H27.416L28.8358 12.6349L27.3531 14.2249H24.1622V13.1454H27.0071V12.0433H24.1622Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 303 B After Width: | Height: | Size: 303 B |
13
src/components/PaymentPage/methods/Card/diners.svg
Normal file
|
After Width: | Height: | Size: 16 KiB |
11
src/components/PaymentPage/methods/Card/discover.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<svg width="40" height="25" viewBox="0 0 40 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.927734" y="0.5" width="38.2857" height="24" rx="3.5" fill="white" stroke="#C7C7C7"/>
|
||||
<path d="M21.7056 14.5831C23.1438 14.5831 24.3097 13.4173 24.3097 11.9791C24.3097 10.5409 23.1438 9.375 21.7056 9.375C20.2674 9.375 19.1016 10.5409 19.1016 11.9791C19.1016 13.4173 20.2674 14.5831 21.7056 14.5831Z" fill="#F26E21"/>
|
||||
<path d="M7.68056 13.2265C7.37371 13.5012 6.97776 13.6203 6.34895 13.6203H6.08805V10.3509H6.34895C6.97767 10.3509 7.35801 10.4622 7.68056 10.7509C8.0168 11.0475 8.21741 11.5066 8.21741 11.981C8.21741 12.4556 8.0168 12.9309 7.68056 13.2265ZM6.54397 9.51367H5.11523V14.4568H6.53679C7.2912 14.4568 7.83649 14.2799 8.31496 13.8862C8.88246 13.4194 9.21937 12.7153 9.21937 11.9875C9.21929 10.5281 8.11994 9.51367 6.54397 9.51367Z" fill="#0B161F"/>
|
||||
<path d="M9.66602 14.4568H10.6373V9.51367H9.66602V14.4568Z" fill="#0B161F"/>
|
||||
<path d="M13.0165 11.4102C12.4328 11.1954 12.2609 11.0542 12.2609 10.7886C12.2609 10.477 12.5668 10.2398 12.9859 10.2398C13.2775 10.2398 13.5162 10.3583 13.7715 10.6392L14.2787 9.98043C13.8607 9.61672 13.36 9.43164 12.8138 9.43164C11.9325 9.43164 11.2593 10.0395 11.2593 10.8463C11.2593 11.5292 11.5728 11.8773 12.4853 12.2042C12.8665 12.337 13.0603 12.4254 13.1578 12.4857C13.3519 12.612 13.4497 12.7891 13.4497 12.9967C13.4497 13.3976 13.1281 13.6935 12.694 13.6935C12.2306 13.6935 11.8572 13.4642 11.6327 13.0342L11.0059 13.6351C11.4533 14.2863 11.9915 14.5766 12.7321 14.5766C13.7408 14.5766 14.4505 13.9085 14.4505 12.9529C14.451 12.1667 14.1228 11.8108 13.0165 11.4102Z" fill="#0B161F"/>
|
||||
<path d="M14.7578 11.9872C14.7578 13.4416 15.9091 14.5682 17.3891 14.5682C17.8079 14.5682 18.166 14.486 18.6076 14.2797V13.1444C18.2185 13.5304 17.8744 13.6857 17.4335 13.6857C16.4544 13.6857 15.7591 12.9813 15.7591 11.9804C15.7591 11.032 16.4766 10.2829 17.3891 10.2829C17.8516 10.2829 18.2035 10.4457 18.6076 10.8389V9.70445C18.1819 9.49006 17.8298 9.40161 17.411 9.40161C15.939 9.40195 14.7578 10.551 14.7578 11.9872Z" fill="#0B161F"/>
|
||||
<path d="M26.4857 12.8341L25.155 9.51367H24.0938L26.2098 14.5834H26.7322L28.8854 9.51367H27.8313L26.4857 12.8341Z" fill="#0B161F"/>
|
||||
<path d="M29.3281 14.4568H32.0856V13.6203H30.2997V12.2847H32.0172V11.4481H30.2997V10.3509H32.0856V9.51367H29.3281V14.4568Z" fill="#0B161F"/>
|
||||
<path d="M33.9802 11.789H33.6971V10.2909H33.9955C34.6021 10.2909 34.9311 10.544 34.9311 11.0249C34.9311 11.5212 34.6021 11.789 33.9802 11.789ZM35.9308 10.9725C35.9308 10.0465 35.2887 9.51367 34.1668 9.51367H32.7246V14.4568H33.6968V12.4703H33.8244L35.1687 14.4568H36.3647L34.7944 12.3743C35.528 12.2262 35.9308 11.7291 35.9308 10.9725Z" fill="#0B161F"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
2
src/components/PaymentPage/methods/Card/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './Button'
|
||||
export * from './Modal'
|
||||
6
src/components/PaymentPage/methods/Card/mastercard.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="40" height="26" viewBox="0 0 40 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.857422" y="0.50708" width="38.2857" height="24" rx="3.5" fill="white" stroke="#C7C7C7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.6198 19.2054C28.5208 19.2054 31.6831 16.0807 31.6831 12.2262C31.6831 8.37175 28.5208 5.24707 24.6198 5.24707C22.8715 5.24707 21.2715 5.87473 20.0381 6.91445C18.8048 5.87492 17.2049 5.24738 15.4568 5.24738C11.5559 5.24738 8.39355 8.37206 8.39355 12.2266C8.39355 16.081 11.5559 19.2057 15.4568 19.2057C17.2051 19.2057 18.8051 18.5781 20.0385 17.5383C21.2719 18.5779 22.8717 19.2054 24.6198 19.2054Z" fill="#ED0006"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0381 17.538C21.5568 16.2579 22.5198 14.353 22.5198 12.226C22.5198 10.0989 21.5568 8.19413 20.0381 6.91402C21.2715 5.87441 22.8714 5.24683 24.6196 5.24683C28.5205 5.24683 31.6828 8.37151 31.6828 12.226C31.6828 16.0805 28.5205 19.2052 24.6196 19.2052C22.8714 19.2052 21.2715 18.5776 20.0381 17.538Z" fill="#F9A000"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0383 17.5382C21.557 16.258 22.52 14.3533 22.52 12.2262C22.52 10.0992 21.557 8.19441 20.0383 6.91431C18.5196 8.19441 17.5566 10.0992 17.5566 12.2262C17.5566 14.3533 18.5196 16.258 20.0383 17.5382Z" fill="#FF5E00"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
4
src/components/PaymentPage/methods/Card/visa.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="40" height="26" viewBox="0 0 40 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.571289" y="0.50708" width="38.2857" height="24" rx="3.5" fill="white" stroke="#C7C7C7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5123 16.6824H10.0603L8.22151 10.3638C8.13424 10.0731 7.94893 9.81616 7.67634 9.69505C6.99608 9.3907 6.24647 9.14849 5.42871 9.02633V8.78306H9.37881C9.92398 8.78306 10.3329 9.14849 10.401 9.57289L11.3551 14.1307L13.8059 8.78306H16.1899L12.5123 16.6824ZM17.5528 16.6824H15.2371L17.144 8.78306H19.4597L17.5528 16.6824ZM22.4557 10.9714C22.5238 10.5459 22.9327 10.3026 23.4098 10.3026C24.1594 10.2416 24.9759 10.3637 25.6574 10.667L26.0663 8.96626C25.3848 8.72299 24.6352 8.60083 23.9549 8.60083C21.7073 8.60083 20.0718 9.695 20.0718 11.2136C20.0718 12.3688 21.2303 12.9754 22.048 13.3408C22.9327 13.7052 23.2735 13.9485 23.2053 14.3129C23.2053 14.8594 22.5238 15.1027 21.8436 15.1027C21.0258 15.1027 20.2081 14.9205 19.4597 14.6162L19.0508 16.318C19.8685 16.6213 20.7532 16.7434 21.571 16.7434C24.0912 16.8034 25.6574 15.7103 25.6574 14.0696C25.6574 12.0034 22.4557 11.8823 22.4557 10.9714ZM33.7621 16.6824L31.9233 8.78306H29.9483C29.5394 8.78306 29.1305 9.02633 28.9942 9.3907L25.5893 16.6824H27.9732L28.449 15.5282H31.3781L31.6507 16.6824H33.7621ZM30.289 10.9106L30.9693 13.8877H29.0624L30.289 10.9106Z" fill="#172B85"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
5
src/components/PaymentPage/methods/GooglePay/Banner.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import gPaySafeCheckout from './Google-Pay.png'
|
||||
|
||||
export function GooglePayBanner(): JSX.Element {
|
||||
return <img src={gPaySafeCheckout} alt='Guaranteed safe checkout' />
|
||||
}
|
||||
14
src/components/PaymentPage/methods/GooglePay/Button.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import routes from '../../../../routes'
|
||||
import MainButton from '../../../MainButton'
|
||||
import GooglePay from './G-Pay.svg'
|
||||
|
||||
export function GooglePayButton(): JSX.Element {
|
||||
const navigate = useNavigate()
|
||||
const handleClick = () => navigate(routes.client.wallpaper())
|
||||
return (
|
||||
<MainButton onClick={handleClick}>
|
||||
<img className='payment-btn' src={GooglePay} alt='Google Pay' />
|
||||
</MainButton>
|
||||
)
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 80 38.1" style="enable-background:new 0 0 80 38.1;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#5F6368;}
|
||||
.st0{fill:#FFF;}
|
||||
.st1{fill:#4285F4;}
|
||||
.st2{fill:#34A853;}
|
||||
.st3{fill:#FBBC04;}
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
2
src/components/PaymentPage/methods/GooglePay/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './Banner'
|
||||
export * from './Button'
|
||||
3
src/components/PaymentPage/methods/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './ApplePay'
|
||||
export * from './GooglePay'
|
||||
export * from './Card'
|
||||
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
@ -37,3 +37,28 @@
|
||||
line-height: 1.5;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.payment-inforamtion {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
letter-spacing: .0008em;
|
||||
}
|
||||
|
||||
.payment-chargebee {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.payment-card-list {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 12px auto;
|
||||
width: 213px;
|
||||
}
|
||||
|
||||
.payment-modal-header {
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #c7c7c7;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@ -8,13 +8,13 @@ type PaymentItem = {
|
||||
description: string
|
||||
}
|
||||
|
||||
type PaymentProps = {
|
||||
type PaymentTableProps = {
|
||||
currency: Currency
|
||||
locale: Locale
|
||||
items: PaymentItem[]
|
||||
}
|
||||
|
||||
function Payment({ currency, locale, items }: PaymentProps): JSX.Element {
|
||||
function PaymentTable({ currency, locale, items }: PaymentTableProps): JSX.Element {
|
||||
const { t } = useTranslation()
|
||||
const total = items.reduce((acc, item) => acc + item.price, 0)
|
||||
const totalPrice = new Price(total, currency, locale)
|
||||
@ -49,5 +49,5 @@ function Payment({ currency, locale, items }: PaymentProps): JSX.Element {
|
||||
)
|
||||
}
|
||||
|
||||
export default Payment
|
||||
export default PaymentTable
|
||||
export { Price, Currency, Locale }
|
||||
@ -5,7 +5,7 @@ import { selectors } from '../../store'
|
||||
import MainButton from '../MainButton'
|
||||
import Policy from '../Policy'
|
||||
import Countdown from '../Countdown'
|
||||
import Payment, { Currency, Locale } from '../Payment'
|
||||
import PaymentTable, { Currency, Locale } from '../PaymentTable'
|
||||
import UserHeader from '../UserHeader'
|
||||
import CallToAction from '../CallToAction'
|
||||
import routes from '../../routes'
|
||||
@ -33,7 +33,7 @@ function SubscriptionPage(): JSX.Element {
|
||||
<section className='page'>
|
||||
<CallToAction />
|
||||
<Countdown start={10}/>
|
||||
<Payment items={paymentItems} currency={currency} locale={locale}/>
|
||||
<PaymentTable items={paymentItems} currency={currency} locale={locale}/>
|
||||
<MainButton onClick={handleClick}>{t('get_access')}</MainButton>
|
||||
<Policy>
|
||||
{t('subscription_text', {
|
||||
|
||||
8
src/config.ts
Normal file
@ -0,0 +1,8 @@
|
||||
const config = {
|
||||
chargebee: {
|
||||
site: 'kefirapp-test',
|
||||
publishableKey: 'test_VtWSamZEfP175nqGZhkD0uvoouHieElv',
|
||||
},
|
||||
}
|
||||
|
||||
export default config
|
||||
@ -103,6 +103,10 @@ a,button,div,input,select,textarea {
|
||||
margin-bottom: 45px;
|
||||
}
|
||||
|
||||
.mb-0 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.pa {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@ -8,10 +8,12 @@ import { store } from './store'
|
||||
import { AuthProvider } from './auth'
|
||||
import { ApiContext, createApi } from './api'
|
||||
import { LegalContext, buildLegal } from './legal'
|
||||
import { PaymentContext } from './payment'
|
||||
import { getClientLocale, buildResources, fallbackLng } from './locales'
|
||||
import type { AppConfig } from './types'
|
||||
import App from './components/App'
|
||||
|
||||
const init = async () => {
|
||||
const init = async (config: AppConfig) => {
|
||||
const api = createApi()
|
||||
const lng = getClientLocale()
|
||||
const response = await api.getElements({ locale: lng })
|
||||
@ -20,6 +22,7 @@ const init = async () => {
|
||||
const i18nextInstance = i18next.createInstance()
|
||||
const options = { lng, resources, fallbackLng, postProcess: [ `reactPostprocessor` ] }
|
||||
await i18nextInstance.use(initReactI18next).use(new ReactPostprocessor()).init(options)
|
||||
window.Chargebee.init(config.chargebee)
|
||||
return (
|
||||
<React.StrictMode>
|
||||
<I18nextProvider i18n={i18nextInstance}>
|
||||
@ -28,7 +31,9 @@ const init = async () => {
|
||||
<ApiContext.Provider value={api}>
|
||||
<AuthProvider>
|
||||
<LegalContext.Provider value={legal}>
|
||||
<App />
|
||||
<PaymentContext.Provider value={window.Chargebee.getInstance()}>
|
||||
<App />
|
||||
</PaymentContext.Provider>
|
||||
</LegalContext.Provider>
|
||||
</AuthProvider>
|
||||
</ApiContext.Provider>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import config from './config'
|
||||
import init from './init'
|
||||
import 'react-circular-progressbar/dist/styles.css'
|
||||
import './fonts.css'
|
||||
@ -17,7 +18,7 @@ const getRootElement = (id: string): HTMLElement => {
|
||||
}
|
||||
|
||||
const startApp = async () => {
|
||||
const vdom = await init()
|
||||
const vdom = await init(config)
|
||||
const rootElement = getRootElement('root')
|
||||
|
||||
ReactDOM.createRoot(rootElement).render(vdom)
|
||||
|
||||
4
src/payment/PaymentContext.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { createContext } from 'react'
|
||||
import { ChargebeeInstance } from '@chargebee/chargebee-js-types'
|
||||
|
||||
export const PaymentContext = createContext<ChargebeeInstance>({} as ChargebeeInstance)
|
||||
2
src/payment/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './PaymentContext'
|
||||
export * from './usePayment'
|
||||
28
src/payment/usePayment.ts
Normal file
@ -0,0 +1,28 @@
|
||||
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<void>
|
||||
handlePayment: () => Promise<PaymentIntent>
|
||||
}
|
||||
|
||||
export const usePayment = () => {
|
||||
const chargebee = useContext(PaymentContext)
|
||||
const [googlePay, setGooglePay] = useState(null)
|
||||
const [applePay, setApplePay] = useState<ApplePayHandler | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
chargebee.load('google-pay'),
|
||||
chargebee.load('apple-pay'),
|
||||
]).then(([googlePay, applePay]) => {
|
||||
setGooglePay(googlePay)
|
||||
setApplePay(applePay)
|
||||
})
|
||||
}, [chargebee])
|
||||
|
||||
return { chargebee, googlePay, applePay }
|
||||
}
|
||||
11
src/types.ts
@ -1,3 +1,12 @@
|
||||
import { Chargebee } from '@chargebee/chargebee-js-types'
|
||||
import config from './config'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
Chargebee: typeof Chargebee
|
||||
}
|
||||
}
|
||||
|
||||
export interface FormField<T> {
|
||||
name: string
|
||||
value: T
|
||||
@ -12,3 +21,5 @@ export interface SignupForm {
|
||||
birthdate: string
|
||||
birthtime: string
|
||||
}
|
||||
|
||||
export type AppConfig = typeof config
|
||||
|
||||