Commit 9af4040f by siigure Committed by GitHub

feat: add new UI components for pricing plans (#7188)

* feat: update pricing page UI

* refactor(price): update PricePlanTabs styles and enhance translation support

* refactor(price): simplify PricePlanTabs and enhance tab handling logic

* refactor(price): streamline tab handling in PriceBox and import PriceTabType from constants
parent 7da51869
......@@ -918,7 +918,7 @@
"pay_corporate_payment": "Payment to the public",
"pay_money": "Amount payable",
"pay_success": "Payment successfully",
"pay_year_tip": "Pay 10 months, enjoy 1 year!",
"pay_year_tip": "Pay <italic>{{count}}</italic> months, enjoy 1 year!",
"permission.Collaborator": "Collaborator",
"permission.Manage": "Manage",
"permission.No InheritPermission": "Permission Inheritance Restricted",
......@@ -1080,6 +1080,11 @@
"support.wallet.subscription.Extra plan": "Extra Resource Pack",
"support.wallet.subscription.Extra plan tip": "When the standard package is not enough, you can purchase extra resource packs to continue using",
"support.wallet.subscription.FAQ": "FAQ",
"support.wallet.subscription.Basic plan tab": "Basic Plan",
"support.wallet.subscription.Extra points tab": "Extra Points",
"support.wallet.subscription.Extra dataset tab": "Extra Dataset Index",
"support.wallet.subscription.Extra points and dataset tab": "Extra Points / Dataset Index",
"support.wallet.subscription.Purchase plan": "Purchase Plan",
"support.wallet.subscription.Sub plan": "Subscription Package",
"support.wallet.subscription.Sub plan tip": "Free to use [{{title}}] or upgrade to a higher package",
"support.wallet.subscription.Sub plan tip wecom": "Purchase a plan to enjoy application services",
......@@ -1090,7 +1095,9 @@
"support.wallet.subscription.ai_model": "AI Language Model",
"support.wallet.subscription.extra_plan_disabled_tip": "Please subscribe to a plan first before purchasing extra resource packs.",
"support.wallet.subscription.mode.Month": "Month",
"support.wallet.subscription.mode.Month pay": "Monthly",
"support.wallet.subscription.mode.Year": "Year",
"support.wallet.subscription.mode.Year pay": "Yearly",
"support.wallet.subscription.per_year": "/ year",
"support.wallet.subscription.point": " Points",
"support.wallet.subscription.standardSubLevel.advanced": "Advanced",
......
......@@ -19,7 +19,7 @@
"custom_config_details": "定制配置详情",
"day": "天",
"extra_ai_points": "额外 AI 积分",
"extra_dataset_size": "额外知识库量",
"extra_dataset_size": "额外知识库索引量",
"generation_time": "生成时间",
"has_invoice": "是否已开票",
"hour": "小时",
......
......@@ -112,7 +112,7 @@
"bill_expired": "订单已过期",
"bill_not_found": "订单不存在",
"bill_not_pay_processed": "非在线订单",
"button.extra_dataset_size_tip": "您正在购买【额外知识库量】",
"button.extra_dataset_size_tip": "您正在购买【额外知识库索引量】",
"button.extra_points_tip": "您正在购买【额外 AI 积分】",
"can_copy_content_tip": "无法使用浏览器自动复制,请手动复制下面内容",
"cancel_bill": "取消",
......@@ -918,7 +918,7 @@
"pay_corporate_payment": "对公支付",
"pay_money": "应付金额",
"pay_success": "支付成功",
"pay_year_tip": "支付 10 个月,畅享 1 年!",
"pay_year_tip": "支付 <italic>{{count}}</italic> 个月,畅享一年!",
"permission.Collaborator": "协作者",
"permission.Manage": "管理",
"permission.No InheritPermission": "已限制权限,不再继承父级文件夹的权限,",
......@@ -1050,7 +1050,7 @@
"support.wallet.App_amount_not_sufficient": "您的应用数量已达上限,请升级套餐后继续使用。",
"support.wallet.Buy": "购买",
"support.wallet.Dataset_amount_not_sufficient": "您的知识库数量已达上限,请升级套餐后继续使用。",
"support.wallet.Dataset_not_sufficient": "您的知识库容量不足,请先升级套餐或购买额外知识库量后继续使用。",
"support.wallet.Dataset_not_sufficient": "您的知识库容量不足,请先升级套餐或购买额外知识库索引量后继续使用。",
"support.wallet.Not sufficient": "您的 AI 积分不足,请先升级套餐或购买额外 AI 积分后继续使用。",
"support.wallet.Not_sufficient_contact_admin": "您的 AI 积分不足,请联系团队管理员。",
"support.wallet.Team_member_over_size": "您的团队成员数量已达上限,请升级套餐后继续使用。",
......@@ -1075,11 +1075,16 @@
"support.wallet.subscription.Dataset size": "知识库索引量",
"support.wallet.subscription.Extra ai points": "额外 AI 积分",
"support.wallet.subscription.Extra dataset description": "支持根据实际需求为知识库索引量延长有效期",
"support.wallet.subscription.Extra dataset size": "额外知识库量",
"support.wallet.subscription.Extra dataset size": "额外知识库索引量",
"support.wallet.subscription.Extra dataset unit": "组/1个月",
"support.wallet.subscription.Extra plan": "额外资源包",
"support.wallet.subscription.Extra plan tip": "标准套餐不够时,您可以购买额外资源包继续使用",
"support.wallet.subscription.Extra plan tip": "基础套餐容量不足时,可购买额外资源包继续使用",
"support.wallet.subscription.FAQ": "常见问题",
"support.wallet.subscription.Basic plan tab": "基础套餐",
"support.wallet.subscription.Extra points tab": "额外积分",
"support.wallet.subscription.Extra dataset tab": "额外知识库索引量",
"support.wallet.subscription.Extra points and dataset tab": "额外积分/知识库索引量",
"support.wallet.subscription.Purchase plan": "购买套餐",
"support.wallet.subscription.Sub plan": "订阅套餐",
"support.wallet.subscription.Sub plan tip": "免费使用【{{title}}】或升级更高的套餐",
"support.wallet.subscription.Sub plan tip wecom": "购买套餐以享受应用服务",
......@@ -1090,7 +1095,9 @@
"support.wallet.subscription.ai_model": "AI语言模型",
"support.wallet.subscription.extra_plan_disabled_tip": "如需购买额外资源包,请先订阅套餐。",
"support.wallet.subscription.mode.Month": "按月",
"support.wallet.subscription.mode.Month pay": "按月付",
"support.wallet.subscription.mode.Year": "按年",
"support.wallet.subscription.mode.Year pay": "按年付",
"support.wallet.subscription.per_year": "/ 年",
"support.wallet.subscription.point": "积分",
"support.wallet.subscription.standardSubLevel.advanced": "高级版",
......
......@@ -19,7 +19,7 @@
"custom_config_details": "定制配置詳情",
"day": "天",
"extra_ai_points": "AI 積分運算標準",
"extra_dataset_size": "額外知識庫量",
"extra_dataset_size": "額外知識庫索引量",
"generation_time": "生成時間",
"has_invoice": "是否已開票",
"hour": "小時",
......
......@@ -111,7 +111,7 @@
"bill_expired": "訂單已過期",
"bill_not_found": "訂單不存在",
"bill_not_pay_processed": "非在線訂單",
"button.extra_dataset_size_tip": "您正在購買【額外知識庫量】",
"button.extra_dataset_size_tip": "您正在購買【額外知識庫索引量】",
"button.extra_points_tip": "您正在購買【額外 AI 積分】",
"can_copy_content_tip": "無法使用瀏覽器自動複製,請手動複製下面內容",
"cancel_bill": "取消",
......@@ -908,7 +908,7 @@
"pay_corporate_payment": "對公支付",
"pay_money": "應付金額",
"pay_success": "支付成功",
"pay_year_tip": "支付 10 個月,暢享 1 年!",
"pay_year_tip": "支付 <italic>{{count}}</italic> 個月,暢享一年!",
"permission.Collaborator": "協作者",
"permission.Manage": "管理",
"permission.No InheritPermission": "已限制權限,不再繼承上層資料夾的權限",
......@@ -1039,7 +1039,7 @@
"support.wallet.App_amount_not_sufficient": "您的應用數量已達上限,請升級套餐後繼續使用。",
"support.wallet.Buy": "購買",
"support.wallet.Dataset_amount_not_sufficient": "您的知識庫數量已達上限,請升級套餐後繼續使用。",
"support.wallet.Dataset_not_sufficient": "您的知識庫容量不足,請先升級套餐或購買額外知識庫量後繼續使用。",
"support.wallet.Dataset_not_sufficient": "您的知識庫容量不足,請先升級套餐或購買額外知識庫索引量後繼續使用。",
"support.wallet.Not sufficient": "您的 AI 點數不足,請先升級方案或購買額外 AI 點數後繼續使用。",
"support.wallet.Not_sufficient_contact_admin": "您的 AI 點數不足,請聯絡團隊管理員。",
"support.wallet.Team_member_over_size": "您的團隊成員數量已達上限,請升級套餐後繼續使用。",
......@@ -1064,11 +1064,16 @@
"support.wallet.subscription.Dataset size": "知識庫索引量",
"support.wallet.subscription.Extra ai points": "額外 AI 點數",
"support.wallet.subscription.Extra dataset description": "支持根據實際需求為知識庫索引量延長有效期",
"support.wallet.subscription.Extra dataset size": "額外知識庫量",
"support.wallet.subscription.Extra dataset size": "額外知識庫索引量",
"support.wallet.subscription.Extra dataset unit": "組/1個月",
"support.wallet.subscription.Extra plan": "額外資源包",
"support.wallet.subscription.Extra plan tip": "當標準方案不足時,您可以購買額外資源包繼續使用",
"support.wallet.subscription.Extra plan tip": "基礎套餐容量不足時,可購買額外資源包繼續使用",
"support.wallet.subscription.FAQ": "常見問題",
"support.wallet.subscription.Basic plan tab": "基礎套餐",
"support.wallet.subscription.Extra points tab": "額外積分",
"support.wallet.subscription.Extra dataset tab": "額外知識庫索引量",
"support.wallet.subscription.Extra points and dataset tab": "額外積分/知識庫索引量",
"support.wallet.subscription.Purchase plan": "購買套餐",
"support.wallet.subscription.Sub plan": "訂閱方案",
"support.wallet.subscription.Sub plan tip": "免費使用【{{title}}】或升級更進階的方案",
"support.wallet.subscription.Sub plan tip wecom": "購買方案以享受應用服務",
......@@ -1079,7 +1084,9 @@
"support.wallet.subscription.ai_model": "AI 語言模型",
"support.wallet.subscription.extra_plan_disabled_tip": "如需購買額外資源包,請先訂閱方案。",
"support.wallet.subscription.mode.Month": "按月",
"support.wallet.subscription.mode.Month pay": "按月付",
"support.wallet.subscription.mode.Year": "按年",
"support.wallet.subscription.mode.Year pay": "按年付",
"support.wallet.subscription.per_year": "/ 年",
"support.wallet.subscription.point": "點數",
"support.wallet.subscription.standardSubLevel.advanced": "高級版",
......
<svg width="152" height="152" viewBox="0 0 152 152" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_633_5296)">
<path d="M76 122.916C101.911 122.916 122.916 101.911 122.916 76C122.916 50.089 101.911 29.084 76 29.084C50.089 29.084 29.084 50.089 29.084 76C29.084 101.911 50.089 122.916 76 122.916Z" stroke="url(#paint0_linear_633_5296)" stroke-opacity="0.1"/>
<path d="M76 138.775C110.67 138.775 138.775 110.67 138.775 76.0001C138.775 41.3304 110.67 13.2251 76 13.2251C41.3303 13.2251 13.225 41.3304 13.225 76.0001C13.225 110.67 41.3303 138.775 76 138.775Z" stroke="url(#paint1_linear_633_5296)" stroke-opacity="0.05" stroke-width="0.955"/>
<path d="M76 150.969C117.404 150.969 150.969 117.404 150.969 76C150.969 34.5958 117.404 1.03101 76 1.03101C34.5958 1.03101 1.03101 34.5958 1.03101 76C1.03101 117.404 34.5958 150.969 76 150.969Z" stroke="url(#paint2_linear_633_5296)" stroke-opacity="0.05"/>
<g filter="url(#filter0_d_633_5296)">
<path d="M104 76C104 60.536 91.464 48 76 48C60.536 48 48 60.536 48 76C48 91.464 60.536 104 76 104C91.464 104 104 91.464 104 76Z" fill="#F0F4FF"/>
<path d="M103.25 76C103.25 60.9502 91.0498 48.75 76 48.75C60.9502 48.75 48.75 60.9502 48.75 76C48.75 91.0498 60.9502 103.25 76 103.25C91.0498 103.25 103.25 91.0498 103.25 76Z" stroke="#3370FF" stroke-width="1.5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M68.89 69.7C68.909 69.738 68.966 69.828 69.14 69.965C69.426 70.189 69.901 70.435 70.577 70.66C71.919 71.107 73.837 71.4 76 71.4C78.163 71.4 80.08 71.107 81.422 70.66C82.099 70.435 82.574 70.19 82.859 69.965C83.033 69.828 83.091 69.738 83.109 69.7C83.091 69.662 83.033 69.572 82.859 69.435C82.574 69.21 82.099 68.965 81.423 68.74C80.08 68.293 78.162 68 76 68C73.837 68 71.92 68.293 70.577 68.74C69.901 68.965 69.427 69.21 69.141 69.435C68.966 69.572 68.908 69.662 68.89 69.7ZM83.115 72.134C82.786 72.292 82.429 72.434 82.054 72.558C80.459 73.088 78.318 73.4 76 73.4C73.682 73.4 71.541 73.089 69.946 72.558C69.5837 72.4393 69.2293 72.2976 68.885 72.134V75.988C68.893 76.013 68.938 76.108 69.139 76.268C69.422 76.492 69.894 76.737 70.568 76.962C71.906 77.407 73.822 77.7 76 77.7C78.177 77.7 80.094 77.407 81.432 76.961C82.106 76.737 82.578 76.491 82.861 76.268C83.061 76.108 83.107 76.013 83.115 75.988V72.134ZM83.115 78.439C82.789 78.596 82.435 78.735 82.065 78.859C80.472 79.389 78.331 79.7 76 79.7C73.67 79.7 71.528 79.39 69.936 78.859C69.5772 78.7413 69.2261 78.6011 68.885 78.439V82.289C68.893 82.313 68.938 82.409 69.139 82.568C69.422 82.792 69.894 83.037 70.568 83.262C71.906 83.707 73.822 84 76 84C78.177 84 80.094 83.707 81.432 83.261C82.106 83.037 82.578 82.791 82.861 82.568C83.061 82.408 83.107 82.313 83.115 82.288V78.439ZM85.115 82.3C85.115 83.109 84.625 83.722 84.101 84.137C83.567 84.559 82.856 84.895 82.064 85.159C80.472 85.689 78.331 86 76 86C73.67 86 71.528 85.69 69.936 85.159C69.144 84.895 68.433 84.559 67.898 84.136C67.374 83.722 66.885 83.109 66.885 82.3V69.7C66.885 68.889 67.38 68.276 67.905 67.863C68.44 67.442 69.154 67.106 69.946 66.842C71.541 66.312 73.682 66 76 66C78.318 66 80.459 66.311 82.054 66.842C82.846 67.106 83.56 67.442 84.096 67.863C84.62 68.276 85.115 68.889 85.115 69.7V82.3Z" fill="#3370FF"/>
</g>
</g>
<defs>
<filter id="filter0_d_633_5296" x="48" y="48" width="56" height="56" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feColorMatrix type="matrix" values="0 0 0 0 0.2 0 0 0 0 0.439216 0 0 0 0 1 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_633_5296"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_633_5296" result="shape"/>
</filter>
<linearGradient id="paint0_linear_633_5296" x1="28.584" y1="76" x2="123.416" y2="76" gradientUnits="userSpaceOnUse">
<stop stop-color="#3370FF"/>
<stop offset="1" stop-color="#3370FF" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint1_linear_633_5296" x1="131.526" y1="59.1491" x2="27.88" y2="115.987" gradientUnits="userSpaceOnUse">
<stop stop-color="#3370FF"/>
<stop offset="1" stop-color="#3370FF" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint2_linear_633_5296" x1="40.484" y1="145.317" x2="134.681" y2="27.572" gradientUnits="userSpaceOnUse">
<stop stop-color="#3370FF"/>
<stop offset="1" stop-color="#3370FF" stop-opacity="0"/>
</linearGradient>
<clipPath id="clip0_633_5296">
<rect width="152" height="152" fill="white"/>
</clipPath>
</defs>
</svg>
import { Box, Flex, Grid, Button, VStack, HStack } from '@chakra-ui/react';
import { Box, Flex, Grid, Button, VStack, HStack, type BoxProps } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import React, { useState } from 'react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
......@@ -17,6 +17,10 @@ import { formatActivityExpirationTime } from './utils';
import { useUserStore } from '@/web/support/user/useUserStore';
import { StandardSubLevelEnum } from '@fastgpt/global/support/wallet/sub/constants';
const PLAN_CARD_MAX_WIDTH = '483px';
const DUAL_CARD_GAP = '20px';
const DUAL_CARD_CONTAINER_MAX_WIDTH = `calc(${PLAN_CARD_MAX_WIDTH} * 2 + ${DUAL_CARD_GAP})`;
const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
const { t, i18n } = useTranslation();
const { toast } = useToast();
......@@ -119,21 +123,34 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
subPlans?.activityExpirationTime
);
const planCardProps = {
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
w: ['100%', PLAN_CARD_MAX_WIDTH],
h: ['auto', '488px'],
p: ['16px', '24px 32px'],
flexShrink: 0,
bg: 'white',
borderRadius: 'xl',
borderWidth: '1px',
borderColor: 'myGray.200',
boxShadow: '1.5',
overflow: 'hidden'
} satisfies BoxProps;
return (
<VStack>
<Grid gridTemplateColumns={['1fr', '1fr 1fr']} gap={5} w={['100%', 'auto']}>
<Box
position={'relative'}
bg={'white'}
w={'100%'}
px={[4, 8]}
py={[4, 6]}
borderRadius={'16px'}
borderWidth={'1px'}
borderColor={'myGray.200'}
boxShadow={'0 1px 2px 0 rgba(19, 51, 107, 0.10), 0 0 1px 0 rgba(19, 51, 107, 0.15)'}
overflow={'hidden'}
>
<VStack w={'100%'} alignItems={'center'}>
<Flex
w={['100%', DUAL_CARD_CONTAINER_MAX_WIDTH]}
maxW={['100%', DUAL_CARD_CONTAINER_MAX_WIDTH]}
h={['auto', '488px']}
justifyContent={'center'}
alignItems={'flex-start'}
gap={['16px', DUAL_CARD_GAP]}
flexWrap={['wrap', 'nowrap']}
>
<Box position={'relative'} {...planCardProps}>
{subPlans?.activityExpirationTime && (
<>
<Box
......@@ -174,6 +191,7 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
<Box
position={'relative'}
zIndex={1}
w={'100%'}
fontSize={'18px'}
fontWeight={'500'}
color={'primary.700'}
......@@ -189,10 +207,12 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
<Grid
position={'relative'}
zIndex={1}
w={'100%'}
gridTemplateColumns={['repeat(2, 1fr)', 'repeat(3, 1fr)']}
gap={[2, 3]}
py={[3, 4]}
minHeight={['180px', '220px']}
flex={'1 0 auto'}
>
{extraPointsPackages.map((pkg, index) => (
<Flex
......@@ -256,6 +276,7 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
<Flex
position={'relative'}
zIndex={1}
w={'100%'}
justifyContent={'space-between'}
alignItems={'center'}
>
......@@ -280,6 +301,7 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
<Flex
position={'relative'}
zIndex={1}
w={'100%'}
justifyContent={'space-between'}
alignItems={'center'}
>
......@@ -332,7 +354,14 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
{t('common:support.wallet.Buy')}
</Button>
<HStack position={'relative'} zIndex={1} color={'blue.700'} mt={[4, 6]} spacing={[2, 0]}>
<HStack
position={'relative'}
zIndex={1}
w={'100%'}
color={'blue.700'}
mt={[4, 6]}
spacing={[2, 0]}
>
<MyIcon name={'infoRounded'} w={['16px', '18px']} />
<Box fontSize={['12px', '14px']} fontWeight={'medium'} lineHeight={['1.4', 'normal']}>
{t('common:support.wallet.subscription.Update extra ai points tips')}
......@@ -340,21 +369,15 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
</HStack>
</Box>
{/* dataset */}
<Flex
bg={'white'}
w={'100%'}
px={[4, 8]}
py={[4, 6]}
borderRadius={'16px'}
borderColor={'myGray.200'}
boxShadow={'0 1px 2px 0 rgba(19, 51, 107, 0.10), 0 0 1px 0 rgba(19, 51, 107, 0.15)'}
flexDir="column"
borderWidth={'1px'}
gap={2}
>
<Flex borderBottomWidth={'1px'} borderBottomColor={'myGray.200'} pb={[2, 4]}>
<Flex flexDir="column" gap={[2, 3]} flex={'1 0 0'}>
<Flex position={'relative'} gap={'8px'} {...planCardProps}>
<Flex
borderBottomWidth={'1px'}
borderBottomColor={'myGray.200'}
pb={[2, 4]}
position={'relative'}
w={'100%'}
>
<Flex flexDir="column" gap={[2, 3]} flex={'1 0 0'} pr={[0, '120px']}>
<Box fontSize={['16px', '18px', 'lg']} fontWeight={'500'} color={'primary.700'}>
{t('common:support.wallet.subscription.Extra dataset size')}
</Box>
......@@ -376,13 +399,17 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
{t('common:support.wallet.subscription.Extra dataset description')}
</Box>
</Flex>
<MyIcon
<Box
as={'img'}
display={['none', 'block']}
mt={['-20px', '-30px']}
transform={['translateX(10px)', 'translateX(20px)']}
name={'support/bill/extraDatasetsize'}
fill={'none'}
w={['60px', 'auto']}
position={'absolute'}
top={'-30px'}
right={0}
src={'/imgs/price/extraDatasetIcon.svg'}
alt=""
w={'152px'}
h={'152px'}
pointerEvents={'none'}
/>
</Flex>
......@@ -471,7 +498,7 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
</Flex>
</Flex>
<Box mt={['auto', 4]}>
<Box mt={['auto', 4]} w={'100%'}>
<Button
w={'100%'}
h={['40px', '44px']}
......@@ -500,7 +527,7 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
</Flex>
</Box>
</Flex>
</Grid>
</Flex>
{!!qrPayData && (
<QRCodePayModal
......
import React from 'react';
import { Box, Flex } from '@chakra-ui/react';
import type { PriceTabType } from '@/web/support/wallet/sub/constants';
type PricePlanTabItem = {
label: string;
value: PriceTabType;
};
/** 购买套餐页 Tab,按设计稿使用灰底容器和选中白底 pill 样式 */
const PricePlanTabs = ({
list,
value,
onChange
}: {
list: PricePlanTabItem[];
value: PriceTabType;
onChange: (value: PriceTabType) => void;
}) => {
return (
<Flex
display={'inline-flex'}
p={'4px'}
alignItems={'flex-start'}
gap={'8px'}
borderRadius={'full'}
bg={'myGray.150'}
>
{list.map((item) => {
const isActive = value === item.value;
return (
<Flex
key={item.value}
h={'36px'}
px={'14px'}
flexDirection={'column'}
justifyContent={'center'}
alignItems={'center'}
borderRadius={'full'}
borderWidth={isActive ? '1px' : '0'}
borderColor={isActive ? '#D4D4D4' : 'transparent'}
bg={isActive ? '#FFF' : 'transparent'}
cursor={'pointer'}
userSelect={'none'}
whiteSpace={'nowrap'}
onClick={() => {
if (!isActive) {
onChange(item.value);
}
}}
>
<Box
color={isActive ? 'myGray.900' : '#475569'}
textAlign={'center'}
fontFamily={'Inter, sans-serif'}
fontSize={'14px'}
fontStyle={'normal'}
fontWeight={500}
lineHeight={'21px'}
>
{item.label}
</Box>
</Flex>
);
})}
</Flex>
);
};
export default PricePlanTabs;
import React, { useEffect, useMemo, useState } from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { Box, Button, Flex, Grid } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { Trans, 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';
......@@ -32,12 +32,105 @@ const NEW_PLAN_LEVELS = [
StandardSubLevelEnum.custom
];
/** 按月/按年切换开关,可在页面 header 区域复用 */
export const BillingModeSwitch = ({
value,
onChange
}: {
value: `${SubModeEnum}`;
onChange: (mode: `${SubModeEnum}`) => void;
}) => {
const { t } = useTranslation();
const isYear = value === SubModeEnum.year;
return (
<Flex alignItems={'center'} gap={3}>
<Box
fontFamily={'Inter'}
fontSize={'14px'}
fontStyle={'normal'}
fontWeight={400}
lineHeight={'21px'}
color={'#020617'}
userSelect={'none'}
>
{t('common:support.wallet.subscription.mode.Month pay')}
</Box>
<Flex
role={'switch'}
aria-checked={isYear}
tabIndex={0}
w={'40px'}
h={'24px'}
p={'0 4px'}
alignItems={'center'}
flexShrink={0}
borderRadius={'full'}
bg={'#E8EBF0'}
cursor={'pointer'}
justifyContent={isYear ? 'flex-end' : 'flex-start'}
onClick={() => onChange(isYear ? SubModeEnum.month : SubModeEnum.year)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onChange(isYear ? SubModeEnum.month : SubModeEnum.year);
}
}}
>
<Box
w={'16px'}
h={'16px'}
borderRadius={'full'}
bg={'white'}
boxShadow={'0 1px 2px rgba(19, 51, 107, 0.15)'}
/>
</Flex>
<Box
fontFamily={'Inter'}
fontSize={'14px'}
fontStyle={'normal'}
fontWeight={400}
lineHeight={'20px'}
color={'#475569'}
userSelect={'none'}
>
{t('common:support.wallet.subscription.mode.Year pay')}
</Box>
<Box
as={'span'}
sx={{
color: '#3B82F6',
fontFamily: 'Inter, sans-serif',
fontSize: '14px',
fontStyle: 'normal',
fontWeight: 400,
lineHeight: '20px'
}}
>
<Trans
i18nKey={'common:pay_year_tip'}
values={{ count: 10 }}
components={{
italic: <Box as={'span'} fontStyle={'italic'} />
}}
/>
</Box>
</Flex>
);
};
const Standard = ({
standardPlan: myStandardPlan,
onPaySuccess
onPaySuccess,
selectSubMode: controlledSubMode,
onSelectSubModeChange,
hideBillingToggle = false
}: {
standardPlan?: TeamPlanStandardType;
onPaySuccess?: () => void;
selectSubMode?: `${SubModeEnum}`;
onSelectSubModeChange?: (mode: `${SubModeEnum}`) => void;
hideBillingToggle?: boolean;
}) => {
const { t } = useTranslation();
const { userInfo } = useUserStore();
......@@ -53,9 +146,11 @@ const Standard = ({
const [packageChange, setPackageChange] = useState<PackageChangeStatusEnum>();
const { subPlans, feConfigs } = useSystemStore();
const [selectSubMode, setSelectSubMode] = useState<`${SubModeEnum}`>(
const [internalSubMode, setInternalSubMode] = useState<`${SubModeEnum}`>(
isWecomTeam ? SubModeEnum.year : SubModeEnum.month
);
const selectSubMode = controlledSubMode ?? internalSubMode;
const setSelectSubMode = onSelectSubModeChange ?? setInternalSubMode;
const hasActivityExpiration =
!!subPlans?.activityExpirationTime && selectSubMode === SubModeEnum.year;
......@@ -132,7 +227,7 @@ const Standard = ({
})
: [];
}, [
subPlans?.standard,
subPlans,
isWecomTeam,
selectSubMode,
myStandardPlan?.maxTeamMember,
......@@ -166,56 +261,17 @@ const Standard = ({
return (
<>
<Flex flexDirection={'column'} alignItems={'center'} position={'relative'}>
{!isWecomTeam && (
<Flex>
<Box>
<Box
textAlign={'right'}
color="#DC7E03"
fontWeight="500"
fontStyle="italic"
fontFamily={'JiangChengXieHei'}
fontSize={'14px'}
lineHeight={'20px'}
letterSpacing={'0.1px'}
textTransform={'lowercase'}
mb={2}
mr={'-2'}
>
{t('common:pay_year_tip')}
</Box>
<RowTabs
list={[
{
label: t('common:support.wallet.subscription.mode.Month'),
value: SubModeEnum.month
},
{
label: (
<Box whiteSpace={'nowrap'}>
{t('common:support.wallet.subscription.mode.Year')}
</Box>
),
value: SubModeEnum.year
}
]}
value={selectSubMode}
onChange={(e) => setSelectSubMode(e as `${SubModeEnum}`)}
/>
</Box>
<MyIcon name={'price/pricearrow'} mt={'10px'} ml={'6px'} />
</Flex>
<Flex flexDirection={'column'} alignItems={'center'} position={'relative'} w={'100%'}>
{!hideBillingToggle && !isWecomTeam && (
<BillingModeSwitch value={selectSubMode} onChange={setSelectSubMode} />
)}
{/* card */}
<Grid
mt={[10, '48px']}
gridTemplateColumns={['1fr', 'repeat(2,1fr)', `repeat(${standardSubList.length},1fr)`]}
<Flex
mt={hideBillingToggle ? 0 : '24px'}
gap={[4, 6, 8]}
w={'100%'}
maxW={'1440px'}
minH={'550px'}
flexWrap={['wrap', 'nowrap']}
justifyContent={'center'}
>
{standardSubList.map((item) => {
const isCurrentPlan = item.level === myStandardPlan?.currentSubLevel;
......@@ -238,21 +294,25 @@ const Standard = ({
<Box
key={item.level}
pos={'relative'}
flex={'1 0 0'}
bg={'rgba(255, 255, 255, 0.90)'}
overflow={'hidden'}
display={'flex'}
flexDirection={'column'}
alignItems={'flex-start'}
flexShrink={0}
w={['100%', '300px']}
h={['auto', '777px']}
p={'28px'}
borderRadius={'xl'}
borderRadius={'16px'}
borderWidth={isCurrentPlan ? '2px' : '1.5px'}
boxShadow={'1.5'}
overflow={'hidden'}
{...(isCurrentPlan
? {
borderColor:
hasActivityExpiration && isActivityPlan ? '#BB182C' : 'primary.600'
}
: {
borderColor: 'myGray.150'
})}
borderColor={
isCurrentPlan
? hasActivityExpiration && isActivityPlan
? '#BB182C'
: 'primary.600'
: '#F0F1F6'
}
bg={'rgba(255, 255, 255, 0.90)'}
boxShadow={'0 1px 2px 0 rgba(19, 51, 107, 0.10), 0 0 1px 0 rgba(19, 51, 107, 0.15)'}
>
{hasActivityExpiration &&
(item.level === StandardSubLevelEnum.basic ||
......@@ -600,7 +660,7 @@ const Standard = ({
</Box>
);
})}
</Grid>
</Flex>
{!!qrPayData && packageChange && (
<QRCodePayModal
......@@ -620,56 +680,3 @@ const Standard = ({
};
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', '190px']}
{...(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>
);
};
import { getDocPath } from '@/web/common/system/doc';
import { useSystemStore } from '@/web/common/system/useSystemStore';
export const EXTRA_PLAN_CARD_ROUTE = '/price#extra-plan';
export type PriceTabType = 'standard' | 'extra';
export const isPriceTabType = (value: string): value is PriceTabType =>
value === 'standard' || value === 'extra';
export const getPriceTabRoute = (tab: PriceTabType) => `/price#${tab}`;
export const EXTRA_PLAN_CARD_ROUTE = getPriceTabRoute('extra');
export const getExtraPlanCardRoute = () => {
const subPlans = useSystemStore.getState().subPlans;
return subPlans?.planDescriptionUrl
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment