add page of payment

This commit is contained in:
LittleBoy 2024-05-21 17:31:50 +08:00
parent 61072a7382
commit 914e9d5643
20 changed files with 450 additions and 38 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

View File

@ -0,0 +1,37 @@
const AsiaPayLogo = () => (
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 473 483.1" height="20">
<path fill="#272E87" d="M178,249.9h-8.7c-12.7,1.1-24.9,5.1-35.6,11.9c-16.7,8.7-16.7,30.1-16.7,38v152.8c0,1.6,0,4,1.6,4
c2.4-0.8,1.6,0,4-0.8l14.2-4c1.6,0,2.4-1.6,2.4-3.2v-60.2c7.1,4.8,15.8,9.5,30.1,10.3h5.6c34,0,49.1-35.6,49.1-77.6
C223.9,284.7,209.7,249.9,178,249.9z M172.5,378.9h-3.2c-12.6-0.8-21.4-4.8-30.1-9.5v-80c0-8.7,11.9-19.5,30.9-20.3
c1.3-0.2,2.7-0.3,4-0.3c23.8,0,25.3,28.5,25.3,53C199.4,350.4,197,378.9,172.5,378.9L172.5,378.9z"/>
<path fill="#272E87" d="M291.1,251h-4.7c-18.2,0.8-41.2,8.7-41.2,13.5c-0.1,1.4,0.2,2.7,0.8,4l4,9.5c0.7,1.5,0.8,1.6,1.6,1.6
c3.2,0,16.1-9.5,35.1-9.5c0.8,0,0.5,0.1,1.3,0.1c20.6,0,26.1,9.5,26.1,26.9v10.3c-9.3,0.2-18.5,1-27.7,2.4
c-23,4-49.1,15.9-49.1,49.1c0,26.1,14.3,41.2,38,41.2h11.1c18.2-1.6,30.1-5.6,33.2-7.9c15-9.5,16.6-27.7,16.6-42.8v-57
C336.2,266.9,324.4,251,291.1,251z M314.1,369c0,4.7-14.3,10.3-27.7,11.1h-4.8c-15.1,0-23-11.9-23-23.7
c0-17.4,13.5-24.6,27.7-27.7c11.1-3.2,22.1-3.2,27.7-3.2L314.1,369z"/>
<path fill="#272E87" d="M463,256.2c0-2.4,0.8-5.5-1.6-5.5l-15.8-0.8c-2.4,0-4,0-4,1.6l-0.8,118.8c-7.9,7.1-17.4,9.5-31.7,9.5
c-18.2,0.8-29.3-18.2-30.1-41.2c-1.6-29.3,0-55.5,0-84.7c0-0.8-3.1-3.2-5.5-3.2h-14.2c0,0-3.2,4-3.2,4.8
c0.8,15.8,6.3,114,10.3,121.1c1.6,2.4,4.7,7.1,7.9,11.1c3.2,3.2,7.1,5.5,11.9,8.7c10.3,6.3,42.8,4,55.4-7.1l0.8,13.5l-0.8,15
c-0.8,15.1-0.8,39.6-23.8,40.4c-15,0-22.2-9.5-24.5-1.6c0,0.8-2.4,11.9-1.6,13.5c2.4,3.2,9.5,6.3,22.2,7.1
c16.6,0.8,35.6-7.9,41.2-19.8c9.5-19,8.7-63.3,8.7-80L463,256.2z"/>
<path fill="#F7A500" d="M201,82.8c0-1.6,0.8-2.4,0.8-3.2c0-1.6-0.8-3.2-3.1-4c-7.9-4-24.6-8.7-34.8-8.7c-26.1,0-44.4,16.6-44.4,41.2
c0,44.3,64.9,37.2,64.9,68.1c0,14.2-10.3,21.4-23,21.4c-20.6,0-35.6-10.3-38-10.3c-0.8,0-2.4,0-3.2,2.4l-3.9,9.5
c-0.8,0.8-0.8,1.6-0.8,3.2c0,5.5,30.1,14.3,44.3,14.3c25.3,0,47.5-14.3,47.5-42c0-45.9-65.7-36.4-65.7-68.9
c0-14.2,10.3-19.8,22.2-19.8c15,0,27.7,7.9,29.3,7.9s2.4-0.8,4-3.2L201,82.8z"/>
<path fill="#F7A500" d="M244.5,75.6c0-2.4-0.8-4-3.9-4h-15.1c-2.4,0-4,0.8-4,3.2v134.6c0,2.4,1.6,3.2,4,3.2h15.1
c2.4,0,3.9-0.8,3.9-3.2V75.6z M249.3,19.4c-0.1-9.6-7.8-17.3-17.4-17.4c-10.3,0-18.2,7.9-18.2,17.4c-0.2,9.9,7.7,18,17.6,18.2
c0.2,0,0.4,0,0.6,0C241.3,37.6,249.3,29.7,249.3,19.4"/>
<path className="st1" fill="#F7A500" d="M320.5,67.7h-4.7c-18.2,0.8-41.2,8.7-41.2,13.5c-0.1,1.4,0.1,2.8,0.8,4l4,9.5c0.7,1.5,0.8,1.6,1.6,1.6
c3.2,0,16.1-9.6,35.1-9.6c0.8,0,0.5,0.1,1.3,0.1c20.6,0,26.1,9.5,26.1,26.9V124c-9.3,0.2-18.5,1-27.7,2.4
c-23,4-49.1,15.9-49.1,49.1c0,26.1,14.3,41.2,38,41.2h11.1c18.2-1.6,30.1-5.6,33.2-8c15-9.5,16.6-27.7,16.6-42.8v-57
C365.7,83.6,353.8,67.7,320.5,67.7z M343.5,185.7c0,4.7-14.3,10.3-27.7,11.1H311c-15,0-23-11.9-23-23.8
c0-17.4,13.5-24.6,27.7-27.7c11.1-3.2,22.2-3.2,27.7-3.2L343.5,185.7z"/>
<path fill="#F7A500" d="M55.8,67.7h-4.7C32.9,68.5,9.9,76.4,9.9,81.2c-0.1,1.4,0.1,2.8,0.8,4l3.9,9.5c0.7,1.5,0.8,1.6,1.6,1.6
c3.2,0,16.1-9.6,35.1-9.6c0.8,0,0.5,0.1,1.3,0.1c20.6,0,26.1,9.5,26.1,26.9V124c-9.3,0.2-18.5,1-27.7,2.4
c-23,4-49.1,15.8-49.1,49.1c0,26.1,14.3,41.2,38,41.2h11.1c18.2-1.6,30.1-5.6,33.3-7.9c15-9.6,16.6-27.7,16.6-42.8v-57
C101,83.6,89.1,67.7,55.8,67.7z M78.8,185.7c0,4.7-14.3,10.3-27.7,11.1h-4.8c-15,0-23-11.9-23-23.8c0-17.4,13.5-24.6,27.7-27.7
c11.1-3.2,22.2-3.2,27.7-3.2L78.8,185.7z"/>
</svg>
)
export default AsiaPayLogo

