Files
FastGPT/projects/app/src/pages/price/components/Standard.tsx
Archer 10d8c56e23 V4.8.18 feature (#3565)
* feat: org CRUD (#3380)

* feat: add org schema

* feat: org manage UI

* feat: OrgInfoModal

* feat: org tree view

* feat: org management

* fix: init root org

* feat: org permission for app

* feat: org support for dataset

* fix: disable org role control

* styles: opt type signatures

* fix: remove unused permission

* feat: delete org collaborator

* perf: Team org ui (#3499)

* perf: org ui

* perf: org ui

* feat: org auth for app & dataset (#3498)

* feat: auth org resource permission

* feat: org auth support for app & dataset

* perf: org permission check (#3500)

* i18n (#3501)

* name

* i18n

* feat: support dataset changeOwner (#3483)

* feat: support dataset changeOwner

* chore: update dataset change owner api

* feat: permission manage UI for org (#3503)

* perf: password check;perf: image upload check;perf: sso login check (#3509)

* perf: password check

* perf: image upload check

* perf: sso login check

* force show update notification modal & fix login page text (#3512)

* fix login page English text

* update notification modal

* perf: notify account (#3515)

* perf(plugin): improve searXNG empty result handling and documentation (#3507)

* perf(plugin): improve searXNG empty result handling and documentation

* 修改了文档和代码部分无搜索的结果的反馈

* refactor: org pathId (#3516)

* optimize payment process (#3517)

* feat: support wecom sso (#3518)

* feat: support wecom sso

* chore: remove unused wecom js-sdk dependency

* fix qrcode script (#3520)

* fix qrcode script

* i18n

* perf: full text collection and search code;perf: rename function (#3519)

* perf: full text collection and search code

* perf: rename function

* perf: notify modal

* remove invalid code

* perf: sso login

* perf: pay process

* 4.8.18 test (#3524)

* perf: remove local token

* perf: index

* perf: file encoding;perf: leave team code;@c121914yu perf: full text search code (#3528)

* perf: text encoding

* perf: leave team code

* perf: full text search code

* fix: http status

* perf: embedding search and vector avatar

* perf: async read file (#3531)

* refactor: team permission  manager (#3535)

* perf: classify org, group and member

* refactor: team per manager

* fix: missing functions

* 4.8.18 test (#3543)

* perf: login check

* doc

* perf: llm model config

* perf: team clb config

* fix: MemberModal UI (#3553)

* fix: adapt MemberModal title and icon

* fix: adapt member modal

* fix: search input placeholder

* fix: add button text

* perf: org permission (#3556)

* docs:用户答疑的官方文档补充 (#3540)

* docs:用户答疑的官方文档补充

* 问题回答的内容修补

* share link random avatar (#3541)

* share link random avatar

* fix

* delete unused code

* share page avatar (#3558)

* feat: init 4818

* share page avatar

* feat: tmp upgrade code (#3559)

* feat: tmp upgrade code

* fulltext search test

* update action

* full text tmp code (#3561)

* full text tmp code

* fix: init

* fix: init

* remove tmp code

* remove tmp code

* 4818-alpha

* 4.8.18 test (#3562)

* full text tmp code

* fix: init

* upgrade code

* account log

* account log

* perf: dockerfile

* upgrade code

* chore: update docs app template submission (#3564)

---------

Co-authored-by: a.e. <49438478+I-Info@users.noreply.github.com>
Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
Co-authored-by: heheer <heheer@sealos.io>
Co-authored-by: Jiangween <145003935+Jiangween@users.noreply.github.com>
2025-01-11 15:15:38 +08:00

337 lines
11 KiB
TypeScript

import React, { useMemo, useState } from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { Box, Button, Flex, Grid, HStack } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { StandardSubLevelEnum, SubModeEnum } from '@fastgpt/global/support/wallet/sub/constants';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type';
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
import { getWxPayQRCode } from '@/web/support/wallet/bill/api';
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
import StandardPlanContentList from '@/components/support/wallet/StandardPlanContentList';
export enum PackageChangeStatusEnum {
buy = 'buy',
renewal = 'renewal',
upgrade = 'upgrade'
}
const Standard = ({
standardPlan: myStandardPlan,
onPaySuccess
}: {
standardPlan?: TeamSubSchema;
onPaySuccess?: () => void;
}) => {
const { t } = useTranslation();
const packagePayTextMap = {
[PackageChangeStatusEnum.buy]: t('common:pay.package_tip.buy'),
[PackageChangeStatusEnum.renewal]: t('common:pay.package_tip.renewal'),
[PackageChangeStatusEnum.upgrade]: t('common:pay.package_tip.upgrade')
};
const [packageChange, setPackageChange] = useState<PackageChangeStatusEnum>();
const { subPlans, feConfigs } = useSystemStore();
const [selectSubMode, setSelectSubMode] = useState<`${SubModeEnum}`>(SubModeEnum.month);
const standardSubList = useMemo(() => {
return subPlans?.standard
? Object.entries(subPlans.standard).map(([level, value]) => {
return {
price: value.price * (selectSubMode === SubModeEnum.month ? 1 : 10),
level: level as `${StandardSubLevelEnum}`,
...standardSubLevelMap[level as `${StandardSubLevelEnum}`],
label: value.name || standardSubLevelMap[level as `${StandardSubLevelEnum}`].label, // custom label
maxTeamMember: value.maxTeamMember,
maxAppAmount: value.maxAppAmount,
maxDatasetAmount: value.maxDatasetAmount,
chatHistoryStoreDuration: value.chatHistoryStoreDuration,
maxDatasetSize: value.maxDatasetSize,
permissionCustomApiKey: value.permissionCustomApiKey,
permissionCustomCopyright: value.permissionCustomCopyright,
trainingWeight: value.trainingWeight,
permissionReRank: value.permissionReRank,
totalPoints: value.totalPoints * (selectSubMode === SubModeEnum.month ? 1 : 12),
permissionWebsiteSync: value.permissionWebsiteSync
};
})
: [];
}, [subPlans?.standard, selectSubMode]);
// Pay code
const [qrPayData, setQRPayData] = useState<QRPayProps>();
/* Get pay code */
const { runAsync: onPay, loading: isLoading } = useRequest2(getWxPayQRCode, {
onSuccess(res) {
setQRPayData({
readPrice: res.readPrice,
codeUrl: res.codeUrl,
billId: res.billId
});
}
});
return (
<>
<Flex flexDirection={'column'} alignItems={'center'} position={'relative'}>
<Box>
<RowTabs
list={[
{
label: t('common:support.wallet.subscription.mode.Month'),
value: SubModeEnum.month
},
{
label: (
<Flex>
<Box whiteSpace={'nowrap'}>
{t('common:support.wallet.subscription.mode.Year')}
</Box>
<Box
whiteSpace={'nowrap'}
ml={1}
color={selectSubMode === SubModeEnum.month ? 'red.600' : 'auto'}
>
({t('common:support.wallet.subscription.mode.Year sale')})
</Box>
</Flex>
),
value: SubModeEnum.year
}
]}
value={selectSubMode}
onChange={(e) => setSelectSubMode(e as `${SubModeEnum}`)}
/>
</Box>
{/* card */}
<Grid
mt={[10, '48px']}
gridTemplateColumns={['1fr', 'repeat(2,1fr)', 'repeat(4,1fr)']}
gap={[4, 6, 8]}
w={'100%'}
maxW={'1440px'}
minH={'550px'}
>
{standardSubList.map((item) => {
const isCurrentPlan = item.level === myStandardPlan?.currentSubLevel;
const isHigherLevel =
standardSubLevelMap[item.level].weight >
standardSubLevelMap[myStandardPlan?.currentSubLevel || StandardSubLevelEnum.free]
.weight;
return (
<Box
key={item.level}
pos={'relative'}
flex={'1 0 0'}
bg={isCurrentPlan ? 'blue.50' : 'rgba(255, 255, 255, 0.90)'}
p={'28px'}
borderRadius={'xl'}
borderWidth={isCurrentPlan ? '4px' : '1.5px'}
boxShadow={'1.5'}
{...(isCurrentPlan
? {
borderColor: 'primary.600'
}
: {
borderColor: 'myGray.150'
})}
>
{isCurrentPlan && (
<Box
position={'absolute'}
right={0}
top={'1.62rem'}
px={3}
py={'0.38rem'}
color={'blue.700'}
fontSize={'xs'}
bg={'blue.200'}
fontWeight={'500'}
borderLeftRadius={'sm'}
>
{t('common:is_using')}
</Box>
)}
<Box fontSize={'md'} fontWeight={'500'} color={'myGray.900'}>
{t(item.label as any)}
</Box>
<Box fontSize={['32px', '42px']} fontWeight={'bold'} color={'myGray.900'}>
{item.price}
</Box>
<Box color={'myGray.500'} h={'40px'} fontSize={'xs'}>
{t(item.desc as any, { title: feConfigs?.systemTitle })}
</Box>
{/* Button */}
{(() => {
if (item.level === StandardSubLevelEnum.free) {
return (
<Button
mt={4}
mb={6}
_active={{}}
_hover={{}}
boxShadow={'0'}
cursor={'default'}
w={'100%'}
isDisabled
variant={'whiteBase'}
>
{t('common:free')}
</Button>
);
}
// feature:
// if (
// item.level === myStandardPlan?.nextSubLevel &&
// selectSubMode === myStandardPlan?.nextMode
// ) {
// return (
// <Button mt={4} mb={6} w={'100%'} variant={'whiteBase'} isDisabled>
// {t('common:support.wallet.subscription.Next plan')}
// </Button>
// );
// }
if (isCurrentPlan) {
return (
<Button
mt={4}
mb={6}
w={'100%'}
variant={'primary'}
isLoading={isLoading}
onClick={() => {
setPackageChange(PackageChangeStatusEnum.renewal);
onPay({
type: BillTypeEnum.standSubPlan,
level: item.level,
subMode: selectSubMode
});
}}
>
{t('user:bill.renew_plan')}
</Button>
);
}
if (isHigherLevel) {
return (
<Button
mt={4}
mb={6}
w={'100%'}
variant={'primaryGhost'}
isLoading={isLoading}
onClick={() => {
setPackageChange(PackageChangeStatusEnum.upgrade);
onPay({
type: BillTypeEnum.standSubPlan,
level: item.level,
subMode: selectSubMode
});
}}
>
{t('common:support.wallet.subscription.Upgrade plan')}
</Button>
);
}
return (
<Button
mt={4}
mb={6}
w={'100%'}
variant={'primaryGhost'}
isLoading={isLoading}
onClick={() => {
setPackageChange(PackageChangeStatusEnum.buy);
onPay({
type: BillTypeEnum.standSubPlan,
level: item.level,
subMode: selectSubMode
});
}}
>
{t('user:bill.buy_plan')}
</Button>
);
})()}
{/* function list */}
<StandardPlanContentList level={item.level} mode={selectSubMode} />
</Box>
);
})}
</Grid>
{!!qrPayData && packageChange && (
<QRCodePayModal
tip={packagePayTextMap[packageChange]}
onSuccess={onPaySuccess}
{...qrPayData}
/>
)}
</Flex>
</>
);
};
export default React.memo(Standard);
const RowTabs = ({
list,
value,
onChange
}: {
list: {
icon?: string;
label: string | React.ReactNode;
value: string;
}[];
value: string;
onChange: (e: string) => void;
}) => {
return (
<Box
display={'inline-flex'}
px={'3px'}
py={'3px'}
borderRadius={'md'}
borderWidth={'1px'}
borderColor={'primary.300'}
bg={'primary.50'}
gap={'4px'}
>
{list.map((item) => (
<Flex
key={item.value}
alignItems={'center'}
justifyContent={'center'}
cursor={'pointer'}
borderRadius={'sm'}
px={'12px'}
py={'7px'}
userSelect={'none'}
w={['150px', '170px']}
{...(value === item.value
? {
color: 'white',
boxShadow: '1.5',
bg: 'primary.600'
}
: {
onClick: () => onChange(item.value)
})}
>
{item.icon && <MyIcon name={item.icon as any} mr={1} w={'14px'} />}
<Box fontWeight={'500'}>{item.label}</Box>
</Flex>
))}
</Box>
);
};