fix: pc端修改
This commit is contained in:
@@ -19,30 +19,37 @@ export function SubscribeBilling({ order }: Readonly<SubscribeBillingProps>) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='font-semibold'>{t('billing.billingTitle')}</div>
|
||||
<ul className='grid grid-cols-2 gap-3 *:flex *:items-center *:justify-between lg:grid-cols-1'>
|
||||
<div className='mb-1 font-semibold text-[#225BA9]'>{t('billing.billingTitle')}</div>
|
||||
<ul className='grid grid-cols-1 gap-1 text-[15px] font-light text-[#666] *:flex *:items-center *:justify-between lg:grid-cols-1'>
|
||||
<li>
|
||||
<span className=''>套餐时长</span>
|
||||
<span>
|
||||
{order?.quantity === 1 ? '30天' : ''}
|
||||
{order?.quantity === 12 ? '365天' : ''}
|
||||
</span>
|
||||
</li>
|
||||
{order?.type && [1, 2].includes(order?.type) && (
|
||||
<li>
|
||||
<span className='text-muted-foreground'>{t('billing.duration')}</span>
|
||||
<span className=''>{t('billing.duration')}</span>
|
||||
<span>
|
||||
{order?.quantity || 1} {t(order?.unit_time || 'Month')}
|
||||
</span>
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
<span className='text-muted-foreground'>{t('billing.price')}</span>
|
||||
<span className=''>{t('billing.price')}</span>
|
||||
<span>
|
||||
<Display type='currency' value={order?.price || order?.unit_price} />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className='text-muted-foreground'>{t('billing.productDiscount')}</span>
|
||||
<span className=''>{t('billing.productDiscount')}</span>
|
||||
<span>
|
||||
<Display type='currency' value={order?.discount} />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className='text-muted-foreground'>{t('billing.couponDiscount')}</span>
|
||||
<span className=''>{t('billing.couponDiscount')}</span>
|
||||
<span>
|
||||
<Display type='currency' value={order?.coupon_discount} />
|
||||
</span>
|
||||
@@ -60,9 +67,9 @@ export function SubscribeBilling({ order }: Readonly<SubscribeBillingProps>) {
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<Separator />
|
||||
<div className='flex items-center justify-between font-semibold'>
|
||||
<span className='text-muted-foreground'>{t('billing.total')}</span>
|
||||
<Separator className={'mb-3 mt-4 bg-[#225BA9]'} />
|
||||
<div className='flex items-center justify-between font-semibold text-[#666]'>
|
||||
<span className=''>支付金额</span>
|
||||
<span>
|
||||
<Display type='currency' value={order?.amount} />
|
||||
</span>
|
||||
|
||||
@@ -17,30 +17,30 @@ export function SubscribeDetail({ subscribe }: Readonly<SubscribeDetailProps>) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='font-semibold'>{t('productDetail')}</div>
|
||||
<ul className='grid grid-cols-1 gap-3 *:flex *:items-center *:justify-between lg:grid-cols-1'>
|
||||
<div className='mb-1 font-semibold text-[#225BA9]'>{t('productDetail')}</div>
|
||||
<ul className='grid grid-cols-1 gap-2 text-[15px] font-light text-[#666] *:flex *:items-center *:justify-between lg:grid-cols-1'>
|
||||
{subscribe?.name && (
|
||||
<li className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground line-clamp-2 flex-1'>{subscribe?.name}</span>
|
||||
<span className='line-clamp-2 flex-1'>{subscribe?.name}</span>
|
||||
<span>
|
||||
x <span>{subscribe?.quantity || 1}</span>
|
||||
x <span>1</span>
|
||||
</span>
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
<span className='text-muted-foreground'>{t('availableTraffic')}</span>
|
||||
<span className=''>{t('availableTraffic')}</span>
|
||||
<span>
|
||||
<Display type='traffic' value={subscribe?.traffic} unlimited />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className='text-muted-foreground'>{t('connectionSpeed')}</span>
|
||||
<span className=''>{t('connectionSpeed')}</span>
|
||||
<span>
|
||||
<Display type='trafficSpeed' value={subscribe?.speed_limit} unlimited />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className='text-muted-foreground'>{t('connectedDevices')}</span>
|
||||
<span className=''>{t('connectedDevices')}</span>
|
||||
<span>
|
||||
<Display value={subscribe?.device_limit} type='number' unlimited />
|
||||
</span>
|
||||
|
||||
@@ -1,28 +1,38 @@
|
||||
'use client';
|
||||
|
||||
import CouponInput from '@/components/subscribe/coupon-input';
|
||||
import DurationSelector from '@/components/subscribe/duration-selector';
|
||||
import PaymentMethods from '@/components/subscribe/payment-methods';
|
||||
import CloseSvg from '@/components/CustomIcon/icons/close.svg';
|
||||
import useGlobalStore from '@/config/use-global';
|
||||
import { preCreateOrder, purchase } from '@/services/user/order';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Button } from '@workspace/ui/components/button';
|
||||
import { Card, CardContent } from '@workspace/ui/components/card';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@workspace/ui/components/dialog';
|
||||
import { Separator } from '@workspace/ui/components/separator';
|
||||
import { Tabs, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
|
||||
import { LoaderCircle } from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useCallback, useEffect, useRef, useState, useTransition } from 'react';
|
||||
import {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState,
|
||||
useTransition,
|
||||
} from 'react';
|
||||
import { SubscribeBilling } from './billing';
|
||||
import { SubscribeDetail } from './detail';
|
||||
|
||||
interface PurchaseProps {
|
||||
subscribe?: API.Subscribe;
|
||||
setSubscribe: (subscribe?: API.Subscribe) => void;
|
||||
}
|
||||
|
||||
export default function Purchase({ subscribe, setSubscribe }: Readonly<PurchaseProps>) {
|
||||
interface PurchaseDialogRef {
|
||||
show: (subscribe: API.Subscribe) => void;
|
||||
hide: () => void;
|
||||
}
|
||||
|
||||
const Purchase = forwardRef<PurchaseDialogRef, PurchaseProps>((props, ref) => {
|
||||
const t = useTranslations('subscribe');
|
||||
const { getUserInfo } = useGlobalStore();
|
||||
const router = useRouter();
|
||||
@@ -32,17 +42,39 @@ export default function Purchase({ subscribe, setSubscribe }: Readonly<PurchaseP
|
||||
payment: -1,
|
||||
coupon: '',
|
||||
});
|
||||
const [subscribe, setSubscribe] = useState<API.Subscribe | undefined>(props.subscribe);
|
||||
const [loading, startTransition] = useTransition();
|
||||
const [open, setOpen] = useState(false);
|
||||
const lastSuccessOrderRef = useRef<any>(null);
|
||||
const [tabValue, setTabValue] = useState('year');
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
show: (newSubscribe: API.Subscribe, tabValue: string) => {
|
||||
setSubscribe(newSubscribe);
|
||||
setParams((prev) => ({
|
||||
...prev,
|
||||
subscribe_id: newSubscribe.id,
|
||||
quantity: tabValue === 'year' ? 12 : 1,
|
||||
}));
|
||||
setTabValue(tabValue);
|
||||
setOpen(true);
|
||||
},
|
||||
hide: () => {
|
||||
setOpen(false);
|
||||
setSubscribe(undefined);
|
||||
setParams({ quantity: 1, subscribe_id: 0, payment: -1, coupon: '' });
|
||||
},
|
||||
}));
|
||||
|
||||
const { data: order } = useQuery({
|
||||
enabled: !!subscribe?.id,
|
||||
queryKey: ['preCreateOrder', params],
|
||||
enabled: !!subscribe?.id && params.quantity !== undefined,
|
||||
queryKey: ['preCreateOrder', subscribe?.id, params.quantity],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const { data } = await preCreateOrder({
|
||||
...params,
|
||||
subscribe_id: subscribe?.id as number,
|
||||
quantity: params.quantity as number,
|
||||
} as API.PurchaseOrderRequest);
|
||||
const result = data.data;
|
||||
if (result) {
|
||||
@@ -58,16 +90,6 @@ export default function Purchase({ subscribe, setSubscribe }: Readonly<PurchaseP
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (subscribe) {
|
||||
setParams((prev) => ({
|
||||
...prev,
|
||||
quantity: 1,
|
||||
subscribe_id: subscribe?.id,
|
||||
}));
|
||||
}
|
||||
}, [subscribe]);
|
||||
|
||||
const handleChange = useCallback((field: keyof typeof params, value: string | number) => {
|
||||
setParams((prev) => ({
|
||||
...prev,
|
||||
@@ -81,8 +103,8 @@ export default function Purchase({ subscribe, setSubscribe }: Readonly<PurchaseP
|
||||
const response = await purchase(params as API.PurchaseOrderRequest);
|
||||
const orderNo = response.data.data?.order_no;
|
||||
if (orderNo) {
|
||||
getUserInfo();
|
||||
router.push(`/payment?order_no=${orderNo}`);
|
||||
await getUserInfo();
|
||||
router.push(`/dashboard`);
|
||||
}
|
||||
} catch (error) {
|
||||
/* empty */
|
||||
@@ -91,58 +113,73 @@ export default function Purchase({ subscribe, setSubscribe }: Readonly<PurchaseP
|
||||
}, [params, router, getUserInfo]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={!!subscribe?.id}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setSubscribe(undefined);
|
||||
}}
|
||||
>
|
||||
<DialogContent className='flex h-full max-w-screen-lg flex-col overflow-hidden border-none p-0 md:h-auto'>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent
|
||||
className='rounded-0 flex h-full w-full max-w-full flex-col gap-0 overflow-hidden border-none px-[120px] py-8 sm:h-auto sm:w-[675px] sm:!rounded-[32px] sm:py-12'
|
||||
closeIcon={<Image src={CloseSvg} alt='close' />}
|
||||
closeClassName='right-6 top-6 font-bold text-black opacity-100 focus:ring-0 focus:ring-offset-0'
|
||||
>
|
||||
<DialogHeader className='p-6 pb-0'>
|
||||
<DialogTitle>{t('buySubscription')}</DialogTitle>
|
||||
<DialogTitle className='sr-only'>{t('buySubscription')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className='grid w-full flex-grow gap-3 overflow-auto p-6 pt-0 lg:grid-cols-2'>
|
||||
<Card className='border-transparent shadow-none md:border-inherit md:shadow'>
|
||||
<CardContent className='grid gap-3 p-0 text-sm md:p-6'>
|
||||
<SubscribeDetail
|
||||
subscribe={{
|
||||
...subscribe,
|
||||
quantity: params.quantity,
|
||||
}}
|
||||
/>
|
||||
<Separator />
|
||||
<SubscribeBilling
|
||||
order={{
|
||||
...order,
|
||||
quantity: params.quantity,
|
||||
unit_price: subscribe?.unit_price,
|
||||
}}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className='flex flex-col justify-between text-sm'>
|
||||
<div className='mb-6 grid gap-3'>
|
||||
<DurationSelector
|
||||
quantity={params.quantity!}
|
||||
unitTime={subscribe?.unit_time}
|
||||
discounts={subscribe?.discount}
|
||||
onChange={(value) => {
|
||||
handleChange('quantity', value);
|
||||
}}
|
||||
/>
|
||||
<CouponInput
|
||||
coupon={params.coupon}
|
||||
onChange={(value) => handleChange('coupon', value)}
|
||||
/>
|
||||
<PaymentMethods
|
||||
value={params.payment!}
|
||||
onChange={(value) => {
|
||||
handleChange('payment', value);
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<div className='text-4xl font-bold text-[#0F2C53] sm:mb-8 sm:text-center sm:text-4xl'>
|
||||
购买套餐
|
||||
</div>
|
||||
<div>
|
||||
<Tabs
|
||||
defaultValue='year'
|
||||
className='mt-8 text-center sm:mt-6'
|
||||
value={tabValue}
|
||||
onValueChange={(val) => {
|
||||
if (val === 'year') {
|
||||
handleChange('quantity', 12);
|
||||
} else if (val === 'month') {
|
||||
handleChange('quantity', 1);
|
||||
}
|
||||
setTabValue(val);
|
||||
}}
|
||||
>
|
||||
<TabsList className='mb-8 h-[74px] flex-wrap rounded-full bg-[#EAEAEA] p-2.5'>
|
||||
<TabsTrigger
|
||||
className='rounded-full px-10 py-3.5 text-xl data-[state=active]:bg-[#0F2C53] data-[state=active]:text-white md:px-12'
|
||||
value='year'
|
||||
>
|
||||
年付套餐
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
className='rounded-full px-10 py-3.5 text-xl data-[state=active]:bg-[#0F2C53] data-[state=active]:text-white md:px-12'
|
||||
value='month'
|
||||
>
|
||||
月付套餐
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
<div>
|
||||
<SubscribeDetail
|
||||
subscribe={{
|
||||
...subscribe,
|
||||
quantity: params.quantity,
|
||||
}}
|
||||
/>
|
||||
<Separator className='mb-3 mt-4 bg-[#225BA9]' />
|
||||
<SubscribeBilling
|
||||
order={{
|
||||
...order,
|
||||
quantity: params.quantity,
|
||||
unit_price: subscribe?.unit_price,
|
||||
}}
|
||||
/>
|
||||
<Separator className='mb-3 mt-4 bg-[#225BA9]' />
|
||||
<div className='flex items-center justify-between text-[15px] text-[#225BA9]'>
|
||||
<div>支付方式</div>
|
||||
<div className='font-light text-[#666]'>钱包余额</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-8 flex items-center justify-center'>
|
||||
<Button
|
||||
className='fixed bottom-0 left-0 w-full rounded-none md:relative md:mt-6'
|
||||
className='w-[150px] rounded-full border-[#A8D4ED] bg-[#A8D4ED] text-xl hover:bg-[#225BA9] hover:text-white'
|
||||
disabled={loading}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
@@ -154,4 +191,7 @@ export default function Purchase({ subscribe, setSubscribe }: Readonly<PurchaseP
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Purchase.displayName = 'Purchase';
|
||||
export default Purchase;
|
||||
|
||||
@@ -26,9 +26,10 @@ import { SubscribeDetail } from './detail';
|
||||
interface RenewalProps {
|
||||
id: number;
|
||||
subscribe: API.Subscribe;
|
||||
className: string;
|
||||
}
|
||||
|
||||
export default function Renewal({ id, subscribe }: Readonly<RenewalProps>) {
|
||||
export default function Renewal({ id, subscribe, className }: Readonly<RenewalProps>) {
|
||||
const t = useTranslations('subscribe');
|
||||
const { getUserInfo } = useGlobalStore();
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
@@ -101,7 +102,9 @@ export default function Renewal({ id, subscribe }: Readonly<RenewalProps>) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button size='sm'>{t('renew')}</Button>
|
||||
<Button size='sm' className={className}>
|
||||
续订套餐
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className='flex h-full max-w-screen-lg flex-col overflow-hidden md:h-auto'>
|
||||
<DialogHeader>
|
||||
|
||||
@@ -22,7 +22,7 @@ interface ResetTrafficProps {
|
||||
id: number;
|
||||
replacement?: number;
|
||||
}
|
||||
export default function ResetTraffic({ id, replacement }: Readonly<ResetTrafficProps>) {
|
||||
export default function ResetTraffic({ id, replacement, className }: Readonly<ResetTrafficProps>) {
|
||||
const t = useTranslations('subscribe');
|
||||
const { getUserInfo } = useGlobalStore();
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
@@ -48,7 +48,7 @@ export default function ResetTraffic({ id, replacement }: Readonly<ResetTrafficP
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant='secondary' size='sm'>
|
||||
<Button variant='secondary' size='sm' className={className}>
|
||||
{t('resetTraffic')}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
Reference in New Issue
Block a user