View File

@ -0,0 +1,19 @@
const FlywireLogo = () => (
<svg version="1.1" id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 161 56" width="50">
<path d="M34.2,43.1L40.9,1h5.4l-6.7,42.1L34.2,43.1 M67.2,42.3c-1.3,8.2-6,12.5-13.5,12.5
c-4.6,0-8.2-1.7-10.6-4.6l3-3.7c2.1,2.5,4.6,3.6,7.6,3.6c4.5,0,7.3-2.6,8.2-7.8l0.3-2.1h-0.1c-2.4,2.3-5.3,3.5-8.4,3.5
c-5.8,0-8.6-2.9-8.6-8.5c0-1.1,0.1-2.2,0.3-3.4l2.7-17.3h5.3l-2.7,17.3c-0.2,0.9-0.2,1.7-0.2,2.4c0,3.2,1.4,4.7,4.5,4.7
c3.3,0,6.2-2,8.1-5.5l3-19h5.4L67.2,42.3 M128,19.7l1.1-5.3h-5.3l-4.5,28.8h5.4l2.4-15.4c1.1-5.7,5.2-8.5,9.6-8.5
c0.5,0,0.9,0,1.4,0l0.8-5.1C133.8,13.5,130,16.1,128,19.7 M107.6,14.6l-8.9,19.9l-3.2-19.9h-5.2l-9.1,19.9l-3-19.9h-5.5l4.6,28.7
H83l8.9-19.9L95,43.3h5.5l12.8-28.7H107.6 M109.5,43.3l4.5-28.7h5.4l-4.5,28.7H109.5 M120.8,8.8c0,1.7-1.4,3.1-3.1,3.1
s-3.1-1.4-3.1-3.1s1.4-3.1,3.1-3.1C119.5,5.7,120.8,7.1,120.8,8.8L120.8,8.8z M20.5,22.5l0.5-3.1H1.8l0.8-5h19.2l0.4-2.4
C23.3,5,27.9,1,35,1h3.9l-0.8,4.9l-3.6,0c-3.9,0-6.1,1.9-6.8,6.3l-0.3,2.2h7.4l-0.8,5h-7.4l-4.1,24.2c-1.1,7-5.8,11-12.9,10.9
l-0.4,0l0.8-5.3l0.3,0c4,0,6.1-1.7,6.8-6l2.6-15.7H0.4l0.8-5L20.5,22.5L20.5,22.5z M146.1,39L146.1,39c7.5-0.1,7.7-5.4,7.7-5.4h5
c0,0-1.1,10-12.8,10c-3.6,0-6.6-1.2-8.3-3.4c-1.9-2.4-2.5-5.3-2-8.9l0.4-2.6c1.1-6.7,2.6-15.1,14.3-15.1c3.2,0,5.9,0.9,7.7,2.7
c1.6,1.5,2.5,3.7,2.5,5.8c0,3.1-1.3,5.5-3.8,7c-2.6,1.6-5.8,1.9-8.2,1.9h-7.9c-0.5,2.8-0.2,4.9,0.9,6.1
C142.6,38.5,144.1,39.1,146.1,39z M141.6,25.8L141.6,25.8l-0.2,0.9h7.3c4.6,0,6.7-1.4,6.7-4.4c0-2.5-2.1-4-5.4-4
C145.4,18.3,142.5,21,141.6,25.8z" fill={'#147bd1'}/>
</svg>
)
export default FlywireLogo

