mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-29 01:40:51 +00:00
feat(fe): balance conversion button and modal (#2491)
* feat: add balance conversion api declaration * feat(fe): add conversion modal * fix: show button when standplan and the user has manage permission * feat: hide balance when <= 0
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import React from 'react';
|
||||
import { ModalBody, Box, Button, VStack, HStack, Link } from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Icon from '@fastgpt/web/components/common/Icon';
|
||||
import Tag from '@fastgpt/web/components/common/Tag';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { balanceConversion } from '@/web/support/wallet/bill/api';
|
||||
import Loading from '@fastgpt/web/components/common/MyLoading';
|
||||
|
||||
const ConversionModal = ({
|
||||
onClose,
|
||||
balance,
|
||||
tokens,
|
||||
onOpenContact
|
||||
}: {
|
||||
onClose: () => void;
|
||||
balance: string;
|
||||
tokens: string;
|
||||
onOpenContact: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { runAsync: onConvert, loading } = useRequest2(balanceConversion, {
|
||||
successToast: t('user:bill.convert_success'),
|
||||
errorToast: t('user:bill.convert_error')
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc="support/bill/wallet"
|
||||
iconColor="primary.600"
|
||||
title={t('user:bill.use_balance')}
|
||||
>
|
||||
<ModalBody maxW={'450px'}>
|
||||
{loading && <Loading />}
|
||||
<VStack px="2.25" gap={2} pb="6">
|
||||
<HStack px="4" py="2" color="primary.600" bgColor="primary.50" borderRadius="md">
|
||||
<Icon name="common/info" w="1rem" mr="1" />
|
||||
<Box fontSize={'sm'}>{t('user:bill.use_balance_hint')}</Box>
|
||||
</HStack>
|
||||
<VStack mt={6}>
|
||||
<Box fontSize={'sm'} color="myGray.600" fontWeight="500">
|
||||
{t('user:bill.price')}
|
||||
</Box>
|
||||
<Box fontSize={'xl'} fontWeight={'700'} color="myGray.900">
|
||||
¥15/1000 {t('user:bill.tokens')}
|
||||
</Box>
|
||||
</VStack>
|
||||
<VStack mt={6}>
|
||||
<Box fontSize={'sm'} color="myGray.600" fontWeight="500">
|
||||
{t('user:bill.balance')}
|
||||
</Box>
|
||||
<Box fontSize={'xl'} fontWeight={'700'} color="myGray.900">
|
||||
¥{balance}
|
||||
</Box>
|
||||
</VStack>
|
||||
<VStack mt={6}>
|
||||
<Box fontSize={'sm'} color="myGray.600" fontWeight="500">
|
||||
{t('user:bill.you_can_convert')}
|
||||
</Box>
|
||||
<Box fontSize={'xl'} fontWeight={'700'} color="myGray.900">
|
||||
{tokens} {t('user:bill.tokens')}
|
||||
</Box>
|
||||
<Tag>{t('user:bill.token_expire_1year')}</Tag>
|
||||
</VStack>
|
||||
|
||||
<VStack mt="6">
|
||||
<Button
|
||||
variant={'primary'}
|
||||
alignItems={'center'}
|
||||
fontSize={'sm'}
|
||||
minW={'10rem'}
|
||||
onClick={onConvert}
|
||||
>
|
||||
{t('user:bill.conversion')}
|
||||
</Button>
|
||||
<Link color="primary" mt="2" onClick={onOpenContact}>
|
||||
{t('user:bill.contact_customer_service')}
|
||||
</Link>
|
||||
</VStack>
|
||||
</VStack>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConversionModal;
|
@@ -48,7 +48,7 @@ import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
const StandDetailModal = dynamic(() => import('./standardDetailModal'));
|
||||
const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu'));
|
||||
const PayModal = dynamic(() => import('./PayModal'));
|
||||
const ConversionModal = dynamic(() => import('./ConversionModal'));
|
||||
const UpdatePswModal = dynamic(() => import('./UpdatePswModal'));
|
||||
const UpdateNotification = dynamic(() => import('./UpdateNotificationModal'));
|
||||
const OpenAIAccountModal = dynamic(() => import('./OpenAIAccountModal'));
|
||||
@@ -59,41 +59,45 @@ const Account = () => {
|
||||
const { isPc } = useSystem();
|
||||
const { teamPlanStatus } = useUserStore();
|
||||
const standardPlan = teamPlanStatus?.standardConstants;
|
||||
const { isOpen: isOpenContact, onClose: onCloseContact, onOpen: onOpenContact } = useDisclosure();
|
||||
|
||||
const { initUserInfo } = useUserStore();
|
||||
|
||||
useQuery(['init'], initUserInfo);
|
||||
|
||||
return (
|
||||
<Box py={[3, '28px']} maxW={['95vw', '1080px']} px={[5, 10]} mx={'auto'}>
|
||||
{isPc ? (
|
||||
<Flex justifyContent={'center'}>
|
||||
<Box flex={'0 0 330px'}>
|
||||
<MyInfo />
|
||||
<Box mt={9}>
|
||||
<Other />
|
||||
<>
|
||||
<Box py={[3, '28px']} maxW={['95vw', '1080px']} px={[5, 10]} mx={'auto'}>
|
||||
{isPc ? (
|
||||
<Flex justifyContent={'center'}>
|
||||
<Box flex={'0 0 330px'}>
|
||||
<MyInfo onOpenContact={onOpenContact} />
|
||||
<Box mt={9}>
|
||||
<Other onOpenContact={onOpenContact} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
{!!standardPlan && (
|
||||
<Box ml={'45px'} flex={'1 0 0'} maxW={'600px'}>
|
||||
<PlanUsage />
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
) : (
|
||||
<>
|
||||
<MyInfo />
|
||||
{standardPlan && <PlanUsage />}
|
||||
<Other />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
{!!standardPlan && (
|
||||
<Box ml={'45px'} flex={'1 0 0'} maxW={'600px'}>
|
||||
<PlanUsage />
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
) : (
|
||||
<>
|
||||
<MyInfo onOpenContact={onOpenContact} />
|
||||
{standardPlan && <PlanUsage />}
|
||||
<Other onOpenContact={onOpenContact} />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
{isOpenContact && <CommunityModal onClose={onCloseContact} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Account);
|
||||
|
||||
const MyInfo = () => {
|
||||
const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
|
||||
const theme = useTheme();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { t } = useTranslation();
|
||||
@@ -101,13 +105,14 @@ const MyInfo = () => {
|
||||
const { reset } = useForm<UserUpdateParams>({
|
||||
defaultValues: userInfo as UserType
|
||||
});
|
||||
const { teamPlanStatus } = useUserStore();
|
||||
const standardPlan = teamPlanStatus?.standardConstants;
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const { toast } = useToast();
|
||||
const {
|
||||
isOpen: isOpenPayModal,
|
||||
onClose: onClosePayModal,
|
||||
onOpen: onOpenPayModal
|
||||
isOpen: isOpenConversionModal,
|
||||
onClose: onCloseConversionModal,
|
||||
onOpen: onOpenConversionModal
|
||||
} = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenUpdatePsw,
|
||||
@@ -293,23 +298,30 @@ const MyInfo = () => {
|
||||
<TeamMenu />
|
||||
</Box>
|
||||
</Flex>
|
||||
{feConfigs?.isPlus && (
|
||||
{feConfigs?.isPlus && (userInfo?.team?.balance ?? 0) > 0 && (
|
||||
<Box mt={6} whiteSpace={'nowrap'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box {...labelStyles}>{t('common:user.team.Balance')}: </Box>
|
||||
<Box flex={1}>
|
||||
<strong>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}</strong> 元
|
||||
</Box>
|
||||
{feConfigs?.show_pay && userInfo?.team?.permission.hasWritePer && (
|
||||
<Button variant={'whitePrimary'} size={'sm'} ml={5} onClick={onOpenPayModal}>
|
||||
{t('common:user.Pay')}
|
||||
{userInfo?.permission.hasManagePer && !!standardPlan && (
|
||||
<Button variant={'primary'} size={'sm'} ml={5} onClick={onOpenConversionModal}>
|
||||
{t('user:bill.conversion')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{isOpenPayModal && <PayModal onClose={onClosePayModal} />}
|
||||
{isOpenConversionModal && (
|
||||
<ConversionModal
|
||||
onClose={onCloseConversionModal}
|
||||
balance={formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}
|
||||
tokens={String((userInfo?.team?.balance ?? 0) / 15 / 10)}
|
||||
onOpenContact={onOpenContact}
|
||||
/>
|
||||
)}
|
||||
{isOpenUpdatePsw && <UpdatePswModal onClose={onCloseUpdatePsw} />}
|
||||
{isOpenUpdateNotification && <UpdateNotification onClose={onCloseUpdateNotification} />}
|
||||
<File onSelect={onSelectFile} />
|
||||
@@ -552,7 +564,8 @@ const PlanUsage = () => {
|
||||
</Box>
|
||||
) : null;
|
||||
};
|
||||
const Other = () => {
|
||||
|
||||
const Other = ({ onOpenContact }: { onOpenContact: () => void }) => {
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { feConfigs } = useSystemStore();
|
||||
@@ -563,7 +576,6 @@ const Other = () => {
|
||||
});
|
||||
const { isOpen: isOpenLaf, onClose: onCloseLaf, onOpen: onOpenLaf } = useDisclosure();
|
||||
const { isOpen: isOpenOpenai, onClose: onCloseOpenai, onOpen: onOpenOpenai } = useDisclosure();
|
||||
const { isOpen: isOpenConcat, onClose: onCloseConcat, onOpen: onOpenConcat } = useDisclosure();
|
||||
|
||||
const onclickSave = useCallback(
|
||||
async (data: UserType) => {
|
||||
@@ -686,7 +698,7 @@ const Other = () => {
|
||||
variant={'whiteBase'}
|
||||
justifyContent={'flex-start'}
|
||||
leftIcon={<MyIcon name={'modal/concat'} w={'18px'} color={'myGray.600'} />}
|
||||
onClick={onOpenConcat}
|
||||
onClick={onOpenContact}
|
||||
h={'48px'}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
@@ -710,7 +722,6 @@ const Other = () => {
|
||||
onClose={onCloseOpenai}
|
||||
/>
|
||||
)}
|
||||
{isOpenConcat && <CommunityModal onClose={onCloseConcat} />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user