feat: 修改样式

This commit is contained in:
2025-08-12 02:58:18 -07:00
parent cc522d0754
commit 265519a03d
13 changed files with 567 additions and 648 deletions
@@ -0,0 +1,248 @@
import { Display } from '@/components/display';
import Renewal from '@/components/subscribe/renewal';
import { resetUserSubscribeToken } from '@/services/user/user';
import { Button } from '@workspace/airo-ui/components/button';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@workspace/airo-ui/components/select';
import { useTranslations } from 'next-intl';
import { toast } from 'sonner';
import SvgIcon from '@/components/SvgIcon';
import useGlobalStore from '@/config/use-global';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from '@workspace/airo-ui/components/alert-dialog';
import { Popover, PopoverContent, PopoverTrigger } from '@workspace/airo-ui/components/popover';
import { Tabs, TabsList, TabsTrigger } from '@workspace/airo-ui/components/tabs';
import { differenceInDays } from '@workspace/airo-ui/utils';
import { QRCodeCanvas } from 'qrcode.react';
import { useEffect, useState } from 'react';
import { CopyToClipboard } from 'react-copy-to-clipboard';
interface SubscribeCardProps {
userSubscribeData: API.UserSubscribe;
protocol: string[];
refetch: () => Promise<void>;
}
const SubscribeCard = (props: SubscribeCardProps) => {
const t = useTranslations('dashboard');
const { userSubscribeData } = props;
const { getUserSubscribe } = useGlobalStore();
const [protocol, setProtocol] = useState('');
const [userSubscribeProtocol, setUserSubscribeProtocol] = useState<string[]>([]);
const [userSubscribeProtocolCurrent, setUserSubscribeProtocolCurrent] = useState<number>(0);
useEffect(() => {
const list = getUserSubscribe(userSubscribeData.token, protocol);
setUserSubscribeProtocol(list);
if (list.length > 0) {
setUserSubscribeProtocolCurrent(0);
}
}, [props.userSubscribeData.token, protocol]);
return (
<div className='space-y-2 sm:space-y-4'>
<p className='text-xs font-light text-[#666666] sm:text-sm sm:font-normal'>
{t('copySubscriptionLinkOrScanQrCode')}
</p>
{/* 统计信息 */}
<div className='rounded-[20px] bg-[#EAEAEA] p-4'>
<div className='grid grid-cols-3 gap-4 text-center'>
<div>
<p className='text-[10px] text-[rgba(132,132,132,0.7)] sm:text-xs'>
{t('totalTraffic')}
</p>
<p className='text-xs font-medium text-[#0F2C53] sm:text-lg'>
<Display
type='traffic'
value={userSubscribeData.traffic}
unlimited={!userSubscribeData.traffic}
/>
</p>
</div>
<div>
<p className='text-[10px] text-[rgba(132,132,132,0.7)] sm:text-xs'>
{t('nextResetDays')}
</p>
<p className='text-xs font-medium text-[#0F2C53] sm:text-lg'>
{userSubscribeData.reset_time
? differenceInDays(new Date(userSubscribeData.reset_time), new Date())
: t('noReset')}
</p>
</div>
<div>
<p className='text-[10px] text-[rgba(132,132,132,0.7)] sm:text-xs'>
{t('expirationDays')}
</p>
<p className='text-xs font-medium text-[#0F2C53] sm:text-lg'>
{userSubscribeData.expire_time
? differenceInDays(new Date(userSubscribeData.expire_time), new Date()) ||
t('unknown')
: t('noLimit')}
</p>
</div>
</div>
</div>
{/* 订阅链接 */}
<div className='rounded-[26px] bg-[#EAEAEA] p-2 sm:p-4'>
<div className='mb-3 flex flex-wrap justify-between gap-4'>
{props.protocol.length > 1 && (
<Tabs
value={protocol}
onValueChange={setProtocol}
className='w-full max-w-full md:w-auto'
>
<TabsList className='flex h-full flex-wrap gap-2 bg-transparent p-0'>
{['all', ...(props.protocol || [])].map((item) => (
<TabsTrigger
value={item === 'all' ? '' : item}
key={item}
className='rounded-full bg-[#EAEAEA] px-6 py-1 text-[10px] uppercase text-[#66666673] shadow-[inset_0px_0px_4px_0px_rgba(0,0,0,0.25)] data-[state=active]:bg-[#225BA9] data-[state=active]:text-white'
>
{item}
</TabsTrigger>
))}
</TabsList>
</Tabs>
)}
</div>
<div className={'mb-3 flex items-center justify-center gap-1'}>
<div
className={
'flex flex-1 items-center gap-1 rounded-full bg-[#BABABA] pl-1 sm:gap-2 sm:rounded-[16px] sm:pl-2'
}
>
<Select
value={userSubscribeProtocolCurrent}
onValueChange={setUserSubscribeProtocolCurrent}
>
<SelectTrigger className='h-auto w-auto flex-shrink-0 rounded-[16px] border-none bg-[#D9D9D9] px-2.5 py-0.5 text-[13px] text-sm font-medium text-white shadow-none hover:bg-[#848484] focus:ring-0 sm:h-[35px] sm:rounded-[8px] sm:p-2 [&>svg]:hidden'>
<SelectValue>
<div className='flex flex-col items-center justify-center text-[10px] sm:text-sm'>
<div>
{t('subscriptionUrl')}
{userSubscribeProtocolCurrent + 1}
</div>
<div className='-mt-0.5 h-0 w-0 scale-50 border-l-[5px] border-r-[5px] border-t-[5px] border-l-transparent border-r-transparent border-t-white sm:scale-100'></div>
</div>
</SelectValue>
</SelectTrigger>
<SelectContent>
{userSubscribeProtocol.map((url, index) => (
<SelectItem
key={index}
value={index}
className={'focus:text-accent-foreground bg-[#D9D9D9] focus:bg-[#A8D4ED]'}
>
{t('subscriptionUrl')}
{index + 1}
</SelectItem>
))}
</SelectContent>
</Select>
<div className='flex-1 rounded-full bg-white px-3 py-1 text-[10px] leading-tight text-[#225BA9] shadow-[inset_0px_0px_7.6px_0px_rgba(0,0,0,0.25)] sm:rounded-[16px] sm:text-xs'>
<div className={'flex items-center gap-4 py-1 text-[10px] sm:text-[16px]'}>
<div className={'line-clamp-2 flex-1 break-all'}>
{userSubscribeProtocol[userSubscribeProtocolCurrent]}
</div>
<CopyToClipboard
text={userSubscribeProtocol[userSubscribeProtocolCurrent]}
onCopy={(text, result) => {
if (result) {
toast.success(t('copySuccess'));
}
}}
>
<span className={'cursor-pointer p-1'}>
<SvgIcon name={'copy'}></SvgIcon>
</span>
</CopyToClipboard>
</div>
</div>
</div>
<Popover>
<PopoverTrigger asChild>
<div className={'size-10'}>
<SvgIcon name={'qrcode'} width={'100%'} height={'100%'}></SvgIcon>
</div>
</PopoverTrigger>
<PopoverContent className='w-auto rounded-xl p-4' align='end'>
<div className='flex flex-col items-center gap-2'>
<p className='text-muted-foreground text-center text-xs'>
{t('scanCodeToSubscribe')}
</p>
<QRCodeCanvas
value={userSubscribeProtocol[userSubscribeProtocolCurrent]}
size={120}
bgColor='transparent'
fgColor='rgb(34, 91, 169)'
/>
</div>
</PopoverContent>
</Popover>
</div>
<div className='flex justify-between gap-2'>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
size='sm'
className={
'h-fit rounded-full bg-[#E22C2E] px-3 py-1 text-[10px] text-white sm:h-9 sm:text-xs'
}
variant='destructive'
>
{t('resetSubscription')}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t('prompt')}</AlertDialogTitle>
<AlertDialogDescription>{t('confirmResetSubscription')}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await resetUserSubscribeToken({
user_subscribe_id: userSubscribeData.id || 0,
});
await props.refetch();
toast.success(t('resetSuccess'));
}}
>
{t('confirm')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<Renewal
className='h-fit rounded-full bg-[#A8D4ED] px-3 py-1 text-[10px] text-white sm:h-9 sm:text-xs'
id={userSubscribeData.id}
subscribe={userSubscribeData.subscribe}
/>
</div>
</div>
</div>
);
};
export default SubscribeCard;