✨ 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} />
|
<Display type='currency' value={user?.balance} />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</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>
|
<Card>
|
||||||
<CardHeader className='flex flex-row items-center justify-between space-y-0 p-3 pb-2'>
|
<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>
|
<CardTitle className='text-sm font-medium'>{t('inviteCode')}</CardTitle>
|
||||||
|
|||||||
@ -37,6 +37,11 @@ export const navs = [
|
|||||||
icon: 'uil:wallet',
|
icon: 'uil:wallet',
|
||||||
title: '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",
|
"copyInviteLink": "Copy Invite Link",
|
||||||
"copySuccess": "Invite Link Copied Successfully",
|
"copySuccess": "Invite Link Copied Successfully",
|
||||||
"inviteCode": "Invite Code",
|
"inviteCode": "Invite Code",
|
||||||
"invitees": "Invitees",
|
"recharge": "Recharge"
|
||||||
"recharge": "Recharge",
|
|
||||||
"totalCommission": "Total Commission"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"affiliate": "My Invitation",
|
||||||
"announcement": "Announcement List",
|
"announcement": "Announcement List",
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"document": "Documentation",
|
"document": "Documentation",
|
||||||
|
|||||||
@ -23,6 +23,7 @@ export default getRequestConfig(async () => {
|
|||||||
wallet: (await import(`./${locale}/wallet.json`)).default,
|
wallet: (await import(`./${locale}/wallet.json`)).default,
|
||||||
ticket: (await import(`./${locale}/ticket.json`)).default,
|
ticket: (await import(`./${locale}/ticket.json`)).default,
|
||||||
document: (await import(`./${locale}/document.json`)).default,
|
document: (await import(`./${locale}/document.json`)).default,
|
||||||
|
affiliate: (await import(`./${locale}/affiliate.json`)).default,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
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": "复制邀请链接",
|
"copyInviteLink": "复制邀请链接",
|
||||||
"copySuccess": "邀请链接复制成功",
|
"copySuccess": "邀请链接复制成功",
|
||||||
"inviteCode": "邀请码",
|
"inviteCode": "邀请码",
|
||||||
"invitees": "邀请人数",
|
"recharge": "充值"
|
||||||
"recharge": "充值",
|
|
||||||
"totalCommission": "佣金总额"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"affiliate": "我的邀请",
|
||||||
"announcement": "公告列表",
|
"announcement": "公告列表",
|
||||||
"dashboard": "主页",
|
"dashboard": "主页",
|
||||||
"document": "使用文档",
|
"document": "使用文档",
|
||||||
|
|||||||
@ -21,3 +21,11 @@ export async function getGlobalConfig(options?: { [key: string]: any }) {
|
|||||||
...(options || {}),
|
...(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;
|
subscribe: SubscribeConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type GetTosResponse = {
|
||||||
|
tos_content: string;
|
||||||
|
};
|
||||||
|
|
||||||
type InviteConfig = {
|
type InviteConfig = {
|
||||||
forced_invite: boolean;
|
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 = {
|
type QueryUserAffiliateResponse = {
|
||||||
|
sum: number;
|
||||||
list: UserAffiliate[];
|
list: UserAffiliate[];
|
||||||
total: number;
|
total: number;
|
||||||
};
|
};
|
||||||
@ -353,7 +354,6 @@ declare namespace API {
|
|||||||
type UserAffiliate = {
|
type UserAffiliate = {
|
||||||
email: string;
|
email: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
telegram: number;
|
|
||||||
registered_at: number;
|
registered_at: number;
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
import { Alert, AlertDescription, AlertTitle } from '@shadcn/ui/alert';
|
import { Alert, AlertDescription, AlertTitle } from '@shadcn/ui/alert';
|
||||||
import { Button } from '@shadcn/ui/button';
|
import { Button } from '@shadcn/ui/button';
|
||||||
import { Checkbox } from '@shadcn/ui/checkbox';
|
import { Checkbox } from '@shadcn/ui/checkbox';
|
||||||
|
import { cn } from '@shadcn/ui/lib/utils';
|
||||||
import {
|
import {
|
||||||
ColumnFiltersState,
|
ColumnFiltersState,
|
||||||
getCoreRowModel,
|
getCoreRowModel,
|
||||||
@ -142,12 +143,16 @@ export function ProList<TData, TValue extends Record<string, unknown>>({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-1 items-center justify-end gap-2'>
|
<div className='flex flex-1 items-center justify-end gap-2'>
|
||||||
<Button variant='outline' className='h-8 w-8 p-2' onClick={fetchData}>
|
{params && params?.length > 0 && (
|
||||||
<RefreshCcw className='h-4 w-4' />
|
<>
|
||||||
</Button>
|
<Button variant='outline' className='h-8 w-8 p-2' onClick={fetchData}>
|
||||||
<Button variant='outline' className='h-8 w-8 p-2' onClick={reset}>
|
<RefreshCcw className='h-4 w-4' />
|
||||||
<ListRestart className='h-4 w-4' />
|
</Button>
|
||||||
</Button>
|
<Button variant='outline' className='h-8 w-8 p-2' onClick={reset}>
|
||||||
|
<ListRestart className='h-4 w-4' />
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{header?.toolbar}
|
{header?.toolbar}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -161,7 +166,11 @@ export function ProList<TData, TValue extends Record<string, unknown>>({
|
|||||||
</Alert>
|
</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'>
|
<div className='grid grid-cols-1 gap-4'>
|
||||||
{data.length ? (
|
{data.length ? (
|
||||||
data.map((item, index) => {
|
data.map((item, index) => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user