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
@@ -1,14 +1,40 @@
'use client';
import { Display } from '@/components/display';
import Recharge from '@/components/subscribe/recharge';
import Renewal from '@/components/subscribe/renewal';
import ResetTraffic from '@/components/subscribe/reset-traffic';
import useGlobalStore from '@/config/use-global';
import { getStat } from '@/services/common/common';
import { queryApplicationConfig } from '@/services/user/subscribe';
import { queryUserSubscribe } from '@/services/user/user';
import { getPlatform } from '@/utils/common';
import { queryUserSubscribe, resetUserSubscribeToken } from '@/services/user/user';
import { useQuery } from '@tanstack/react-query';
import { Button } from '@workspace/ui/components/button';
import { Card } from '@workspace/ui/components/card';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@workspace/ui/components/select';
import { useTranslations } from 'next-intl';
import { useState } from 'react';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import { toast } from 'sonner';
import { Empty } from '@/components/empty';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from '@workspace/ui/components/alert-dialog';
import { Tabs, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
import { differenceInDays, formatDate } from '@workspace/ui/utils';
const platforms: (keyof API.ApplicationPlatform)[] = [
'windows',
@@ -24,7 +50,8 @@ export default function Content() {
const { getUserSubscribe, getAppSubLink } = useGlobalStore();
const [protocol, setProtocol] = useState('');
const [userSubscribeProtocol, setUserSubscribeProtocol] = useState<string[]>([]);
const [userSubscribeProtocolCurrent, setUserSubscribeProtocolCurrent] = useState<string>('');
const {
data: userSubscribe = [],
refetch,
@@ -36,16 +63,8 @@ export default function Content() {
return data.data?.list || [];
},
});
const { data: applications } = useQuery({
queryKey: ['queryApplicationConfig'],
queryFn: async () => {
const { data } = await queryApplicationConfig();
return data.data?.applications || [];
},
});
const [platform, setPlatform] = useState<keyof API.ApplicationPlatform>(getPlatform());
const { data } = useQuery({
/*const { data } = useQuery({
queryKey: ['getStat'],
queryFn: async () => {
const { data } = await getStat({
@@ -54,7 +73,21 @@ export default function Content() {
return data.data;
},
refetchOnWindowFocus: false,
});
});*/
const data = {
user: 1,
node: 2,
country: 0,
protocol: ['vmess', 'vless'],
};
useEffect(() => {
if (data && userSubscribe?.length > 0 && !userSubscribeProtocol.length) {
const list = getUserSubscribe(userSubscribe[0]?.token, data.protocol);
setUserSubscribeProtocol(list);
setUserSubscribeProtocolCurrent(list[0]);
}
}, [data, userSubscribe, userSubscribeProtocol.length]);
const statusWatermarks = {
2: t('finished'),
@@ -63,6 +96,7 @@ export default function Content() {
};
const { user } = useGlobalStore();
const totalAssets = (user?.balance || 0) + (user?.commission || 0) + (user?.gift_amount || 0);
return (
<>
<div className={'grid grid-cols-1 gap-6 lg:grid-cols-2'}>
@@ -79,12 +113,18 @@ export default function Content() {
<span className='text-3xl font-medium text-[#091B33]'></span>
</div>
<div className='rounded-[20px] bg-[#EAEAEA] p-4'>
<div className='mb-2 flex items-center justify-between'>
<span className='text-sm text-[#666666]'></span>
<span className='text-sm text-[#225BA9]'></span>
<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]'></span>
<Recharge
className={
'border-0 bg-transparent p-0 text-sm font-normal text-[#225BA9] shadow-none outline-0 hover:bg-transparent'
}
/>
</div>
<div className='text-4xl font-medium text-[#225BA9]'>
<Display type='currency' value={totalAssets} />
</div>
<div className='text-3xl font-medium text-[#225BA9]'>$1680.00</div>
</div>
</Card>
@@ -92,55 +132,92 @@ export default function Content() {
<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>
<span className={'text-xl'}></span>
<div className={'flex items-center justify-between'}>
<span className={'text-xl font-medium'}></span>
<span className={'ml-2.5 rounded-full bg-[#A8D4ED] px-2 text-[8px] text-white'}>
</span>
</div>
<span className={'text-sm text-[#225BA9]'}></span>
<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}
replacement={userSubscribe?.[0]?.subscribe.replacement}
/>
</h3>
<div>
<p className='mb-2 text-sm text-[#666666]'>2026-07-29</p>
<div className='mb-[22px] mt-1 text-sm text-[#666666]'>
{formatDate(userSubscribe?.[0]?.expire_time, false)}
</div>
<div className='mt-2'>
<span className='text-3xl font-medium text-[#091B33]'>Pro Plan</span>
<div className='mb-6'>
<span className='text-3xl font-medium text-[#091B33]'>
{userSubscribe?.[0]?.subscribe.name}
</span>
</div>
</div>
<div className='space-y-4'>
<div className='space-y-3'>
<div className='mb-2 flex items-center justify-between'>
<div className='flex items-center gap-2'>
<span className='text-sm'></span>
<div className='flex gap-2'>
<div className='h-4 w-4 rounded-full bg-[#225BA9]'></div>
<div className='h-4 w-4 rounded-full bg-[#D9D9D9]'></div>
<div className='h-4 w-4 rounded-full bg-[#D9D9D9]'></div>
<div className='h-4 w-4 rounded-full bg-[#D9D9D9]'></div>
<div className='h-4 w-4 rounded-full bg-[#D9D9D9]'></div>
<div className='h-4 w-4 rounded-full bg-[#D9D9D9]'></div>
</div>
</div>
<span className='text-sm'>线1/6</span>
</div>
<div>
<div className='mb-1 flex items-center justify-between'>
<span className='text-sm'>使/5.52GB/100GB</span>
<span className='text-sm'>94%</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: '6%' }}></div>
</div>
<div className='mb-4 flex items-center justify-between'>
<div className='flex items-center gap-2'>
<span className='text-sm'></span>
<div className='flex gap-2'>
<div className='h-4 w-4 rounded-full bg-[#225BA9]'></div>
<div className='h-4 w-4 rounded-full bg-[#D9D9D9]'></div>
<div className='h-4 w-4 rounded-full bg-[#D9D9D9]'></div>
<div className='h-4 w-4 rounded-full bg-[#D9D9D9]'></div>
<div className='h-4 w-4 rounded-full bg-[#D9D9D9]'></div>
<div className='h-4 w-4 rounded-full bg-[#D9D9D9]'></div>
</div>
</div>
<span className='text-sm'>
线undefined/{userSubscribe?.[0]?.subscribe.device_limit}
</span>
</div>
<div>
<div className='mb-1 flex items-center justify-between'>
<span className='text-sm'>
使/
<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-sm'>
{100 -
(
(userSubscribe?.[0]?.upload + userSubscribe?.[0]?.download) /
userSubscribe?.[0]?.traffic
).toFixed(0)}
%
</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: `${((userSubscribe?.[0]?.upload + userSubscribe?.[0]?.download) / userSubscribe?.[0]?.traffic).toFixed(0)}%`,
}}
></div>
</div>
</div>
</Card>
{/* 网站公告 Card */}
<Card className='rounded-[20px] border border-[#EAEAEA] bg-gradient-to-b from-white to-[#EAEAEA] p-6'>
<Card className='relative rounded-[20px] border border-[#EAEAEA] bg-gradient-to-b from-white to-[#EAEAEA] p-6 pb-0'>
<div
className={'absolute bottom-0 left-0 right-0 h-[60px] bg-white/30 backdrop-blur-[1px]'}
></div>
<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>
<Button className='border-0 bg-transparent p-0 text-sm font-normal text-[#225BA9] shadow-none outline-0 hover:bg-transparent'>
</Button>
</div>
<div className='space-y-4'>
@@ -157,8 +234,8 @@ export default function Content() {
{/* 系统通知列表 */}
<div className='space-y-3'>
<div className='rounded-[20px] border bg-white p-4'>
<p className='mb-2 line-clamp-2 text-sm text-[#225BA9]'>
<div className='flex items-center gap-2 rounded-[20px] border bg-white p-4'>
<p className='mb-2 line-clamp-2 flex-1 text-sm text-[#225BA9]'>
<br />
IDR20250729115302USDT ...
@@ -169,18 +246,7 @@ export default function Content() {
</div>
<div className='rounded-[20px] border bg-white p-4'>
<p className='mb-2 line-clamp-2 text-sm text-[#225BA9]'>
<br />
IDR20250729115302USDT ...
</p>
<div className='text-right'>
<span className='text-sm text-[#225BA9]'></span>
</div>
</div>
<div className='rounded-[20px] border bg-white p-4'>
<p className='mb-2 line-clamp-2 text-sm text-[#225BA9]'>
<p className='mb-2 line-clamp-2 flex-1 text-sm text-[#225BA9]'>
<br />
IDR20250729115302USDT ...
@@ -195,79 +261,165 @@ export default function Content() {
{/* 我的订阅 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-4'>
<div className='mb-4 flex items-center justify-between'>
<h3 className='text-xl font-medium text-[#666666]'></h3>
<Link
href={'/document'}
className='border-0 bg-transparent p-0 text-sm font-normal text-[#225BA9] shadow-none outline-0 hover:bg-transparent'
>
</Link>
</div>
<div className='space-y-4'>
<p className='text-sm text-[#666666]'></p>
{userSubscribe?.[0] && data.protocol ? (
<div className='space-y-4'>
<p className='text-sm text-[#666666]'></p>
{/* 统计信息 */}
<div className='rounded-[20px] bg-[#EAEAEA] p-4'>
<div className='grid grid-cols-3 gap-4 text-center'>
<div>
<p className='text-xs text-[rgba(132,132,132,0.7)]'></p>
<p className='text-lg font-medium text-[#0F2C53]'>100.00 GB</p>
{/* 统计信息 */}
<div className='rounded-[20px] bg-[#EAEAEA] p-4'>
<div className='grid grid-cols-3 gap-4 text-center'>
<div>
<p className='text-xs text-[rgba(132,132,132,0.7)]'></p>
<p className='text-lg font-medium text-[#0F2C53]'>
<Display
type='traffic'
value={userSubscribe?.[0]?.traffic}
unlimited={!userSubscribe?.[0]?.traffic}
/>
</p>
</div>
<div>
<p className='text-xs text-[rgba(132,132,132,0.7)]'>{t('nextResetDays')}</p>
<p className='text-lg font-medium text-[#0F2C53]'>
{userSubscribe?.[0]
? differenceInDays(new Date(userSubscribe?.[0].reset_time), new Date())
: t('noReset')}
</p>
</div>
<div>
<p className='text-xs text-[rgba(132,132,132,0.7)]'>{t('expirationDays')}</p>
<p className='text-lg font-medium text-[#0F2C53]'>
{userSubscribe?.[0]?.expire_time
? differenceInDays(new Date(userSubscribe?.[0].expire_time), new Date()) ||
t('unknown')
: t('noLimit')}
</p>
</div>
</div>
<div>
<p className='text-xs text-[rgba(132,132,132,0.7)]'>/</p>
<p className='text-lg font-medium text-[#0F2C53]'>28</p>
</div>
{/* 订阅链接 */}
<div className='rounded-[26px] bg-[#EAEAEA] p-4'>
<div className='mb-3 flex flex-wrap justify-between gap-4'>
{data?.protocol && data?.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 *:flex-auto'>
{['all', ...(data?.protocol || [])].map((item) => (
<TabsTrigger
value={item === 'all' ? '' : item}
key={item}
className='un 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>
<p className='text-xs text-[rgba(132,132,132,0.7)]'>/</p>
<p className='text-lg font-medium text-[#0F2C53]'>363</p>
<div className={'mb-3 flex items-center justify-center gap-1'}>
<div className={'flex items-center gap-2 rounded-[16px] bg-[#BABABA] pl-2'}>
<Select
value={userSubscribeProtocolCurrent}
onValueChange={setUserSubscribeProtocolCurrent}
>
<SelectTrigger className='h-[35px] w-20 flex-shrink-0 rounded-[8px] border-none bg-[#D9D9D9] bg-transparent p-2 text-[13px] text-sm font-medium text-white shadow-none hover:bg-[#848484] focus:ring-0 [&>svg]:hidden'>
<SelectValue>
<div className='flex flex-col items-center justify-center'>
<div>{t('subscriptionUrl')}1</div>
<div className='h-0 w-0 border-l-[5px] border-r-[5px] border-t-[5px] border-l-transparent border-r-transparent border-t-white'></div>
</div>
</SelectValue>
</SelectTrigger>
<SelectContent>
{userSubscribeProtocol.map((url, index) => (
<SelectItem
key={url}
value={url}
className={
'focus:text-accent-foreground bg-[#D9D9D9] focus:bg-[#A8D4ED]'
}
>
{t('subscriptionUrl')}
{index + 1}
</SelectItem>
))}
</SelectContent>
</Select>
<div className='flex-1 rounded-[16px] bg-white p-3 text-xs text-[#225BA9] shadow-[inset_0px_0px_7.6px_0px_rgba(0,0,0,0.25)]'>
<div className={'line-clamp-2 break-all'}>{userSubscribeProtocolCurrent}</div>
</div>
</div>
<div className={'ml-3 h-[40px] w-[40px] flex-shrink-0 rounded-lg bg-black'}></div>
</div>
<div className='flex justify-between gap-2'>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
size='sm'
className={'rounded-full bg-[#E22C2E] px-3 py-1 text-xs text-white'}
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: userSubscribe?.[0]?.id,
});
await refetch();
toast.success(t('resetSuccess'));
}}
>
{t('confirm')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<Renewal
className='rounded-full bg-[#A8D4ED] px-3 py-1 text-xs text-white'
id={userSubscribe?.[0]?.id}
subscribe={userSubscribe?.[0]?.subscribe}
/>
</div>
</div>
</div>
{/* 订阅链接 */}
<div className='rounded-[26px] bg-[#EAEAEA] p-4'>
<div className='mb-2'>
{/* 协议选择 */}
<div className='flex flex-wrap gap-2'>
<button className='rounded-full bg-[#225BA9] px-4 py-1 text-xs text-white'>
ALL
</button>
<button className='rounded-full bg-[#EAEAEA] px-4 py-1 text-xs text-[#666666] shadow-[inset_0px_0px_4px_0px_rgba(0,0,0,0.25)]'>
VMESS
</button>
<button className='rounded-full bg-[#EAEAEA] px-4 py-1 text-xs text-[#666666] shadow-[inset_0px_0px_4px_0px_rgba(0,0,0,0.25)]'>
VLESS
</button>
<button className='rounded-full bg-[#EAEAEA] px-4 py-1 text-xs text-[#666666] shadow-[inset_0px_0px_4px_0px_rgba(0,0,0,0.25)]'>
TROJAN
</button>
<button className='rounded-full bg-[#EAEAEA] px-4 py-1 text-xs text-[#666666] shadow-[inset_0px_0px_4px_0px_rgba(0,0,0,0.25)]'>
SHADOW SOCKS
</button>
</div>
</div>
<div className={'mb-2 flex items-center justify-center gap-1'}>
<span className='w-20 flex-shrink-0 text-sm font-medium text-[#666666]'>
1
</span>
<div className='line-clamp-2 flex-1 rounded-[16px] bg-white p-3 text-xs text-[#225BA9] shadow-[inset_0px_0px_7.6px_0px_rgba(0,0,0,0.25)]'>
https://api.kxsw.us/api/subscribe?token=512ce3958ef939c668aaf8442d51dd5f
</div>
<div className={'size-6 flex-shrink-0 rounded-lg bg-black'}></div>
</div>
<div className='flex justify-between gap-2'>
<button className='rounded-full bg-[#E22C2E] px-3 py-1 text-xs text-white'>
</button>
<button className='rounded-full bg-[#A8D4ED] px-3 py-1 text-xs text-white'>
</button>
</div>
</div>
</div>
) : (
<Empty />
)}
</Card>
</div>
{/*{userSubscribe.length ? (
<>
<div className='flex items-center justify-between'>
<h2 className='flex items-center gap-1.5 font-semibold'>
<Icon icon='uil:servers' className='size-5' />
{t('mySubscriptions')}
</h2>
<div className='flex gap-2'>
@@ -286,27 +438,7 @@ export default function Content() {
</Button>
</div>
</div>
<div className='flex flex-wrap justify-between gap-4'>
{data?.protocol && data?.protocol.length > 1 && (
<Tabs
value={protocol}
onValueChange={setProtocol}
className='w-full max-w-full md:w-auto'
>
<TabsList className='flex *:flex-auto'>
{['all', ...(data?.protocol || [])].map((item) => (
<TabsTrigger
value={item === 'all' ? '' : item}
key={item}
className='px-1 uppercase lg:px-3'
>
{item}
</TabsTrigger>
))}
</TabsList>
</Tabs>
)}
</div>
{userSubscribe.map((item) => {
return (
<Card
@@ -426,7 +558,6 @@ export default function Content() {
<li>
<span className='text-muted-foreground'>{t('expirationDays')}</span>
<span className='text-2xl font-semibold'>
{}
{item.expire_time
? differenceInDays(new Date(item.expire_time), new Date()) || t('unknown')
: t('noLimit')}
@@ -443,104 +574,11 @@ export default function Content() {
{t('subscriptionUrl')} {index + 1}
</CardTitle>
<CopyToClipboard
text={url}
onCopy={(text, result) => {
if (result) {
toast.success(t('copySuccess'));
}
}}
>
<span
className='text-primary hover:bg-accent mr-4 flex cursor-pointer rounded p-2 text-sm'
onClick={(e) => e.stopPropagation()}
>
<Icon icon='uil:copy' className='mr-2 size-5' />
{t('copy')}
</span>
</CopyToClipboard>
111
</div>
</AccordionTrigger>
<AccordionContent>
<div className='grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6'>
{applications
?.filter((application) => {
const platformApps = application.platform?.[platform];
return platformApps && platformApps.length > 0;
})
.map((application) => {
const platformApps = application.platform?.[platform];
const app =
platformApps?.find((item) => item.is_default) ||
platformApps?.[0];
if (!app) return null;
const handleCopy = (text: string, result: boolean) => {
if (result) {
const href = getAppSubLink(application.subscribe_type, url);
const showSuccessMessage = () => {
toast.success(
<>
<p>{t('copySuccess')}</p>
<br />
<p>{t('manualImportMessage')}</p>
</>,
);
};
if (isBrowser() && href) {
window.location.href = href;
const checkRedirect = setTimeout(() => {
if (window.location.href !== href) {
showSuccessMessage();
}
clearTimeout(checkRedirect);
}, 1000);
return;
}
showSuccessMessage();
}
};
return (
<div
key={application.name}
className='text-muted-foreground flex size-full flex-col items-center justify-between gap-2 text-xs'
>
<span>{application.name}</span>
{application.icon && (
<Image
src={application.icon}
alt={application.name}
width={64}
height={64}
className='p-1'
/>
)}
<div className='flex'>
<Button
size='sm'
variant='secondary'
className='rounded-r-none px-1.5'
asChild
>
<Link href={app.url}>{t('download')}</Link>
</Button>
<CopyToClipboard
text={getAppSubLink(application.subscribe_type, url) || url}
onCopy={handleCopy}
>
<Button size='sm' className='rounded-l-none p-2'>
{t('import')}
</Button>
</CopyToClipboard>
</div>
</div>
);
})}
<div className='text-muted-foreground hidden size-full flex-col items-center justify-between gap-2 text-sm lg:flex'>
<span>{t('qrCode')}</span>
<QRCodeCanvas
@@ -567,7 +605,7 @@ export default function Content() {
<Icon icon='uil:shop' className='size-5' />
{t('purchaseSubscription')}
</h2>
<Subscribe />
</>
)}*/}
</>
@@ -1,11 +1,8 @@
import Announcement from '@/components/announcement';
import { cookies } from 'next/headers';
import Content from './content';
export default async function Page() {
return (
<div className='flex min-h-[calc(100vh-64px-58px-32px-114px)] w-full flex-col gap-4 overflow-hidden'>
<Announcement type='pinned' Authorization={(await cookies()).get('Authorization')?.value} />
<Content />
</div>
);
@@ -0,0 +1,103 @@
'use client';
import { useQuery } from '@tanstack/react-query';
import { Dialog, DialogContent } from '@workspace/airo-ui/components/dialog';
import { Separator } from '@workspace/ui/components/separator';
import { useTranslations } from 'next-intl';
import { useRouter } from 'next/navigation';
import { forwardRef, useImperativeHandle, useState } from 'react';
import { SubscribeBilling } from '@/components/subscribe/billing';
import { SubscribeDetail } from '@/components/subscribe/detail';
import useGlobalStore from '@/config/use-global';
import { queryOrderDetail } from '@/services/user/order';
interface OrderDetailDialogProps {
orderNo?: string;
}
interface OrderDetailDialogRef {
show: (orderNo: string) => void;
hide: () => void;
}
const OrderDetailDialog = forwardRef<OrderDetailDialogRef, OrderDetailDialogProps>((props, ref) => {
const t = useTranslations('subscribe');
const { getUserInfo } = useGlobalStore();
const router = useRouter();
const [open, setOpen] = useState(false);
const [orderNo, setOrderNo] = useState<string | undefined>(props.orderNo);
const [enabled, setEnabled] = useState(true);
useImperativeHandle(ref, () => ({
show: (newOrderNo: string) => {
setOrderNo(newOrderNo);
setEnabled(true);
setOpen(true);
},
hide: () => {
setOpen(false);
setOrderNo(undefined);
setEnabled(false);
},
}));
const { data, isLoading } = useQuery({
enabled: enabled && !!orderNo,
queryKey: ['queryOrderDetail', orderNo],
queryFn: async () => {
const { data } = await queryOrderDetail({ order_no: orderNo! });
if (data?.data?.status !== 1) {
getUserInfo();
setEnabled(false);
}
return data?.data;
},
refetchInterval: 3000,
});
const handleClose = () => {
setOpen(false);
setOrderNo(undefined);
setEnabled(false);
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className='sm:w-[675px]'>
<div className='text-4xl font-bold text-[#0F2C53] sm:mb-8 sm:text-center sm:text-4xl'>
</div>
<div className='text-[16px] font-bold text-[#666]'>
<div></div>
<div className='text-[12px] font-light text-[#4D4D4D]'>{orderNo}</div>
</div>
<Separator className='mb-3 mt-2 h-[2px] bg-[#225BA9]' />
<div className='text-[15px] text-[#225BA9]'>
<div></div>
<div className='font-light text-[#666]'></div>
</div>
<Separator className='mb-3 mt-4 bg-[#225BA9]' />
<div>
<SubscribeDetail
subscribe={{
...data?.subscribe,
quantity: data?.quantity,
}}
/>
<Separator className='mb-3 mt-4 bg-[#225BA9]' />
<SubscribeBilling
order={{
...data,
unit_price: data?.subscribe?.unit_price,
}}
/>
<Separator className='mb-3 mt-4 bg-[#225BA9]' />
</div>
</DialogContent>
</Dialog>
);
});
OrderDetailDialog.displayName = 'OrderDetailDialog';
export default OrderDetailDialog;
@@ -9,80 +9,88 @@ import { Card, CardContent } from '@workspace/ui/components/card';
import { formatDate } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
import { useRef } from 'react';
import OrderDetailDialog from './components/OrderDetailDialog';
export default function Page() {
const t = useTranslations('order');
const ref = useRef<ProListActions>(null);
const OrderDetailDialogRef = useRef<typeof OrderDetailDialog>(null);
return (
<ProList<API.OrderDetail, Record<string, unknown>>
action={ref}
request={async (pagination, filter) => {
const response = await queryOrderList({ ...pagination, ...filter });
return {
list: response.data.data?.list || [],
total: response.data.data?.total || 0,
};
}}
renderItem={(item) => {
return (
<Card className='rounded-[32px] border border-[#D9D9D9] px-3 pb-5 pt-3 shadow-[0px_0px_4.5px_0px_rgba(0,0,0,0.25)]'>
<div className={'flex justify-between'}>
<div>
<div className={'text-[#666]'}>{t('orderNo')}</div>
<p className='text-xs text-[#4D4D4D]'>{item.order_no}</p>
</div>
<div>
{item.status === 1 ? (
<>
<Button className='min-w-[150px] rounded-full border-[#0F2C53] bg-[#0F2C53] px-[35px] py-[9px] text-center text-xl font-bold hover:bg-[#225BA9] hover:text-white'>
{t('payment')}
</Button>
<div>
<ProList<API.OrderDetail, Record<string, unknown>>
action={ref}
request={async (pagination, filter) => {
const response = await queryOrderList({ ...pagination, ...filter });
return {
list: response.data.data?.list || [],
total: response.data.data?.total || 0,
};
}}
renderItem={(item) => {
return (
<Card className='rounded-[32px] border border-[#D9D9D9] pb-5 pl-16 pr-3 pt-3 shadow-[0px_0px_4.5px_0px_rgba(0,0,0,0.25)]'>
<div className={'flex justify-between'}>
<div>
<div className={'text-[#666]'}>{t('orderNo')}</div>
<p className='text-xs text-[#4D4D4D]'>{item.order_no}</p>
</div>
<div>
{item.status === 1 ? (
<>
<Button
className='min-w-[150px] rounded-full border-[#F8BFD2] bg-[#F8BFD2] px-[35px] py-[9px] text-center text-xl font-bold hover:border-[#F8BFD2] hover:bg-[#FF4248] hover:text-white'
onClick={async () => {
await closeOrder({ orderNo: item.order_no });
ref.current?.refresh();
}}
>
</Button>
<Button className='ml-3 min-w-[150px] rounded-full border-[#A8D4ED] bg-[#A8D4ED] px-[35px] py-[9px] text-center text-xl font-bold hover:border-[#225BA9] hover:bg-[#225BA9] hover:text-white'>
{t('payment')}
</Button>
</>
) : (
<Button
onClick={() => OrderDetailDialogRef?.current?.show(item.order_no)}
className='min-w-[150px] rounded-full border-[#0F2C53] bg-[#0F2C53] px-[35px] py-[9px] text-center text-xl font-bold hover:bg-[#225BA9] hover:text-white'
onClick={async () => {
await closeOrder({ orderNo: item.order_no });
ref.current?.refresh();
}}
>
{t('cancel')}
{t('detail')}
</Button>
</>
) : (
<Button className='min-w-[150px] rounded-full border-[#0F2C53] bg-[#0F2C53] px-[35px] py-[9px] text-center text-xl font-bold hover:bg-[#225BA9] hover:text-white'>
{t('detail')}
</Button>
)}
)}
</div>
</div>
</div>
<CardContent className='p-3 text-sm'>
<ul className='grid grid-cols-2 gap-3 *:flex *:flex-col lg:grid-cols-4'>
<li>
<span className='text-[#225BA9]'>{t('name')}</span>
<span className={'font-bold text-[#091B33]'}>
{item.subscribe.name || t(`type.${item.type}`)}
</span>
</li>
<li className='font-semibold'>
<span className='text-[#225BA9]'>{t('paymentAmount')}</span>
<span>
<Display type='currency' value={item.amount} />
</span>
</li>
<li className='font-semibold'>
<span className='text-[#225BA9]'>{t('status.0')}</span>
<span>{t(`status.${item.status}`)}</span>
</li>
<li className='font-semibold'>
<span className='text-[#225BA9]'>{t('createdAt')}</span>
<time>{formatDate(item.created_at)}</time>
</li>
</ul>
</CardContent>
</Card>
);
}}
empty={<Empty />}
/>
<CardContent className='px-0 py-3 text-sm'>
<ul className='grid grid-cols-2 gap-3 *:flex *:flex-col lg:grid-cols-4'>
<li>
<span className='text-[#225BA9]'>{t('name')}</span>
<span className={'font-bold text-[#091B33]'}>
{item.subscribe.name || t(`type.${item.type}`)}
</span>
</li>
<li className='font-semibold'>
<span className='text-[#225BA9]'>{t('paymentAmount')}</span>
<span>
<Display type='currency' value={item.amount} />
</span>
</li>
<li className='font-semibold'>
<span className='text-[#225BA9]'>{t('status.0')}</span>
<span>{t(`status.${item.status}`)}</span>
</li>
<li className='font-semibold'>
<span className='text-[#225BA9]'>{t('createdAt')}</span>
<time>{formatDate(item.created_at)}</time>
</li>
</ul>
</CardContent>
</Card>
);
}}
empty={<Empty />}
/>
<OrderDetailDialog ref={OrderDetailDialogRef} />
</div>
);
}
@@ -4,8 +4,9 @@ import { querySubscribeList } from '@/services/user/subscribe';
import { useQuery } from '@tanstack/react-query';
import { Tabs, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
import { useTranslations } from 'next-intl';
import { useMemo, useState } from 'react';
import { useMemo, useRef, useState } from 'react';
import { LoginDialogProvider } from '@/app/auth/LoginDialogContext';
import { TabContent } from '@/components/main/OfferDialog/TabContent';
import { ProcessedPlanData } from '@/components/main/OfferDialog/types';
import Purchase from '@/components/subscribe/purchase';
@@ -13,7 +14,6 @@ import { unitConversion } from '@workspace/ui/utils';
export default function Page() {
const t = useTranslations('subscribe');
const [subscribe, setSubscribe] = useState<API.Subscribe | undefined>();
const [tabValue, setTabValue] = useState<'year' | 'month'>('year');
const { data, isLoading, error, refetch } = useQuery({
@@ -60,54 +60,61 @@ export default function Page() {
const handleSubscribe = (plan: ProcessedPlanData) => {
console.log('用户选择了套餐:', plan);
// 这里可以添加订阅逻辑,比如跳转到支付页面或显示确认对话框
setSubscribe(plan);
PurchaseRef.current.show(plan, tabValue);
};
const PurchaseRef = useRef<{
show: (subscribe: API.Subscribe, tabValue: string) => void;
hide: () => void;
}>(null);
return (
<>
<div className={'text-4xl font-bold text-[#0F2C53] md:mb-4 md:text-center md:text-5xl'}>
</div>
<div className={'text-lg font-medium text-[#666666] md:text-center'}>
</div>
<div>
<Tabs
defaultValue='year'
className={'mt-8 text-center md:mt-16'}
value={tabValue}
onValueChange={(value) => setTabValue(value as 'year' | 'month')}
>
<TabsList className='mb-8 h-[74px] flex-wrap rounded-full bg-[#EAEAEA] p-2.5 md:mb-16'>
<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>
<TabContent
tabValue={tabValue}
yearlyPlans={yearlyPlans}
monthlyPlans={monthlyPlans}
isLoading={isLoading}
error={error}
onRetry={refetch}
onSubscribe={handleSubscribe}
/>
</div>
<Purchase subscribe={subscribe} setSubscribe={setSubscribe} />
<LoginDialogProvider>
<div className={'text-4xl font-bold text-[#0F2C53] md:mb-4 md:text-center md:text-5xl'}>
</div>
<div className={'text-lg font-medium text-[#666666] md:text-center'}>
</div>
<div>
<Tabs
defaultValue='year'
className={'mt-8 text-center md:mt-16'}
value={tabValue}
onValueChange={(value) => setTabValue(value as 'year' | 'month')}
>
<TabsList className='mb-8 h-[74px] flex-wrap rounded-full bg-[#EAEAEA] p-2.5 md:mb-16'>
<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>
<TabContent
tabValue={tabValue}
yearlyPlans={yearlyPlans}
monthlyPlans={monthlyPlans}
isLoading={isLoading}
error={error}
onRetry={refetch}
onSubscribe={handleSubscribe}
/>
</div>
<Purchase ref={PurchaseRef} />
</LoginDialogProvider>
</>
);
}
@@ -88,7 +88,13 @@ export default function Page() {
toolbar: (
<Dialog open={create?.open} onOpenChange={(open) => setCreate({ open })}>
<DialogTrigger asChild>
<Button size='sm'>{t('createTicket')}</Button>
<Button
className={
'min-w-[150px] rounded-full border-[#0F2C53] bg-[#0F2C53] px-[35px] py-[9px] text-center text-xl font-bold hover:bg-[#225BA9] hover:text-white'
}
>
{t('createTicket')}
</Button>
</DialogTrigger>
<DialogContent className='sm:max-w-[425px]'>
<DialogHeader>
@@ -156,8 +162,8 @@ export default function Page() {
}}
renderItem={(item) => {
return (
<Card className='overflow-hidden'>
<CardHeader className='bg-muted/50 flex flex-row items-center justify-between gap-2 space-y-0 p-3'>
<Card className='overflow-hidden pl-16'>
<CardHeader className='flex flex-row items-center justify-between gap-2 space-y-0 bg-transparent p-3'>
<CardTitle>
<span
className={cn(
@@ -176,13 +182,25 @@ export default function Page() {
<CardDescription className='flex gap-2'>
{item.status !== 4 ? (
<>
<Button key='reply' size='sm' onClick={() => setTicketId(item.id)}>
<Button
key='reply'
className={
'ml-3 min-w-[150px] rounded-full border-[#A8D4ED] bg-[#A8D4ED] px-[35px] py-[9px] text-center text-xl font-bold hover:border-[#225BA9] hover:bg-[#225BA9] hover:text-white'
}
onClick={() => setTicketId(item.id)}
>
{t('reply')}
</Button>
<ConfirmButton
key='close'
trigger={
<Button variant='destructive' size='sm'>
<Button
variant='destructive'
className={
'min-w-[150px] rounded-full border-[#F8BFD2] bg-[#F8BFD2] px-[35px] py-[9px] text-center text-xl font-bold hover:border-[#F8BFD2] hover:bg-[#FF4248] hover:text-white'
}
size='sm'
>
{t('close')}
</Button>
}
@@ -208,16 +226,18 @@ export default function Page() {
<CardContent className='p-3 text-sm'>
<ul className='grid gap-3 *:flex *:flex-col lg:grid-cols-3'>
<li>
<span className='text-muted-foreground'>{t('title')}</span>
<span> {item.title}</span>
<span className='text-[15px] font-normal text-[#225BA9]'>{t('title')}</span>
<span className={'font-bold'}> {item.title}</span>
</li>
<li className='font-semibold'>
<span className='text-muted-foreground'>{t('description')}</span>
<time>{item.description}</time>
<li className=''>
<span className='text-[15px] font-normal text-[#225BA9]'>
{t('description')}
</span>
<time className={'font-bold'}>{item.description}</time>
</li>
<li className='font-semibold'>
<span className='text-muted-foreground'>{t('updatedAt')}</span>
<time>{formatDate(item.updated_at)}</time>
<li className=''>
<span className='text-[15px] font-normal text-[#225BA9]'>{t('updatedAt')}</span>
<time className={'font-bold'}>{formatDate(item.updated_at)}</time>
</li>
</ul>
</CardContent>
@@ -11,8 +11,9 @@ import { useRef } from 'react';
import { Empty } from '@/components/empty';
import Recharge from '@/components/subscribe/recharge';
import { Button } from '@workspace/ui/components/button';
import { formatDate, isBrowser } from '@workspace/ui/utils';
import { formatDate } from '@workspace/ui/utils';
import { Copy } from 'lucide-react';
import Link from 'next/link';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { toast } from 'sonner';
@@ -62,27 +63,27 @@ export default function Page() {
<Display type='currency' value={user?.commission} />
</p>
</div>
<div className='rounded-[20px] bg-[#EAEAEA] p-4 shadow-sm transition-all duration-300 hover:shadow-md'>
<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>
<span className={'text-[#225BA9]'}></span>
<Link href='/affiliate' className={'text-[#225BA9]'}>
</Link>
</p>
<p className='flex justify-between text-2xl font-bold text-[#225BA9]'>
<span> {user?.refer_code}</span>
{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' />
</Button>
</CopyToClipboard>
)}
<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>
+5 -4
View File
@@ -2,10 +2,11 @@
import { Hero } from '@/components/main/hero';
import { ProductShowcase } from '@/components/main/product-showcase/index';
import { Stats } from '@/components/main/stats';*/
import NewHeader from '@/components/Header/NewHeader';
import Header from '@/components/Header/Header';
import { queryUserInfo } from '@/services/user/user';
import { cookies } from 'next/headers';
import { LoginDialogProvider } from '@/app/auth/LoginDialogContext';
import FooterCopyright from '@/components/main/FooterCopyright';
import FullScreenVideoBackground from '@/components/main/FullScreenVideoBackground';
import HomeContent from '@/components/main/HomeContent';
@@ -30,13 +31,13 @@ export default async function Home() {
}
return (
<>
<NewHeader />
<LoginDialogProvider>
<Header />
<FullScreenVideoBackground />
<main className='fixed inset-0 z-10 flex items-center justify-center'>
<HomeContent />
</main>
<FooterCopyright />
</>
</LoginDialogProvider>
);
}
@@ -1,40 +0,0 @@
import EmailAuthForm from '@/app/auth/email/auth-form';
import CloseSvg from '@/components/CustomIcon/icons/close.svg';
import { Dialog, DialogContent, DialogTitle } from '@workspace/ui/components/dialog';
import Image from 'next/image';
import { forwardRef, useImperativeHandle, useState } from 'react';
export interface EmailAuthDialogRef {
show: () => void;
hide: () => void;
}
const EmailAuthDialog = forwardRef<EmailAuthDialogRef>((props, ref) => {
const [open, setOpen] = useState(false);
useImperativeHandle(ref, () => ({
show: () => setOpen(true),
hide: () => setOpen(false),
}));
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent
className={
'rounded-0 h-full w-full px-12 py-[4.5rem] sm:h-auto sm:w-[496px] sm:!rounded-[50px]'
}
closeIcon={<Image src={CloseSvg} alt={'close'} />}
closeClassName={
'right-[40px] top-[30px] font-bold text-black opacity-100 focus:ring-0 focus:ring-offset-0'
}
>
<DialogTitle className={'sr-only'}>title</DialogTitle>
<div className={'min-h-[524px]'}>
<EmailAuthForm />
</div>
</DialogContent>
</Dialog>
);
});
export default EmailAuthDialog;
+83
View File
@@ -0,0 +1,83 @@
'use client';
import EmailAuthForm from '@/app/auth/email/auth-form';
import CloseSvg from '@/components/CustomIcon/icons/close.svg';
import { Dialog, DialogContent, DialogTitle } from '@workspace/ui/components/dialog';
import Image from 'next/image';
import {
createContext,
forwardRef,
ReactNode,
useContext,
useImperativeHandle,
useRef,
useState,
} from 'react';
type LoginDialogContextType = {
openLoginDialog: (isRedirect?: boolean) => void;
closeLoginDialog: () => void;
};
const LoginDialogContext = createContext<LoginDialogContextType | undefined>(undefined);
export function LoginDialogProvider({ children }: { children: ReactNode }) {
const dialogRef = useRef<{ show: (isRedirect?: boolean) => void; hide: () => void }>(null);
const openLoginDialog = (isRedirect = true) => dialogRef.current?.show(isRedirect);
const closeLoginDialog = () => dialogRef.current?.hide();
return (
<LoginDialogContext.Provider value={{ openLoginDialog, closeLoginDialog }}>
<LoginDialog ref={dialogRef} />
{children}
</LoginDialogContext.Provider>
);
}
export function useLoginDialog() {
const context = useContext(LoginDialogContext);
if (!context) {
throw new Error('useLoginDialog must be used within a LoginDialogProvider');
}
return context;
}
interface LoginDialogRef {
show: () => void;
hide: () => void;
}
const LoginDialog = forwardRef<LoginDialogRef>((props, ref) => {
const [open, setOpen] = useState(false);
const [isRedirect, setIsRedirect] = useState(false);
useImperativeHandle(ref, () => ({
show: (isRedirect = ture) => {
setOpen(true);
setIsRedirect(isRedirect);
},
hide,
}));
function hide() {
setIsRedirect(false);
setOpen(false);
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent
className='rounded-0 h-full w-full px-12 py-[4.5rem] sm:h-auto sm:w-[496px] sm:!rounded-[50px]'
closeIcon={<Image src={CloseSvg} alt='close' />}
closeClassName='right-[40px] top-[30px] font-bold text-black opacity-100 focus:ring-0 focus:ring-offset-0'
>
<DialogTitle className='sr-only'>Login</DialogTitle>
<div className='min-h-[524px]'>
<EmailAuthForm hide={hide} isRedirect={isRedirect} />
</div>
</DialogContent>
</Dialog>
);
});
LoginDialog.displayName = 'LoginDialog';
+11 -4
View File
@@ -10,12 +10,13 @@ import {
NEXT_PUBLIC_DEFAULT_USER_EMAIL,
NEXT_PUBLIC_DEFAULT_USER_PASSWORD,
} from '@/config/constants';
import useGlobalStore from '@/config/use-global';
import { getRedirectUrl, setAuthorization } from '@/utils/common';
import LoginForm from './login-form';
import RegisterForm from './register-form';
import ResetForm from './reset-form';
export default function EmailAuthForm() {
export default function EmailAuthForm(props: { isRedirect: boolean; hide: () => void }) {
const t = useTranslations('auth');
const router = useRouter();
const [type, setType] = useState<'login' | 'register' | 'reset'>('login');
@@ -27,13 +28,19 @@ export default function EmailAuthForm() {
email: NEXT_PUBLIC_DEFAULT_USER_EMAIL,
password: NEXT_PUBLIC_DEFAULT_USER_PASSWORD,
});
const { getUserInfo } = useGlobalStore();
const handleFormSubmit = async (params: any) => {
const onLogin = async (token?: string) => {
if (!token) return;
setAuthorization(token);
router.replace(getRedirectUrl());
router.refresh();
if (props.isRedirect) {
router.replace(getRedirectUrl());
router.refresh();
} else {
await getUserInfo();
props.hide();
}
};
startTransition(async () => {
try {
@@ -1,33 +0,0 @@
'use client';
import { bindOAuthCallback } from '@/services/user/user';
import { getAllUrlParams } from '@/utils/common';
import { usePathname, useRouter } from 'next/navigation';
import { useEffect } from 'react';
interface CertificationProps {
platform: string;
children: React.ReactNode;
}
export default function Certification({ platform, children }: CertificationProps) {
const router = useRouter();
const pathname = usePathname();
useEffect(() => {
const searchParams = getAllUrlParams();
bindOAuthCallback({
method: platform,
callback: searchParams,
})
.then((res) => {
router.replace('/profile');
router.refresh();
})
.catch((error) => {
router.replace('/');
});
}, [pathname]);
return children;
}
-61
View File
@@ -1,61 +0,0 @@
import HyperText from '@workspace/ui/components/hyper-text';
import { OrbitingCircles } from '@workspace/ui/components/orbiting-circles';
import { Icon } from '@workspace/ui/custom-components/icon';
import { getTranslations } from 'next-intl/server';
import Certification from './certification';
export async function generateStaticParams() {
return [
{
platform: 'telegram',
},
{
platform: 'apple',
},
{
platform: 'facebook',
},
{
platform: 'google',
},
{
platform: 'github',
},
];
}
export default async function Page({
params,
}: {
params: Promise<{
platform: string;
}>;
}) {
const { platform } = await params;
const t = await getTranslations('auth');
return (
<Certification platform={platform}>
<div className='bg-background relative flex h-screen w-full flex-col items-center justify-center overflow-hidden'>
<div className='pointer-events-none flex animate-pulse flex-col items-center whitespace-pre-wrap bg-gradient-to-r from-blue-500 via-indigo-500 to-violet-500 bg-clip-text text-center font-black tracking-tight text-transparent dark:from-blue-400 dark:via-indigo-300 dark:to-violet-400'>
<HyperText className='text-xl uppercase md:text-2xl'>{platform}</HyperText>
<HyperText className='text-lg md:text-xl'>{t('authenticating')}</HyperText>
</div>
<OrbitingCircles iconSize={40} speed={0.8}>
<Icon icon='logos:telegram' className='size-12' />
<Icon icon='uil:apple' className='size-12' />
<Icon icon='logos:google-icon' className='size-12' />
<Icon icon='logos:facebook' className='size-12' />
<Icon icon='uil:github' className='size-12' />
</OrbitingCircles>
<OrbitingCircles iconSize={30} radius={100} reverse speed={0.4}>
<Icon icon='logos:telegram' className='size-10' />
<Icon icon='uil:apple' className='size-10' />
<Icon icon='logos:google-icon' className='size-10' />
<Icon icon='logos:facebook' className='size-10' />
<Icon icon='uil:github' className='size-10' />
</OrbitingCircles>
</div>
</Certification>
);
}
@@ -1,38 +0,0 @@
'use client';
import { oAuthLoginGetToken } from '@/services/common/oauth';
import { getAllUrlParams, getRedirectUrl, setAuthorization } from '@/utils/common';
import { usePathname, useRouter } from 'next/navigation';
import { useEffect } from 'react';
interface CertificationProps {
platform: string;
children: React.ReactNode;
}
export default function Certification({ platform, children }: CertificationProps) {
const router = useRouter();
const pathname = usePathname();
useEffect(() => {
const searchParams = getAllUrlParams();
oAuthLoginGetToken({
method: platform,
callback: searchParams,
})
.then((res) => {
const token = res?.data?.data?.token;
if (!token) {
throw new Error('Invalid token');
}
setAuthorization(token);
router.replace(getRedirectUrl());
router.refresh();
})
.catch((error) => {
router.replace('/');
});
}, [pathname]);
return children;
}
-61
View File
@@ -1,61 +0,0 @@
import HyperText from '@workspace/ui/components/hyper-text';
import { OrbitingCircles } from '@workspace/ui/components/orbiting-circles';
import { Icon } from '@workspace/ui/custom-components/icon';
import { getTranslations } from 'next-intl/server';
import Certification from './certification';
export async function generateStaticParams() {
return [
{
platform: 'telegram',
},
{
platform: 'apple',
},
{
platform: 'facebook',
},
{
platform: 'google',
},
{
platform: 'github',
},
];
}
export default async function Page({
params,
}: {
params: Promise<{
platform: string;
}>;
}) {
const { platform } = await params;
const t = await getTranslations('auth');
return (
<Certification platform={platform}>
<div className='bg-background relative flex h-screen w-full flex-col items-center justify-center overflow-hidden'>
<div className='pointer-events-none flex animate-pulse flex-col items-center whitespace-pre-wrap bg-gradient-to-r from-blue-500 via-indigo-500 to-violet-500 bg-clip-text text-center font-black tracking-tight text-transparent dark:from-blue-400 dark:via-indigo-300 dark:to-violet-400'>
<HyperText className='text-xl uppercase md:text-2xl'>{platform}</HyperText>
<HyperText className='text-lg md:text-xl'>{t('authenticating')}</HyperText>
</div>
<OrbitingCircles iconSize={40} speed={0.8}>
<Icon icon='logos:telegram' className='size-12' />
<Icon icon='uil:apple' className='size-12' />
<Icon icon='logos:google-icon' className='size-12' />
<Icon icon='logos:facebook' className='size-12' />
<Icon icon='uil:github' className='size-12' />
</OrbitingCircles>
<OrbitingCircles iconSize={30} radius={100} reverse speed={0.4}>
<Icon icon='logos:telegram' className='size-10' />
<Icon icon='uil:apple' className='size-10' />
<Icon icon='logos:google-icon' className='size-10' />
<Icon icon='logos:facebook' className='size-10' />
<Icon icon='uil:github' className='size-10' />
</OrbitingCircles>
</div>
</Certification>
);
}