| @@ -4,10 +4,10 @@ import React from 'react' | |||
| import { useTranslation } from 'react-i18next' | |||
| import useSWR from 'swr' | |||
| import { | |||
| RiExternalLinkLine, | |||
| RiArrowRightUpLine, | |||
| } from '@remixicon/react' | |||
| import PlanComp from '../plan' | |||
| import { ReceiptList } from '../../base/icons/src/vender/line/financeAndECommerce' | |||
| import Divider from '@/app/components/base/divider' | |||
| import { fetchBillingUrl } from '@/service/billing' | |||
| import { useAppContext } from '@/context/app-context' | |||
| import { useProviderContext } from '@/context/provider-context' | |||
| @@ -25,13 +25,13 @@ const Billing: FC = () => { | |||
| <div> | |||
| <PlanComp loc={'billing-page'} /> | |||
| {enableBilling && isCurrentWorkspaceManager && billingUrl && ( | |||
| <a className='mt-5 flex px-6 justify-between h-12 items-center bg-gray-50 rounded-xl cursor-pointer' href={billingUrl} target='_blank' rel='noopener noreferrer'> | |||
| <div className='flex items-center'> | |||
| <ReceiptList className='w-4 h-4 text-gray-700' /> | |||
| <div className='ml-2 text-sm font-normal text-gray-700'>{t('billing.viewBilling')}</div> | |||
| </div> | |||
| <RiExternalLinkLine className='w-3 h-3' /> | |||
| </a> | |||
| <> | |||
| <Divider className='my-4' /> | |||
| <a className='flex items-center text-text-accent-light-mode-only system-xs-medium cursor-pointer' href={billingUrl} target='_blank' rel='noopener noreferrer'> | |||
| <span className='pr-0.5'>{t('billing.viewBilling')}</span> | |||
| <RiArrowRightUpLine className='w-4 h-4' /> | |||
| </a> | |||
| </> | |||
| )} | |||
| </div> | |||
| ) | |||
| @@ -2,36 +2,21 @@ | |||
| import type { FC } from 'react' | |||
| import React from 'react' | |||
| import { useTranslation } from 'react-i18next' | |||
| import { | |||
| RiBook2Line, | |||
| RiBox3Line, | |||
| RiFileEditLine, | |||
| RiGroup3Line, | |||
| RiGroupLine, | |||
| RiSquareLine, | |||
| } from '@remixicon/react' | |||
| import { Plan, SelfHostedPlan } from '../type' | |||
| import VectorSpaceInfo from '../usage-info/vector-space-info' | |||
| import AppsInfo from '../usage-info/apps-info' | |||
| import UpgradeBtn from '../upgrade-btn' | |||
| import { User01 } from '../../base/icons/src/vender/line/users' | |||
| import { MessageFastPlus } from '../../base/icons/src/vender/line/communication' | |||
| import { FileUpload } from '../../base/icons/src/vender/line/files' | |||
| import cn from '@/utils/classnames' | |||
| import { useProviderContext } from '@/context/provider-context' | |||
| import UsageInfo from '@/app/components/billing/usage-info' | |||
| const typeStyle = { | |||
| [Plan.sandbox]: { | |||
| textClassNames: 'text-gray-900', | |||
| bg: 'linear-gradient(113deg, rgba(255, 255, 255, 0.51) 3.51%, rgba(255, 255, 255, 0.00) 111.71%), #EAECF0', | |||
| }, | |||
| [Plan.professional]: { | |||
| textClassNames: 'text-[#026AA2]', | |||
| bg: 'linear-gradient(113deg, rgba(255, 255, 255, 0.51) 3.51%, rgba(255, 255, 255, 0.00) 111.71%), #E0F2FE', | |||
| }, | |||
| [Plan.team]: { | |||
| textClassNames: 'text-[#3538CD]', | |||
| bg: 'linear-gradient(113deg, rgba(255, 255, 255, 0.51) 3.51%, rgba(255, 255, 255, 0.00) 111.71%), #E0EAFF', | |||
| }, | |||
| [SelfHostedPlan.enterprise]: { | |||
| textClassNames: 'text-[#DC6803]', | |||
| bg: 'linear-gradient(113deg, rgba(255, 255, 255, 0.51) 3.51%, rgba(255, 255, 255, 0.00) 111.71%), #FFEED3', | |||
| }, | |||
| } | |||
| type Props = { | |||
| loc: string | |||
| } | |||
| @@ -50,74 +35,62 @@ const PlanComp: FC<Props> = ({ | |||
| total, | |||
| } = plan | |||
| const isInHeader = loc === 'header' | |||
| return ( | |||
| <div | |||
| className='rounded-xl border border-white select-none' | |||
| style={{ | |||
| background: typeStyle[type].bg, | |||
| boxShadow: '5px 7px 12px 0px rgba(0, 0, 0, 0.06)', | |||
| }} | |||
| > | |||
| <div className='flex justify-between px-6 py-5 items-center'> | |||
| <div> | |||
| <div | |||
| className='leading-[18px] text-xs font-normal opacity-70' | |||
| style={{ | |||
| color: 'rgba(0, 0, 0, 0.64)', | |||
| }} | |||
| > | |||
| {t('billing.currentPlan')} | |||
| </div> | |||
| <div className={cn(typeStyle[type].textClassNames, 'leading-[125%] text-lg font-semibold uppercase')}> | |||
| {t(`billing.plans.${type}.name`)} | |||
| <div className='bg-background-section-burn rounded-2xl border-[0.5px] border-effects-highlight-lightmode-off'> | |||
| <div className='p-6 pb-2'> | |||
| {plan.type === Plan.sandbox && ( | |||
| <RiBox3Line className='w-7 h-7 text-text-primary'/> | |||
| )} | |||
| {plan.type === Plan.professional && ( | |||
| <RiSquareLine className='w-7 h-7 rotate-90 text-util-colors-blue-brand-blue-brand-600'/> | |||
| )} | |||
| {plan.type === Plan.team && ( | |||
| <RiGroup3Line className='w-7 h-7 text-util-colors-indigo-indigo-600'/> | |||
| )} | |||
| {(plan.type as any) === SelfHostedPlan.enterprise && ( | |||
| <RiGroup3Line className='w-7 h-7 text-util-colors-indigo-indigo-600'/> | |||
| )} | |||
| <div className='mt-1 flex items-center'> | |||
| <div className='grow'> | |||
| <div className='mb-1 flex items-center gap-1'> | |||
| <div className='text-text-primary system-md-semibold-uppercase'>{t(`billing.plans.${type}.name`)}</div> | |||
| <div className='px-1 py-0.5 border border-divider-deep rounded-[5px] text-text-tertiary system-2xs-medium-uppercase'>{t('billing.currentPlan')}</div> | |||
| </div> | |||
| <div className='system-xs-regular text-util-colors-gray-gray-600'>{t(`billing.plans.${type}.for`)}</div> | |||
| </div> | |||
| {(plan.type as any) !== SelfHostedPlan.enterprise && ( | |||
| <UpgradeBtn | |||
| className='shrink-0' | |||
| isPlain={type === Plan.team} | |||
| isShort | |||
| loc={loc} | |||
| /> | |||
| )} | |||
| </div> | |||
| {(!isInHeader || (isInHeader && type !== Plan.sandbox)) && ( | |||
| <UpgradeBtn | |||
| className='flex-shrink-0' | |||
| isPlain={type !== Plan.sandbox} | |||
| loc={loc} | |||
| /> | |||
| )} | |||
| </div> | |||
| {/* Plan detail */} | |||
| <div className='rounded-xl bg-white px-6 py-3'> | |||
| <div className='p-2 grid content-start grid-cols-3 gap-1'> | |||
| <AppsInfo /> | |||
| <UsageInfo | |||
| className='py-3' | |||
| Icon={User01} | |||
| Icon={RiGroupLine} | |||
| name={t('billing.usagePage.teamMembers')} | |||
| usage={usage.teamMembers} | |||
| total={total.teamMembers} | |||
| /> | |||
| <AppsInfo className='py-3' /> | |||
| <VectorSpaceInfo className='py-3' /> | |||
| <UsageInfo | |||
| className='py-3' | |||
| Icon={MessageFastPlus} | |||
| name={t('billing.usagePage.annotationQuota')} | |||
| usage={usage.annotatedResponse} | |||
| total={total.annotatedResponse} | |||
| /> | |||
| <UsageInfo | |||
| className='py-3' | |||
| Icon={FileUpload} | |||
| Icon={RiBook2Line} | |||
| name={t('billing.usagePage.documentsUploadQuota')} | |||
| usage={usage.documentsUploadQuota} | |||
| total={total.documentsUploadQuota} | |||
| /> | |||
| {isInHeader && type === Plan.sandbox && ( | |||
| <UpgradeBtn | |||
| className='flex-shrink-0 my-3' | |||
| isFull | |||
| size='lg' | |||
| isPlain={type !== Plan.sandbox} | |||
| loc={loc} | |||
| /> | |||
| )} | |||
| <VectorSpaceInfo /> | |||
| <UsageInfo | |||
| Icon={RiFileEditLine} | |||
| name={t('billing.usagePage.annotationQuota')} | |||
| usage={usage.annotatedResponse} | |||
| total={total.annotatedResponse} | |||
| /> | |||
| </div> | |||
| </div> | |||
| ) | |||
| @@ -60,7 +60,6 @@ const Pricing: FC<Props> = ({ | |||
| <div className='py-2 flex items-center justify-between h-[64px]'> | |||
| <TabSlider | |||
| value={currentPlan} | |||
| itemWidth={170} | |||
| className='inline-flex' | |||
| options={[ | |||
| { | |||
| @@ -1,18 +1,20 @@ | |||
| import cn from '@/utils/classnames' | |||
| type ProgressBarProps = { | |||
| percent: number | |||
| color: string | |||
| } | |||
| const ProgressBar = ({ | |||
| percent = 0, | |||
| color = '#2970FF', | |||
| }: ProgressBarProps) => { | |||
| return ( | |||
| <div className='bg-[#F2F4F7] rounded-[4px] overflow-hidden'> | |||
| <div className='bg-components-progress-bar-bg rounded-[6px] overflow-hidden'> | |||
| <div | |||
| className='h-2 rounded-[4px]' | |||
| className={cn('h-1 rounded-[6px]', color)} | |||
| style={{ | |||
| width: `${Math.min(percent, 100)}%`, | |||
| backgroundColor: color, | |||
| }} | |||
| /> | |||
| </div> | |||
| @@ -3,8 +3,8 @@ import type { FC } from 'react' | |||
| import React from 'react' | |||
| import { useTranslation } from 'react-i18next' | |||
| import PremiumBadge from '../../base/premium-badge' | |||
| import Button from '@/app/components/base/button' | |||
| import { SparklesSoft } from '@/app/components/base/icons/src/public/common' | |||
| import cn from '@/utils/classnames' | |||
| import { useModalContext } from '@/context/modal-context' | |||
| type Props = { | |||
| @@ -17,23 +17,7 @@ type Props = { | |||
| loc?: string | |||
| } | |||
| const PlainBtn = ({ className, onClick }: { className?: string; onClick: () => void }) => { | |||
| const { t } = useTranslation() | |||
| return ( | |||
| <div | |||
| className={cn(className, 'flex items-center h-8 px-3 rounded-lg border border-gray-200 bg-white shadow-sm cursor-pointer')} | |||
| onClick={onClick} | |||
| > | |||
| <div className='leading-[18px] text-[13px] font-medium text-gray-700'> | |||
| {t('billing.upgradeBtn.plain')} | |||
| </div> | |||
| </div> | |||
| ) | |||
| } | |||
| const UpgradeBtn: FC<Props> = ({ | |||
| className, | |||
| isPlain = false, | |||
| isShort = false, | |||
| onClick: _onClick, | |||
| @@ -56,8 +40,13 @@ const UpgradeBtn: FC<Props> = ({ | |||
| } | |||
| } | |||
| if (isPlain) | |||
| return <PlainBtn onClick={onClick} className={className} /> | |||
| if (isPlain) { | |||
| return ( | |||
| <Button onClick={onClick}> | |||
| {t('billing.upgradeBtn.plain')} | |||
| </Button> | |||
| ) | |||
| } | |||
| return ( | |||
| <PremiumBadge | |||
| @@ -2,7 +2,9 @@ | |||
| import type { FC } from 'react' | |||
| import React from 'react' | |||
| import { useTranslation } from 'react-i18next' | |||
| import { ChatBot } from '../../base/icons/src/vender/line/communication' | |||
| import { | |||
| RiApps2Line, | |||
| } from '@remixicon/react' | |||
| import UsageInfo from '../usage-info' | |||
| import { useProviderContext } from '@/context/provider-context' | |||
| @@ -22,7 +24,7 @@ const AppsInfo: FC<Props> = ({ | |||
| return ( | |||
| <UsageInfo | |||
| className={className} | |||
| Icon={ChatBot} | |||
| Icon={RiApps2Line} | |||
| name={t('billing.usagePage.buildApps')} | |||
| usage={usage.buildApps} | |||
| total={total.buildApps} | |||
| @@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next' | |||
| import ProgressBar from '../progress-bar' | |||
| import { NUM_INFINITE } from '../config' | |||
| import Tooltip from '@/app/components/base/tooltip' | |||
| import cn from '@/utils/classnames' | |||
| type Props = { | |||
| className?: string | |||
| @@ -33,43 +34,37 @@ const UsageInfo: FC<Props> = ({ | |||
| const percent = usage / total * 100 | |||
| const color = (() => { | |||
| if (percent < LOW) | |||
| return '#155EEF' | |||
| return 'bg-components-progress-bar-progress-solid' | |||
| if (percent < MIDDLE) | |||
| return '#F79009' | |||
| return 'bg-components-progress-warning-progress' | |||
| return '#F04438' | |||
| return 'bg-components-progress-error-progress' | |||
| })() | |||
| return ( | |||
| <div className={className}> | |||
| <div className='flex justify-between h-5 items-center'> | |||
| <div className='flex items-center'> | |||
| <Icon className='w-4 h-4 text-gray-700' /> | |||
| <div className='mx-1 leading-5 text-sm font-medium text-gray-700'>{name}</div> | |||
| {tooltip && ( | |||
| <Tooltip | |||
| popupContent={ | |||
| <div className='w-[180px]'> | |||
| {tooltip} | |||
| </div> | |||
| } | |||
| /> | |||
| )} | |||
| </div> | |||
| <div className='flex items-center leading-[18px] text-[13px] font-normal'> | |||
| <div style={{ | |||
| color: percent < LOW ? '#344054' : color, | |||
| }}>{usage}{unit}</div> | |||
| <div className='mx-1 text-gray-300'>/</div> | |||
| <div className='text-gray-500'>{total === NUM_INFINITE ? t('billing.plansCommon.unlimited') : `${total}${unit}`}</div> | |||
| </div> | |||
| <div className={cn('p-4 flex flex-col gap-2 rounded-xl bg-components-panel-bg', className)}> | |||
| <Icon className='w-4 h-4 text-text-tertiary' /> | |||
| <div className='flex items-center gap-1'> | |||
| <div className='system-xs-medium text-text-tertiary'>{name}</div> | |||
| {tooltip && ( | |||
| <Tooltip | |||
| popupContent={ | |||
| <div className='w-[180px]'> | |||
| {tooltip} | |||
| </div> | |||
| } | |||
| /> | |||
| )} | |||
| </div> | |||
| <div className='mt-2'> | |||
| <ProgressBar | |||
| percent={percent} | |||
| color={color} | |||
| /> | |||
| <div className='flex items-center gap-1 system-md-semibold text-text-primary'> | |||
| {usage} | |||
| <div className='system-md-regular text-text-quaternary'>/</div> | |||
| <div>{total === NUM_INFINITE ? t('billing.plansCommon.unlimited') : `${total}${unit}`}</div> | |||
| </div> | |||
| <ProgressBar | |||
| percent={percent} | |||
| color={color} | |||
| /> | |||
| </div> | |||
| ) | |||
| } | |||
| @@ -1,8 +1,10 @@ | |||
| 'use client' | |||
| import type { FC } from 'react' | |||
| import React from 'react' | |||
| import { | |||
| RiHardDrive3Line, | |||
| } from '@remixicon/react' | |||
| import { useTranslation } from 'react-i18next' | |||
| import { ArtificialBrain } from '../../base/icons/src/vender/line/development' | |||
| import UsageInfo from '../usage-info' | |||
| import { useProviderContext } from '@/context/provider-context' | |||
| @@ -22,7 +24,7 @@ const VectorSpaceInfo: FC<Props> = ({ | |||
| return ( | |||
| <UsageInfo | |||
| className={className} | |||
| Icon={ArtificialBrain} | |||
| Icon={RiHardDrive3Line} | |||
| name={t('billing.usagePage.vectorSpace')} | |||
| tooltip={t('billing.usagePage.vectorSpaceTooltip') as string} | |||
| usage={usage.vectorSpace} | |||
| @@ -1,44 +1,36 @@ | |||
| import { useTranslation } from 'react-i18next' | |||
| import CustomWebAppBrand from '../custom-web-app-brand' | |||
| import s from '../style.module.css' | |||
| import GridMask from '@/app/components/base/grid-mask' | |||
| import UpgradeBtn from '@/app/components/billing/upgrade-btn' | |||
| import { useProviderContext } from '@/context/provider-context' | |||
| import { Plan } from '@/app/components/billing/type' | |||
| import { contactSalesUrl } from '@/app/components/billing/config' | |||
| import { useModalContext } from '@/context/modal-context' | |||
| const CustomPage = () => { | |||
| const { t } = useTranslation() | |||
| const { plan, enableBilling } = useProviderContext() | |||
| const { setShowPricingModal } = useModalContext() | |||
| const showBillingTip = enableBilling && plan.type === Plan.sandbox | |||
| const showContact = enableBilling && (plan.type === Plan.professional || plan.type === Plan.team) | |||
| return ( | |||
| <div className='flex flex-col'> | |||
| { | |||
| showBillingTip && ( | |||
| <GridMask canvasClassName='!rounded-xl'> | |||
| <div className='flex justify-between mb-1 px-6 py-5 h-[88px] shadow-md rounded-xl border-[0.5px] border-gray-200'> | |||
| <div className={`${s.textGradient} leading-[24px] text-base font-semibold`}> | |||
| <div>{t('custom.upgradeTip.prefix')}</div> | |||
| <div>{t('custom.upgradeTip.suffix')}</div> | |||
| </div> | |||
| <UpgradeBtn /> | |||
| </div> | |||
| </GridMask> | |||
| ) | |||
| } | |||
| <CustomWebAppBrand /> | |||
| { | |||
| showContact && ( | |||
| <div className='absolute bottom-0 h-[50px] leading-[50px] text-xs text-gray-500'> | |||
| {t('custom.customize.prefix')} | |||
| <a className='text-[#155EEF]' href={contactSalesUrl} target='_blank' rel='noopener noreferrer'>{t('custom.customize.contactUs')}</a> | |||
| {t('custom.customize.suffix')} | |||
| {showBillingTip && ( | |||
| <div className='flex justify-between mb-1 p-4 pl-6 bg-gradient-to-r from-components-input-border-active-prompt-1 to-components-input-border-active-prompt-2 shadow-lg backdrop-blur-sm rounded-xl'> | |||
| <div className='space-y-1 text-text-primary-on-surface'> | |||
| <div className='title-xl-semi-bold'>{t('custom.upgradeTip.title')}</div> | |||
| <div className='system-sm-regular'>{t('custom.upgradeTip.des')}</div> | |||
| </div> | |||
| ) | |||
| } | |||
| <div className='w-[120px] h-10 flex items-center justify-center bg-white rounded-3xl shadow-xs system-md-semibold text-text-accent cursor-pointer hover:opacity-95' onClick={() => setShowPricingModal()}>{t('billing.upgradeBtn.encourageShort')}</div> | |||
| </div> | |||
| )} | |||
| <CustomWebAppBrand /> | |||
| {showContact && ( | |||
| <div className='absolute bottom-0 h-[50px] leading-[50px] text-xs text-text-quaternary'> | |||
| {t('custom.customize.prefix')} | |||
| <a className='text-text-accent' href={contactSalesUrl} target='_blank' rel='noopener noreferrer'>{t('custom.customize.contactUs')}</a> | |||
| {t('custom.customize.suffix')} | |||
| </div> | |||
| )} | |||
| </div> | |||
| ) | |||
| } | |||
| @@ -2,22 +2,28 @@ import type { ChangeEvent } from 'react' | |||
| import { useState } from 'react' | |||
| import { useTranslation } from 'react-i18next' | |||
| import { | |||
| RiEditBoxLine, | |||
| RiEqualizer2Line, | |||
| RiExchange2Fill, | |||
| RiImageAddLine, | |||
| RiLayoutLeft2Line, | |||
| RiLoader2Line, | |||
| RiPlayLargeLine, | |||
| } from '@remixicon/react' | |||
| import s from './style.module.css' | |||
| import LogoSite from '@/app/components/base/logo/logo-site' | |||
| import Switch from '@/app/components/base/switch' | |||
| import Button from '@/app/components/base/button' | |||
| import { MessageDotsCircle } from '@/app/components/base/icons/src/vender/solid/communication' | |||
| import { ImagePlus } from '@/app/components/base/icons/src/vender/line/images' | |||
| import Divider from '@/app/components/base/divider' | |||
| import { useProviderContext } from '@/context/provider-context' | |||
| import { Plan } from '@/app/components/billing/type' | |||
| import { imageUpload } from '@/app/components/base/image-uploader/utils' | |||
| import { useToastContext } from '@/app/components/base/toast' | |||
| import { BubbleTextMod } from '@/app/components/base/icons/src/vender/solid/communication' | |||
| import { | |||
| updateCurrentWorkspace, | |||
| } from '@/service/common' | |||
| import { useAppContext } from '@/context/app-context' | |||
| import cn from '@/utils/classnames' | |||
| const ALLOW_FILE_EXTENSIONS = ['svg', 'png'] | |||
| @@ -107,32 +113,7 @@ const CustomWebAppBrand = () => { | |||
| return ( | |||
| <div className='py-4'> | |||
| <div className='mb-2 text-sm font-medium text-gray-900'>{t('custom.webapp.title')}</div> | |||
| <div className='relative mb-4 pl-4 pb-6 pr-[119px] rounded-xl border-[0.5px] border-black/8 shadow-xs bg-gray-50 overflow-hidden'> | |||
| <div className={`${s.mask} absolute top-0 left-0 w-full -bottom-2 z-10`}></div> | |||
| <div className='flex items-center -mt-2 mb-4 p-6 bg-white rounded-xl'> | |||
| <div className='flex items-center px-4 w-[125px] h-9 rounded-lg bg-primary-600 border-[0.5px] border-primary-700 shadow-xs'> | |||
| <MessageDotsCircle className='shrink-0 mr-2 w-4 h-4 text-white' /> | |||
| <div className='grow h-2 rounded-sm bg-white opacity-50' /> | |||
| </div> | |||
| </div> | |||
| <div className='flex items-center h-5 justify-between'> | |||
| <div className='w-[369px] h-1.5 rounded-sm bg-gray-200 opacity-80' /> | |||
| { | |||
| !webappBrandRemoved && ( | |||
| <div className='flex items-center text-[10px] font-medium text-gray-400'> | |||
| POWERED BY | |||
| { | |||
| webappLogo | |||
| ? <img src={`${webappLogo}?hash=${imgKey}`} alt='logo' className='ml-2 block w-auto h-5' /> | |||
| : <LogoSite className='ml-2 !h-5' /> | |||
| } | |||
| </div> | |||
| ) | |||
| } | |||
| </div> | |||
| </div> | |||
| <div className='flex items-center justify-between mb-2 px-4 h-14 rounded-xl border-[0.5px] border-gray-200 bg-gray-50 text-sm font-medium text-gray-900'> | |||
| <div className='flex items-center justify-between mb-2 p-4 rounded-xl bg-background-section-burn system-md-medium text-text-primary'> | |||
| {t('custom.webapp.removeBrand')} | |||
| <Switch | |||
| size='l' | |||
| @@ -141,34 +122,38 @@ const CustomWebAppBrand = () => { | |||
| onChange={handleSwitch} | |||
| /> | |||
| </div> | |||
| <div className={` | |||
| flex items-center justify-between px-4 py-3 rounded-xl border-[0.5px] border-gray-200 bg-gray-50 | |||
| ${webappBrandRemoved && 'opacity-30'} | |||
| `}> | |||
| <div className={cn('flex items-center justify-between h-14 px-4 rounded-xl bg-background-section-burn', webappBrandRemoved && 'opacity-30')}> | |||
| <div> | |||
| <div className='leading-5 text-sm font-medium text-gray-900'>{t('custom.webapp.changeLogo')}</div> | |||
| <div className='leading-[18px] text-xs text-gray-500'>{t('custom.webapp.changeLogoTip')}</div> | |||
| <div className='system-md-medium text-text-primary'>{t('custom.webapp.changeLogo')}</div> | |||
| <div className='system-xs-regular text-text-tertiary'>{t('custom.webapp.changeLogoTip')}</div> | |||
| </div> | |||
| <div className='flex items-center'> | |||
| {(uploadDisabled || (!webappLogo && !webappBrandRemoved)) && ( | |||
| <> | |||
| <Button | |||
| variant='ghost' | |||
| disabled={uploadDisabled || (!webappLogo && !webappBrandRemoved)} | |||
| onClick={handleRestore} | |||
| > | |||
| {t('custom.restore')} | |||
| </Button> | |||
| <div className='mx-2 h-5 w-[1px] bg-divider-regular'></div> | |||
| </> | |||
| )} | |||
| { | |||
| !uploading && ( | |||
| <Button | |||
| className={` | |||
| relative mr-2 | |||
| `} | |||
| className='relative mr-2' | |||
| disabled={uploadDisabled} | |||
| > | |||
| <ImagePlus className='mr-2 w-4 h-4' /> | |||
| <RiImageAddLine className='mr-1 w-4 h-4' /> | |||
| { | |||
| (webappLogo || fileId) | |||
| ? t('custom.change') | |||
| : t('custom.upload') | |||
| } | |||
| <input | |||
| className={` | |||
| absolute block inset-0 opacity-0 text-[0] w-full | |||
| ${uploadDisabled ? 'cursor-not-allowed' : 'cursor-pointer'} | |||
| `} | |||
| className={cn('absolute block inset-0 opacity-0 text-[0] w-full', uploadDisabled ? 'cursor-not-allowed' : 'cursor-pointer')} | |||
| onClick={e => (e.target as HTMLInputElement).value = ''} | |||
| type='file' | |||
| accept={ALLOW_FILE_EXTENSIONS.map(ext => `.${ext}`).join(',')} | |||
| @@ -184,7 +169,7 @@ const CustomWebAppBrand = () => { | |||
| className='relative mr-2' | |||
| disabled={true} | |||
| > | |||
| <RiLoader2Line className='animate-spin mr-2 w-4 h-4' /> | |||
| <RiLoader2Line className='animate-spin mr-1 w-4 h-4' /> | |||
| {t('custom.uploading')} | |||
| </Button> | |||
| ) | |||
| @@ -193,37 +178,140 @@ const CustomWebAppBrand = () => { | |||
| fileId && ( | |||
| <> | |||
| <Button | |||
| variant='primary' | |||
| className='mr-2' | |||
| onClick={handleApply} | |||
| onClick={handleCancel} | |||
| disabled={webappBrandRemoved || !isCurrentWorkspaceManager} | |||
| > | |||
| {t('custom.apply')} | |||
| {t('common.operation.cancel')} | |||
| </Button> | |||
| <Button | |||
| variant='primary' | |||
| className='mr-2' | |||
| onClick={handleCancel} | |||
| onClick={handleApply} | |||
| disabled={webappBrandRemoved || !isCurrentWorkspaceManager} | |||
| > | |||
| {t('common.operation.cancel')} | |||
| {t('custom.apply')} | |||
| </Button> | |||
| </> | |||
| ) | |||
| } | |||
| <div className='mr-2 h-5 w-[1px] bg-black/5'></div> | |||
| <Button | |||
| disabled={uploadDisabled || (!webappLogo && !webappBrandRemoved)} | |||
| onClick={handleRestore} | |||
| > | |||
| {t('custom.restore')} | |||
| </Button> | |||
| </div> | |||
| </div> | |||
| { | |||
| uploadProgress === -1 && ( | |||
| <div className='mt-2 text-xs text-[#D92D20]'>{t('custom.uploadedFail')}</div> | |||
| ) | |||
| } | |||
| {uploadProgress === -1 && ( | |||
| <div className='mt-2 text-xs text-[#D92D20]'>{t('custom.uploadedFail')}</div> | |||
| )} | |||
| <div className='mt-5 mb-2 flex items-center gap-2'> | |||
| <div className='shrink-0 system-xs-medium-uppercase text-text-tertiary'>{t('appOverview.overview.appInfo.preview')}</div> | |||
| <Divider bgStyle='gradient' className='grow' /> | |||
| </div> | |||
| <div className='relative mb-2 flex items-center gap-3'> | |||
| {/* chat card */} | |||
| <div className='grow basis-1/2 h-[320px] flex bg-background-default-burn rounded-2xl border-[0.5px] border-components-panel-border-subtle overflow-hidden'> | |||
| <div className='shrink-0 h-full w-[232px] p-1 pr-0 flex flex-col'> | |||
| <div className='p-3 pr-2 flex items-center gap-3'> | |||
| <div className={cn('w-8 h-8 inline-flex items-center justify-center rounded-lg border border-divider-regular', 'bg-components-icon-bg-blue-light-solid')}> | |||
| <BubbleTextMod className='w-4 h-4 text-components-avatar-shape-fill-stop-100' /> | |||
| </div> | |||
| <div className='grow system-md-semibold text-text-secondary'>Chatflow App</div> | |||
| <div className='p-1.5'> | |||
| <RiLayoutLeft2Line className='w-4 h-4 text-text-tertiary' /> | |||
| </div> | |||
| </div> | |||
| <div className='shrink-0 px-4 py-3'> | |||
| <Button variant='secondary-accent' className='w-full justify-center'> | |||
| <RiEditBoxLine className='w-4 h-4 mr-1' /> | |||
| <div className='p-1 opacity-20'> | |||
| <div className='h-2 w-[94px] rounded-sm bg-text-accent-light-mode-only'></div> | |||
| </div> | |||
| </Button> | |||
| </div> | |||
| <div className='grow px-3 pt-5'> | |||
| <div className='h-8 px-3 py-1 flex items-center'> | |||
| <div className='w-14 h-2 rounded-sm bg-text-quaternary opacity-20'></div> | |||
| </div> | |||
| <div className='h-8 px-3 py-1 flex items-center'> | |||
| <div className='w-[168px] h-2 rounded-sm bg-text-quaternary opacity-20'></div> | |||
| </div> | |||
| <div className='h-8 px-3 py-1 flex items-center'> | |||
| <div className='w-[128px] h-2 rounded-sm bg-text-quaternary opacity-20'></div> | |||
| </div> | |||
| </div> | |||
| <div className='shrink-0 p-3 flex items-center justify-between'> | |||
| <div className='p-1.5'> | |||
| <RiEqualizer2Line className='w-4 h-4 text-text-tertiary' /> | |||
| </div> | |||
| <div className='flex items-center gap-1.5'> | |||
| {!webappBrandRemoved && ( | |||
| <> | |||
| <div className='text-text-tertiary system-2xs-medium-uppercase'>POWERED BY</div> | |||
| {webappLogo | |||
| ? <img src={`${webappLogo}?hash=${imgKey}`} alt='logo' className='block w-auto h-5' /> | |||
| : <LogoSite className='!h-5' /> | |||
| } | |||
| </> | |||
| )} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div className='grow flex flex-col justify-between w-[138px] p-2 pr-0'> | |||
| <div className='grow pt-16 pl-[22px] pb-4 flex flex-col justify-between bg-chatbot-bg rounded-l-2xl border-[0.5px] border-r-0 border-components-panel-border-subtle'> | |||
| <div className='w-[720px] px-4 py-3 bg-chat-bubble-bg rounded-2xl border border-divider-subtle'> | |||
| <div className='mb-1 text-text-primary body-md-regular'>Hello! How can I assist you today?</div> | |||
| <Button size='small'> | |||
| <div className='w-[144px] h-2 rounded-sm bg-text-quaternary opacity-20'></div> | |||
| </Button> | |||
| </div> | |||
| <div className='w-[578px] h-[52px] flex items-center pl-3.5 rounded-xl bg-components-panel-bg-blur backdrop-blur-sm border border-components-chat-input-border shadow-md text-text-placeholder body-lg-regular'>Talk to Dify</div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {/* workflow card */} | |||
| <div className='grow basis-1/2 h-[320px] flex flex-col bg-background-default-burn rounded-2xl border-[0.5px] border-components-panel-border-subtle overflow-hidden'> | |||
| <div className='w-full p-4 pb-0 border-b-[0.5px] border-divider-subtle'> | |||
| <div className='mb-2 flex items-center gap-3'> | |||
| <div className={cn('w-8 h-8 inline-flex items-center justify-center rounded-lg border border-divider-regular', 'bg-components-icon-bg-indigo-solid')}> | |||
| <RiExchange2Fill className='w-4 h-4 text-components-avatar-shape-fill-stop-100' /> | |||
| </div> | |||
| <div className='grow system-md-semibold text-text-secondary'>Workflow App</div> | |||
| <div className='p-1.5'> | |||
| <RiLayoutLeft2Line className='w-4 h-4 text-text-tertiary' /> | |||
| </div> | |||
| </div> | |||
| <div className='flex items-center gap-4'> | |||
| <div className='shrink-0 h-10 flex items-center border-b-2 border-components-tab-active text-text-primary system-md-semibold-uppercase'>RUN ONCE</div> | |||
| <div className='grow h-10 flex items-center border-b-2 border-transparent text-text-tertiary system-md-semibold-uppercase'>RUN BATCH</div> | |||
| </div> | |||
| </div> | |||
| <div className='grow bg-components-panel-bg'> | |||
| <div className='p-4 pb-1'> | |||
| <div className='mb-1 py-2'> | |||
| <div className='w-20 h-2 rounded-sm bg-text-quaternary opacity-20'></div> | |||
| </div> | |||
| <div className='w-full h-16 rounded-lg bg-components-input-bg-normal '></div> | |||
| </div> | |||
| <div className='px-4 py-3 flex items-center justify-between'> | |||
| <Button size='small'> | |||
| <div className='w-10 h-2 rounded-sm bg-text-quaternary opacity-20'></div> | |||
| </Button> | |||
| <Button variant='primary' size='small' disabled> | |||
| <RiPlayLargeLine className='mr-1 w-4 h-4' /> | |||
| <span>Execute</span> | |||
| </Button> | |||
| </div> | |||
| </div> | |||
| <div className='shrink-0 h-12 p-4 pt-3 flex items-center gap-1.5 bg-components-panel-bg'> | |||
| {!webappBrandRemoved && ( | |||
| <> | |||
| <div className='text-text-tertiary system-2xs-medium-uppercase'>POWERED BY</div> | |||
| {webappLogo | |||
| ? <img src={`${webappLogo}?hash=${imgKey}`} alt='logo' className='block w-auto h-5' /> | |||
| : <LogoSite className='!h-5' /> | |||
| } | |||
| </> | |||
| )} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ) | |||
| } | |||
| @@ -31,7 +31,7 @@ const Field: FC<Props> = ({ | |||
| return ( | |||
| <div className={cn(className)}> | |||
| <div className='flex py-[7px]'> | |||
| <div className={cn(labelClassName, 'flex items-center h-[18px] text-[13px] font-medium text-gray-900')}>{label} </div> | |||
| <div className={cn(labelClassName, 'flex items-center h-[18px] text-[13px] font-medium text-text-primary')}>{label} </div> | |||
| {isRequired && <span className='ml-0.5 text-xs font-semibold text-[#D92D20]'>*</span>} | |||
| {tooltip && ( | |||
| <Tooltip | |||
| @@ -1,23 +1,25 @@ | |||
| import { useTranslation } from 'react-i18next' | |||
| import { Webhooks } from '@/app/components/base/icons/src/vender/line/development' | |||
| import { BookOpen01 } from '@/app/components/base/icons/src/vender/line/education' | |||
| import { | |||
| RiExternalLinkLine, | |||
| RiPuzzle2Line, | |||
| } from '@remixicon/react' | |||
| const Empty = () => { | |||
| const { t } = useTranslation() | |||
| return ( | |||
| <div className='mb-2 p-6 rounded-2xl bg-gray-50'> | |||
| <div className='flex items-center justify-center mb-3 w-12 h-12 rounded-[10px] border border-[#EAECF5]'> | |||
| <Webhooks className='w-6 h-6 text-gray-500' /> | |||
| <div className='mb-2 p-6 rounded-xl bg-background-section'> | |||
| <div className='flex items-center justify-center mb-3 w-10 h-10 rounded-[10px] bg-components-card-bg-alt backdrop-blur-sm border-[0.5px] border-components-card-border shadow-lg'> | |||
| <RiPuzzle2Line className='w-5 h-5 text-text-accent' /> | |||
| </div> | |||
| <div className='mb-2 text-sm text-gray-600'>{t('common.apiBasedExtension.title')}</div> | |||
| <div className='mb-1 text-text-secondary system-sm-medium'>{t('common.apiBasedExtension.title')}</div> | |||
| <a | |||
| className='flex items-center mb-2 h-[18px] text-xs text-primary-600' | |||
| className='flex items-center system-xs-regular text-text-accent' | |||
| href={t('common.apiBasedExtension.linkUrl') || '/'} | |||
| target='_blank' rel='noopener noreferrer' | |||
| > | |||
| <BookOpen01 className='mr-1 w-3 h-3' /> | |||
| {t('common.apiBasedExtension.link')} | |||
| <RiExternalLinkLine className='ml-1 w-3 h-3' /> | |||
| </a> | |||
| </div> | |||
| ) | |||
| @@ -5,6 +5,7 @@ import { | |||
| } from '@remixicon/react' | |||
| import Item from './item' | |||
| import Empty from './empty' | |||
| import Button from '@/app/components/base/button' | |||
| import { useModalContext } from '@/context/modal-context' | |||
| import { fetchApiBasedExtensionList } from '@/service/common' | |||
| @@ -41,13 +42,14 @@ const ApiBasedExtensionPage = () => { | |||
| )) | |||
| ) | |||
| } | |||
| <div | |||
| className='flex items-center justify-center px-3 h-8 text-[13px] font-medium text-gray-700 rounded-lg bg-gray-50 cursor-pointer' | |||
| <Button | |||
| variant='secondary' | |||
| className='w-full' | |||
| onClick={handleOpenApiBasedExtensionModal} | |||
| > | |||
| <RiAddLine className='mr-2 w-4 h-4' /> | |||
| <RiAddLine className='mr-1 w-4 h-4' /> | |||
| {t('common.apiBasedExtension.add')} | |||
| </div> | |||
| </Button> | |||
| </div> | |||
| ) | |||
| } | |||
| @@ -3,8 +3,9 @@ import { useState } from 'react' | |||
| import { useTranslation } from 'react-i18next' | |||
| import { | |||
| RiDeleteBinLine, | |||
| RiEditLine, | |||
| } from '@remixicon/react' | |||
| import { Edit02 } from '@/app/components/base/icons/src/vender/line/general' | |||
| import Button from '@/app/components/base/button' | |||
| import type { ApiBasedExtension } from '@/models/common' | |||
| import { useModalContext } from '@/context/modal-context' | |||
| import { deleteApiBasedExtension } from '@/service/common' | |||
| @@ -36,25 +37,25 @@ const Item: FC<ItemProps> = ({ | |||
| } | |||
| return ( | |||
| <div className='group flex items-center mb-2 px-4 py-2 border-[0.5px] border-transparent rounded-xl bg-gray-50 hover:border-gray-200 hover:shadow-xs'> | |||
| <div className='group flex items-center mb-2 px-4 py-2 border-[0.5px] border-transparent rounded-xl bg-components-input-bg-normal hover:border-components-input-border-active hover:shadow-xs'> | |||
| <div className='grow'> | |||
| <div className='mb-0.5 text-[13px] font-medium text-gray-700'>{data.name}</div> | |||
| <div className='text-xs text-gray-500'>{data.api_endpoint}</div> | |||
| <div className='mb-0.5 text-[13px] font-medium text-text-secondary'>{data.name}</div> | |||
| <div className='text-xs text-text-tertiary'>{data.api_endpoint}</div> | |||
| </div> | |||
| <div className='hidden group-hover:flex items-center'> | |||
| <div | |||
| className='flex items-center mr-1 px-3 h-7 bg-white text-xs font-medium text-gray-700 rounded-md border-[0.5px] border-gray-200 shadow-xs cursor-pointer' | |||
| <Button | |||
| className='mr-1' | |||
| onClick={handleOpenApiBasedExtensionModal} | |||
| > | |||
| <Edit02 className='mr-[5px] w-3.5 h-3.5' /> | |||
| <RiEditLine className='mr-1 w-4 h-4' /> | |||
| {t('common.operation.edit')} | |||
| </div> | |||
| <div | |||
| className='flex items-center justify-center w-7 h-7 bg-white text-gray-700 rounded-md border-[0.5px] border-gray-200 shadow-xs cursor-pointer' | |||
| </Button> | |||
| <Button | |||
| onClick={() => setShowDeleteConfirm(true)} | |||
| > | |||
| <RiDeleteBinLine className='w-4 h-4' /> | |||
| </div> | |||
| <RiDeleteBinLine className='mr-1 w-4 h-4' /> | |||
| {t('common.operation.delete')} | |||
| </Button> | |||
| </div> | |||
| { | |||
| showDeleteConfirm | |||
| @@ -101,9 +101,9 @@ const ApiBasedExtensionModal: FC<ApiBasedExtensionModalProps> = ({ | |||
| <a | |||
| href={t('common.apiBasedExtension.linkUrl') || '/'} | |||
| target='_blank' rel='noopener noreferrer' | |||
| className='group flex items-center text-xs text-text-tertiary font-normal hover:text-text-accent' | |||
| className='group flex items-center text-xs text-text-accent font-normal' | |||
| > | |||
| <BookOpen01 className='mr-1 w-3 h-3 text-text-tertiary group-hover:text-text-accent' /> | |||
| <BookOpen01 className='mr-1 w-3 h-3' /> | |||
| {t('common.apiBasedExtension.link')} | |||
| </a> | |||
| </div> | |||
| @@ -11,6 +11,7 @@ import { | |||
| import { Menu, Transition } from '@headlessui/react' | |||
| import { syncDataSourceNotion, updateDataSourceNotionAction } from '@/service/common' | |||
| import Toast from '@/app/components/base/toast' | |||
| import cn from '@/utils/classnames' | |||
| type OperateProps = { | |||
| payload: { | |||
| @@ -23,13 +24,6 @@ export default function Operate({ | |||
| payload, | |||
| onAuthAgain, | |||
| }: OperateProps) { | |||
| const itemClassName = ` | |||
| flex px-3 py-2 hover:bg-gray-50 text-sm text-gray-700 | |||
| cursor-pointer | |||
| ` | |||
| const itemIconClassName = ` | |||
| mr-2 mt-[2px] w-4 h-4 text-gray-500 | |||
| ` | |||
| const { t } = useTranslation() | |||
| const { mutate } = useSWRConfig() | |||
| @@ -54,8 +48,8 @@ export default function Operate({ | |||
| { | |||
| ({ open }) => ( | |||
| <> | |||
| <Menu.Button className={`flex items-center justify-center w-8 h-8 rounded-lg hover:bg-gray-100 ${open && 'bg-gray-100'}`}> | |||
| <RiMoreFill className='w-4 h-4' /> | |||
| <Menu.Button className={cn('flex items-center justify-center w-8 h-8 rounded-lg hover:bg-state-base-hover', open && 'bg-state-base-hover')}> | |||
| <RiMoreFill className='w-4 h-4 text-text-secondary' /> | |||
| </Menu.Button> | |||
| <Transition | |||
| as={Fragment} | |||
| @@ -66,40 +60,34 @@ export default function Operate({ | |||
| leaveFrom="transform opacity-100 scale-100" | |||
| leaveTo="transform opacity-0 scale-95" | |||
| > | |||
| <Menu.Items | |||
| className=" | |||
| absolute right-0 top-9 w-60 max-w-80 | |||
| divide-y divide-gray-100 origin-top-right rounded-lg bg-white | |||
| shadow-lg | |||
| " | |||
| > | |||
| <Menu.Items className="absolute right-0 top-9 w-60 max-w-80 origin-top-right rounded-xl bg-components-panel-bg-blur backdrop-blur-sm border-[0.5px] border-components-panel-border shadow-lg"> | |||
| <div className="px-1 py-1"> | |||
| <Menu.Item> | |||
| <div | |||
| className={itemClassName} | |||
| className='flex px-3 py-2 hover:bg-state-base-hover rounded-lg cursor-pointer' | |||
| onClick={onAuthAgain} | |||
| > | |||
| <RiStickyNoteAddLine className={itemIconClassName} /> | |||
| <RiStickyNoteAddLine className='mr-2 mt-[2px] w-4 h-4 text-text-tertiary' /> | |||
| <div> | |||
| <div className='leading-5'>{t('common.dataSource.notion.changeAuthorizedPages')}</div> | |||
| <div className='leading-5 text-xs text-gray-500'> | |||
| <div className='system-sm-semibold text-text-secondary'>{t('common.dataSource.notion.changeAuthorizedPages')}</div> | |||
| <div className='text-text-tertiary system-xs-regular'> | |||
| {payload.total} {t('common.dataSource.notion.pagesAuthorized')} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </Menu.Item> | |||
| <Menu.Item> | |||
| <div className={itemClassName} onClick={handleSync}> | |||
| <RiLoopLeftLine className={itemIconClassName} /> | |||
| <div className='leading-5'>{t('common.dataSource.notion.sync')}</div> | |||
| <div className='flex px-3 py-2 hover:bg-state-base-hover rounded-lg cursor-pointer' onClick={handleSync}> | |||
| <RiLoopLeftLine className='mr-2 mt-[2px] w-4 h-4 text-text-tertiary' /> | |||
| <div className='system-sm-semibold text-text-secondary'>{t('common.dataSource.notion.sync')}</div> | |||
| </div> | |||
| </Menu.Item> | |||
| </div> | |||
| <Menu.Item> | |||
| <div className='p-1'> | |||
| <div className={itemClassName} onClick={handleRemove}> | |||
| <RiDeleteBinLine className={itemIconClassName} /> | |||
| <div className='leading-5'>{t('common.dataSource.notion.remove')}</div> | |||
| <div className='p-1 border-t border-divider-subtle'> | |||
| <div className='flex px-3 py-2 hover:bg-state-base-hover rounded-lg cursor-pointer' onClick={handleRemove}> | |||
| <RiDeleteBinLine className='mr-2 mt-[2px] w-4 h-4 text-text-tertiary' /> | |||
| <div className='system-sm-semibold text-text-secondary'>{t('common.dataSource.notion.remove')}</div> | |||
| </div> | |||
| </div> | |||
| </Menu.Item> | |||
| @@ -89,11 +89,11 @@ const ConfigFirecrawlModal: FC<Props> = ({ | |||
| return ( | |||
| <PortalToFollowElem open> | |||
| <PortalToFollowElemContent className='w-full h-full z-[60]'> | |||
| <div className='fixed inset-0 flex items-center justify-center bg-black/[.25]'> | |||
| <div className='mx-2 w-[640px] max-h-[calc(100vh-120px)] bg-white shadow-xl rounded-2xl overflow-y-auto'> | |||
| <div className='fixed inset-0 flex items-center justify-center bg-background-overlay'> | |||
| <div className='mx-2 w-[640px] max-h-[calc(100vh-120px)] bg-components-panel-bg shadow-xl rounded-2xl overflow-y-auto'> | |||
| <div className='px-8 pt-8'> | |||
| <div className='flex justify-between items-center mb-4'> | |||
| <div className='text-xl font-semibold text-gray-900'>{t(`${I18N_PREFIX}.configFirecrawl`)}</div> | |||
| <div className='system-xl-semibold text-text-primary'>{t(`${I18N_PREFIX}.configFirecrawl`)}</div> | |||
| </div> | |||
| <div className='space-y-4'> | |||
| @@ -114,7 +114,7 @@ const ConfigFirecrawlModal: FC<Props> = ({ | |||
| /> | |||
| </div> | |||
| <div className='my-8 flex justify-between items-center h-8'> | |||
| <a className='flex items-center space-x-1 leading-[18px] text-xs font-normal text-[#155EEF]' target='_blank' href='https://www.firecrawl.dev/account'> | |||
| <a className='flex items-center space-x-1 leading-[18px] text-xs font-normal text-text-accent' target='_blank' href='https://www.firecrawl.dev/account'> | |||
| <span>{t(`${I18N_PREFIX}.getApiKeyLinkText`)}</span> | |||
| <LinkExternal02 className='w-3 h-3' /> | |||
| </a> | |||
| @@ -138,12 +138,12 @@ const ConfigFirecrawlModal: FC<Props> = ({ | |||
| </div> | |||
| </div> | |||
| <div className='border-t-[0.5px] border-t-black/5'> | |||
| <div className='flex justify-center items-center py-3 bg-gray-50 text-xs text-gray-500'> | |||
| <Lock01 className='mr-1 w-3 h-3 text-gray-500' /> | |||
| <div className='border-t-[0.5px] border-t-divider-regular'> | |||
| <div className='flex justify-center items-center py-3 bg-background-section-burn text-xs text-text-tertiary'> | |||
| <Lock01 className='mr-1 w-3 h-3 text-text-tertiary' /> | |||
| {t('common.modelProvider.encrypted.front')} | |||
| <a | |||
| className='text-primary-600 mx-1' | |||
| className='text-text-accent mx-1' | |||
| target='_blank' rel='noopener noreferrer' | |||
| href='https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html' | |||
| > | |||
| @@ -75,11 +75,11 @@ const ConfigJinaReaderModal: FC<Props> = ({ | |||
| return ( | |||
| <PortalToFollowElem open> | |||
| <PortalToFollowElemContent className='w-full h-full z-[60]'> | |||
| <div className='fixed inset-0 flex items-center justify-center bg-black/[.25]'> | |||
| <div className='mx-2 w-[640px] max-h-[calc(100vh-120px)] bg-white shadow-xl rounded-2xl overflow-y-auto'> | |||
| <div className='fixed inset-0 flex items-center justify-center bg-background-overlay'> | |||
| <div className='mx-2 w-[640px] max-h-[calc(100vh-120px)] bg-components-panel-bg shadow-xl rounded-2xl overflow-y-auto'> | |||
| <div className='px-8 pt-8'> | |||
| <div className='flex justify-between items-center mb-4'> | |||
| <div className='text-xl font-semibold text-gray-900'>{t(`${I18N_PREFIX}.configJinaReader`)}</div> | |||
| <div className='system-xl-semibold text-text-primary'>{t(`${I18N_PREFIX}.configJinaReader`)}</div> | |||
| </div> | |||
| <div className='space-y-4'> | |||
| @@ -93,7 +93,7 @@ const ConfigJinaReaderModal: FC<Props> = ({ | |||
| /> | |||
| </div> | |||
| <div className='my-8 flex justify-between items-center h-8'> | |||
| <a className='flex items-center space-x-1 leading-[18px] text-xs font-normal text-[#155EEF]' target='_blank' href='https://jina.ai/reader/'> | |||
| <a className='flex items-center space-x-1 leading-[18px] text-xs font-normal text-text-accent' target='_blank' href='https://jina.ai/reader/'> | |||
| <span>{t(`${I18N_PREFIX}.getApiKeyLinkText`)}</span> | |||
| <LinkExternal02 className='w-3 h-3' /> | |||
| </a> | |||
| @@ -117,12 +117,12 @@ const ConfigJinaReaderModal: FC<Props> = ({ | |||
| </div> | |||
| </div> | |||
| <div className='border-t-[0.5px] border-t-black/5'> | |||
| <div className='flex justify-center items-center py-3 bg-gray-50 text-xs text-gray-500'> | |||
| <Lock01 className='mr-1 w-3 h-3 text-gray-500' /> | |||
| <div className='border-t-[0.5px] border-t-divider-regular'> | |||
| <div className='flex justify-center items-center py-3 bg-background-section-burn text-xs text-text-tertiary'> | |||
| <Lock01 className='mr-1 w-3 h-3 text-text-tertiary' /> | |||
| {t('common.modelProvider.encrypted.front')} | |||
| <a | |||
| className='text-primary-600 mx-1' | |||
| className='text-text-accent mx-1' | |||
| target='_blank' rel='noopener noreferrer' | |||
| href='https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html' | |||
| > | |||
| @@ -85,10 +85,10 @@ const DataSourceWebsite: FC<Props> = ({ provider }) => { | |||
| logo: ({ className }: { className: string }) => ( | |||
| item.provider === DataSourceProvider.fireCrawl | |||
| ? ( | |||
| <div className={cn(className, 'flex items-center justify-center w-5 h-5 bg-white border border-gray-100 text-xs font-medium text-gray-500 rounded ml-3')}>🔥</div> | |||
| <div className={cn(className, 'flex items-center justify-center w-5 h-5 !bg-background-default border border-divider-subtle text-xs font-medium text-text-tertiary rounded ml-3')}>🔥</div> | |||
| ) | |||
| : ( | |||
| <div className={cn(className, 'flex items-center justify-center w-5 h-5 bg-white border border-gray-100 text-xs font-medium text-gray-500 rounded ml-3')}> | |||
| <div className={cn(className, 'flex items-center justify-center w-5 h-5 !bg-background-default border border-divider-subtle text-xs font-medium text-text-tertiary rounded ml-3')}> | |||
| <span className={s.jinaLogo} /> | |||
| </div> | |||
| ) | |||
| @@ -52,7 +52,7 @@ const ConfigItem: FC<Props> = ({ | |||
| ? <Indicator className='shrink-0 mr-[6px]' color='green' /> | |||
| : <Indicator className='shrink-0 mr-[6px]' color='yellow' /> | |||
| } | |||
| <div className={`shrink-0 mr-3 text-xs font-medium uppercase ${payload.isActive ? 'text-util-colors-green-green-600' : 'text-util-colors-warning-warning-600'}`}> | |||
| <div className={`shrink-0 mr-3 system-xs-semibold-uppercase ${payload.isActive ? 'text-util-colors-green-green-600' : 'text-util-colors-warning-warning-600'}`}> | |||
| { | |||
| payload.isActive | |||
| ? t(isNotion ? 'common.dataSource.notion.connected' : 'common.dataSource.website.active') | |||
| @@ -70,7 +70,7 @@ const ConfigItem: FC<Props> = ({ | |||
| { | |||
| isWebsite && !readOnly && ( | |||
| <div className='p-2 text-text-tertiary cursor-pointer rounded-md hover:bg-black/5' onClick={onRemove} > | |||
| <div className='p-2 text-text-tertiary cursor-pointer rounded-md hover:bg-state-base-hover' onClick={onRemove} > | |||
| <RiDeleteBinLine className='w-4 h-4' /> | |||
| </div> | |||
| ) | |||
| @@ -8,12 +8,13 @@ import ConfigItem from './config-item' | |||
| import s from './style.module.css' | |||
| import { DataSourceType } from './types' | |||
| import Button from '@/app/components/base/button' | |||
| import { DataSourceProvider } from '@/models/common' | |||
| import cn from '@/utils/classnames' | |||
| type Props = { | |||
| type: DataSourceType | |||
| provider: DataSourceProvider | |||
| provider?: DataSourceProvider | |||
| isConfigured: boolean | |||
| onConfigure: () => void | |||
| readOnly: boolean | |||
| @@ -43,13 +44,13 @@ const Panel: FC<Props> = ({ | |||
| return ( | |||
| <div className='mb-2 bg-background-section-burn rounded-xl'> | |||
| <div className='flex items-center px-3 py-[9px]'> | |||
| <div className={cn(s[`${type}-icon`], 'w-8 h-8 mr-3 border border-divider-subtle rounded-lg bg-background-default')} /> | |||
| <div className={cn(s[`${type}-icon`], 'w-8 h-8 mr-3 border border-divider-subtle rounded-lg !bg-background-default')} /> | |||
| <div className='grow'> | |||
| <div className='flex items-center h-5'> | |||
| <div className='text-sm font-medium text-text-primary'>{t(`common.dataSource.${type}.title`)}</div> | |||
| {isWebsite && ( | |||
| <div className='ml-1 leading-[18px] px-1.5 rounded-md bg-white border border-gray-100 text-xs font-medium text-gray-700'> | |||
| <span className='text-gray-500'>{t('common.dataSource.website.with')}</span> { provider === DataSourceProvider.fireCrawl ? '🔥 Firecrawl' : 'Jina Reader'} | |||
| <div className='ml-1 leading-[18px] px-1.5 rounded-md bg-components-badge-white-to-dark text-xs font-medium text-text-secondary'> | |||
| <span className='text-text-tertiary'>{t('common.dataSource.website.with')}</span> { provider === DataSourceProvider.fireCrawl ? '🔥 Firecrawl' : 'Jina Reader'} | |||
| </div> | |||
| )} | |||
| </div> | |||
| @@ -66,16 +67,13 @@ const Panel: FC<Props> = ({ | |||
| { | |||
| isConfigured | |||
| ? ( | |||
| <div | |||
| className={ | |||
| `flex items-center ml-3 px-3 h-7 bg-white border border-gray-200 | |||
| rounded-md text-xs font-medium text-gray-700 | |||
| ${!readOnly ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}` | |||
| } | |||
| <Button | |||
| disabled={readOnly} | |||
| className='ml-3' | |||
| onClick={onConfigure} | |||
| > | |||
| {t('common.dataSource.configure')} | |||
| </div> | |||
| </Button> | |||
| ) | |||
| : ( | |||
| <> | |||
| @@ -194,12 +194,12 @@ export default function AccountSetting({ | |||
| </div> | |||
| <div ref={scrollRef} className='w-full pb-4 bg-components-panel-bg overflow-y-auto'> | |||
| <div className={cn('sticky top-0 mx-8 pt-[27px] pb-2 mb-[18px] flex items-center bg-components-panel-bg z-20', scrolled && 'border-b border-divider-regular')}> | |||
| <div className='shrink-0 text-text-primary title-2xl-semi-bold'>{activeItem?.name}</div> | |||
| { | |||
| activeItem?.description && ( | |||
| <div className='shrink-0 ml-2 text-xs text-text-tertiary'>{activeItem?.description}</div> | |||
| ) | |||
| } | |||
| <div className='shrink-0 text-text-primary title-2xl-semi-bold'> | |||
| {activeItem?.name} | |||
| {activeItem?.description && ( | |||
| <div className='mt-1 system-sm-regular text-text-tertiary'>{activeItem?.description}</div> | |||
| )} | |||
| </div> | |||
| {activeItem?.key === 'provider' && ( | |||
| <div className='grow flex justify-end'> | |||
| <Input | |||
| @@ -18,9 +18,11 @@ import type { InvitationResult } from '@/models/common' | |||
| import LogoEmbeddedChatHeader from '@/app/components/base/logo/logo-embedded-chat-header' | |||
| import { useProviderContext } from '@/context/provider-context' | |||
| import { Plan } from '@/app/components/billing/type' | |||
| import Button from '@/app/components/base/button' | |||
| import UpgradeBtn from '@/app/components/billing/upgrade-btn' | |||
| import { NUM_INFINITE } from '@/app/components/billing/config' | |||
| import { LanguagesSupported } from '@/i18n/language' | |||
| import cn from '@/utils/classnames' | |||
| dayjs.extend(relativeTime) | |||
| const MembersPage = () => { | |||
| @@ -35,7 +37,13 @@ const MembersPage = () => { | |||
| const { locale } = useContext(I18n) | |||
| const { userProfile, currentWorkspace, isCurrentWorkspaceOwner, isCurrentWorkspaceManager, systemFeatures } = useAppContext() | |||
| const { data, mutate } = useSWR({ url: '/workspaces/current/members' }, fetchMembers) | |||
| const { data, mutate } = useSWR( | |||
| { | |||
| url: '/workspaces/current/members', | |||
| params: {}, | |||
| }, | |||
| fetchMembers, | |||
| ) | |||
| const [inviteModalVisible, setInviteModalVisible] = useState(false) | |||
| const [invitationResults, setInvitationResults] = useState<InvitationResult[]>([]) | |||
| const [invitedModalVisible, setInvitedModalVisible] = useState(false) | |||
| @@ -47,17 +55,17 @@ const MembersPage = () => { | |||
| return ( | |||
| <> | |||
| <div className='flex flex-col'> | |||
| <div className='flex items-center mb-4 p-3 gap-1 bg-gray-50 rounded-2xl'> | |||
| <LogoEmbeddedChatHeader className='!w-10 !h-10' /> | |||
| <div className='grow mx-2'> | |||
| <div className='text-sm font-medium text-gray-900'>{currentWorkspace?.name}</div> | |||
| <div className='flex items-center mb-4 p-3 pr-5 gap-3 bg-gradient-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1 rounded-xl border-t-[0.5px] border-l-[0.5px] border-divider-subtle'> | |||
| <LogoEmbeddedChatHeader className='!w-12 !h-12' /> | |||
| <div className='grow'> | |||
| <div className='system-md-semibold text-text-secondary'>{currentWorkspace?.name}</div> | |||
| {enableBilling && ( | |||
| <div className='text-xs text-gray-500'> | |||
| <div className='mt-1 system-xs-medium text-text-tertiary'> | |||
| {isNotUnlimitedMemberPlan | |||
| ? ( | |||
| <div className='flex space-x-1'> | |||
| <div>{t('billing.plansCommon.member')}{locale !== LanguagesSupported[1] && accounts.length > 1 && 's'}</div> | |||
| <div className='text-gray-700'>{accounts.length}</div> | |||
| <div className=''>{accounts.length}</div> | |||
| <div>/</div> | |||
| <div>{plan.total.teamMembers === NUM_INFINITE ? t('billing.plansCommon.unlimited') : plan.total.teamMembers}</div> | |||
| </div> | |||
| @@ -75,14 +83,10 @@ const MembersPage = () => { | |||
| {isMemberFull && ( | |||
| <UpgradeBtn className='mr-2' loc='member-invite' /> | |||
| )} | |||
| <div className={ | |||
| `shrink-0 flex items-center py-[7px] px-3 border-[0.5px] border-gray-200 | |||
| text-[13px] font-medium text-primary-600 bg-white | |||
| shadow-xs rounded-lg ${(isCurrentWorkspaceManager && !isMemberFull) ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}` | |||
| } onClick={() => (isCurrentWorkspaceManager && !isMemberFull) && setInviteModalVisible(true)}> | |||
| <RiUserAddLine className='w-4 h-4 mr-2 ' /> | |||
| <Button variant='primary' className={cn('shrink-0')} disabled={!isCurrentWorkspaceManager || isMemberFull} onClick={() => setInviteModalVisible(true)}> | |||
| <RiUserAddLine className='w-4 h-4 mr-1' /> | |||
| {t('common.members.invite')} | |||
| </div> | |||
| </Button> | |||
| </div> | |||
| <div className='overflow-visible lg:overflow-visible'> | |||
| <div className='flex items-center py-[7px] border-b border-divider-regular min-w-[480px]'> | |||
| @@ -99,18 +103,18 @@ const MembersPage = () => { | |||
| <div className=''> | |||
| <div className='text-text-secondary system-sm-medium'> | |||
| {account.name} | |||
| {account.status === 'pending' && <span className='ml-1 system-xs-regular text-[#DC6803]'>{t('common.members.pending')}</span>} | |||
| {account.status === 'pending' && <span className='ml-1 system-xs-medium text-text-warning'>{t('common.members.pending')}</span>} | |||
| {userProfile.email === account.email && <span className='system-xs-regular text-text-tertiary'>{t('common.members.you')}</span>} | |||
| </div> | |||
| <div className='text-text-tertiary system-xs-regular'>{account.email}</div> | |||
| </div> | |||
| </div> | |||
| <div className='shrink-0 flex items-center w-[104px] py-2 system-xs-regular text-text-secondary'>{dayjs(Number((account.last_active_at || account.created_at)) * 1000).locale(locale === 'zh-Hans' ? 'zh-cn' : 'en').fromNow()}</div> | |||
| <div className='shrink-0 flex items-center w-[104px] py-2 system-sm-regular text-text-secondary'>{dayjs(Number((account.last_active_at || account.created_at)) * 1000).locale(locale === 'zh-Hans' ? 'zh-cn' : 'en').fromNow()}</div> | |||
| <div className='shrink-0 w-[96px] flex items-center'> | |||
| { | |||
| ((isCurrentWorkspaceOwner && account.role !== 'owner') || (isCurrentWorkspaceManager && !['owner', 'admin'].includes(account.role))) | |||
| ? <Operation member={account} operatorRole={currentWorkspace.role} onOperate={mutate} /> | |||
| : <div className='px-3 system-xs-regular text-text-secondary'>{RoleMap[account.role] || RoleMap.normal}</div> | |||
| : <div className='px-3 system-sm-regular text-text-secondary'>{RoleMap[account.role] || RoleMap.normal}</div> | |||
| } | |||
| </div> | |||
| </div> | |||
| @@ -1,7 +1,7 @@ | |||
| 'use client' | |||
| import { useCallback, useState } from 'react' | |||
| import { useContext } from 'use-context-selector' | |||
| import { XMarkIcon } from '@heroicons/react/24/outline' | |||
| import { RiCloseLine } from '@remixicon/react' | |||
| import { useTranslation } from 'react-i18next' | |||
| import { ReactMultiEmail } from 'react-multi-email' | |||
| import { RiErrorWarningFill } from '@remixicon/react' | |||
| @@ -59,10 +59,10 @@ const InviteModal = ({ | |||
| <div className={cn(s.wrap)}> | |||
| <Modal overflowVisible isShow onClose={() => { }} className={cn(s.modal)}> | |||
| <div className='flex justify-between mb-2'> | |||
| <div className='text-xl font-semibold text-gray-900'>{t('common.members.inviteTeamMember')}</div> | |||
| <XMarkIcon className='w-4 h-4 cursor-pointer' onClick={onCancel} /> | |||
| <div className='text-xl font-semibold text-text-primary'>{t('common.members.inviteTeamMember')}</div> | |||
| <RiCloseLine className='w-4 h-4 cursor-pointer text-text-tertiary' onClick={onCancel} /> | |||
| </div> | |||
| <div className='mb-3 text-[13px] text-gray-500'>{t('common.members.inviteTeamMemberTip')}</div> | |||
| <div className='mb-3 text-[13px] text-text-tertiary'>{t('common.members.inviteTeamMemberTip')}</div> | |||
| {!isEmailSetup && ( | |||
| <div className='grow basis-0 overflow-y-auto pb-4'> | |||
| <div className='relative mb-1 p-2 rounded-xl border border-components-panel-border shadow-xs'> | |||
| @@ -80,19 +80,18 @@ const InviteModal = ({ | |||
| )} | |||
| <div> | |||
| <div className='mb-2 text-sm font-medium text-gray-900'>{t('common.members.email')}</div> | |||
| <div className='mb-2 text-sm font-medium text-text-primary'>{t('common.members.email')}</div> | |||
| <div className='mb-8 h-36 flex items-stretch'> | |||
| <ReactMultiEmail | |||
| className={cn('w-full pt-2 px-3 outline-none border-none', | |||
| 'appearance-none text-sm text-gray-900 rounded-lg overflow-y-auto', | |||
| s.emailsInput, | |||
| className={cn('w-full pt-2 px-3 outline-none !bg-components-input-bg-normal border-components-input-border-active', | |||
| 'appearance-none text-sm !text-text-primary rounded-lg overflow-y-auto', | |||
| )} | |||
| autoFocus | |||
| emails={emails} | |||
| inputClassName='bg-transparent' | |||
| onChange={setEmails} | |||
| getLabel={(email, index, removeEmail) => | |||
| <div data-tag key={index} className={cn(s.emailBackground)}> | |||
| <div data-tag key={index} className={cn('bg-components-button-secondary-bg')}> | |||
| <div data-tag-item>{email}</div> | |||
| <span data-tag-handle onClick={() => removeEmail(index)}> | |||
| × | |||
| @@ -34,53 +34,53 @@ const RoleSelector = ({ value, onChange }: RoleSelectorProps) => { | |||
| onClick={() => setOpen(v => !v)} | |||
| className='block' | |||
| > | |||
| <div className={cn('flex items-center px-3 py-2 rounded-lg bg-gray-100 cursor-pointer hover:bg-gray-200', open && 'bg-gray-200')}> | |||
| <div className='grow mr-2 text-gray-900 text-sm leading-5'>{t('common.members.invitedAsRole', { role: t(`common.members.${toHump(value)}`) })}</div> | |||
| <RiArrowDownSLine className='shrink-0 w-4 h-4 text-gray-700' /> | |||
| <div className={cn('flex items-center px-3 py-2 rounded-lg bg-components-input-bg-normal cursor-pointer hover:bg-state-base-hover', open && 'bg-state-base-hover')}> | |||
| <div className='grow mr-2 text-text-primary text-sm leading-5'>{t('common.members.invitedAsRole', { role: t(`common.members.${toHump(value)}`) })}</div> | |||
| <RiArrowDownSLine className='shrink-0 w-4 h-4 text-text-secondary' /> | |||
| </div> | |||
| </PortalToFollowElemTrigger> | |||
| <PortalToFollowElemContent className='z-[1002]'> | |||
| <div className='relative w-[336px] bg-white rounded-lg border-[0.5px] bg-gray-200 shadow-lg'> | |||
| <div className='relative w-[336px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg'> | |||
| <div className='p-1'> | |||
| <div className='p-2 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => { | |||
| <div className='p-2 rounded-lg hover:bg-state-base-hover cursor-pointer' onClick={() => { | |||
| onChange('normal') | |||
| setOpen(false) | |||
| }}> | |||
| <div className='relative pl-5'> | |||
| <div className='text-gray-700 text-sm leading-5'>{t('common.members.normal')}</div> | |||
| <div className='text-gray-500 text-xs leading-[18px]'>{t('common.members.normalTip')}</div> | |||
| {value === 'normal' && <Check className='absolute top-0.5 left-0 w-4 h-4 text-primary-600'/>} | |||
| <div className='text-text-secondary text-sm leading-5'>{t('common.members.normal')}</div> | |||
| <div className='text-text-tertiary text-xs leading-[18px]'>{t('common.members.normalTip')}</div> | |||
| {value === 'normal' && <Check className='absolute top-0.5 left-0 w-4 h-4 text-text-accent'/>} | |||
| </div> | |||
| </div> | |||
| <div className='p-2 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => { | |||
| <div className='p-2 rounded-lg hover:bg-state-base-hover cursor-pointer' onClick={() => { | |||
| onChange('editor') | |||
| setOpen(false) | |||
| }}> | |||
| <div className='relative pl-5'> | |||
| <div className='text-gray-700 text-sm leading-5'>{t('common.members.editor')}</div> | |||
| <div className='text-gray-500 text-xs leading-[18px]'>{t('common.members.editorTip')}</div> | |||
| {value === 'editor' && <Check className='absolute top-0.5 left-0 w-4 h-4 text-primary-600'/>} | |||
| <div className='text-text-secondary text-sm leading-5'>{t('common.members.editor')}</div> | |||
| <div className='text-text-tertiary text-xs leading-[18px]'>{t('common.members.editorTip')}</div> | |||
| {value === 'editor' && <Check className='absolute top-0.5 left-0 w-4 h-4 text-text-accent'/>} | |||
| </div> | |||
| </div> | |||
| <div className='p-2 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => { | |||
| <div className='p-2 rounded-lg hover:bg-state-base-hover cursor-pointer' onClick={() => { | |||
| onChange('admin') | |||
| setOpen(false) | |||
| }}> | |||
| <div className='relative pl-5'> | |||
| <div className='text-gray-700 text-sm leading-5'>{t('common.members.admin')}</div> | |||
| <div className='text-gray-500 text-xs leading-[18px]'>{t('common.members.adminTip')}</div> | |||
| {value === 'admin' && <Check className='absolute top-0.5 left-0 w-4 h-4 text-primary-600'/>} | |||
| <div className='text-text-secondary text-sm leading-5'>{t('common.members.admin')}</div> | |||
| <div className='text-text-tertiary text-xs leading-[18px]'>{t('common.members.adminTip')}</div> | |||
| {value === 'admin' && <Check className='absolute top-0.5 left-0 w-4 h-4 text-text-accent'/>} | |||
| </div> | |||
| </div> | |||
| {datasetOperatorEnabled && ( | |||
| <div className='p-2 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => { | |||
| <div className='p-2 rounded-lg hover:bg-state-base-hover cursor-pointer' onClick={() => { | |||
| onChange('dataset_operator') | |||
| setOpen(false) | |||
| }}> | |||
| <div className='relative pl-5'> | |||
| <div className='text-gray-700 text-sm leading-5'>{t('common.members.datasetOperator')}</div> | |||
| <div className='text-gray-500 text-xs leading-[18px]'>{t('common.members.datasetOperatorTip')}</div> | |||
| {value === 'dataset_operator' && <Check className='absolute top-0.5 left-0 w-4 h-4 text-primary-600'/>} | |||
| <div className='text-text-secondary text-sm leading-5'>{t('common.members.datasetOperator')}</div> | |||
| <div className='text-text-tertiary text-xs leading-[18px]'>{t('common.members.datasetOperatorTip')}</div> | |||
| {value === 'dataset_operator' && <Check className='absolute top-0.5 left-0 w-4 h-4 text-text-accent'/>} | |||
| </div> | |||
| </div> | |||
| )} | |||
| @@ -1,7 +1,7 @@ | |||
| .modal { | |||
| padding: 32px !important; | |||
| width: 480px !important; | |||
| background: linear-gradient(180deg, rgba(3, 152, 85, 0.05) 0%, rgba(3, 152, 85, 0) 22.44%), #F9FAFB !important; | |||
| /* background: linear-gradient(180deg, rgba(3, 152, 85, 0.05) 0%, rgba(3, 152, 85, 0) 22.44%), #F9FAFB !important; */ | |||
| } | |||
| .copyIcon { | |||
| @@ -33,25 +33,25 @@ const InvitedModal = ({ | |||
| <div className='flex justify-between mb-3'> | |||
| <div className=' | |||
| w-12 h-12 flex items-center justify-center rounded-xl | |||
| bg-white border-[0.5px] border-gray-100 | |||
| bg-background-section-burn border-[0.5px] border-components-panel-border | |||
| shadow-xl | |||
| '> | |||
| <CheckCircleIcon className='w-[22px] h-[22px] text-[#039855]' /> | |||
| </div> | |||
| <XMarkIcon className='w-4 h-4 cursor-pointer' onClick={onCancel} /> | |||
| </div> | |||
| <div className='mb-1 text-xl font-semibold text-gray-900'>{t('common.members.invitationSent')}</div> | |||
| <div className='mb-1 text-xl font-semibold text-text-primary'>{t('common.members.invitationSent')}</div> | |||
| {!IS_CE_EDITION && ( | |||
| <div className='mb-10 text-sm text-gray-500'>{t('common.members.invitationSentTip')}</div> | |||
| <div className='mb-10 text-sm text-text-tertiary'>{t('common.members.invitationSentTip')}</div> | |||
| )} | |||
| {IS_CE_EDITION && ( | |||
| <> | |||
| <div className='mb-5 text-sm text-gray-500'>{t('common.members.invitationSentTip')}</div> | |||
| <div className='mb-5 text-sm text-text-tertiary'>{t('common.members.invitationSentTip')}</div> | |||
| <div className='flex flex-col gap-2 mb-9'> | |||
| { | |||
| !!successInvitationResults.length | |||
| && <> | |||
| <div className='py-2 text-sm font-Medium text-gray-900'>{t('common.members.invitationLink')}</div> | |||
| <div className='py-2 text-sm font-Medium text-text-primary'>{t('common.members.invitationLink')}</div> | |||
| {successInvitationResults.map(item => | |||
| <InvitationLink key={item.email} value={item} />)} | |||
| </> | |||
| @@ -59,7 +59,7 @@ const InvitedModal = ({ | |||
| { | |||
| !!failedInvitationResults.length | |||
| && <> | |||
| <div className='py-2 text-sm font-Medium text-gray-900'>{t('common.members.failedInvitationEmails')}</div> | |||
| <div className='py-2 text-sm font-Medium text-text-primary'>{t('common.members.failedInvitationEmails')}</div> | |||
| <div className='flex flex-wrap justify-between gap-y-1'> | |||
| { | |||
| failedInvitationResults.map(item => | |||
| @@ -35,21 +35,21 @@ const InvitationLink = ({ | |||
| }, [isCopied]) | |||
| return ( | |||
| <div className='flex rounded-lg bg-gray-100 hover:bg-gray-100 border border-gray-200 py-2 items-center'> | |||
| <div className="flex items-center flex-grow h-5"> | |||
| <div className='flex-grow bg-gray-100 text-[13px] relative h-full'> | |||
| <div className='flex rounded-lg bg-components-input-bg-normal hover:bg-state-base-hover border border-components-input-border-active py-2 items-center'> | |||
| <div className="flex items-center grow h-5"> | |||
| <div className='grow text-[13px] relative h-full'> | |||
| <Tooltip | |||
| popupContent={isCopied ? `${t('appApi.copied')}` : `${t('appApi.copy')}`} | |||
| > | |||
| <div className='absolute top-0 left-0 w-full pl-2 pr-2 truncate cursor-pointer r-0' onClick={copyHandle}>{value.url}</div> | |||
| </Tooltip> | |||
| </div> | |||
| <div className="flex-shrink-0 h-4 bg-gray-200 border" /> | |||
| <div className="shrink-0 h-4 bg-divider-regular border" /> | |||
| <Tooltip | |||
| popupContent={isCopied ? `${t('appApi.copied')}` : `${t('appApi.copy')}`} | |||
| > | |||
| <div className="px-0.5 flex-shrink-0"> | |||
| <div className={`box-border w-[30px] h-[30px] flex items-center justify-center rounded-lg hover:bg-gray-100 cursor-pointer ${s.copyIcon} ${isCopied ? s.copied : ''}`} onClick={copyHandle}> | |||
| <div className="px-0.5 shrink-0"> | |||
| <div className={`box-border w-[30px] h-[30px] flex items-center justify-center rounded-lg hover:bg-state-base-hover cursor-pointer ${s.copyIcon} ${isCopied ? s.copied : ''}`} onClick={copyHandle}> | |||
| </div> | |||
| </div> | |||
| </Tooltip> | |||
| @@ -1,3 +0,0 @@ | |||
| .popup { | |||
| box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); | |||
| } | |||
| @@ -4,26 +4,12 @@ import { Fragment, useMemo } from 'react' | |||
| import { useContext } from 'use-context-selector' | |||
| import { Menu, Transition } from '@headlessui/react' | |||
| import { CheckIcon, ChevronDownIcon } from '@heroicons/react/24/outline' | |||
| import s from './index.module.css' | |||
| import { useProviderContext } from '@/context/provider-context' | |||
| import cn from '@/utils/classnames' | |||
| import type { Member } from '@/models/common' | |||
| import { deleteMemberOrCancelInvitation, updateMemberRole } from '@/service/common' | |||
| import { ToastContext } from '@/app/components/base/toast' | |||
| const itemClassName = ` | |||
| flex px-3 py-2 cursor-pointer hover:bg-gray-50 rounded-lg | |||
| ` | |||
| const itemIconClassName = ` | |||
| w-4 h-4 mt-[2px] mr-1 text-primary-600 | |||
| ` | |||
| const itemTitleClassName = ` | |||
| leading-[20px] text-sm text-gray-700 whitespace-nowrap | |||
| ` | |||
| const itemDescClassName = ` | |||
| leading-[18px] text-xs text-gray-500 whitespace-nowrap | |||
| ` | |||
| type IOperationProps = { | |||
| member: Member | |||
| operatorRole: string | |||
| @@ -90,15 +76,9 @@ const Operation = ({ | |||
| { | |||
| ({ open }) => ( | |||
| <> | |||
| <Menu.Button className={cn( | |||
| ` | |||
| group flex items-center justify-between w-full h-full | |||
| hover:bg-gray-100 cursor-pointer ${open && 'bg-gray-100'} | |||
| text-[13px] text-gray-700 px-3 | |||
| `, | |||
| )}> | |||
| <Menu.Button className={cn('group px-3 flex items-center justify-between w-full h-full system-sm-regular text-text-secondary cursor-pointer hover:bg-state-base-hover', open && 'bg-state-base-hover')}> | |||
| {RoleMap[member.role] || RoleMap.normal} | |||
| <ChevronDownIcon className={`w-4 h-4 group-hover:block ${open ? 'block' : 'hidden'}`} /> | |||
| <ChevronDownIcon className={cn('w-4 h-4 group-hover:block', open ? 'block' : 'hidden')} /> | |||
| </Menu.Button> | |||
| <Transition | |||
| as={Fragment} | |||
| @@ -110,27 +90,21 @@ const Operation = ({ | |||
| leaveTo="transform opacity-0 scale-95" | |||
| > | |||
| <Menu.Items | |||
| className={cn( | |||
| ` | |||
| absolute right-0 top-[52px] z-10 bg-white border-[0.5px] border-gray-200 | |||
| divide-y divide-gray-100 origin-top-right rounded-lg | |||
| `, | |||
| s.popup, | |||
| )} | |||
| className={cn('absolute origin-top-right right-0 top-[52px] z-10 bg-components-panel-bg-blur backdrop-blur-sm rounded-xl border-[0.5px] border-components-panel-border shadow-lg')} | |||
| > | |||
| <div className="px-1 py-1"> | |||
| <div className="p-1"> | |||
| { | |||
| roleList.map(role => ( | |||
| <Menu.Item key={role}> | |||
| <div className={itemClassName} onClick={() => handleUpdateMemberRole(role)}> | |||
| <div className='flex px-3 py-2 cursor-pointer hover:bg-state-base-hover rounded-lg' onClick={() => handleUpdateMemberRole(role)}> | |||
| { | |||
| role === member.role | |||
| ? <CheckIcon className={itemIconClassName} /> | |||
| : <div className={itemIconClassName} /> | |||
| ? <CheckIcon className='w-4 h-4 mt-[2px] mr-1 text-text-accent' /> | |||
| : <div className='w-4 h-4 mt-[2px] mr-1 text-text-accent' /> | |||
| } | |||
| <div> | |||
| <div className={itemTitleClassName}>{t(`common.members.${toHump(role)}`)}</div> | |||
| <div className={itemDescClassName}>{t(`common.members.${toHump(role)}Tip`)}</div> | |||
| <div className='system-sm-semibold text-text-secondary whitespace-nowrap'>{t(`common.members.${toHump(role)}`)}</div> | |||
| <div className='system-xs-regular text-text-tertiary whitespace-nowrap'>{t(`common.members.${toHump(role)}Tip`)}</div> | |||
| </div> | |||
| </div> | |||
| </Menu.Item> | |||
| @@ -138,12 +112,12 @@ const Operation = ({ | |||
| } | |||
| </div> | |||
| <Menu.Item> | |||
| <div className='px-1 py-1'> | |||
| <div className={itemClassName} onClick={handleDeleteMemberOrCancelInvitation}> | |||
| <div className={itemIconClassName} /> | |||
| <div className='p-1 border-t border-divider-subtle'> | |||
| <div className='flex px-3 py-2 cursor-pointer hover:bg-state-base-hover rounded-lg' onClick={handleDeleteMemberOrCancelInvitation}> | |||
| <div className='w-4 h-4 mt-[2px] mr-1 text-text-accent' /> | |||
| <div> | |||
| <div className={itemTitleClassName}>{t('common.members.removeFromTeam')}</div> | |||
| <div className={itemDescClassName}>{t('common.members.removeFromTeamTip')}</div> | |||
| <div className='system-sm-semibold text-text-secondary whitespace-nowrap'>{t('common.members.removeFromTeam')}</div> | |||
| <div className='system-xs-regular text-text-tertiary whitespace-nowrap'>{t('common.members.removeFromTeamTip')}</div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -10,7 +10,7 @@ const translation = { | |||
| }, | |||
| teamMembers: 'Team Members', | |||
| upgradeBtn: { | |||
| plain: 'Upgrade Plan', | |||
| plain: 'View Plan', | |||
| encourage: 'Upgrade Now', | |||
| encourageShort: 'Upgrade', | |||
| }, | |||
| @@ -101,18 +101,22 @@ const translation = { | |||
| plans: { | |||
| sandbox: { | |||
| name: 'Sandbox', | |||
| for: 'Free Trial of Core Capabilities', | |||
| description: 'Free Trial of Core Capabilities', | |||
| }, | |||
| professional: { | |||
| name: 'Professional', | |||
| for: 'For Independent Developers/Small Teams', | |||
| description: 'For Independent Developers/Small Teams', | |||
| }, | |||
| team: { | |||
| name: 'Team', | |||
| for: 'For Medium-sized Teams', | |||
| description: 'For Medium-sized Teams', | |||
| }, | |||
| community: { | |||
| name: 'Community', | |||
| for: 'For Individual Users, Small Teams, or Non-commercial Projects', | |||
| description: 'For Individual Users, Small Teams, or Non-commercial Projects', | |||
| price: 'Free', | |||
| btnText: 'Get Started with Community', | |||
| @@ -125,6 +129,7 @@ const translation = { | |||
| }, | |||
| premium: { | |||
| name: 'Premium', | |||
| for: 'For Mid-sized Organizations and Teams', | |||
| description: 'For Mid-sized Organizations and Teams', | |||
| price: 'Scalable', | |||
| priceTip: 'Based on Cloud Marketplace', | |||
| @@ -140,6 +145,7 @@ const translation = { | |||
| }, | |||
| enterprise: { | |||
| name: 'Enterprise', | |||
| for: 'For large-sized Teams', | |||
| description: 'For Enterprise Require Organization-wide Security, Compliance, Scalability, Control and More Advanced Features', | |||
| price: 'Custom', | |||
| priceTip: 'Annual Billing Only', | |||
| @@ -1,6 +1,8 @@ | |||
| const translation = { | |||
| custom: 'Customization', | |||
| upgradeTip: { | |||
| title: 'Upgrade your plan', | |||
| des: 'Upgrade your plan to customize your brand', | |||
| prefix: 'Upgrade your plan to', | |||
| suffix: 'customize your brand.', | |||
| }, | |||
| @@ -9,7 +9,7 @@ const translation = { | |||
| vectorSpaceTooltip: '采用高质量索引模式的文档会消耗知识数据存储资源。当知识数据存储达到限制时,将不会上传新文档。', | |||
| }, | |||
| upgradeBtn: { | |||
| plain: '升级套餐', | |||
| plain: '查看套餐', | |||
| encourage: '立即升级', | |||
| encourageShort: '升级', | |||
| }, | |||
| @@ -100,18 +100,22 @@ const translation = { | |||
| plans: { | |||
| sandbox: { | |||
| name: 'Sandbox', | |||
| for: '核心能力的免费试用', | |||
| description: '核心功能免费试用', | |||
| }, | |||
| professional: { | |||
| name: 'Professional', | |||
| for: '适合独立开发者或小团队', | |||
| description: '对于独立开发者/小团队', | |||
| }, | |||
| team: { | |||
| name: 'Team', | |||
| for: '适合中等规模的团队', | |||
| description: '对于中型团队', | |||
| }, | |||
| community: { | |||
| name: 'Community', | |||
| for: '适用于个人用户、小型团队或非商业项目', | |||
| description: '适用于个人用户、小型团队或非商业项目', | |||
| price: '免费', | |||
| btnText: '开始使用', | |||
| @@ -124,6 +128,7 @@ const translation = { | |||
| }, | |||
| premium: { | |||
| name: 'Premium', | |||
| for: '对于中型组织和团队', | |||
| description: '对于中型组织和团队', | |||
| price: '可扩展', | |||
| priceTip: '基于云市场', | |||
| @@ -139,6 +144,7 @@ const translation = { | |||
| }, | |||
| enterprise: { | |||
| name: 'Enterprise', | |||
| for: '适合大人员规模的团队', | |||
| description: '对于需要组织范围内的安全性、合规性、可扩展性、控制和更高级功能的企业', | |||
| price: '定制', | |||
| priceTip: '仅按年计费', | |||
| @@ -1,6 +1,8 @@ | |||
| const translation = { | |||
| custom: '定制', | |||
| upgradeTip: { | |||
| title: '升级您的计划', | |||
| des: '升级您的计划来定制您的品牌。', | |||
| prefix: '升级您的计划以', | |||
| suffix: '定制您的品牌。', | |||
| }, | |||