From 3377240c3bfa41561eafbc22bee8f5e2b4736c92 Mon Sep 17 00:00:00 2001 From: yyh Date: Mon, 2 Mar 2026 15:18:32 +0800 Subject: [PATCH] refactor(web): align account dropdown submenu semantics --- .../header/account-dropdown/compliance.tsx | 152 +++++++++++------- .../header/account-dropdown/index.tsx | 1 - .../header/account-dropdown/support.tsx | 1 - 3 files changed, 91 insertions(+), 63 deletions(-) diff --git a/web/app/components/header/account-dropdown/compliance.tsx b/web/app/components/header/account-dropdown/compliance.tsx index 8d90a659d3..58d2f29a61 100644 --- a/web/app/components/header/account-dropdown/compliance.tsx +++ b/web/app/components/header/account-dropdown/compliance.tsx @@ -1,8 +1,8 @@ -import type { FC, MouseEvent, ReactNode } from 'react' +import type { ReactNode } from 'react' import { useMutation } from '@tanstack/react-query' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import { DropdownMenuGroup, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from '@/app/components/base/ui/dropdown-menu' +import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from '@/app/components/base/ui/dropdown-menu' import { Plan } from '@/app/components/billing/type' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import { useModalContext } from '@/context/modal-context' @@ -10,20 +10,20 @@ import { useProviderContext } from '@/context/provider-context' import { getDocDownloadUrl } from '@/service/common' import { cn } from '@/utils/classnames' import { downloadUrl } from '@/utils/download' -import Button from '../../base/button' import Gdpr from '../../base/icons/src/public/common/Gdpr' import Iso from '../../base/icons/src/public/common/Iso' import Soc2 from '../../base/icons/src/public/common/Soc2' import SparklesSoft from '../../base/icons/src/public/common/SparklesSoft' import PremiumBadge from '../../base/premium-badge' +import Spinner from '../../base/spinner' import Toast from '../../base/toast' import Tooltip from '../../base/tooltip' const submenuTriggerClassName = '!mx-0 !h-8 !rounded-lg !px-3 data-[highlighted]:!bg-state-base-hover' +const submenuItemClassName = '!mx-0 !h-10 !rounded-lg !py-1 !pl-1 !pr-2 data-[highlighted]:!bg-state-base-hover' const menuLabelClassName = 'grow px-1 text-text-secondary system-md-regular' const menuLeadingIconClassName = 'size-4 shrink-0 text-text-tertiary' const menuTrailingIconClassName = 'size-[14px] shrink-0 text-text-tertiary' -const complianceRowClassName = 'mx-0 flex h-10 w-full items-center gap-1 rounded-lg py-1 pl-1 pr-2 text-text-secondary system-md-regular' enum DocName { SOC2_Type_I = 'SOC2_Type_I', @@ -52,28 +52,70 @@ function ComplianceMenuItemContent({ ) } -type UpgradeOrDownloadProps = { - doc_name: DocName +type ComplianceDocActionVisualProps = { + isCurrentPlanCanDownload: boolean + isPending: boolean + tooltipText: string + downloadText: string + upgradeText: string } -const UpgradeOrDownload: FC = ({ doc_name }) => { +function ComplianceDocActionVisual({ + isCurrentPlanCanDownload, + isPending, + tooltipText, + downloadText, + upgradeText, +}: ComplianceDocActionVisualProps) { + if (isCurrentPlanCanDownload) { + return ( +
+ + {downloadText} + {isPending && } +
+ ) + } + + return ( + + + +
+ {upgradeText} +
+
+
+ ) +} + +type ComplianceDocRowItemProps = { + icon: ReactNode + label: ReactNode + docName: DocName +} + +function ComplianceDocRowItem({ + icon, + label, + docName, +}: ComplianceDocRowItemProps) { const { t } = useTranslation() const { plan } = useProviderContext() const { setShowPricingModal, setShowAccountSettingModal } = useModalContext() const isFreePlan = plan.type === Plan.sandbox - const handlePlanClick = useCallback(() => { - if (isFreePlan) - setShowPricingModal() - else - setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.BILLING }) - }, [isFreePlan, setShowAccountSettingModal, setShowPricingModal]) - const { isPending, mutate: downloadCompliance } = useMutation({ - mutationKey: ['downloadCompliance', doc_name], + mutationKey: ['downloadCompliance', docName], mutationFn: async () => { try { - const ret = await getDocDownloadUrl(doc_name) + const ret = await getDocDownloadUrl(docName) downloadUrl({ url: ret.url }) Toast.notify({ type: 'success', @@ -89,6 +131,7 @@ const UpgradeOrDownload: FC = ({ doc_name }) => { } }, }) + const whichPlanCanDownloadCompliance = { [DocName.SOC2_Type_I]: [Plan.professional, Plan.team], [DocName.SOC2_Type_II]: [Plan.team], @@ -96,56 +139,44 @@ const UpgradeOrDownload: FC = ({ doc_name }) => { [DocName.GDPR]: [Plan.team, Plan.professional, Plan.sandbox], } - const isCurrentPlanCanDownload = whichPlanCanDownloadCompliance[doc_name].includes(plan.type) - const handleDownloadClick = useCallback((e: MouseEvent) => { - e.preventDefault() - downloadCompliance() - }, [downloadCompliance]) - if (isCurrentPlanCanDownload) { - return ( - - ) - } + const isCurrentPlanCanDownload = whichPlanCanDownloadCompliance[docName].includes(plan.type) + + const handleSelect = useCallback(() => { + if (isCurrentPlanCanDownload) { + if (!isPending) + downloadCompliance() + return + } + + if (isFreePlan) + setShowPricingModal() + else + setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.BILLING }) + }, [downloadCompliance, isCurrentPlanCanDownload, isFreePlan, isPending, setShowAccountSettingModal, setShowPricingModal]) + const upgradeTooltip: Record = { [Plan.sandbox]: t('compliance.sandboxUpgradeTooltip', { ns: 'common' }), [Plan.professional]: t('compliance.professionalUpgradeTooltip', { ns: 'common' }), [Plan.team]: '', [Plan.enterprise]: '', } - return ( - - - -
- - {t('upgradeBtn.encourageShort', { ns: 'billing' })} - -
-
-
- ) -} -type ComplianceDocRowProps = { - icon: ReactNode - label: ReactNode - docName: DocName -} - -function ComplianceDocRow({ - icon, - label, - docName, -}: ComplianceDocRowProps) { return ( -
+ {icon}
{label}
- -
+ + ) } @@ -162,26 +193,25 @@ export default function Compliance() { /> - } label={t('compliance.soc2Type1', { ns: 'common' })} docName={DocName.SOC2_Type_I} /> - } label={t('compliance.soc2Type2', { ns: 'common' })} docName={DocName.SOC2_Type_II} /> - } label={t('compliance.iso27001', { ns: 'common' })} docName={DocName.ISO_27001} /> - } label={t('compliance.gdpr', { ns: 'common' })} docName={DocName.GDPR} diff --git a/web/app/components/header/account-dropdown/index.tsx b/web/app/components/header/account-dropdown/index.tsx index 0109a66956..d682d0ccb8 100644 --- a/web/app/components/header/account-dropdown/index.tsx +++ b/web/app/components/header/account-dropdown/index.tsx @@ -167,7 +167,6 @@ export default function AppSelector() { diff --git a/web/app/components/header/account-dropdown/support.tsx b/web/app/components/header/account-dropdown/support.tsx index 545b4d8b4d..9ee5d03862 100644 --- a/web/app/components/header/account-dropdown/support.tsx +++ b/web/app/components/header/account-dropdown/support.tsx @@ -61,7 +61,6 @@ export default function Support({ closeAccountDropdown }: SupportProps) { />