mirror of
https://github.com/perfect-panel/ppanel-web.git
synced 2026-02-06 03:30:25 -05:00
✨ feat(affiliate): Affiliate Detail
This commit is contained in:
parent
98c1c3047f
commit
a782c17a59
76
apps/user/app/(main)/(user)/affiliate/page.tsx
Normal file
76
apps/user/app/(main)/(user)/affiliate/page.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
'use client';
|
||||
import { Display } from '@/components/display';
|
||||
import { ProList } from '@/components/pro-list';
|
||||
import useGlobalStore from '@/config/use-global';
|
||||
import { queryUserAffiliate } from '@/services/user/user';
|
||||
import { formatDate } from '@repo/ui/utils';
|
||||
import { Button } from '@shadcn/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@shadcn/ui/card';
|
||||
import { toast } from '@shadcn/ui/lib/sonner';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function Page() {
|
||||
const t = useTranslations('affiliate');
|
||||
const { user } = useGlobalStore();
|
||||
const [sum, setSum] = useState<number>();
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-4'>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('totalCommission')}</CardTitle>
|
||||
<CardDescription>{t('commissionInfo')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='text-2xl font-bold'>
|
||||
<Display type='currency' value={sum} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className='flex flex-row items-center justify-between space-y-0'>
|
||||
<CardTitle>{t('inviteCode')}</CardTitle>
|
||||
<Button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(`${location.origin}/auth?invite=${user?.refer_code}`);
|
||||
toast.success(t('copySuccess'));
|
||||
}}
|
||||
>
|
||||
{t('copyInviteLink')}
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent className='text-2xl font-bold'>{user?.refer_code}</CardContent>
|
||||
</Card>
|
||||
<ProList<API.UserAffiliate, Record<string, unknown>>
|
||||
request={async (pagination, filter) => {
|
||||
const response = await queryUserAffiliate({ ...pagination, ...filter });
|
||||
setSum(response.data.data?.sum);
|
||||
return {
|
||||
list: response.data.data?.list || [],
|
||||
total: response.data.data?.total || 0,
|
||||
};
|
||||
}}
|
||||
header={{
|
||||
title: t('inviteRecords'),
|
||||
}}
|
||||
renderItem={(item) => {
|
||||
return (
|
||||
<Card className='overflow-hidden'>
|
||||
<CardContent className='p-3 text-sm'>
|
||||
<ul className='grid grid-cols-2 gap-3 *:flex *:flex-col'>
|
||||
<li className='font-semibold'>
|
||||
<span className='text-muted-foreground'>{t('userEmail')}</span>
|
||||
<span>{item.email}</span>
|
||||
</li>
|
||||
<li className='font-semibold'>
|
||||
<span className='text-muted-foreground'>{t('registrationTime')}</span>
|
||||
<time>{formatDate(item.registered_at)}</time>
|
||||
</li>
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -25,20 +25,6 @@ export function SidebarRight({ ...props }: React.ComponentProps<typeof Sidebar>)
|
||||
<Display type='currency' value={user?.balance} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className='flex flex-row items-center justify-between space-y-0 p-3 pb-2'>
|
||||
<CardTitle className='text-sm font-medium'>{t('totalCommission')}</CardTitle>
|
||||
<Icon icon='mdi:money' className='text-muted-foreground text-2xl' />
|
||||
</CardHeader>
|
||||
<CardContent className='p-3 text-2xl font-bold'>0.00</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className='flex flex-row items-center justify-between space-y-0 p-3 pb-2'>
|
||||
<CardTitle className='text-sm font-medium'>{t('invitees')}</CardTitle>
|
||||
<Icon icon='mdi:users' className='text-muted-foreground text-2xl' />
|
||||
</CardHeader>
|
||||
<CardContent className='p-3 text-2xl font-bold'>0</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className='flex flex-row items-center justify-between space-y-0 p-3 pb-2'>
|
||||
<CardTitle className='text-sm font-medium'>{t('inviteCode')}</CardTitle>
|
||||
|
||||
@ -37,6 +37,11 @@ export const navs = [
|
||||
icon: 'uil:wallet',
|
||||
title: 'wallet',
|
||||
},
|
||||
{
|
||||
url: '/affiliate',
|
||||
icon: 'uil:users-alt',
|
||||
title: 'affiliate',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
10
apps/user/locales/en-US/affiliate.json
Normal file
10
apps/user/locales/en-US/affiliate.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"commissionInfo": "Statistics of the commission, automatically transferred to balance",
|
||||
"copyInviteLink": "Copy Invite Link",
|
||||
"copySuccess": "Copied Successfully",
|
||||
"inviteCode": "Invite Code",
|
||||
"inviteRecords": "Invite Records",
|
||||
"registrationTime": "Registration Time",
|
||||
"totalCommission": "Total Commission",
|
||||
"userEmail": "User Email"
|
||||
}
|
||||
@ -3,7 +3,5 @@
|
||||
"copyInviteLink": "Copy Invite Link",
|
||||
"copySuccess": "Invite Link Copied Successfully",
|
||||
"inviteCode": "Invite Code",
|
||||
"invitees": "Invitees",
|
||||
"recharge": "Recharge",
|
||||
"totalCommission": "Total Commission"
|
||||
"recharge": "Recharge"
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"affiliate": "My Invitation",
|
||||
"announcement": "Announcement List",
|
||||
"dashboard": "Dashboard",
|
||||
"document": "Documentation",
|
||||
|
||||
@ -23,6 +23,7 @@ export default getRequestConfig(async () => {
|
||||
wallet: (await import(`./${locale}/wallet.json`)).default,
|
||||
ticket: (await import(`./${locale}/ticket.json`)).default,
|
||||
document: (await import(`./${locale}/document.json`)).default,
|
||||
affiliate: (await import(`./${locale}/affiliate.json`)).default,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
10
apps/user/locales/zh-CN/affiliate.json
Normal file
10
apps/user/locales/zh-CN/affiliate.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"commissionInfo": "统计金额,邀请佣金自动转入余额",
|
||||
"copyInviteLink": "复制邀请链接",
|
||||
"copySuccess": "复制成功",
|
||||
"inviteCode": "邀请码",
|
||||
"inviteRecords": "邀请记录",
|
||||
"registrationTime": "注册时间",
|
||||
"totalCommission": "佣金总额",
|
||||
"userEmail": "用户邮箱"
|
||||
}
|
||||
@ -3,7 +3,5 @@
|
||||
"copyInviteLink": "复制邀请链接",
|
||||
"copySuccess": "邀请链接复制成功",
|
||||
"inviteCode": "邀请码",
|
||||
"invitees": "邀请人数",
|
||||
"recharge": "充值",
|
||||
"totalCommission": "佣金总额"
|
||||
"recharge": "充值"
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"affiliate": "我的邀请",
|
||||
"announcement": "公告列表",
|
||||
"dashboard": "主页",
|
||||
"document": "使用文档",
|
||||
|
||||
@ -21,3 +21,11 @@ export async function getGlobalConfig(options?: { [key: string]: any }) {
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** Get Tos Content GET /v1/common/site/tos */
|
||||
export async function getTos(options?: { [key: string]: any }) {
|
||||
return request<API.Response & { data?: API.GetTosResponse }>('/v1/common/site/tos', {
|
||||
method: 'GET',
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
4
apps/user/services/common/typings.d.ts
vendored
4
apps/user/services/common/typings.d.ts
vendored
@ -25,6 +25,10 @@ declare namespace API {
|
||||
subscribe: SubscribeConfig;
|
||||
};
|
||||
|
||||
type GetTosResponse = {
|
||||
tos_content: string;
|
||||
};
|
||||
|
||||
type InviteConfig = {
|
||||
forced_invite: boolean;
|
||||
};
|
||||
|
||||
2
apps/user/services/user/typings.d.ts
vendored
2
apps/user/services/user/typings.d.ts
vendored
@ -212,6 +212,7 @@ declare namespace API {
|
||||
};
|
||||
|
||||
type QueryUserAffiliateResponse = {
|
||||
sum: number;
|
||||
list: UserAffiliate[];
|
||||
total: number;
|
||||
};
|
||||
@ -353,7 +354,6 @@ declare namespace API {
|
||||
type UserAffiliate = {
|
||||
email: string;
|
||||
avatar: string;
|
||||
telegram: number;
|
||||
registered_at: number;
|
||||
enable: boolean;
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import { Alert, AlertDescription, AlertTitle } from '@shadcn/ui/alert';
|
||||
import { Button } from '@shadcn/ui/button';
|
||||
import { Checkbox } from '@shadcn/ui/checkbox';
|
||||
import { cn } from '@shadcn/ui/lib/utils';
|
||||
import {
|
||||
ColumnFiltersState,
|
||||
getCoreRowModel,
|
||||
@ -142,12 +143,16 @@ export function ProList<TData, TValue extends Record<string, unknown>>({
|
||||
)}
|
||||
</div>
|
||||
<div className='flex flex-1 items-center justify-end gap-2'>
|
||||
<Button variant='outline' className='h-8 w-8 p-2' onClick={fetchData}>
|
||||
<RefreshCcw className='h-4 w-4' />
|
||||
</Button>
|
||||
<Button variant='outline' className='h-8 w-8 p-2' onClick={reset}>
|
||||
<ListRestart className='h-4 w-4' />
|
||||
</Button>
|
||||
{params && params?.length > 0 && (
|
||||
<>
|
||||
<Button variant='outline' className='h-8 w-8 p-2' onClick={fetchData}>
|
||||
<RefreshCcw className='h-4 w-4' />
|
||||
</Button>
|
||||
<Button variant='outline' className='h-8 w-8 p-2' onClick={reset}>
|
||||
<ListRestart className='h-4 w-4' />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{header?.toolbar}
|
||||
</div>
|
||||
</div>
|
||||
@ -161,7 +166,11 @@ export function ProList<TData, TValue extends Record<string, unknown>>({
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className='relative overflow-x-auto'>
|
||||
<div
|
||||
className={cn('relative overflow-x-auto', {
|
||||
'rounded-xl border': data.length === 0,
|
||||
})}
|
||||
>
|
||||
<div className='grid grid-cols-1 gap-4'>
|
||||
{data.length ? (
|
||||
data.map((item, index) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user