416 lines
16 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 { AiroButton } from '@workspace/airo-ui/components/AiroButton';
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 SvgIcon from '@/components/SvgIcon';
import { queryAnnouncement } from '@/services/user/announcement';
import { queryOrderList } from '@/services/user/order';
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: 5,
// pinned: false,
// popup: false,
});
return data.data?.announcements || [];
},
});
const { data: orderData } = useQuery({
queryKey: ['orderData'],
queryFn: async () => {
const { data } = await queryOrderList({ status: 5, page: 1, size: 1 });
return data?.data?.list?.[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);
function openPopupWindow(item) {
// features 字符串用于控制窗口的特性
const pageWidth = 600; // 弹出窗口的宽度
const pageHeight = 550; // 弹出窗口的高度
const {
availLeft, // 返回浏览器可用空间左边距离屏幕(系统桌面)左边界的距离。
availHeight, // 浏览器在显示屏上的可用高度,即当前屏幕高度
availWidth, // 浏览器在显示屏上的可用宽度,即当前屏幕宽度
} = window.screen;
const pageTop = (availHeight - pageHeight) / 2; // 窗口的垂直位置
let pageLeft = (availWidth - pageWidth) / 2; // 窗口的水平位置;
pageLeft += availLeft; // 加上屏幕偏移值
const features = `width=${pageWidth},height=${pageHeight},left=${pageLeft},top=${pageTop},toolbar=no,location=no,menubar=no`;
console.log(features);
window.open(item.download, item.title, features);
}
return (
<>
<div className={'grid grid-cols-1 gap-[10px] sm:gap-5 2xl:grid-cols-4'}>
{/* 快捷下载 Card */}
<Card className='rounded-[20px] border border-[#D9D9D9] p-6 shadow-[0px_0px_52.6px_1px_rgba(15,44,83,0.05)] 2xl:order-2 2xl:col-span-1'>
<div className='flex items-center justify-between'>
<h3 className='text-base font-medium text-[#666666] sm:text-xl'>
{t('quickDownloads')}
</h3>
</div>
<div className={'text-xs font-normal leading-[1.5] text-[#666]'}>{t('selectOS')}</div>
<div
className={
'mt-2.5 flex items-center justify-around rounded-2xl bg-[#EAEAEA] px-4 pb-3 pt-[18px] text-xs text-[#0F2C53]'
}
>
{[
{
label: 'iOS',
icon: 'Group 77',
href: 'https://apps.apple.com/us/app/shadowrocket/id932747118?l=zh-Hans-CN',
},
{
label: 'Mac',
icon: 'Group 77',
href: 'https://apps.apple.com/us/app/shadowrocket/id932747118?l=zh-Hans-CN',
},
{
label: 'Win',
icon: 'Group 75',
href: 'https://down.airoport.xin/Hiddify-Windows-Setup-x64.msix',
},
{
label: 'Android',
icon: 'Group 75',
href: 'https://down.airoport.xin/Hiddify-Android-universal.apk',
},
].map((v) => {
return (
<a
key={v.label}
href={v.href}
target={'_blank'}
className={'cursor-pointer text-center'}
>
<div className={''}>
<SvgIcon name={v.icon}></SvgIcon>
</div>
{v.label}
</a>
);
})}
</div>
<div
className={
'mt-2.5 flex h-[37px] items-center justify-between rounded-full bg-[#EAEAEA] pl-4 text-[#666]'
}
>
<span className={'text-sm font-medium'}>{t('freeAppleID')}</span>
<AiroButton
className={'m-1'}
variant={'primary'}
onClick={() => {
openPopupWindow({
download: 'https://aunlock.laomaos.com/share/DEXVzMSP',
title: 'Shadowrocket',
});
}}
>
{t('get')}
</AiroButton>
</div>
</Card>
{/* 我的订阅 Card */}
<Card className='rounded-[20px] border border-[#D9D9D9] p-6 shadow-[0px_0px_52.6px_1px_rgba(15,44,83,0.05)] 2xl:order-3 2xl:col-span-2'>
<div className=''>
<div className={'flex items-center justify-between'}>
<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>
</div>
<SubScribeCard
userSubscribeData={userSubscribe?.[0] || {}}
protocol={data?.protocol || []}
refetch={refetch}
/>
</Card>
{/* 账户概况 Card */}
<Card className='rounded-[20px] border border-[#D9D9D9] p-6 shadow-[0px_0px_52.6px_1px_rgba(15,44,83,0.05)] 2xl:order-1 2xl:col-span-1'>
<div className='mb-1'>
<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-3.5'>
<span className='text-2xl font-medium text-[#091B33]'>
{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-5 py-2.5'>
<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-semibold text-[#225BA9] shadow-none outline-0 hover:bg-transparent'
}
/>
</div>
<div className='text-xl font-medium text-[#225BA9] sm:text-2xl'>
<Display type='currency' value={totalAssets} />
</div>
</div>
</Card>
{/* 套餐状态 Card */}
<Card className='2xl:order-0 rounded-[20px] border border-[#D9D9D9] p-6 text-[#666666] shadow-[0px_0px_52.6px_1px_rgba(15,44,83,0.05)] 2xl:col-span-2'>
<div className=''>
<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>
) : (
<span className={'ml-2.5 rounded-full bg-[#666666] px-2 text-[8px] text-white'}>
</span>
)}
</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>
<div>
<div className='mt-1 text-xs font-light text-[#666666] sm:text-sm'>
{t('planExpirationTime')}
{formatDate(userSubscribe?.[0]?.expire_time, false) || '暂无'}
</div>
<div className='mb-3 mt-1 sm:mb-5'>
<span className='text-2xl font-medium text-[#091B33]'>
{userSubscribe?.[0]?.subscribe.name ? (
userSubscribe?.[0]?.subscribe.name
) : (
<span className={'text-[#848484]'}></span>
)}
</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 || 6 }).map(
(_, index) => {
return (
<div
key={index}
className={`h-2.5 w-2.5 rounded-full sm:h-4 sm:w-4 ${index < (userSubscribe?.[0]?.subscribe.device_limit || 0) > 1 ? 'bg-[#225BA9]' : 'bg-[#D9D9D9]'}`}
></div>
);
},
)}
</div>
</div>
<span className='text-xs font-light sm:text-sm'>
{t('online')}
{data?.online_device || 0}/{userSubscribe?.[0]?.subscribe.device_limit || 0}
</span>
</div>
<div>
<div className='mb-1 flex items-center justify-between font-light'>
<span className='text-xs sm:text-sm'>
{t('usedTrafficTotalTraffic')}
{userSubscribe?.[0]?.subscribe.device_limit ? (
<>
<Display
type='traffic'
value={userSubscribe?.[0]?.upload + userSubscribe?.[0]?.download}
unlimited={!userSubscribe?.[0]?.traffic}
/>
/
<Display
type='traffic'
value={userSubscribe?.[0]?.traffic || 0}
unlimited={!userSubscribe?.[0]?.traffic}
/>
</>
) : (
'0GB/0GB'
)}
</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>
</div>
</Card>
{/* 网站公告 Card */}
<Card className='relative rounded-[20px] border border-[#EAEAEA] bg-gradient-to-b from-white to-[#EAEAEA] p-6 pb-0 2xl:order-4 2xl:col-span-2'>
<div className='mb-3 flex items-center justify-between sm:mb-1'>
<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
key={item.id}
className={`${item.pinned ? 'bg-[#B5C9E2]' : 'bg-white'} flex items-center rounded-[20px] px-4 py-1.5`}
>
<p
className={`${item.pinned ? 'text-white' : 'text-[#4D4D4D]'} line-clamp-2 flex-1 text-[10px] sm:text-sm`}
>
{item.pinned && (
<span className={'text-[#225BA9]'}>{t('pinnedAnnouncement')}</span>
)}
<span>{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>
</div>
</>
);
}