fix: pc端修改

This commit is contained in:
2025-08-06 00:38:29 -07:00
parent f6f5c2828b
commit 19fe24660e
142 changed files with 162226 additions and 851 deletions
@@ -8,8 +8,7 @@ import Image from 'next/legacy/image';
import Link from 'next/link';
import LanguageSwitch from '../language-switch';
// import ThemeSwitch from '../theme-switch';
import EmailAuthDialog, { EmailAuthDialogRef } from '@/app/auth/EmailAuthDialog/EmailAuthDialog';
import { useRef } from 'react';
import { useLoginDialog } from '@/app/auth/LoginDialogContext';
import { UserNav } from '../user-nav';
export default function Header() {
@@ -18,20 +17,11 @@ export default function Header() {
const { user } = useGlobalStore();
const Logo = (
<Link href='/' className='-mt-2.5 flex items-center gap-2 font-bold'>
<Image
src={'image.png'}
width={102}
height={49}
alt='logo'
fill={true}
objectFit='cover'
unoptimized
/>
<Image src={'image.png'} width={102} height={49} alt='logo' objectFit='cover' unoptimized />
</Link>
);
const dialogRef = useRef<EmailAuthDialogRef>(null);
const { openLoginDialog } = useLoginDialog();
return (
<>
<header className='fixed top-10 z-50 w-full'>
@@ -47,7 +37,7 @@ export default function Header() {
{!user && (
<Link
href='#'
onClick={() => dialogRef.current?.show()}
onClick={() => openLoginDialog()}
className={cn(
buttonVariants({
size: 'lg',
@@ -63,8 +53,6 @@ export default function Header() {
</div>
</div>
</header>
{/* 登录注册弹窗 */}
<EmailAuthDialog ref={dialogRef} />
</>
);
}
-48
View File
@@ -1,48 +0,0 @@
'use client';
import useGlobalStore from '@/config/use-global';
import { buttonVariants } from '@workspace/ui/components/button';
import { useTranslations } from 'next-intl';
import Image from 'next/legacy/image';
import Link from 'next/link';
import LanguageSwitch from '../language-switch';
import { UserNav } from '../user-nav';
export default function Header() {
const t = useTranslations('common');
const { common, user } = useGlobalStore();
const { site } = common;
const Logo = (
<Link href='/' className='flex items-center gap-2 text-lg font-bold'>
{site.site_logo && (
<Image src={site.site_logo} width={48} height={48} alt='logo' unoptimized />
)}
<span className=''>{site.site_name}</span>
</Link>
);
return (
<header className='sticky top-0 z-50 border-b backdrop-blur-md'>
<div className='container flex h-16 items-center justify-between'>
<nav className='flex-col gap-6 text-lg font-medium md:flex md:flex-row md:items-center md:gap-5 md:text-sm lg:gap-6'>
{Logo}
</nav>
<div className='flex flex-1 items-center justify-end gap-2'>
<LanguageSwitch />
{/*<ThemeSwitch />*/}
<UserNav />
{!user && (
<Link
href='/'
className={buttonVariants({
size: 'sm',
})}
>
{t('login')}
</Link>
)}
</div>
</div>
</header>
);
}
+89 -51
View File
@@ -2,19 +2,12 @@
import { Display } from '@/components/display';
import { Empty } from '@/components/empty';
import { ProList } from '@/components/pro-list';
import useGlobalStore from '@/config/use-global';
import { queryUserAffiliate, queryUserAffiliateList } from '@/services/user/user';
import { useQuery } from '@tanstack/react-query';
import { Button } from '@workspace/ui/components/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@workspace/ui/components/card';
import { formatDate, isBrowser } from '@workspace/ui/utils';
import { Card, CardContent } from '@workspace/ui/components/card';
import { formatDate } from '@workspace/ui/utils';
import { Copy } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { useState } from 'react';
@@ -33,52 +26,97 @@ export default function Affiliate() {
},
});
const { data: inviteList = [] } = useQuery({
queryKey: ['queryUserAffiliateList'],
queryFn: async () => {
const response = await queryUserAffiliateList({
page: 1,
size: 3,
});
return response.data.data?.list || [];
},
});
return (
<div className='flex flex-col gap-4'>
<Card>
<CardHeader>
<CardTitle>{t('totalCommission')}</CardTitle>
<CardDescription>{t('commissionInfo')}</CardDescription>
</CardHeader>
<CardContent>
<div className='flex items-baseline gap-2'>
<span className='text-3xl font-bold'>
<Display type='currency' value={data?.total_commission} />
</span>
<span className='text-muted-foreground text-sm'>
({t('commissionRate')}: {common?.invite?.referral_percentage}%)
</span>
<div className='grid grid-cols-2 gap-4'>
<Card
className={
'rounded-[20px] border border-[#D9D9D9] p-6 shadow-[0px_0px_52.6px_1px_rgba(15,44,83,0.05)]'
}
>
<CardContent className={'p-0 text-[#666]'}>
<div className={'mb-6'}>
<div className={'text-xl font-bold'}>{t('totalCommission')}</div>
<div className={'text-[15px] font-light'}></div>
</div>
<div className={'text-[32px] font-bold text-[#091B33]'}>7</div>
<div className={'grid grid-cols-2 gap-5'}>
<div className='rounded-[20px] bg-[#EAEAEA] p-4 shadow-sm transition-all duration-300 hover:shadow-md'>
<p className='mb-3 text-sm font-medium text-[#666] opacity-80'></p>
<p className='text-2xl font-bold text-[#225BA9]'>
<Display type='currency' value={data?.total_commission} />
</p>
</div>
<div className='rounded-[20px] border-2 border-[#D9D9D9] p-4 shadow-sm transition-all duration-300 hover:shadow-md'>
<p className='mb-3 flex justify-between text-sm font-medium text-[#666] opacity-80'>
<span></span>
</p>
<p className='flex justify-between text-2xl font-bold text-[#225BA9]'>
<span> {user?.refer_code}</span>
<CopyToClipboard
text={`${location?.origin}/?invite=${user?.refer_code}`}
onCopy={(text, result) => {
if (result) {
toast.success(t('copySuccess'));
}
}}
>
<Button variant='secondary' size='sm' className='gap-2'>
<Copy className='h-4 w-4' />
</Button>
</CopyToClipboard>
</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader className='flex flex-row items-center justify-between space-y-0'>
<CardTitle className='text-lg font-medium'>{t('inviteCode')}</CardTitle>
</CardHeader>
<CardContent>
<div className='flex items-center justify-between'>
<code className='bg-muted rounded px-2 py-1 text-2xl font-bold'>
{user?.refer_code}
</code>
{isBrowser() && (
<CopyToClipboard
text={`${location?.origin}/auth?invite=${user?.refer_code}`}
onCopy={(text, result) => {
if (result) {
toast.success(t('copySuccess'));
}
}}
>
<Button variant='secondary' size='sm' className='gap-2'>
<Copy className='h-4 w-4' />
{t('copyInviteLink')}
</Button>
</CopyToClipboard>
)}
</div>
</CardContent>
<Card className='rounded-[20px] border border-[#EAEAEA] bg-gradient-to-b from-white to-[#EAEAEA] p-6'>
<div className='mb-4 flex items-center justify-between'>
<h3 className='text-xl font-medium text-[#666666]'></h3>
<span className='text-sm text-[#225BA9]'></span>
</div>
<div className='space-y-4'>
{inviteList?.length ? (
<div className='relative space-y-3'>
<div
className={
'absolute bottom-0 left-0 right-0 h-[60px] bg-white/30 backdrop-blur-[1px]'
}
></div>
{inviteList?.map((invite) => {
return (
<div className='flex flex-wrap justify-between gap-2 rounded-[20px] bg-white px-6 py-2'>
<div>
<div className={'text-[#225BA9]'}></div>
<div className={'font-bold text-[#091B33]'}>{invite.identifier}</div>
</div>
<div>
<div className={'text-[#225BA9]'}></div>
<div className={'font-bold text-[#091B33]'}>
{formatDate(invite.registered_at)}
</div>
</div>
</div>
);
})}
</div>
) : (
<Empty />
)}
</div>
</Card>
<ProList<API.UserAffiliate, Record<string, unknown>>
{/*<ProList<API.UserAffiliate, Record<string, unknown>>
request={async (pagination, filter) => {
const response = await queryUserAffiliateList({ ...pagination, ...filter });
setSum(response.data.data?.sum);
@@ -109,7 +147,7 @@ export default function Affiliate() {
);
}}
empty={<Empty />}
/>
/>*/}
</div>
);
}
+6 -1
View File
@@ -1,5 +1,10 @@
import { queryAnnouncement } from '@/services/user/announcement';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@workspace/ui/components/dialog';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@workspace/airo-ui/components/dialog';
import { Markdown } from '@workspace/ui/custom-components/markdown';
import { getTranslations } from 'next-intl/server';
+2 -2
View File
@@ -19,10 +19,10 @@ export function Display<T extends number | undefined | null>({
}: DisplayProps<T>): string {
const t = useTranslations('common');
const { common } = useGlobalStore();
const { currency } = common;
// const { currency } = common;
if (type === 'currency') {
const formattedValue = `${currency?.currency_symbol ?? ''}${unitConversion('centsToDollars', value as number)?.toFixed(2) ?? '0.00'}`;
const formattedValue = `$ ${unitConversion('centsToDollars', value as number)?.toFixed(2) ?? '0.00'}`;
return formattedValue;
}
+31 -11
View File
@@ -67,15 +67,33 @@ const PriceDisplay = ({ plan }: { plan: ProcessedPlanData }) => (
</div>
);
import { useLoginDialog } from '@/app/auth/LoginDialogContext';
import Purchase from '@/components/subscribe/purchase';
import useGlobalStore from '@/config/use-global';
// 订阅按钮组件
const SubscribeButton = ({ onClick }: { onClick?: () => void }) => (
<button
onClick={onClick}
className='h-10 w-full rounded-full bg-[#0F2C53] text-sm font-medium text-white shadow-md transition-all duration-300 hover:bg-[#0A2C47] sm:h-10 sm:text-sm md:h-[40px] md:text-[14px]'
>
</button>
);
const SubscribeButton = ({ onClick }: { onClick?: () => void }) => {
const { user } = useGlobalStore();
const { openLoginDialog } = useLoginDialog();
function handleClick() {
console.log('click', user);
if (!user) {
// 强制登陆
openLoginDialog(false);
return;
}
onClick();
}
return (
<button
onClick={handleClick}
className='h-10 w-full rounded-full bg-[#0F2C53] text-sm font-medium text-white shadow-md transition-all duration-300 hover:bg-[#225BA9] sm:h-10 sm:text-sm md:h-[40px] md:text-[14px]'
>
</button>
);
};
// 星级评分组件
const StarRating = ({ rating, maxRating = 5 }: { rating: number; maxRating?: number }) => (
@@ -101,7 +119,7 @@ const FeatureList = ({ plan }: { plan: ProcessedPlanData }) => {
return (
<div className='mt-6 space-y-0 sm:mt-6'>
<ul className='list-disc space-y-1'>
<ul className='list-disc space-y-1 pl-5'>
{features.map((feature) => (
<li
key={feature.label}
@@ -143,7 +161,7 @@ const PlanCard = forwardRef<
return (
<div
ref={ref}
className='relative w-full max-w-[345px] cursor-pointer rounded-[20px] border border-[#D9D9D9] bg-white p-4 transition-all duration-300 hover:shadow-lg sm:p-6 md:p-8'
className='relative w-full max-w-[345px] cursor-pointer rounded-[20px] border border-[#D9D9D9] bg-white p-8 transition-all duration-300 hover:shadow-lg sm:p-10'
>
{/* 套餐名称 */}
<h3 className='mb-4 text-left text-sm font-normal sm:mb-6 sm:text-base'>{plan.name}</h3>
@@ -295,12 +313,13 @@ const OfferDialog = forwardRef<OfferDialogRef>((props, ref) => {
show: () => setOpen(true),
hide: () => setOpen(false),
}));
const PurchaseRef = useRef<{ show: (subscribe: API.Subscribe) => void; hide: () => void }>(null);
// 处理订阅点击
const handleSubscribe = (plan: ProcessedPlanData) => {
setSelectedPlan(plan);
console.log('用户选择了套餐:', plan);
// 这里可以添加订阅逻辑,比如跳转到支付页面或显示确认对话框
PurchaseRef.current.show(plan, tabValue);
};
// 处理套餐数据的工具函数
@@ -395,6 +414,7 @@ const OfferDialog = forwardRef<OfferDialogRef>((props, ref) => {
/>
</ScrollArea>
</div>
<Purchase ref={PurchaseRef} />
</DialogContent>
</Dialog>
);
+5 -2
View File
@@ -1,8 +1,11 @@
'use client';
import { ProList as _ProList, ProListProps } from '@workspace/ui/custom-components/pro-list';
import { ProList as _ProList, ProListProps } from '@workspace/airo-ui/custom-components/pro-list';
import { useTranslations } from 'next-intl';
export { type ProListActions, type ProListProps } from '@workspace/ui/custom-components/pro-list';
export {
type ProListActions,
type ProListProps,
} from '@workspace/airo-ui/custom-components/pro-list';
export function ProList<TData, TValue extends Record<string, unknown>>(
props: ProListProps<TData, TValue>,
+16 -9
View File
@@ -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>
+7 -7
View File
@@ -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>
+110 -70
View File
@@ -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;
+5 -2
View File
@@ -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>
+1 -1
View File
@@ -40,7 +40,7 @@ export function UserNav({ from = '' }: { from?: string }) {
<Icon icon='lucide:ellipsis' className='text-muted-foreground !size-6' />
</div>
) : (
<Avatar className='h-16 w-16 cursor-pointer'>
<Avatar className='h-14 w-14 cursor-pointer sm:h-16 sm:w-16'>
<AvatarImage
alt={user?.avatar ?? ''}
src={user?.auth_methods?.[0]?.auth_identifier ?? ''}