307 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import {
AffiliateDialog,
AffiliateDialogRef,
} from '@/components/affiliate/components/AffiliateDialog';
import { Display } from '@/components/display';
import { Empty } from '@/components/empty';
import SvgIcon from '@/components/SvgIcon';
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 } from '@workspace/ui/components/card';
import { Input } from '@workspace/ui/components/input';
import { formatDate } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
import { useRef, useState } from 'react';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { toast } from 'sonner';
export default function Affiliate() {
const t = useTranslations('affiliate');
const { user, common } = useGlobalStore();
const [sum, setSum] = useState<number>();
const { data } = useQuery({
queryKey: ['queryUserAffiliate'],
queryFn: async () => {
const response = await queryUserAffiliate();
return response.data.data;
},
});
const { data: inviteList = [] } = useQuery({
queryKey: ['queryUserAffiliateList'],
queryFn: async () => {
const response = await queryUserAffiliateList({
page: 1,
size: 3,
});
return response.data.data?.list || [];
},
});
const dialogRef = useRef<AffiliateDialogRef>(null);
return (
<div className='grid grid-cols-1 gap-4 sm:grid-cols-2'>
<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={'sm:mb-6'}>
<div className={'font-bold sm:text-xl'}>{t('totalCommission')}</div>
<div className={'text-xs font-light sm:text-[15px]'}>
</div>
</div>
<div className={'mb-3 text-xl font-bold text-[#091B33] sm:text-[32px]'}>
7
</div>
<div className={'grid grid-cols-2 gap-[10px] sm:grid-cols-1 sm:gap-5 lg:grid-cols-2'}>
<div className='rounded-[20px] bg-[#EAEAEA] px-4 py-2 shadow-sm transition-all duration-300 hover:shadow-md sm:py-4'>
<p className='font-medium text-[#666] opacity-80 sm:mb-3 sm:text-sm'></p>
<p className='text-xl font-bold text-[#225BA9] sm:text-2xl'>
<Display type='currency' value={data?.total_commission} />
</p>
</div>
<div className='rounded-[20px] border-2 border-[#D9D9D9] px-4 py-2 shadow-sm transition-all duration-300 hover:shadow-md sm:py-4'>
<p className='flex justify-between font-medium text-[#666] opacity-80 sm:mb-3 sm:text-sm'>
<CopyToClipboard
text={`${location?.origin}/?invite=${user?.refer_code}`}
onCopy={(text, result) => {
if (result) {
toast.success(t('copySuccess'));
}
}}
>
<Button
variant='link'
size='sm'
className='h-auto p-0 px-0 sm:hidden [&_svg]:size-5'
>
<SvgIcon name={'copy'}></SvgIcon>
</Button>
</CopyToClipboard>
</p>
<p className='flex justify-between text-xl font-bold text-[#225BA9] sm:text-2xl'>
<span> {user?.refer_code}</span>
<CopyToClipboard
text={`${location?.origin}/?invite=${user?.refer_code}`}
onCopy={(text, result) => {
if (result) {
toast.success(t('copySuccess'));
}
}}
>
<Button
variant='link'
size='sm'
className='hidden gap-2 p-0 sm:block [&_svg]:size-5'
>
<SvgIcon name={'copy'}></SvgIcon>
</Button>
</CopyToClipboard>
</p>
</div>
</div>
</CardContent>
</Card>
<Card className='order-2 rounded-[20px] border border-[#EAEAEA] bg-gradient-to-b from-white to-[#EAEAEA] p-6 md:order-none'>
<div className='mb-4 flex items-center justify-between'>
<h3 className='font-medium text-[#666666] sm:text-xl'></h3>
<span
className='cursor-pointer text-sm text-[#225BA9]'
onClick={() => dialogRef.current.open()}
>
</span>
</div>
<div className='space-y-2 sm: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 text-[10px] sm:text-base'>
<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>
<Card className='min-w-[322px] rounded-[20px] border border-[#EAEAEA] bg-[#EAEAEA] p-6 text-[12px] sm:text-[16px] md:min-w-[496px]'>
<div className='flex items-center justify-between'>
<h3 className='text-[15px] font-medium text-[#0F2C53] sm:text-xl'></h3>
</div>
<div className={'mb-4 text-[10px] font-light text-[#0F2C53] sm:text-base'}>
*Pro
Plan计算
</div>
{(() => {
// 假设以 Pro 计划计算:$60/月,$576/年
const MONTHLY_PRICE = 60;
const YEARLY_PRICE = 576;
const [count, setCount] = useState<number>(10);
const clamp = (n: number) => Math.max(0, Math.min(10000, Math.floor(n)));
const firstMonth = count * MONTHLY_PRICE * 0.5; // 50%
const firstYear = count * YEARLY_PRICE * 0.3; // 30%
const recurMonth = count * MONTHLY_PRICE * 0.2; // 20%
const recurYear = count * YEARLY_PRICE * 0.2; // 20%
return (
<div className='space-y-4'>
{/* 计算面板容器 */}
<div className='grid grid-cols-[1.5fr_2.5fr_3fr] items-stretch rounded-[34px] bg-white/10 px-5 pb-6 shadow-[inset_0_0_15.7px_0_rgba(0,0,0,0.25)]'>
{/* 左:行表头(月付套餐 / 年付套餐) */}
<div className='flex flex-col justify-stretch font-semibold text-[#0F2C53]'>
<div className='flex h-[56px] items-center justify-center border-b-[3px] border-white'></div>
<div className='flex h-[81px] items-center justify-center border-b-[3px] border-white'>
</div>
<div className='flex h-[81px] items-center justify-center border-b-[3px] border-white'>
</div>
</div>
{/* 中:首充用户(双层圆角 + 三行) */}
<div className='relative'>
<div
className={
'absolute left-1 top-6 z-10 h-[90%] w-full rounded-[14px] bg-[#225BA9] sm:left-2.5 sm:top-8'
}
></div>
<div className={'absolute bottom-0 z-0 h-[3px] w-full bg-white'}></div>
<div className='absolute z-20 w-full'>
<div className='overflow-hidden rounded-[14px] text-center text-[#0F2C53]'>
<div className={'rounded-t-[14px] bg-[#A8D4ED] px-1 sm:px-4'}>
<div className='mt-3 flex h-[44px] items-center justify-center border-b-[3px] border-white font-bold'>
</div>
</div>
<div className={'bg-[#A8D4ED] px-1 sm:px-4'}>
<div className='grid h-[81px] grid-cols-1 grid-rows-2 border-b-[3px] border-white'>
<div className='flex h-full items-center justify-center border-b-[1px] border-white font-semibold'>
50%
</div>
<div className='flex h-full flex-1 items-center justify-center text-[10px] text-[#225BA9] sm:text-[15px]'>
up to{' '}
<span className='font-semibold'>
<Display type='currency' value={firstMonth} />
</span>
</div>
</div>
</div>
<div className={'bg-[#A8D4ED]'}>
<div className='grid h-[81px] grid-cols-1 grid-rows-2 bg-[#A8D4ED]'>
<div className='flex h-full items-center justify-center border-b-[1px] border-white font-semibold'>
30%
</div>
<div className='flex h-full items-center justify-center text-[10px] text-[#225BA9] sm:text-[15px]'>
up to{' '}
<span className='font-semibold'>
<Display type='currency' value={firstYear} />
</span>
</div>
</div>
</div>
</div>
</div>
{/* 蓝色投影块 */}
<div className='pointer-events-none absolute inset-0 -z-10 translate-x-2 translate-y-2 rounded-[20px] bg-[#225BA9]' />
</div>
{/* 右:再次充值用户(灰卡 + 三行) */}
<div className='text-center text-[#0F2C53]'>
<div className='flex h-[56px] items-center justify-center border-b-[3px] border-white pt-3 font-bold'>
</div>
<div className='grid h-[81px] grid-cols-1 grid-rows-2 items-center justify-center border-b-[3px] border-white'>
<div className='flex h-full items-center justify-center border-b-[1px] border-white font-semibold'>
20%
</div>
<div className='flex h-full w-full items-center justify-center text-[10px] text-[#225BA9] sm:text-[15px]'>
up to{' '}
<span className='font-semibold'>
<Display type='currency' value={recurMonth} />
</span>{' '}
/
</div>
</div>
<div className='grid h-[81px] grid-cols-1 grid-rows-2 border-b-[3px] border-white'>
<div className='flex h-full items-center justify-center border-b-[1px] border-white font-semibold'>
20%
</div>
<div className='flex h-full items-center justify-center text-[10px] text-[#225BA9] sm:text-[15px]'>
up to{' '}
<span className='font-semibold'>
<Display type='currency' value={recurYear} />
</span>{' '}
/
</div>
</div>
</div>
</div>
{/* 用户数调节 */}
<div className='flex items-center justify-center gap-4'>
<Button
variant='secondary'
size='icon'
className='h-6 w-9 rounded-full bg-[#0F2C53] font-bold text-white hover:bg-[#225BA9] sm:h-9'
onClick={() => setCount((v) => clamp(v - 1))}
>
</Button>
<div className='inline-flex items-center gap-1 rounded-full bg-[#D9D9D9] px-3 font-medium text-[#0F2C53] shadow-[inset_0px_0px_4px_0px_rgba(0,0,0,0.25)]'>
<Input
value={count}
onChange={(e) => setCount(clamp(Number(e.target.value) || 0))}
className='h-6 border-0 p-0 text-center text-sm focus-visible:ring-0 sm:h-8'
style={{ width: `${Math.max(3, String(count).length + 1)}ch` }}
/>
<span className='text-sm'>Users</span>
</div>
<Button
variant='secondary'
size='icon'
className='h-6 w-9 rounded-full bg-[#0F2C53] font-bold text-white hover:bg-[#225BA9] sm:h-9'
onClick={() => setCount((v) => clamp(v + 1))}
>
+
</Button>
</div>
</div>
);
})()}
</Card>
<AffiliateDialog ref={dialogRef} />
</div>
);
}