fix: show build app limit in app creation modal (#16940)

This commit is contained in:
KVOJJJin 2025-03-28 15:39:21 +08:00 committed by GitHub
parent c7fcfc863d
commit 9feafb6dbd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 78 additions and 65 deletions

View File

@ -214,6 +214,7 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate }: CreateAppProps)
/>
</div>
</div>
{isAppsFull && <AppsFull className='mt-4' loc='app-create' />}
<div className='flex items-center justify-between pb-10 pt-5'>
<div className='system-xs-regular flex cursor-pointer items-center gap-1 text-text-tertiary' onClick={onCreateFromTemplate}>
<span>{t('app.newApp.noIdeaTip')}</span>
@ -251,13 +252,6 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate }: CreateAppProps)
</div>
</div>
</div>
{
isAppsFull && (
<div className='px-8 py-2'>
<AppsFull loc='app-create' />
</div>
)
}
</>
}
type CreateAppDialogProps = CreateAppProps & {

View File

@ -96,7 +96,7 @@ const DuplicateAppModal = ({
className='h-10'
/>
</div>
{isAppsFull && <AppsFull loc='app-duplicate-create' />}
{isAppsFull && <AppsFull className='mt-4' loc='app-duplicate-create' />}
</div>
<div className='flex flex-row-reverse'>
<Button disabled={isAppsFull} className='ml-2 w-24' variant='primary' onClick={submit}>{t('app.duplicate')}</Button>

View File

@ -3,35 +3,82 @@ import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import UpgradeBtn from '../upgrade-btn'
import AppsInfo from '../usage-info/apps-info'
import ProgressBar from '@/app/components/billing/progress-bar'
import Button from '@/app/components/base/button'
import { mailToSupport } from '@/app/components/header/utils/util'
import { useProviderContext } from '@/context/provider-context'
import { useAppContext } from '@/context/app-context'
import { Plan } from '@/app/components/billing/type'
import s from './style.module.css'
import cn from '@/utils/classnames'
import GridMask from '@/app/components/base/grid-mask'
const AppsFull: FC<{ loc: string; className?: string }> = ({
const LOW = 50
const MIDDLE = 80
const AppsFull: FC<{ loc: string; className?: string; }> = ({
loc,
className,
}) => {
const { t } = useTranslation()
const { plan } = useProviderContext()
const { userProfile, langeniusVersionInfo } = useAppContext()
const isTeam = plan.type === Plan.team
const usage = plan.usage.buildApps
const total = plan.total.buildApps
const percent = usage / total * 100
const color = (() => {
if (percent < LOW)
return 'bg-components-progress-bar-progress-solid'
if (percent < MIDDLE)
return 'bg-components-progress-warning-progress'
return 'bg-components-progress-error-progress'
})()
return (
<GridMask wrapperClassName='rounded-lg' canvasClassName='rounded-lg' gradientClassName='rounded-lg'>
<div className={cn(
'mt-6 flex cursor-pointer flex-col rounded-lg border-2 border-solid border-transparent px-3.5 py-4 shadow-md transition-all duration-200 ease-in-out',
'flex flex-col gap-3 rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg p-4 shadow-xs backdrop-blur-sm',
className,
)}>
<div className='flex items-center justify-between'>
<div className={cn(s.textGradient, 'text-base font-semibold leading-[24px]')}>
<div>{t('billing.apps.fullTipLine1')}</div>
<div>{t('billing.apps.fullTipLine2')}</div>
<div className='flex justify-between'>
{!isTeam && (
<div>
<div className={cn('title-xl-semi-bold mb-1', s.textGradient)}>
{t('billing.apps.fullTip1')}
</div>
<div className='flex'>
<UpgradeBtn loc={loc} />
<div className='system-xs-regular text-text-tertiary'>{t('billing.apps.fullTip1des')}</div>
</div>
)}
{isTeam && (
<div>
<div className={cn('title-xl-semi-bold mb-1', s.textGradient)}>
{t('billing.apps.fullTip2')}
</div>
<div className='system-xs-regular text-text-tertiary'>{t('billing.apps.fullTip2des')}</div>
</div>
)}
{(plan.type === Plan.sandbox || plan.type === Plan.professional) && (
<UpgradeBtn isShort loc={loc} />
)}
{plan.type !== Plan.sandbox && plan.type !== Plan.professional && (
<Button variant='secondary-accent'>
<a target='_blank' rel='noopener noreferrer' href={mailToSupport(userProfile.email, plan.type, langeniusVersionInfo.current_version)}>
{t('billing.apps.contactUs')}
</a>
</Button>
)}
</div>
<div className='flex flex-col gap-2'>
<div className='system-xs-medium flex items-center justify-between text-text-secondary'>
<div>{t('billing.usagePage.buildApps')}</div>
<div>{usage}/{total}</div>
</div>
<ProgressBar
percent={percent}
color={color}
/>
</div>
</div>
<AppsInfo className='mt-4' />
</div>
</GridMask>
)
}
export default React.memo(AppsFull)

View File

@ -1,5 +1,5 @@
.textGradient {
background: linear-gradient(92deg, #2250F2 -29.55%, #0EBCF3 75.22%);
background: linear-gradient(92deg, #0EBCF3 -29.55%, #2250F2 75.22%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;

View File

@ -1,27 +0,0 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import UpgradeBtn from '../upgrade-btn'
import s from './style.module.css'
import cn from '@/utils/classnames'
import GridMask from '@/app/components/base/grid-mask'
const AppsFull: FC = () => {
const { t } = useTranslation()
return (
<GridMask wrapperClassName='rounded-lg' canvasClassName='rounded-lg' gradientClassName='rounded-lg'>
<div className='col-span-1 flex min-h-[160px] cursor-pointer flex-col rounded-lg border-2 border-solid border-transparent px-3.5 pt-3.5 shadow-xs transition-all duration-200 ease-in-out hover:shadow-lg'>
<div className={cn(s.textGradient, 'text-base font-semibold leading-[24px]')}>
<div>{t('billing.apps.fullTipLine1')}</div>
<div>{t('billing.apps.fullTipLine2')}</div>
</div>
<div className='mt-8 flex'>
<UpgradeBtn loc='app-create' />
</div>
</div>
</GridMask>
)
}
export default React.memo(AppsFull)

View File

@ -1,7 +0,0 @@
.textGradient {
background: linear-gradient(92deg, #2250F2 -29.55%, #0EBCF3 75.22%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-fill-color: transparent;
}

View File

@ -142,7 +142,7 @@ const CreateAppModal = ({
<p className='body-xs-regular text-text-tertiary'>{t('app.answerIcon.descriptionInExplore')}</p>
</div>
)}
{!isEditModal && isAppsFull && <AppsFull loc='app-explore-create' />}
{!isEditModal && isAppsFull && <AppsFull className='mt-4' loc='app-explore-create' />}
</div>
<div className='flex flex-row-reverse'>
<Button disabled={!isEditModal && isAppsFull} className='ml-2 w-24' variant='primary' onClick={submit}>{!isEditModal ? t('common.operation.create') : t('common.operation.save')}</Button>

View File

@ -170,8 +170,11 @@ const translation = {
fullSolution: 'Upgrade your plan to get more space.',
},
apps: {
fullTipLine1: 'Upgrade your plan to',
fullTipLine2: 'build more apps.',
fullTip1: 'Upgrade to create more apps',
fullTip1des: 'You\'ve reached the limit of build apps on this plan',
fullTip2: 'Plan limit reached',
fullTip2des: 'It is recommended to clean up inactive applications to free up usage, or contact us.',
contactUs: 'Contact us',
},
annotatedResponse: {
fullTipLine1: 'Upgrade your plan to',

View File

@ -169,8 +169,11 @@ const translation = {
fullSolution: '升级您的套餐以获得更多空间。',
},
apps: {
fullTipLine1: '升级您的套餐以',
fullTipLine2: '构建更多的程序。',
fullTip1: '升级以创建更多应用',
fullTip1des: '您已达到此计划上构建应用的限制',
fullTip2: '计划限制已达到',
fullTip2des: '推荐您清理不活跃的应用或者联系我们',
contactUs: '联系我们',
},
annotatedResponse: {
fullTipLine1: '升级您的套餐以',