invite url
This commit is contained in:
@@ -119,17 +119,26 @@
|
|||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"Account": "Account",
|
"Account": "Account",
|
||||||
|
"Amount of earnings": "Earnings",
|
||||||
|
"Amount of inviter": "Inviter",
|
||||||
"Application Name": "Application Name",
|
"Application Name": "Application Name",
|
||||||
"Avatar": "Avatar",
|
"Avatar": "Avatar",
|
||||||
"Balance": "Balance",
|
"Balance": "Balance",
|
||||||
"Bill Detail": "Bill Detail",
|
"Bill Detail": "Bill Detail",
|
||||||
"Change": "Change",
|
"Change": "Change",
|
||||||
|
"Copy invite url": "Copy invitation link",
|
||||||
|
"Invite Url": "Invite Url",
|
||||||
|
"Invite url tip": "Friends who register through this link will be permanently bound to you, and you will get a certain balance reward when they recharge. In addition, when friends register with their mobile phone number, you will get 5 yuan reward immediately.",
|
||||||
"Notice": "Notice",
|
"Notice": "Notice",
|
||||||
"Old password is error": "Old password is error",
|
"Old password is error": "Old password is error",
|
||||||
"OpenAI Account Setting": "OpenAI Account Setting",
|
"OpenAI Account Setting": "OpenAI Account Setting",
|
||||||
"Password": "Password",
|
"Password": "Password",
|
||||||
"Pay": "Pay",
|
"Pay": "Pay",
|
||||||
"Personal Information": "Personal",
|
"Personal Information": "Personal",
|
||||||
|
"Promotion": "Promotion",
|
||||||
|
"Promotion Rate": "Promotion Rate",
|
||||||
|
"Promotion Record": "Promotion",
|
||||||
|
"Promotion rate tip": "You will be rewarded with a percentage of the balance when your friends top up",
|
||||||
"Recharge Record": "Recharge",
|
"Recharge Record": "Recharge",
|
||||||
"Replace": "Replace",
|
"Replace": "Replace",
|
||||||
"Set OpenAI Account Failed": "Set OpenAI account failed",
|
"Set OpenAI Account Failed": "Set OpenAI account failed",
|
||||||
|
|||||||
@@ -119,17 +119,26 @@
|
|||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"Account": "账号",
|
"Account": "账号",
|
||||||
|
"Amount of earnings": "收益(¥)",
|
||||||
|
"Amount of inviter": "累计邀请人数",
|
||||||
"Application Name": "应用名",
|
"Application Name": "应用名",
|
||||||
"Avatar": "头像",
|
"Avatar": "头像",
|
||||||
"Balance": "余额",
|
"Balance": "余额",
|
||||||
"Bill Detail": "账单详情",
|
"Bill Detail": "账单详情",
|
||||||
"Change": "变更",
|
"Change": "变更",
|
||||||
|
"Copy invite url": "复制邀请链接",
|
||||||
|
"Invite Url": "邀请链接",
|
||||||
|
"Invite url tip": "通过该链接注册的好友将永久与你绑定,其充值时你会获得一定余额奖励。\n此外,好友使用手机号注册时,你将立即获得 5 元奖励。",
|
||||||
"Notice": "通知",
|
"Notice": "通知",
|
||||||
"Old password is error": "旧密码错误",
|
"Old password is error": "旧密码错误",
|
||||||
"OpenAI Account Setting": "OpenAI 账号配置",
|
"OpenAI Account Setting": "OpenAI 账号配置",
|
||||||
"Password": "密码",
|
"Password": "密码",
|
||||||
"Pay": "充值",
|
"Pay": "充值",
|
||||||
"Personal Information": "个人信息",
|
"Personal Information": "个人信息",
|
||||||
|
"Promotion": "",
|
||||||
|
"Promotion Rate": "返现比例",
|
||||||
|
"Promotion Record": "推广记录",
|
||||||
|
"Promotion rate tip": "好友充值时你将获得一定比例的余额奖励",
|
||||||
"Recharge Record": "充值记录",
|
"Recharge Record": "充值记录",
|
||||||
"Replace": "更换",
|
"Replace": "更换",
|
||||||
"Set OpenAI Account Failed": "设置 OpenAI 账号异常",
|
"Set OpenAI Account Failed": "设置 OpenAI 账号异常",
|
||||||
@@ -140,6 +149,10 @@
|
|||||||
"Update Password": "修改密码",
|
"Update Password": "修改密码",
|
||||||
"Update password failed": "修改密码异常",
|
"Update password failed": "修改密码异常",
|
||||||
"Update password succseful": "修改密码成功",
|
"Update password succseful": "修改密码成功",
|
||||||
"Usage Record": "使用记录"
|
"Usage Record": "使用记录",
|
||||||
|
"promption": {
|
||||||
|
"register": "好友注册",
|
||||||
|
"pay": "好友充值"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { GET, POST, PUT } from './request';
|
import { GET, POST, PUT } from './request';
|
||||||
import { createHashPassword } from '@/utils/tools';
|
import { createHashPassword } from '@/utils/tools';
|
||||||
import { ResLogin } from './response/user';
|
import type { ResLogin, PromotionRecordType } from './response/user';
|
||||||
import { UserAuthTypeEnum } from '@/constants/common';
|
import { UserAuthTypeEnum } from '@/constants/common';
|
||||||
import { UserBillType, UserType, UserUpdateParams } from '@/types/user';
|
import { UserBillType, UserType, UserUpdateParams } from '@/types/user';
|
||||||
import type { PagingData, RequestPaging } from '@/types';
|
import type { PagingData, RequestPaging } from '@/types';
|
||||||
@@ -13,7 +13,8 @@ export const sendAuthCode = (data: {
|
|||||||
}) => POST('/user/sendAuthCode', data);
|
}) => POST('/user/sendAuthCode', data);
|
||||||
|
|
||||||
export const getTokenLogin = () => GET<UserType>('/user/account/tokenLogin');
|
export const getTokenLogin = () => GET<UserType>('/user/account/tokenLogin');
|
||||||
export const gitLogin = (code: string) => GET<ResLogin>('/user/account/gitLogin', { code });
|
export const gitLogin = (params: { code: string; inviterId?: string }) =>
|
||||||
|
GET<ResLogin>('/user/account/gitLogin', params);
|
||||||
|
|
||||||
export const postRegister = ({
|
export const postRegister = ({
|
||||||
username,
|
username,
|
||||||
@@ -82,3 +83,14 @@ export const getInforms = (data: RequestPaging) =>
|
|||||||
|
|
||||||
export const getUnreadCount = () => GET<number>(`/user/inform/countUnread`);
|
export const getUnreadCount = () => GET<number>(`/user/inform/countUnread`);
|
||||||
export const readInform = (id: string) => GET(`/user/inform/read`, { id });
|
export const readInform = (id: string) => GET(`/user/inform/read`, { id });
|
||||||
|
|
||||||
|
/* get promotion init data */
|
||||||
|
export const getPromotionInitData = () =>
|
||||||
|
GET<{
|
||||||
|
invitedAmount: number;
|
||||||
|
earningsAmount: number;
|
||||||
|
}>('/user/promotion/getPromotionData');
|
||||||
|
|
||||||
|
/* promotion records */
|
||||||
|
export const getPromotionRecords = (data: RequestPaging) =>
|
||||||
|
POST<PromotionRecordType>(`/user/promotion/getPromotions`, data);
|
||||||
|
|||||||
8
client/src/components/Icon/icons/light/promotion.svg
Normal file
8
client/src/components/Icon/icons/light/promotion.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1692522401431"
|
||||||
|
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4063"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64">
|
||||||
|
<path
|
||||||
|
d="M855 789.1V127.4L633 274.9H169V641h81l59.5 255.7H508L448.4 641H633l222 148.1z m-46-86l-146-97.5V310.2l146-97v489.9zM450 850.6H346L297.1 641h104L450 850.6zM239.2 595H215V320.9h274V595H239.2z m377.8 0h-82V320.9h82V595z"
|
||||||
|
p-id="4064"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 605 B |
@@ -75,7 +75,8 @@ const map = {
|
|||||||
outlink_iframe: require('./icons/outlink/iframe.svg').default,
|
outlink_iframe: require('./icons/outlink/iframe.svg').default,
|
||||||
addCircle: require('./icons/circle/add.svg').default,
|
addCircle: require('./icons/circle/add.svg').default,
|
||||||
playFill: require('./icons/fill/play.svg').default,
|
playFill: require('./icons/fill/play.svg').default,
|
||||||
courseLight: require('./icons/light/course.svg').default
|
courseLight: require('./icons/light/course.svg').default,
|
||||||
|
promotionLight: require('./icons/light/promotion.svg').default
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IconName = keyof typeof map;
|
export type IconName = keyof typeof map;
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Box, Flex, useTheme } from '@chakra-ui/react';
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
import type { GridProps } from '@chakra-ui/react';
|
import type { GridProps } from '@chakra-ui/react';
|
||||||
import MyIcon, { type IconName } from '../Icon';
|
import MyIcon, { type IconName } from '../Icon';
|
||||||
import { useTranslation } from 'next-i18next';
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export interface Props extends GridProps {
|
export interface Props extends GridProps {
|
||||||
@@ -13,7 +12,6 @@ export interface Props extends GridProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
|
const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const sizeMap = useMemo(() => {
|
const sizeMap = useMemo(() => {
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case 'sm':
|
case 'sm':
|
||||||
@@ -63,7 +61,7 @@ const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) =>
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MyIcon mr={2} name={item.icon as IconName} w={'16px'} />
|
<MyIcon mr={2} name={item.icon as IconName} w={'16px'} />
|
||||||
{t(item.label)}
|
{item.label}
|
||||||
</Flex>
|
</Flex>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ const Tabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
|
|||||||
p={sizeMap.outP}
|
p={sizeMap.outP}
|
||||||
borderRadius={'sm'}
|
borderRadius={'sm'}
|
||||||
fontSize={sizeMap.fontSize}
|
fontSize={sizeMap.fontSize}
|
||||||
|
overflowX={'auto'}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{list.map((item) => (
|
{list.map((item) => (
|
||||||
@@ -50,6 +51,8 @@ const Tabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
|
|||||||
py={sizeMap.inlineP}
|
py={sizeMap.inlineP}
|
||||||
textAlign={'center'}
|
textAlign={'center'}
|
||||||
borderBottom={'2px solid transparent'}
|
borderBottom={'2px solid transparent'}
|
||||||
|
px={3}
|
||||||
|
whiteSpace={'nowrap'}
|
||||||
{...(activeId === item.id
|
{...(activeId === item.id
|
||||||
? {
|
? {
|
||||||
color: 'myBlue.700',
|
color: 'myBlue.700',
|
||||||
|
|||||||
@@ -16,17 +16,10 @@ export const BillSourceMap: Record<`${BillSourceEnum}`, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export enum PromotionEnum {
|
export enum PromotionEnum {
|
||||||
invite = 'invite',
|
register = 'register',
|
||||||
shareModel = 'shareModel',
|
pay = 'pay'
|
||||||
withdraw = 'withdraw'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PromotionTypeMap = {
|
|
||||||
[PromotionEnum.invite]: '好友充值',
|
|
||||||
[PromotionEnum.shareModel]: '应用分享',
|
|
||||||
[PromotionEnum.withdraw]: '提现'
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum InformTypeEnum {
|
export enum InformTypeEnum {
|
||||||
system = 'system'
|
system = 'system'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ const queryClient = new QueryClient({
|
|||||||
|
|
||||||
function App({ Component, pageProps }: AppProps) {
|
function App({ Component, pageProps }: AppProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { hiId } = router.query as { hiId?: string };
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
|
|
||||||
const [scripts, setScripts] = useState<FeConfigsType['scripts']>([]);
|
const [scripts, setScripts] = useState<FeConfigsType['scripts']>([]);
|
||||||
@@ -50,6 +51,10 @@ function App({ Component, pageProps }: AppProps) {
|
|||||||
})();
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
hiId && localStorage.setItem('inviterId', hiId);
|
||||||
|
}, [hiId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const lang = getLangStore() || 'zh';
|
const lang = getLangStore() || 'zh';
|
||||||
i18n?.changeLanguage?.(lang);
|
i18n?.changeLanguage?.(lang);
|
||||||
|
|||||||
148
client/src/pages/account/components/Promotion.tsx
Normal file
148
client/src/pages/account/components/Promotion.tsx
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Grid,
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
BoxProps,
|
||||||
|
useTheme,
|
||||||
|
Button,
|
||||||
|
Table,
|
||||||
|
Thead,
|
||||||
|
Tbody,
|
||||||
|
Tr,
|
||||||
|
Th,
|
||||||
|
Td,
|
||||||
|
TableContainer
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getPromotionInitData, getPromotionRecords } from '@/api/user';
|
||||||
|
import { useUserStore } from '@/store/user';
|
||||||
|
import { useLoading } from '@/hooks/useLoading';
|
||||||
|
|
||||||
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
|
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||||
|
import { useCopyData } from '@/utils/tools';
|
||||||
|
import { usePagination } from '@/hooks/usePagination';
|
||||||
|
import { PromotionRecordType } from '@/api/response/user';
|
||||||
|
import MyIcon from '@/components/Icon';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
const Promotion = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const theme = useTheme();
|
||||||
|
const { copyData } = useCopyData();
|
||||||
|
const { userInfo } = useUserStore();
|
||||||
|
const { Loading } = useLoading();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: promotionRecords,
|
||||||
|
isLoading,
|
||||||
|
total,
|
||||||
|
pageSize,
|
||||||
|
Pagination
|
||||||
|
} = usePagination<PromotionRecordType>({
|
||||||
|
api: getPromotionRecords
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: { invitedAmount = 0, earningsAmount = 0 } = {} } = useQuery(
|
||||||
|
['getPromotionInitData'],
|
||||||
|
getPromotionInitData
|
||||||
|
);
|
||||||
|
|
||||||
|
const statisticsStyles: BoxProps = {
|
||||||
|
p: [4, 5],
|
||||||
|
border: theme.borders.base,
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: ['md', 'xl'],
|
||||||
|
borderRadius: 'md'
|
||||||
|
};
|
||||||
|
const titleStyles: BoxProps = {
|
||||||
|
mt: 2,
|
||||||
|
fontSize: ['lg', '28px'],
|
||||||
|
fontWeight: 'bold'
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex flexDirection={'column'} py={[0, 5]} px={5} h={'100%'} position={'relative'}>
|
||||||
|
<Grid gridTemplateColumns={['1fr 1fr', 'repeat(2,1fr)', 'repeat(4,1fr)']} gridGap={5}>
|
||||||
|
<Box {...statisticsStyles}>
|
||||||
|
<Box>{t('user.Amount of inviter')}</Box>
|
||||||
|
<Box {...titleStyles}>{invitedAmount}</Box>
|
||||||
|
</Box>
|
||||||
|
<Box {...statisticsStyles}>
|
||||||
|
<Box>{t('user.Amount of earnings')}</Box>
|
||||||
|
<Box {...titleStyles}>{earningsAmount}</Box>
|
||||||
|
</Box>
|
||||||
|
<Box {...statisticsStyles}>
|
||||||
|
<Flex alignItems={'center'} justifyContent={'center'}>
|
||||||
|
<Box>{t('user.Promotion Rate')}</Box>
|
||||||
|
<MyTooltip label={t('user.Promotion rate tip')}>
|
||||||
|
<QuestionOutlineIcon ml={1} />
|
||||||
|
</MyTooltip>
|
||||||
|
</Flex>
|
||||||
|
<Box {...titleStyles}>{userInfo?.promotionRate || 15}%</Box>
|
||||||
|
</Box>
|
||||||
|
<Box {...statisticsStyles}>
|
||||||
|
<Flex alignItems={'center'} justifyContent={'center'}>
|
||||||
|
<Box>{t('user.Invite Url')}</Box>
|
||||||
|
<MyTooltip label={t('user.Invite url tip')}>
|
||||||
|
<QuestionOutlineIcon ml={1} />
|
||||||
|
</MyTooltip>
|
||||||
|
</Flex>
|
||||||
|
<Button
|
||||||
|
mt={4}
|
||||||
|
variant={'base'}
|
||||||
|
fontSize={'sm'}
|
||||||
|
onClick={() => {
|
||||||
|
copyData(`${location.origin}/?hiId=${userInfo?._id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('user.Copy invite url')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Box mt={5}>
|
||||||
|
<TableContainer position={'relative'} overflow={'hidden'} minH={'100px'}>
|
||||||
|
<Table>
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>时间</Th>
|
||||||
|
<Th>类型</Th>
|
||||||
|
<Th>金额</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody fontSize={'sm'}>
|
||||||
|
{promotionRecords.map((item) => (
|
||||||
|
<Tr key={item._id}>
|
||||||
|
<Td>
|
||||||
|
{item.createTime ? dayjs(item.createTime).format('YYYY/MM/DD HH:mm:ss') : '-'}
|
||||||
|
</Td>
|
||||||
|
<Td>{t(`user.promotion.${item.type}`)}</Td>
|
||||||
|
<Td>{item.amount}</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
{!isLoading && promotionRecords.length === 0 && (
|
||||||
|
<Flex mt={'10vh'} flexDirection={'column'} alignItems={'center'}>
|
||||||
|
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||||
|
<Box mt={2} color={'myGray.500'}>
|
||||||
|
无邀请记录~
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
{total > pageSize && (
|
||||||
|
<Flex mt={4} justifyContent={'flex-end'}>
|
||||||
|
<Pagination />
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
<Loading loading={isLoading} fixed={false} />
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Promotion;
|
||||||
@@ -12,7 +12,11 @@ import Tabs from '@/components/Tabs';
|
|||||||
import UserInfo from './components/Info';
|
import UserInfo from './components/Info';
|
||||||
import { serviceSideProps } from '@/utils/i18n';
|
import { serviceSideProps } from '@/utils/i18n';
|
||||||
import { feConfigs } from '@/store/static';
|
import { feConfigs } from '@/store/static';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const Promotion = dynamic(() => import('./components/Promotion'), {
|
||||||
|
ssr: false
|
||||||
|
});
|
||||||
const BillTable = dynamic(() => import('./components/BillTable'), {
|
const BillTable = dynamic(() => import('./components/BillTable'), {
|
||||||
ssr: false
|
ssr: false
|
||||||
});
|
});
|
||||||
@@ -25,6 +29,7 @@ const InformTable = dynamic(() => import('./components/InformTable'), {
|
|||||||
|
|
||||||
enum TabEnum {
|
enum TabEnum {
|
||||||
'info' = 'info',
|
'info' = 'info',
|
||||||
|
'promotion' = 'promotion',
|
||||||
'bill' = 'bill',
|
'bill' = 'bill',
|
||||||
'pay' = 'pay',
|
'pay' = 'pay',
|
||||||
'inform' = 'inform',
|
'inform' = 'inform',
|
||||||
@@ -32,40 +37,42 @@ enum TabEnum {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const tabList = useRef([
|
const tabList = useRef([
|
||||||
{
|
{
|
||||||
icon: 'meLight',
|
icon: 'meLight',
|
||||||
label: 'user.Personal Information',
|
label: t('user.Personal Information'),
|
||||||
id: TabEnum.info,
|
id: TabEnum.info
|
||||||
Component: <BillTable />
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
icon: 'billRecordLight',
|
icon: 'billRecordLight',
|
||||||
label: 'user.Usage Record',
|
label: t('user.Usage Record'),
|
||||||
id: TabEnum.bill,
|
id: TabEnum.bill
|
||||||
Component: <BillTable />
|
|
||||||
},
|
},
|
||||||
...(feConfigs?.show_userDetail
|
...(feConfigs?.show_userDetail
|
||||||
? [
|
? [
|
||||||
|
{
|
||||||
|
icon: 'promotionLight',
|
||||||
|
label: t('user.Promotion Record'),
|
||||||
|
id: TabEnum.promotion
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: 'payRecordLight',
|
icon: 'payRecordLight',
|
||||||
label: 'user.Recharge Record',
|
label: t('user.Recharge Record'),
|
||||||
id: TabEnum.pay,
|
id: TabEnum.pay
|
||||||
Component: <PayRecordTable />
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
{
|
{
|
||||||
icon: 'informLight',
|
icon: 'informLight',
|
||||||
label: 'user.Notice',
|
label: t('user.Notice'),
|
||||||
id: TabEnum.inform,
|
id: TabEnum.inform
|
||||||
Component: <InformTable />
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'loginoutLight',
|
icon: 'loginoutLight',
|
||||||
label: 'user.Sign Out',
|
label: t('user.Sign Out'),
|
||||||
id: TabEnum.loginout,
|
id: TabEnum.loginout
|
||||||
Component: () => <></>
|
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -122,7 +129,6 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
|||||||
<Box mb={3}>
|
<Box mb={3}>
|
||||||
<Tabs
|
<Tabs
|
||||||
m={'auto'}
|
m={'auto'}
|
||||||
w={'90%'}
|
|
||||||
size={isPc ? 'md' : 'sm'}
|
size={isPc ? 'md' : 'sm'}
|
||||||
list={tabList.current.map((item) => ({
|
list={tabList.current.map((item) => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
@@ -136,6 +142,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
|||||||
|
|
||||||
<Box flex={'1 0 0'} h={'100%'} pb={[4, 0]}>
|
<Box flex={'1 0 0'} h={'100%'} pb={[4, 0]}>
|
||||||
{currentTab === TabEnum.info && <UserInfo />}
|
{currentTab === TabEnum.info && <UserInfo />}
|
||||||
|
{currentTab === TabEnum.promotion && <Promotion />}
|
||||||
{currentTab === TabEnum.bill && <BillTable />}
|
{currentTab === TabEnum.bill && <BillTable />}
|
||||||
{currentTab === TabEnum.pay && <PayRecordTable />}
|
{currentTab === TabEnum.pay && <PayRecordTable />}
|
||||||
{currentTab === TabEnum.inform && <InformTable />}
|
{currentTab === TabEnum.inform && <InformTable />}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ type GithubUserType = {
|
|||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||||
try {
|
try {
|
||||||
const { code } = req.query as { code: string };
|
const { code, inviterId } = req.query as { code: string; inviterId?: string };
|
||||||
|
|
||||||
const { data: gitAccessToken } = await axios.post<string>(
|
const { data: gitAccessToken } = await axios.post<string>(
|
||||||
`https://github.com/login/oauth/access_token?client_id=${global.feConfigs.gitLoginKey}&client_secret=${global.systemEnv.gitLoginSecret}&code=${code}`
|
`https://github.com/login/oauth/access_token?client_id=${global.feConfigs.gitLoginKey}&client_secret=${global.systemEnv.gitLoginSecret}&code=${code}`
|
||||||
@@ -51,7 +51,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err?.code === 500) {
|
if (err?.code === 500) {
|
||||||
jsonRes(res, {
|
jsonRes(res, {
|
||||||
data: await registerUser({ username, avatar: avatar_url, res })
|
data: await registerUser({ username, avatar: avatar_url, res, inviterId })
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -88,17 +88,21 @@ export async function loginByUsername({
|
|||||||
export async function registerUser({
|
export async function registerUser({
|
||||||
username,
|
username,
|
||||||
avatar,
|
avatar,
|
||||||
|
inviterId,
|
||||||
res
|
res
|
||||||
}: {
|
}: {
|
||||||
username: string;
|
username: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
|
inviterId?: string;
|
||||||
res: NextApiResponse;
|
res: NextApiResponse;
|
||||||
}) {
|
}) {
|
||||||
const response = await User.create({
|
const response = await User.create({
|
||||||
username,
|
username,
|
||||||
avatar,
|
avatar,
|
||||||
password: nanoid()
|
password: nanoid(),
|
||||||
|
inviterId
|
||||||
});
|
});
|
||||||
|
console.log(response, '-=-=-=');
|
||||||
|
|
||||||
// 根据 id 获取用户信息
|
// 根据 id 获取用户信息
|
||||||
const user = await User.findById(response._id);
|
const user = await User.findById(response._id);
|
||||||
|
|||||||
51
client/src/pages/api/user/promotion/getPromotionData.ts
Normal file
51
client/src/pages/api/user/promotion/getPromotionData.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { jsonRes } from '@/service/response';
|
||||||
|
import { connectToDatabase, User, promotionRecord } from '@/service/mongo';
|
||||||
|
import { authUser } from '@/service/utils/auth';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
try {
|
||||||
|
await connectToDatabase();
|
||||||
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
|
|
||||||
|
const invitedAmount = await User.countDocuments({
|
||||||
|
inviterId: userId
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计算累计合
|
||||||
|
const countHistory: { totalAmount: number }[] = await promotionRecord.aggregate([
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
userId: new mongoose.Types.ObjectId(userId),
|
||||||
|
amount: { $gt: 0 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: null, // 分组条件,这里使用 null 表示不分组
|
||||||
|
totalAmount: { $sum: '$amount' } // 计算 amount 字段的总和
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$project: {
|
||||||
|
_id: false, // 排除 _id 字段
|
||||||
|
totalAmount: true // 只返回 totalAmount 字段
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
jsonRes(res, {
|
||||||
|
data: {
|
||||||
|
invitedAmount,
|
||||||
|
historyAmount: countHistory[0]?.totalAmount || 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
jsonRes(res, {
|
||||||
|
code: 500,
|
||||||
|
error: err
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
44
client/src/pages/api/user/promotion/getPromotions.ts
Normal file
44
client/src/pages/api/user/promotion/getPromotions.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { jsonRes } from '@/service/response';
|
||||||
|
import { connectToDatabase, promotionRecord } from '@/service/mongo';
|
||||||
|
import { authUser } from '@/service/utils/auth';
|
||||||
|
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
try {
|
||||||
|
let { pageNum = 1, pageSize = 10 } = req.body as {
|
||||||
|
pageNum: number;
|
||||||
|
pageSize: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
|
|
||||||
|
await connectToDatabase();
|
||||||
|
|
||||||
|
const data = await promotionRecord
|
||||||
|
.find(
|
||||||
|
{
|
||||||
|
userId
|
||||||
|
},
|
||||||
|
'_id createTime type amount'
|
||||||
|
)
|
||||||
|
.sort({ _id: -1 })
|
||||||
|
.skip((pageNum - 1) * pageSize)
|
||||||
|
.limit(pageSize);
|
||||||
|
|
||||||
|
jsonRes(res, {
|
||||||
|
data: {
|
||||||
|
pageNum,
|
||||||
|
pageSize,
|
||||||
|
data,
|
||||||
|
total: await promotionRecord.countDocuments({
|
||||||
|
userId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
jsonRes(res, {
|
||||||
|
code: 500,
|
||||||
|
error: err
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,6 @@ interface RegisterType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
|
const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
|
||||||
const { inviterId = '' } = useRouter().query as { inviterId: string };
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@@ -58,7 +57,7 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
|
|||||||
username,
|
username,
|
||||||
code,
|
code,
|
||||||
password,
|
password,
|
||||||
inviterId: inviterId || localStorage.getItem('inviterId') || ''
|
inviterId: localStorage.getItem('inviterId') || ''
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
toast({
|
toast({
|
||||||
@@ -81,7 +80,7 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
|
|||||||
}
|
}
|
||||||
setRequesting(false);
|
setRequesting(false);
|
||||||
},
|
},
|
||||||
[inviterId, loginSuccess, toast]
|
[loginSuccess, toast]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -44,7 +44,10 @@ const provider = ({ code }: { code: string }) => {
|
|||||||
try {
|
try {
|
||||||
const res = await (async () => {
|
const res = await (async () => {
|
||||||
if (loginStore.provider === 'git') {
|
if (loginStore.provider === 'git') {
|
||||||
return gitLogin(code);
|
return gitLogin({
|
||||||
|
code,
|
||||||
|
inviterId: localStorage.getItem('inviterId') || ''
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const PromotionRecordSchema = new Schema({
|
|||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
enum: ['invite', 'shareModel', 'withdraw']
|
enum: ['invite', 'register']
|
||||||
},
|
},
|
||||||
amount: {
|
amount: {
|
||||||
type: Number,
|
type: Number,
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ const UserSchema = new Schema({
|
|||||||
default: '/icon/human.png'
|
default: '/icon/human.png'
|
||||||
},
|
},
|
||||||
balance: {
|
balance: {
|
||||||
// 平台余额,不可提现
|
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 2 * PRICE_SCALE
|
default: 2 * PRICE_SCALE
|
||||||
},
|
},
|
||||||
@@ -35,6 +34,10 @@ const UserSchema = new Schema({
|
|||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'user'
|
ref: 'user'
|
||||||
},
|
},
|
||||||
|
promotionRate: {
|
||||||
|
type: Number,
|
||||||
|
default: 15
|
||||||
|
},
|
||||||
limit: {
|
limit: {
|
||||||
exportKbTime: {
|
exportKbTime: {
|
||||||
// Every half hour
|
// Every half hour
|
||||||
|
|||||||
2
client/src/types/mongoSchema.d.ts
vendored
2
client/src/types/mongoSchema.d.ts
vendored
@@ -13,8 +13,8 @@ export interface UserModelSchema {
|
|||||||
password: string;
|
password: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
balance: number;
|
balance: number;
|
||||||
|
promotionRate: number;
|
||||||
inviterId?: string;
|
inviterId?: string;
|
||||||
promotionAmount: number;
|
|
||||||
openaiKey: string;
|
openaiKey: string;
|
||||||
createTime: number;
|
createTime: number;
|
||||||
openaiAccount?: {
|
openaiAccount?: {
|
||||||
|
|||||||
1
client/src/types/user.d.ts
vendored
1
client/src/types/user.d.ts
vendored
@@ -5,6 +5,7 @@ export interface UserType {
|
|||||||
username: string;
|
username: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
balance: number;
|
balance: number;
|
||||||
|
promotionRate: UserModelSchema['promotionRate'];
|
||||||
openaiAccount: UserModelSchema['openaiAccount'];
|
openaiAccount: UserModelSchema['openaiAccount'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user