| /> | /> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {isAppsFull && <AppsFull className='mt-4' loc='app-create' />} | |||||
| <div className='flex items-center justify-between pb-10 pt-5'> | <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}> | <div className='system-xs-regular flex cursor-pointer items-center gap-1 text-text-tertiary' onClick={onCreateFromTemplate}> | ||||
| <span>{t('app.newApp.noIdeaTip')}</span> | <span>{t('app.newApp.noIdeaTip')}</span> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| { | |||||
| isAppsFull && ( | |||||
| <div className='px-8 py-2'> | |||||
| <AppsFull loc='app-create' /> | |||||
| </div> | |||||
| ) | |||||
| } | |||||
| </> | </> | ||||
| } | } | ||||
| type CreateAppDialogProps = CreateAppProps & { | type CreateAppDialogProps = CreateAppProps & { |
| className='h-10' | className='h-10' | ||||
| /> | /> | ||||
| </div> | </div> | ||||
| {isAppsFull && <AppsFull loc='app-duplicate-create' />} | |||||
| {isAppsFull && <AppsFull className='mt-4' loc='app-duplicate-create' />} | |||||
| </div> | </div> | ||||
| <div className='flex flex-row-reverse'> | <div className='flex flex-row-reverse'> | ||||
| <Button disabled={isAppsFull} className='ml-2 w-24' variant='primary' onClick={submit}>{t('app.duplicate')}</Button> | <Button disabled={isAppsFull} className='ml-2 w-24' variant='primary' onClick={submit}>{t('app.duplicate')}</Button> |
| import React from 'react' | import React from 'react' | ||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| import UpgradeBtn from '../upgrade-btn' | 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 s from './style.module.css' | ||||
| import cn from '@/utils/classnames' | 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, | loc, | ||||
| className, | className, | ||||
| }) => { | }) => { | ||||
| const { t } = useTranslation() | 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 ( | 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', | |||||
| 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={cn( | |||||
| '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 justify-between'> | |||||
| {!isTeam && ( | |||||
| <div> | |||||
| <div className={cn('title-xl-semi-bold mb-1', s.textGradient)}> | |||||
| {t('billing.apps.fullTip1')} | |||||
| </div> | |||||
| <div className='system-xs-regular text-text-tertiary'>{t('billing.apps.fullTip1des')}</div> | |||||
| </div> | </div> | ||||
| <div className='flex'> | |||||
| <UpgradeBtn loc={loc} /> | |||||
| )} | |||||
| {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> | </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> | </div> | ||||
| <AppsInfo className='mt-4' /> | |||||
| <ProgressBar | |||||
| percent={percent} | |||||
| color={color} | |||||
| /> | |||||
| </div> | </div> | ||||
| </GridMask> | |||||
| </div> | |||||
| ) | ) | ||||
| } | } | ||||
| export default React.memo(AppsFull) | export default React.memo(AppsFull) |
| .textGradient { | .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-background-clip: text; | ||||
| -webkit-text-fill-color: transparent; | -webkit-text-fill-color: transparent; | ||||
| background-clip: text; | background-clip: text; |
| '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) |
| .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; | |||||
| } |
| <p className='body-xs-regular text-text-tertiary'>{t('app.answerIcon.descriptionInExplore')}</p> | <p className='body-xs-regular text-text-tertiary'>{t('app.answerIcon.descriptionInExplore')}</p> | ||||
| </div> | </div> | ||||
| )} | )} | ||||
| {!isEditModal && isAppsFull && <AppsFull loc='app-explore-create' />} | |||||
| {!isEditModal && isAppsFull && <AppsFull className='mt-4' loc='app-explore-create' />} | |||||
| </div> | </div> | ||||
| <div className='flex flex-row-reverse'> | <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> | <Button disabled={!isEditModal && isAppsFull} className='ml-2 w-24' variant='primary' onClick={submit}>{!isEditModal ? t('common.operation.create') : t('common.operation.save')}</Button> |
| fullSolution: 'Upgrade your plan to get more space.', | fullSolution: 'Upgrade your plan to get more space.', | ||||
| }, | }, | ||||
| apps: { | 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: { | annotatedResponse: { | ||||
| fullTipLine1: 'Upgrade your plan to', | fullTipLine1: 'Upgrade your plan to', |
| fullSolution: '升级您的套餐以获得更多空间。', | fullSolution: '升级您的套餐以获得更多空间。', | ||||
| }, | }, | ||||
| apps: { | apps: { | ||||
| fullTipLine1: '升级您的套餐以', | |||||
| fullTipLine2: '构建更多的程序。', | |||||
| fullTip1: '升级以创建更多应用', | |||||
| fullTip1des: '您已达到此计划上构建应用的限制', | |||||
| fullTip2: '计划限制已达到', | |||||
| fullTip2des: '推荐您清理不活跃的应用或者联系我们', | |||||
| contactUs: '联系我们', | |||||
| }, | }, | ||||
| annotatedResponse: { | annotatedResponse: { | ||||
| fullTipLine1: '升级您的套餐以', | fullTipLine1: '升级您的套餐以', |