309 lines
12 KiB
TypeScript
309 lines
12 KiB
TypeScript
'use client';
|
|
|
|
import { Display } from '@/components/display';
|
|
import Recharge from '@/components/subscribe/recharge';
|
|
import ResetTraffic from '@/components/subscribe/reset-traffic';
|
|
import useGlobalStore from '@/config/use-global';
|
|
import { getStat } from '@/services/common/common';
|
|
import { queryUserSubscribe } from '@/services/user/user';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { Button } from '@workspace/airo-ui/components/button';
|
|
import { Card } from '@workspace/airo-ui/components/card';
|
|
import { useTranslations } from 'next-intl';
|
|
import Link from 'next/link';
|
|
import { useRef } from 'react';
|
|
import SubScribeCard from './components/SubscribeCard/index';
|
|
|
|
import {
|
|
AnnouncementDialog,
|
|
DialogRef,
|
|
} from '@/app/(main)/(content)/(user)/dashboard/components/Announcement/Dialog';
|
|
import {
|
|
Popup,
|
|
PopupRef,
|
|
} from '@/app/(main)/(content)/(user)/dashboard/components/Announcement/Popup';
|
|
import { Empty } from '@/components/empty';
|
|
import { queryAnnouncement } from '@/services/user/announcement';
|
|
import { queryOrderList } from '@/services/user/order';
|
|
import { default as Airo_Empty } from '@workspace/airo-ui/custom-components/empty';
|
|
import { formatDate } from '@workspace/airo-ui/utils';
|
|
|
|
const platforms: (keyof API.ApplicationPlatform)[] = [
|
|
'windows',
|
|
'macos',
|
|
'linux',
|
|
'ios',
|
|
'android',
|
|
'harmony',
|
|
];
|
|
|
|
export default function Content() {
|
|
const t = useTranslations('dashboard');
|
|
|
|
const { data: userSubscribe = [], refetch } = useQuery({
|
|
queryKey: ['queryUserSubscribe'],
|
|
queryFn: async () => {
|
|
const { data } = await queryUserSubscribe();
|
|
return data.data?.list || [];
|
|
},
|
|
});
|
|
|
|
const { data } = useQuery({
|
|
queryKey: ['getStat'],
|
|
queryFn: async () => {
|
|
const { data } = await getStat({
|
|
skipErrorHandler: true,
|
|
});
|
|
return data.data;
|
|
},
|
|
refetchOnWindowFocus: false,
|
|
});
|
|
|
|
const { data: announcementData } = useQuery({
|
|
queryKey: ['queryAnnouncement'],
|
|
queryFn: async () => {
|
|
const { data } = await queryAnnouncement({
|
|
page: 1,
|
|
size: 4,
|
|
pinned: true,
|
|
popup: true,
|
|
});
|
|
return data.data?.announcements || [];
|
|
},
|
|
});
|
|
|
|
const { data: orderData } = useQuery({
|
|
queryKey: ['orderData'],
|
|
queryFn: async () => {
|
|
const { data } = await queryOrderList({ status: 5, page: 1, size: 10 });
|
|
return data?.[0] ?? {};
|
|
},
|
|
});
|
|
|
|
const statusWatermarks = {
|
|
2: t('finished'),
|
|
3: t('expired'),
|
|
4: t('deducted'),
|
|
};
|
|
|
|
const { user } = useGlobalStore();
|
|
const totalAssets = (user?.balance || 0) + (user?.commission || 0) + (user?.gift_amount || 0);
|
|
|
|
const popupRef = useRef<PopupRef>(null);
|
|
const dialogRef = useRef<DialogRef>(null);
|
|
return (
|
|
<>
|
|
<div className={'grid grid-cols-1 gap-[10px] sm:gap-6 lg:grid-cols-2'}>
|
|
{/* 账户概况 Card */}
|
|
<Card className='rounded-[20px] border border-[#D9D9D9] p-6 shadow-[0px_0px_52.6px_1px_rgba(15,44,83,0.05)]'>
|
|
<div className='mb-1 sm:mb-4'>
|
|
<h3 className='text-base font-medium text-[#666666] sm:text-xl'>
|
|
{t('accountOverview')}
|
|
</h3>
|
|
<p className='mt-1 text-xs text-[#666666] sm:text-sm'>
|
|
{user?.auth_methods?.[0]?.auth_identifier}
|
|
</p>
|
|
</div>
|
|
|
|
<div className='mb-3 sm:mb-6'>
|
|
<span className='text-2xl font-medium text-[#091B33] sm:text-3xl'>
|
|
{userSubscribe?.length > 0 && userSubscribe[0]?.status === 1 && orderData
|
|
? orderData?.quantity === 1
|
|
? t('annualMonthPlanUser')
|
|
: t('annualYearPlanUser')
|
|
: t('noYPlan')}
|
|
</span>
|
|
</div>
|
|
|
|
<div className='rounded-[20px] bg-[#EAEAEA] px-4 py-[10px]'>
|
|
<div className='flex items-center justify-between'>
|
|
<span className='text-sm font-light text-[#666666]'>{t('accountBalance')}</span>
|
|
<Recharge
|
|
className={
|
|
'min-w-[50px] border-0 bg-transparent p-0 text-sm font-normal text-[#225BA9] shadow-none outline-0 hover:bg-transparent'
|
|
}
|
|
/>
|
|
</div>
|
|
<div className='text-xl font-medium text-[#225BA9] sm:text-4xl'>
|
|
<Display type='currency' value={totalAssets} />
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
|
|
{/* 套餐状态 Card */}
|
|
<Card className='rounded-[20px] border border-[#D9D9D9] p-6 text-[#666666] shadow-[0px_0px_52.6px_1px_rgba(15,44,83,0.05)]'>
|
|
<div className='mb-4'>
|
|
<h3 className='flex items-center justify-between text-[#666666]'>
|
|
<div className={'flex items-center justify-between'}>
|
|
<span className={'text-base font-medium sm:text-xl'}>{t('planStatus')}</span>
|
|
{userSubscribe?.length > 0 && userSubscribe?.[0]?.status === 1 ? (
|
|
<span className={'ml-2.5 rounded-full bg-[#A8D4ED] px-2 text-[8px] text-white'}>
|
|
{t('inEffect')}
|
|
</span>
|
|
) : null}
|
|
</div>
|
|
<ResetTraffic
|
|
className={
|
|
'border-0 bg-transparent p-0 text-sm font-normal text-[#225BA9] shadow-none outline-0 hover:bg-transparent'
|
|
}
|
|
id={userSubscribe?.[0]?.id || 0}
|
|
replacement={userSubscribe?.[0]?.subscribe.replacement}
|
|
/>
|
|
</h3>
|
|
</div>
|
|
{userSubscribe?.length ? (
|
|
<>
|
|
<div className='mb-2 text-sm text-[#666666] sm:mb-[22px] sm:mt-1'>
|
|
{t('planExpirationTime')}
|
|
{formatDate(userSubscribe?.[0]?.expire_time, false)}
|
|
</div>
|
|
<div className='mb-3 sm:mb-6'>
|
|
<span className='text-2xl font-medium text-[#091B33] sm:text-3xl'>
|
|
{userSubscribe?.[0]?.subscribe.name}
|
|
</span>
|
|
</div>
|
|
<div className='mb-4 flex items-center justify-between'>
|
|
<div className='flex items-center gap-2'>
|
|
<span className='text-xs sm:text-sm'>{t('availableDevices')}</span>
|
|
<div className='flex gap-2'>
|
|
{Array.from({ length: userSubscribe?.[0]?.subscribe.device_limit }).map(
|
|
(_, index) => {
|
|
return (
|
|
<div
|
|
key={index}
|
|
className={`h-4 w-4 rounded-full ${index < (userSubscribe?.[0]?.subscribe.device_limit || 0) > 1 ? 'bg-[#225BA9]' : 'bg-[#D9D9D9]'}`}
|
|
></div>
|
|
);
|
|
},
|
|
)}
|
|
</div>
|
|
</div>
|
|
<span className='text-xs sm:text-sm'>
|
|
{t('online')}
|
|
{data?.online_device} / {userSubscribe?.[0]?.subscribe.device_limit}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<div className='mb-1 flex items-center justify-between'>
|
|
<span className='text-xs sm:text-sm'>
|
|
{t('usedTrafficTotalTraffic')}
|
|
<Display
|
|
type='traffic'
|
|
value={userSubscribe?.[0]?.upload + userSubscribe?.[0]?.download}
|
|
unlimited={!userSubscribe?.[0]?.traffic}
|
|
/>
|
|
/{' '}
|
|
<Display
|
|
type='traffic'
|
|
value={userSubscribe?.[0]?.traffic}
|
|
unlimited={!userSubscribe?.[0]?.traffic}
|
|
/>
|
|
</span>
|
|
<span className='text-xs sm:text-sm'>
|
|
{t('remaining')}
|
|
{100 -
|
|
Math.round(
|
|
(((userSubscribe?.[0]?.upload || 0) + (userSubscribe?.[0]?.download || 0)) /
|
|
(userSubscribe?.[0]?.traffic || 1)) *
|
|
100,
|
|
)}
|
|
%
|
|
</span>
|
|
</div>
|
|
<div className='flex h-5 w-full items-center rounded-[20px] bg-[#EAEAEA] p-0.5'>
|
|
<div
|
|
className={'h-full rounded-[20px] bg-[#225BA9]'}
|
|
style={{
|
|
width: `${Math.round((((userSubscribe?.[0]?.upload || 0) + (userSubscribe?.[0]?.download || 0)) / (userSubscribe?.[0]?.traffic || 1)) * 100)}%`,
|
|
}}
|
|
></div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<Airo_Empty className={'py-0'} description={t('noPlanAvailable')} />
|
|
)}
|
|
</Card>
|
|
{/* 网站公告 Card */}
|
|
<Card className='relative order-4 rounded-[20px] border border-[#EAEAEA] bg-gradient-to-b from-white to-[#EAEAEA] p-6 pb-0 sm:order-none'>
|
|
<div className='mb-3 flex items-center justify-between sm:mb-4'>
|
|
<h3 className='text-base font-medium text-[#666666] sm:text-xl'>
|
|
{t('siteAnnouncements')}
|
|
</h3>
|
|
{announcementData?.length ? (
|
|
<Button
|
|
className='border-0 bg-transparent p-0 text-sm font-normal text-[#225BA9] shadow-none outline-0 hover:bg-transparent'
|
|
onClick={() => dialogRef.current?.open()}
|
|
>
|
|
{t('more')}
|
|
</Button>
|
|
) : null}
|
|
</div>
|
|
|
|
{announcementData?.length ? (
|
|
<>
|
|
<div className='space-y-3 sm:space-y-4'>
|
|
{/* 置顶公告 */}
|
|
{announcementData?.map((item) => {
|
|
return (
|
|
<div className='flex items-center rounded-[20px] bg-[#B5C9E2] px-4 py-2 sm:p-4'>
|
|
<p className='line-clamp-2 flex-1 text-[10px] text-[#225BA9] sm:text-sm'>
|
|
{item.pinned && t('pinnedAnnouncement')}{' '}
|
|
<span className={`${item.pinned ? 'text-white' : 'text-[#4D4D4D]'}`}>
|
|
{item.content}
|
|
</span>
|
|
</p>
|
|
<div className='ml-2 w-[65px] text-right'>
|
|
<span
|
|
className='cursor-pointer text-xs text-[#225BA9] sm:text-sm'
|
|
onClick={() => popupRef.current?.open(item)}
|
|
>
|
|
{t('viewDetails')}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
<div
|
|
className={
|
|
'absolute bottom-0 left-0 right-0 h-[60px] bg-white/30 backdrop-blur-[1px]'
|
|
}
|
|
></div>
|
|
</>
|
|
) : (
|
|
<Empty />
|
|
)}
|
|
<Popup ref={popupRef} />
|
|
<AnnouncementDialog ref={dialogRef} />
|
|
</Card>
|
|
|
|
{/* 我的订阅 Card */}
|
|
<Card className='rounded-[20px] border border-[#D9D9D9] p-6 shadow-[0px_0px_52.6px_1px_rgba(15,44,83,0.05)]'>
|
|
<div className='flex items-center justify-between sm:mb-4'>
|
|
<h3 className='text-base font-medium text-[#666666] sm:text-xl'>
|
|
{t('mySubscription')}
|
|
</h3>
|
|
<Link
|
|
href={'/document'}
|
|
className='border-0 bg-transparent p-0 text-sm font-semibold text-[#225BA9] shadow-none outline-0 hover:bg-transparent sm:font-normal'
|
|
>
|
|
{t('beginnerTutorial')}
|
|
</Link>
|
|
</div>
|
|
|
|
{userSubscribe?.[0] && data?.protocol ? (
|
|
<SubScribeCard
|
|
userSubscribeData={userSubscribe?.[0]}
|
|
protocol={data.protocol}
|
|
refetch={refetch}
|
|
/>
|
|
) : (
|
|
<Empty />
|
|
)}
|
|
</Card>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|