View File

@ -0,0 +1,7 @@
export const SuccessIcon = () => (
<svg className="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="1em"
height="1em">
<path
d="M666.272 472.288l-175.616 192a31.904 31.904 0 0 1-23.616 10.4h-0.192a32 32 0 0 1-23.68-10.688l-85.728-96a32 32 0 1 1 47.744-42.624l62.144 69.6 151.712-165.888a32 32 0 1 1 47.232 43.2m-154.24-344.32C300.224 128 128 300.32 128 512c0 211.776 172.224 384 384 384 211.68 0 384-172.224 384-384 0-211.68-172.32-384-384-384"
fill="currentColor"></path>
</svg>)

View File

@ -37,7 +37,9 @@
.space-between {
justify-content: space-between;
}
.space-center{
justify-content: center;
}
.align-center {
display: flex;
align-items: center;
@ -48,9 +50,22 @@
width: 1400px;
margin: auto;
}
.main-bg-container{
.main-content{
.auth-container {
background: url(./images/auth/bg.png);
}
}
.main-bg-container {
background: url(./images/auth/bg.jpg);
.main-content{
display: flex;
align-items:center;
justify-content: center;
background: rgba(255, 255, 255, 0.07);
backdrop-filter: blur(2px);
width: 100vw;
height: 100vh;
}
}
.semi-dropdown-item-active {
@ -63,6 +78,17 @@
}
}
}
.semi-radio-inner{
width: 20px;
height: 20px;
.semi-radio-inner-display{
width: 20px;
height: 20px;
.semi-icon{
font-size: 18px;
}
}
}
.dashboard-menu-container {
padding: var(--dashboard-layout-padding,15px);

View File

@ -1,11 +1,29 @@
import React from "react";
type MoneyFormatProps = {
money:number|string;
currency?:'USD'|'HKD'|'RMB';
money: number | string;
currency?: 'USD' | 'HKD' | 'RMB';
}
const MoneyFormat:React.FC<MoneyFormatProps> = ({money,currency = 'HKD'})=>{
function formatCurrency(amount: string | number) {
// 将金额转换为字符串,并限制小数点后两位
let amountStr = Number(amount).toFixed(2);
// 移除可能的负号
const isNegative = amountStr[0] === '-';
if (isNegative) {
amountStr = amountStr.slice(1);
}
// 将金额字符串拆分为整数和小数部分
const [intPart, decimalPart] = amountStr.split('.');
// 整数部分每三位添加一个逗号
const formattedIntPart = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
// 返回格式化后的金额
return (isNegative ? '-' : '') + formattedIntPart + '.' + decimalPart;
}
const MoneyFormat: React.FC<MoneyFormatProps> = ({money, currency = 'HKD'}) => {
// 将货币数字转换为千分位格式且带2位小数
return <span>{Number(money).toLocaleString('zh-HK', {style: 'currency', currency})}</span>
return <span className={'money-format'}>{currency} {formatCurrency(money)}</span>
}
export default MoneyFormat;

View File

@ -1,5 +1,7 @@
{
"bill": {
"bill_number": "Bill Number",
"download_receipt": "Download receipt",
"title_actual_payment_amount": "Actually Paid",
"title_amount": "Amount",
"title_bill_detail": "Bill Detail",
@ -42,5 +44,11 @@
"exp_time": "bill will expires at",
"student_number": "Student Number",
"student_number_required": "required student number"
},
"pay": {
"amount": "Amount",
"charge": "Charge",
"text_success": "Success",
"total_amount": "Total Amount HKD"
}
}

View File

@ -1,5 +1,7 @@
{
"bill": {
"bill_number": "账单编号",
"download_receipt": "下载收据",
"title_actual_payment_amount": "实付金额",
"title_amount": "账单金额",
"title_bill_detail": "账单详情",
@ -42,5 +44,11 @@
"exp_time": "账单过期时间",
"student_number": "学号",
"student_number_required": "请填写学号"
},
"pay": {
"amount": "应付金额",
"charge": "手续费",
"text_success": "支付成功",
"total_amount": "应付总金额"
}
}

View File

@ -1,5 +1,7 @@
{
"bill": {
"bill_number": "账单编号",
"download_receipt": "下载收据",
"title_actual_payment_amount": "實付金額",
"title_amount": "帳單金額",
"title_bill_detail": "帳單詳情",
@ -42,5 +44,11 @@
"exp_time": "帳單過期時間",
"student_number": "學號",
"student_number_required": "請填入學號"
},
"pay": {
"amount": "应付金额",
"charge": "手续费",
"text_success": "支付成功",
"total_amount": "应付总金额"
}
}

View File

@ -43,6 +43,9 @@ const AuthLogin = () => {
</LogoContainer>
<SubmitContainer>
<Button theme='solid' type='primary' block onClick={showDashboard}>{t('login.submit')}</Button>
<Button style={{marginTop:10}} theme='solid' type='primary' block onClick={()=>{
navigate('/pay')
}}></Button>
</SubmitContainer>
</LoginContainer>)
}

View File

@ -0,0 +1,10 @@
import AppLogo from "@/assets/AppLogo.tsx";
import useConfig from "@/hooks/useConfig.ts";
export const PayLogo = () => {
const {appName} = useConfig()
return (<div className={'align-center space-center'}>
<AppLogo style={{fontSize:18}}/>
{appName && <h2 style={{marginLeft:20}}>{appName}</h2>}
</div>)
}

83
src/pages/pay/index.tsx Normal file
View File

@ -0,0 +1,83 @@
import styles from './pay.module.less'
import {PayLogo} from "@/pages/pay/component";
import {useTranslation} from "react-i18next";
import MoneyFormat from "@/components/money-format.tsx";
import {useState} from "react";
import {Radio, RadioGroup} from '@douyinfe/semi-ui';
import FlywireLogo from "@/assets/images/pay/flywire.tsx";
import AsiaPayLogo from "@/assets/images/pay/asia_pay.tsx";
import {useNavigate} from "react-router-dom";
const BillDetail = [
{title: 'TUITION FEE', value: 67500},
{title: 'CAUTION FEE', value: 400},
{title: 'VISA FEE', value: 500},
]
const PayIndex = () => {
const {t} = useTranslation()
const [payChannel, setPayChannel] = useState('asia_pay')
const navigate = useNavigate()
const startPay = ()=>{
navigate('/success?id=123123')
}
return (<div className={styles.container}>
<PayLogo/>
<div className={`${styles.payAmount} text-center`}>
<div className="total">{t('pay.total_amount')}</div>
<div className={styles.totalAmount}>
<MoneyFormat money={69632.22}/>
</div>
<div className="space-center align-center">
<div>
<span>{t('pay.amount')}: </span>
<span><MoneyFormat money={68400}/></span>
</div>
{payChannel == 'asia_pay' && <div style={{marginLeft: 10}}>
<span>AsiaPay{t('pay.charge')}: </span>
<span><MoneyFormat money={1232.2}/></span>
</div>}
</div>
</div>
<div className={styles.payChanel}>
<RadioGroup type={'card'} onChange={(e) => setPayChannel(e.target.value)} value={payChannel}>
<Radio value={'asia_pay'}>
<AsiaPayLogo/>
<span style={{marginLeft: 5}}>AsiaPay</span>
</Radio>
<Radio value={'flywire'}>
<FlywireLogo/>
<span style={{marginLeft: 5}}>Flywire</span>
</Radio>
</RadioGroup>
</div>
<div className={styles.payDetail}>
<div className="pay-item">
<div className="title">Payment Type:</div>
<div className="value">OFFER FEE</div>
</div>
{BillDetail.map((it, idx) => (
<div key={idx} className="pay-item">
<div className="title">{it.title}:</div>
<div className="value"><MoneyFormat money={it.value}/></div>
</div>
))}
</div>
<div className={styles.billInfo}>
<div className="pay-item">
<span className="title">Bill Number:</span>
<span className="value">20240509114</span>
</div>
<div className="pay-item">
<span className="title">Order Number:</span>
<span className="value">PM-202405211114</span>
</div>
</div>
<div className={styles.payConfirm}>
<div className="student-email">Your Email: xxxx@google.com</div>
<div className="pay-submit">
<button onClick={startPay} className={styles.btnConfirm}>CONFIRM PAY</button>
</div>
</div>
</div>);
}
export default PayIndex;

View File

@ -0,0 +1,122 @@
.border-radius {
border-radius: var(--main-border-radius, 10px);
}
.container {
.border-radius;
padding: 30px 80px;
background: #fff;
width: 600px;
transition: width 0.1s;
}
.payAmount {
color: #fff;
background-color: #50ADA7;
.border-radius;
padding: 25px;
margin: 40px 0 10px;
font-size: 14px;
}
.totalAmount {
font-size: 36px;
margin: 10px 0;
}
.payChanel {
margin: 20px;
text-align: center;
}
.itemValue {
color: #999999;
text-align: left;
padding-left: 10px;
}
.billInfo {
font-size: 12px;
line-height: 20px;
margin-top: 50px;
:global {
.value {
.itemValue;
}
}
}
.payDetail {
font-size: 12px;
line-height: 20px;
:global {
.pay-item {
display: flex;
}
.title {
flex: 1;
text-align: right;
}
.value {
flex: 1;
.itemValue;
}
}
}
.payConfirm {
padding-top: 20px;
margin-top: 20px;
border-top: solid 1px #eee;
text-align: center;
}
.btnConfirm {
width: 300px;
height: 34px;
background-color: #43ABFF;
border-radius: 20px;
color: #fff;
outline: none;
border: none;
margin-top: 20px;
cursor: pointer;
&:active, &:hover {
background-color: #3d9dea;
}
}
.paySuccess {
text-align: center;
margin: 50px 0;
:global{
.icon{
display: block;
margin: auto;
}
}
}
.successIcon {
color: #74C041;
font-size: 100px;
}
@media screen and (max-width: 700px) {
.container {
width: 80%;
padding: 30px 30px;
}
.payAmount {
padding: 25px 10px;
margin-top: 20px;
font-size: 13px;
}
}

33
src/pages/pay/success.tsx Normal file
View File

@ -0,0 +1,33 @@
import styles from "@/pages/pay/pay.module.less";
import {PayLogo} from "@/pages/pay/component";
import {SuccessIcon} from "@/assets/images/pay/success.tsx";
import {useTranslation} from "react-i18next";
import {useDownloadReceiptPDF} from "@/service/generate-pdf.ts";
import {Button} from "@douyinfe/semi-ui";
const PayIndex = () => {
const {t} = useTranslation()
const {downloadPDF} = useDownloadReceiptPDF()
return (<div className={styles.container}>
<PayLogo/>
<div className={styles.paySuccess}>
<div className={styles.successIcon}>
<SuccessIcon/>
</div>
<h2 className="pay-text">
{t('pay.text_success')}
</h2>
</div>
<div className={styles.payConfirm}>
<div className="student-email">{t('bill.bill_number')}: PM-202405211114</div>
<div className="pay-submit">
<Button
theme={'solid'}
onClick={() => downloadPDF({id: Date.now()})}
style={{width:250,marginTop:20}}>{t('bill.download_receipt')}</Button>
</div>
</div>
</div>);
}
export default PayIndex;

View File

@ -1,5 +1,7 @@
import {createBrowserRouter, Navigate, RouteObject, RouterProvider,} from "react-router-dom";
import {lazy, Suspense, useMemo,} from "react";
import {useTranslation} from "react-i18next";
import { LocaleProvider } from '@douyinfe/semi-ui';
import zh_CN from '@douyinfe/semi-ui/lib/es/locale/source/zh_CN';
import zh_TW from '@douyinfe/semi-ui/lib/es/locale/source/zh_TW';
@ -8,8 +10,11 @@ import en_US from '@douyinfe/semi-ui/lib/es/locale/source/en_US';
import ErrorBoundary, {Error404} from "./error.tsx";
import DashboardLayout from "@/routes/layout/dashboard-layout.tsx";
import AuthLogin from "@/pages/auth/login.tsx";
import AuthLayout from "@/routes/layout/auth-layout.tsx";
import {useTranslation} from "react-i18next";
import PayIndex from "@/pages/pay/index.tsx";
import PaySuccess from "@/pages/pay/success.tsx";
import MainLayout from "@/routes/layout/main-layout.tsx";
import Loader from "@/components/loader.tsx";
const ManualIndex = lazy(() => import("@/pages/manual/index.tsx"));
const BillQuery = lazy(() => import("@/pages/bill/query.tsx"));
@ -19,7 +24,7 @@ const BillReconciliation = lazy(() => import("@/pages/bill/reconciliation.tsx"))
const routes: RouteObject[] = [
{
path: '/',
element: <AuthLayout/>,
element: <MainLayout/>,
errorElement: <ErrorBoundary/>,
children: [
{
@ -30,6 +35,14 @@ const routes: RouteObject[] = [
path: 'login',
element: <AuthLogin/>
},
{
path: 'pay',
element: <PayIndex/>
},
{
path: 'success',
element: <PaySuccess/>
},
]
},
{
@ -72,7 +85,7 @@ const AppRouter = () => {
},[i18n.language])
return (<LocaleProvider locale={locale}>
<Suspense>
<Suspense fallback={<Loader />}>
<RouterProvider router={router}/>
</Suspense>
</LocaleProvider>)

View File

@ -1,25 +0,0 @@
import React from "react";
import {Outlet} from "react-router-dom";
import styled from "@emotion/styled";
import {I18nSwitcher} from "@/i18n";
const AuthContainer = styled.div({
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
})
const I18nContainer = styled.div({
position: 'absolute',
top: 10,
right: 10
})
const AuthLayout: React.FC<{ children?: React.ReactNode }> = ({children}) => {
return (<AuthContainer className="auth-container">
<I18nContainer>
<I18nSwitcher/>
</I18nContainer>
{children ? children : <Outlet/>}
</AuthContainer>)
}
export default AuthLayout

View File

@ -0,0 +1,21 @@
import React from "react";
import {Outlet} from "react-router-dom";
import styled from "@emotion/styled";
import {I18nSwitcher} from "@/i18n";
const I18nContainer = styled.div({
position: 'absolute',
top: 10,
right: 10
})
const MainLayout: React.FC<{ children?: React.ReactNode }> = ({children}) => {
return (<div className="main-bg-container">
<div className="main-content">
<I18nContainer>
<I18nSwitcher/>
</I18nContainer>
{children ? children : <Outlet/>}
</div>
</div>)
}
export default MainLayout

View File

@ -1,5 +1,7 @@
import JsPDF from "jspdf";
import autoTable from "jspdf-autotable";
import {useState} from "react";
function drawItem(doc: JsPDF, item: {
title: string;
@ -70,4 +72,23 @@ export function GeneratePdf(filename: string) {
drawItem(doc, {title: 'Please retain this acknowledgement receipt for your record.'}, 185)
doc.save(filename);
}
// use service work generate pdf and download
export function useDownloadReceiptPDF() {
const [loading, setLoading] = useState(false);
const downloadPDF = (bill: Partial<BillModel>) => {
setLoading(true)
setTimeout(() => {
GeneratePdf(`Receipt-${bill.id}.pdf`)
setLoading(false)
}, 100);
}
return {
loading,
downloadPDF
}
}

View File

@ -1,6 +1,6 @@
import {defineConfig} from 'vite'
import react from '@vitejs/plugin-react'
import * as path from "path";
import {resolve} from "path";
// https://vitejs.dev/config/
export default defineConfig(({mode}) => {
@ -12,7 +12,7 @@ export default defineConfig(({mode}) => {
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
'@': resolve(__dirname, './src')
}
}
}