From eaaf6f5978a9ce2f67169a95f85f96d8cd2faad6 Mon Sep 17 00:00:00 2001
From: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
Date: Fri, 23 Aug 2024 17:14:07 +0800
Subject: [PATCH] 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
---
.../web/components/common/Icon/constants.ts | 9 +-
.../common/Icon/icons/support/bill/wallet.svg | 5 ++
.../web/components/common/MyModal/index.tsx | 3 +
packages/web/i18n/zh/user.json | 13 ++-
.../account/components/ConversionModal.tsx | 90 +++++++++++++++++++
.../app/src/pages/account/components/Info.tsx | 85 ++++++++++--------
.../app/src/web/support/wallet/bill/api.ts | 2 +
7 files changed, 165 insertions(+), 42 deletions(-)
create mode 100644 packages/web/components/common/Icon/icons/support/bill/wallet.svg
create mode 100644 projects/app/src/pages/account/components/ConversionModal.tsx
diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts
index 0b4bafaa2..a94f868d5 100644
--- a/packages/web/components/common/Icon/constants.ts
+++ b/packages/web/components/common/Icon/constants.ts
@@ -30,9 +30,9 @@ export const iconPaths = {
'common/gitInlight': () => import('./icons/common/gitInlight.svg'),
'common/gitLight': () => import('./icons/common/gitLight.svg'),
'common/googleFill': () => import('./icons/common/googleFill.svg'),
+ 'common/help': () => import('./icons/common/help.svg'),
'common/importLight': () => import('./icons/common/importLight.svg'),
'common/info': () => import('./icons/common/info.svg'),
- 'common/help': () => import('./icons/common/help.svg'),
'common/inviteLight': () => import('./icons/common/inviteLight.svg'),
'common/language/en': () => import('./icons/common/language/en.svg'),
'common/language/zh': () => import('./icons/common/language/zh.svg'),
@@ -244,10 +244,7 @@ export const iconPaths = {
delete: () => import('./icons/delete.svg'),
edit: () => import('./icons/edit.svg'),
empty: () => import('./icons/empty.svg'),
- paragraph: () => import('./icons/paragraph.svg'),
export: () => import('./icons/export.svg'),
- point: () => import('./icons/point.svg'),
- infoRounded: () => import('./icons/infoRounded.svg'),
'file/csv': () => import('./icons/file/csv.svg'),
'file/fill/csv': () => import('./icons/file/fill/csv.svg'),
'file/fill/doc': () => import('./icons/file/fill/doc.svg'),
@@ -268,6 +265,7 @@ export const iconPaths = {
'file/qaImport': () => import('./icons/file/qaImport.svg'),
'file/uploadFile': () => import('./icons/file/uploadFile.svg'),
history: () => import('./icons/history.svg'),
+ infoRounded: () => import('./icons/infoRounded.svg'),
kbTest: () => import('./icons/kbTest.svg'),
menu: () => import('./icons/menu.svg'),
minus: () => import('./icons/minus.svg'),
@@ -283,11 +281,13 @@ export const iconPaths = {
more: () => import('./icons/more.svg'),
moreLine: () => import('./icons/moreLine.svg'),
out: () => import('./icons/out.svg'),
+ paragraph: () => import('./icons/paragraph.svg'),
'phoneTabbar/me': () => import('./icons/phoneTabbar/me.svg'),
'phoneTabbar/tool': () => import('./icons/phoneTabbar/tool.svg'),
'phoneTabbar/toolFill': () => import('./icons/phoneTabbar/toolFill.svg'),
'plugins/doc2x': () => import('./icons/plugins/doc2x.svg'),
'plugins/textEditor': () => import('./icons/plugins/textEditor.svg'),
+ point: () => import('./icons/point.svg'),
'price/bg': () => import('./icons/price/bg.svg'),
'price/right': () => import('./icons/price/right.svg'),
save: () => import('./icons/save.svg'),
@@ -301,6 +301,7 @@ export const iconPaths = {
'support/bill/payRecordLight': () => import('./icons/support/bill/payRecordLight.svg'),
'support/bill/priceLight': () => import('./icons/support/bill/priceLight.svg'),
'support/bill/shoppingCart': () => import('./icons/support/bill/shoppingCart.svg'),
+ 'support/bill/wallet': () => import('./icons/support/bill/wallet.svg'),
'support/outlink/apikeyFill': () => import('./icons/support/outlink/apikeyFill.svg'),
'support/outlink/apikeyLight': () => import('./icons/support/outlink/apikeyLight.svg'),
'support/outlink/iframeLight': () => import('./icons/support/outlink/iframeLight.svg'),
diff --git a/packages/web/components/common/Icon/icons/support/bill/wallet.svg b/packages/web/components/common/Icon/icons/support/bill/wallet.svg
new file mode 100644
index 000000000..1261d5dd0
--- /dev/null
+++ b/packages/web/components/common/Icon/icons/support/bill/wallet.svg
@@ -0,0 +1,5 @@
+
diff --git a/packages/web/components/common/MyModal/index.tsx b/packages/web/components/common/MyModal/index.tsx
index 9c9988ef0..e0f3fd0c6 100644
--- a/packages/web/components/common/MyModal/index.tsx
+++ b/packages/web/components/common/MyModal/index.tsx
@@ -14,6 +14,7 @@ import Avatar from '../Avatar';
export interface MyModalProps extends ModalContentProps {
iconSrc?: string;
+ iconColor?: string;
title?: any;
isCentered?: boolean;
isLoading?: boolean;
@@ -33,6 +34,7 @@ const MyModal = ({
w = 'auto',
maxW = ['90vw', '600px'],
closeOnOverlayClick = true,
+ iconColor,
...props
}: MyModalProps) => {
const { isPc } = useSystem();
@@ -71,6 +73,7 @@ const MyModal = ({
{iconSrc && (
<>
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 (
+
+
+ {loading && }
+
+
+
+ {t('user:bill.use_balance_hint')}
+
+
+
+ {t('user:bill.price')}
+
+
+ ¥15/1000 {t('user:bill.tokens')}
+
+
+
+
+ {t('user:bill.balance')}
+
+
+ ¥{balance}
+
+
+
+
+ {t('user:bill.you_can_convert')}
+
+
+ {tokens} {t('user:bill.tokens')}
+
+ {t('user:bill.token_expire_1year')}
+
+
+
+
+
+ {t('user:bill.contact_customer_service')}
+
+
+
+
+
+ );
+};
+
+export default ConversionModal;
diff --git a/projects/app/src/pages/account/components/Info.tsx b/projects/app/src/pages/account/components/Info.tsx
index 8a832c476..d9bc8094f 100644
--- a/projects/app/src/pages/account/components/Info.tsx
+++ b/projects/app/src/pages/account/components/Info.tsx
@@ -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 (
-
- {isPc ? (
-
-
-
-
-
+ <>
+
+ {isPc ? (
+
+
+
+
+
+
-
- {!!standardPlan && (
-
-
-
- )}
-
- ) : (
- <>
-
- {standardPlan && }
-
- >
- )}
-
+ {!!standardPlan && (
+
+
+
+ )}
+
+ ) : (
+ <>
+
+ {standardPlan && }
+
+ >
+ )}
+
+ {isOpenContact && }
+ >
);
};
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({
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 = () => {
- {feConfigs?.isPlus && (
+ {feConfigs?.isPlus && (userInfo?.team?.balance ?? 0) > 0 && (
{t('common:user.team.Balance')}:
{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)} 元
- {feConfigs?.show_pay && userInfo?.team?.permission.hasWritePer && (
-
)}
- {isOpenPayModal && }
+ {isOpenConversionModal && (
+
+ )}
{isOpenUpdatePsw && }
{isOpenUpdateNotification && }
@@ -552,7 +564,8 @@ const PlanUsage = () => {
) : 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={}
- onClick={onOpenConcat}
+ onClick={onOpenContact}
h={'48px'}
fontSize={'sm'}
>
@@ -710,7 +722,6 @@ const Other = () => {
onClose={onCloseOpenai}
/>
)}
- {isOpenConcat && }
);
};
diff --git a/projects/app/src/web/support/wallet/bill/api.ts b/projects/app/src/web/support/wallet/bill/api.ts
index d6dc86700..ce178f38a 100644
--- a/projects/app/src/web/support/wallet/bill/api.ts
+++ b/projects/app/src/web/support/wallet/bill/api.ts
@@ -20,3 +20,5 @@ export const checkBalancePayResult = (payId: string) =>
} catch (error) {}
return data;
});
+
+export const balanceConversion = () => GET(`/proApi/support/wallet/bill/balanceConversion`);