| import { useWorkspacesContext } from '@/context/workspace-context' | import { useWorkspacesContext } from '@/context/workspace-context' | ||||
| import { useProviderContext } from '@/context/provider-context' | import { useProviderContext } from '@/context/provider-context' | ||||
| import { ToastContext } from '@/app/components/base/toast' | import { ToastContext } from '@/app/components/base/toast' | ||||
| import PremiumBadge from '@/app/components/base/premium-badge' | |||||
| const WorkplaceSelector = () => { | const WorkplaceSelector = () => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| <div className='flex py-1 pl-3 pr-2 items-center gap-2 self-stretch hover:bg-state-base-hover rounded-lg' key={workspace.id} onClick={() => handleSwitchWorkspace(workspace.id)}> | <div className='flex py-1 pl-3 pr-2 items-center gap-2 self-stretch hover:bg-state-base-hover rounded-lg' key={workspace.id} onClick={() => handleSwitchWorkspace(workspace.id)}> | ||||
| <div className='flex items-center justify-center w-6 h-6 bg-[#EFF4FF] rounded-md text-xs font-medium text-primary-600'>{workspace.name[0].toLocaleUpperCase()}</div> | <div className='flex items-center justify-center w-6 h-6 bg-[#EFF4FF] rounded-md text-xs font-medium text-primary-600'>{workspace.name[0].toLocaleUpperCase()}</div> | ||||
| <div className='line-clamp-1 grow overflow-hidden text-text-secondary text-ellipsis system-md-regular cursor-pointer'>{workspace.name}</div> | <div className='line-clamp-1 grow overflow-hidden text-text-secondary text-ellipsis system-md-regular cursor-pointer'>{workspace.name}</div> | ||||
| { | |||||
| <PremiumBadge size='s' color='gray' allowHover={false}> | |||||
| <div className='system-2xs-medium'> | |||||
| <span className='p-[2px]'> | |||||
| {plan.type === 'professional' ? 'PRO' : plan.type.toUpperCase()} | |||||
| </span> | |||||
| </div> | |||||
| </PremiumBadge> | |||||
| } | |||||
| </div> | </div> | ||||
| )) | )) | ||||
| } | } |
| import { useBoolean } from 'ahooks' | import { useBoolean } from 'ahooks' | ||||
| import { useSelectedLayoutSegment } from 'next/navigation' | import { useSelectedLayoutSegment } from 'next/navigation' | ||||
| import { Bars3Icon } from '@heroicons/react/20/solid' | import { Bars3Icon } from '@heroicons/react/20/solid' | ||||
| import { SparklesSoft } from '@/app/components/base/icons/src/public/common' | |||||
| import PremiumBadge from '../base/premium-badge' | |||||
| import AccountDropdown from './account-dropdown' | import AccountDropdown from './account-dropdown' | ||||
| import AppNav from './app-nav' | import AppNav from './app-nav' | ||||
| import DatasetNav from './dataset-nav' | import DatasetNav from './dataset-nav' | ||||
| import PluginsNav from './plugins-nav' | import PluginsNav from './plugins-nav' | ||||
| import ExploreNav from './explore-nav' | import ExploreNav from './explore-nav' | ||||
| import ToolsNav from './tools-nav' | import ToolsNav from './tools-nav' | ||||
| import LicenseNav from './license-env' | |||||
| import { WorkspaceProvider } from '@/context/workspace-context' | import { WorkspaceProvider } from '@/context/workspace-context' | ||||
| import { useAppContext } from '@/context/app-context' | import { useAppContext } from '@/context/app-context' | ||||
| import LogoSite from '@/app/components/base/logo/logo-site' | import LogoSite from '@/app/components/base/logo/logo-site' | ||||
| import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' | import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' | ||||
| import { useProviderContext } from '@/context/provider-context' | import { useProviderContext } from '@/context/provider-context' | ||||
| import { useModalContext } from '@/context/modal-context' | import { useModalContext } from '@/context/modal-context' | ||||
| import { useTranslation } from 'react-i18next' | |||||
| import LicenseNav from './license-env' | |||||
| import PlanBadge from './plan-badge' | |||||
| import { Plan } from '../billing/type' | |||||
| const navClassName = ` | const navClassName = ` | ||||
| flex items-center relative mr-0 sm:mr-3 px-3 h-8 rounded-xl | flex items-center relative mr-0 sm:mr-3 px-3 h-8 rounded-xl | ||||
| const Header = () => { | const Header = () => { | ||||
| const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext() | const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext() | ||||
| const { t } = useTranslation() | |||||
| const selectedSegment = useSelectedLayoutSegment() | const selectedSegment = useSelectedLayoutSegment() | ||||
| const media = useBreakpoints() | const media = useBreakpoints() | ||||
| const isMobile = media === MediaType.mobile | const isMobile = media === MediaType.mobile | ||||
| const [isShowNavMenu, { toggle, setFalse: hideNavMenu }] = useBoolean(false) | const [isShowNavMenu, { toggle, setFalse: hideNavMenu }] = useBoolean(false) | ||||
| const { enableBilling, plan } = useProviderContext() | const { enableBilling, plan } = useProviderContext() | ||||
| const { setShowPricingModal, setShowAccountSettingModal } = useModalContext() | const { setShowPricingModal, setShowAccountSettingModal } = useModalContext() | ||||
| const isFreePlan = plan.type === 'sandbox' | |||||
| const isFreePlan = plan.type === Plan.sandbox | |||||
| const handlePlanClick = useCallback(() => { | const handlePlanClick = useCallback(() => { | ||||
| if (isFreePlan) | if (isFreePlan) | ||||
| setShowPricingModal() | setShowPricingModal() | ||||
| <WorkspaceProvider> | <WorkspaceProvider> | ||||
| <WorkplaceSelector /> | <WorkplaceSelector /> | ||||
| </WorkspaceProvider> | </WorkspaceProvider> | ||||
| {enableBilling && ( | |||||
| <div className='select-none'> | |||||
| <PremiumBadge color='blue' allowHover={true} onClick={handlePlanClick}> | |||||
| <SparklesSoft className='flex items-center py-[1px] pl-[3px] w-3.5 h-3.5 text-components-premium-badge-indigo-text-stop-0' /> | |||||
| <div className='system-xs-medium'> | |||||
| <span className='p-1'> | |||||
| {t('billing.upgradeBtn.encourageShort')} | |||||
| </span> | |||||
| </div> | |||||
| </PremiumBadge> | |||||
| </div> | |||||
| )} | |||||
| {enableBilling ? <PlanBadge allowHover sandboxAsUpgrade plan={plan.type} onClick={handlePlanClick} /> : <LicenseNav />} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| } | } | ||||
| <LogoSite /> | <LogoSite /> | ||||
| </Link> | </Link> | ||||
| <div className='font-light text-divider-deep'>/</div> | <div className='font-light text-divider-deep'>/</div> | ||||
| { | |||||
| enableBilling && ( | |||||
| <div className='select-none'> | |||||
| <PremiumBadge color='blue' allowHover={true} onClick={handlePlanClick}> | |||||
| <SparklesSoft className='flex items-center py-[1px] pl-[3px] w-3.5 h-3.5 text-components-premium-badge-indigo-text-stop-0' /> | |||||
| <div className='system-xs-medium'> | |||||
| <span className='p-1'> | |||||
| {t('billing.upgradeBtn.encourageShort')} | |||||
| </span> | |||||
| </div> | |||||
| </PremiumBadge> | |||||
| </div> | |||||
| ) | |||||
| } | |||||
| {enableBilling ? <PlanBadge allowHover sandboxAsUpgrade plan={plan.type} onClick={handlePlanClick} /> : <LicenseNav />} | |||||
| </div > | </div > | ||||
| )} | )} | ||||
| { | { | ||||
| ) | ) | ||||
| } | } | ||||
| <div className='flex items-center shrink-0'> | <div className='flex items-center shrink-0'> | ||||
| <LicenseNav /> | |||||
| <EnvNav /> | <EnvNav /> | ||||
| <div className='mr-3'> | <div className='mr-3'> | ||||
| <PluginsNav /> | <PluginsNav /> |
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| import { useContextSelector } from 'use-context-selector' | import { useContextSelector } from 'use-context-selector' | ||||
| import dayjs from 'dayjs' | import dayjs from 'dayjs' | ||||
| import PremiumBadge from '../../base/premium-badge' | |||||
| import { RiHourglass2Fill } from '@remixicon/react' | |||||
| const LicenseNav = () => { | const LicenseNav = () => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| if (systemFeatures.license?.status === LicenseStatus.EXPIRING) { | if (systemFeatures.license?.status === LicenseStatus.EXPIRING) { | ||||
| const expiredAt = systemFeatures.license?.expired_at | const expiredAt = systemFeatures.license?.expired_at | ||||
| const count = dayjs(expiredAt).diff(dayjs(), 'days') | const count = dayjs(expiredAt).diff(dayjs(), 'days') | ||||
| return <div className='px-2 py-1 mr-4 rounded-full bg-util-colors-orange-orange-50 border-util-colors-orange-orange-100 system-xs-medium text-util-colors-orange-orange-600'> | |||||
| {count <= 1 && <span>{t('common.license.expiring', { count })}</span>} | |||||
| {count > 1 && <span>{t('common.license.expiring_plural', { count })}</span>} | |||||
| </div> | |||||
| return <PremiumBadge color='orange' className='select-none'> | |||||
| <RiHourglass2Fill className='flex items-center pl-0.5 size-3 text-components-premium-badge-indigo-text-stop-0' /> | |||||
| {count <= 1 && <span className='system-xs-medium px-0.5'>{t('common.license.expiring', { count })}</span>} | |||||
| {count > 1 && <span className='system-xs-medium px-0.5'>{t('common.license.expiring_plural', { count })}</span>} | |||||
| </PremiumBadge> | |||||
| } | } | ||||
| if (systemFeatures.license.status === LicenseStatus.ACTIVE) { | if (systemFeatures.license.status === LicenseStatus.ACTIVE) { | ||||
| return <div className='px-2 py-1 mr-4 rounded-md bg-util-colors-indigo-indigo-50 border-util-colors-indigo-indigo-100 system-xs-medium text-util-colors-indigo-indigo-600'> | |||||
| Enterprise | |||||
| </div> | |||||
| return <PremiumBadge color="indigo" className='select-none'> | |||||
| <span className='system-xs-medium px-1'>Enterprise</span> | |||||
| </PremiumBadge> | |||||
| } | } | ||||
| return null | return null | ||||
| } | } |
| import { useProviderContext } from '@/context/provider-context' | |||||
| import classNames from '@/utils/classnames' | |||||
| import type { FC } from 'react' | |||||
| import { useTranslation } from 'react-i18next' | |||||
| import { SparklesSoft } from '../../base/icons/src/public/common' | |||||
| import PremiumBadge from '../../base/premium-badge' | |||||
| import { Plan } from '../../billing/type' | |||||
| type PlanBadgeProps = { | |||||
| plan: Plan | |||||
| size?: 's' | 'm' | |||||
| allowHover?: boolean | |||||
| sandboxAsUpgrade?: boolean | |||||
| onClick?: () => void | |||||
| } | |||||
| const PlanBadge: FC<PlanBadgeProps> = ({ plan, allowHover, size = 'm', sandboxAsUpgrade = false, onClick }) => { | |||||
| const { isFetchedPlan } = useProviderContext() | |||||
| const { t } = useTranslation() | |||||
| if (!isFetchedPlan) return null | |||||
| if (plan === Plan.sandbox && sandboxAsUpgrade) { | |||||
| return <div className='select-none'> | |||||
| <PremiumBadge color='blue' allowHover={allowHover} onClick={onClick}> | |||||
| <SparklesSoft className='flex items-center py-[1px] pl-[3px] w-3.5 h-3.5 text-components-premium-badge-indigo-text-stop-0' /> | |||||
| <div className='system-xs-medium'> | |||||
| <span className='p-1'> | |||||
| {t('billing.upgradeBtn.encourageShort')} | |||||
| </span> | |||||
| </div> | |||||
| </PremiumBadge> | |||||
| </div> | |||||
| } | |||||
| if (plan === Plan.sandbox) { | |||||
| return <div className='select-none'> | |||||
| <PremiumBadge size={size} color='gray' allowHover={allowHover} onClick={onClick}> | |||||
| <div className={classNames(size === 's' ? 'system-2xs-medium-uppercase' : 'system-xs-medium-uppercase')}> | |||||
| <span className='p-1'> | |||||
| {plan} | |||||
| </span> | |||||
| </div> | |||||
| </PremiumBadge> | |||||
| </div> | |||||
| } | |||||
| if (plan === Plan.professional) { | |||||
| return <div className='select-none'> | |||||
| <PremiumBadge size={size} color='blue' allowHover={allowHover} onClick={onClick}> | |||||
| <div className={classNames(size === 's' ? 'system-2xs-medium-uppercase' : 'system-xs-medium-uppercase')}> | |||||
| <span className='p-1'> | |||||
| pro | |||||
| </span> | |||||
| </div> | |||||
| </PremiumBadge> | |||||
| </div> | |||||
| } | |||||
| if (plan === Plan.team) { | |||||
| return <div className='select-none'> | |||||
| <PremiumBadge size={size} color='indigo' allowHover={allowHover} onClick={onClick}> | |||||
| <div className={classNames(size === 's' ? 'system-2xs-medium-uppercase' : 'system-xs-medium-uppercase')}> | |||||
| <span className='p-1'> | |||||
| {plan} | |||||
| </span> | |||||
| </div> | |||||
| </PremiumBadge> | |||||
| </div> | |||||
| } | |||||
| return null | |||||
| } | |||||
| export default PlanBadge |