import { useState } from 'react' import { AlertTriangleIcon, ShieldAlertIcon, XIcon } from 'lucide-react' import { useTranslation } from 'react-i18next' import type { IPicGoCloudLifecycleInfo, IPicGoCloudLifecyclePhase } from '#/types/cloud' import { Button } from '@/components/ui/button' import { cn } from '@/lib/utils' import { cloudAdapter } from '@/adapters/cloud' import { usePicGoCloudBillingQuery } from '@/queries/picgo-cloud-billing' import { PICGO_GUI_LIFECYCLE_BANNER_DISMISSED_PHASE_KEY } from '@/utils/consts' import { isAbnormalLifecyclePhase } from '@/utils/plan' type LifecycleBannerVariant = 'inline' | 'floating' interface LifecycleBannerProps { variant: LifecycleBannerVariant } function resolveBannerCopy (phase: IPicGoCloudLifecyclePhase): { titleKey: ILocalesKey descKey: ILocalesKey } { switch (phase) { case 'grace': return { titleKey: 'PICGO_CLOUD_LIFECYCLE_BANNER_GRACE_TITLE', descKey: 'PICGO_CLOUD_LIFECYCLE_BANNER_GRACE_DESC' } case 'frozen': return { titleKey: 'PICGO_CLOUD_LIFECYCLE_BANNER_FROZEN_TITLE', descKey: 'PICGO_CLOUD_LIFECYCLE_BANNER_FROZEN_DESC' } case 'pending_cleanup': return { titleKey: 'PICGO_CLOUD_LIFECYCLE_BANNER_PENDING_CLEANUP_TITLE', descKey: 'PICGO_CLOUD_LIFECYCLE_BANNER_PENDING_CLEANUP_DESC' } default: return { titleKey: 'PICGO_CLOUD_LIFECYCLE_BANNER_GRACE_TITLE', descKey: 'PICGO_CLOUD_LIFECYCLE_BANNER_GRACE_DESC' } } } const PHASE_TONE: Record = { active: { container: '', icon: '', Icon: AlertTriangleIcon, button: '' }, grace: { container: 'border-yellow-500/40 bg-yellow-50 text-yellow-900 dark:bg-yellow-950/30 dark:text-yellow-200', icon: 'text-yellow-600 dark:text-yellow-400', Icon: AlertTriangleIcon, button: 'bg-orange-500 text-white hover:bg-orange-600 dark:bg-orange-500 dark:text-white dark:hover:bg-orange-600' }, frozen: { container: 'border-red-500/40 bg-red-50 text-red-900 dark:bg-red-950/30 dark:text-red-200', icon: 'text-red-600 dark:text-red-400', Icon: ShieldAlertIcon, button: 'bg-red-600 text-white hover:bg-red-700 dark:bg-red-600 dark:text-white dark:hover:bg-red-700' }, pending_cleanup: { container: 'border-red-600/50 bg-red-100 text-red-950 dark:bg-red-950/40 dark:text-red-100', icon: 'text-red-700 dark:text-red-300', Icon: ShieldAlertIcon, button: 'bg-red-700 text-white hover:bg-red-800 dark:bg-red-700 dark:text-white dark:hover:bg-red-800' } } export function LifecycleBanner ({ variant }: LifecycleBannerProps) { const { data: billing } = usePicGoCloudBillingQuery() const lifecycle: IPicGoCloudLifecycleInfo | undefined = billing?.lifecycle const phase = lifecycle?.phase const [dismissedPhase, setDismissedPhase] = useState(() => { if (typeof window === 'undefined') return null try { return window.localStorage.getItem(PICGO_GUI_LIFECYCLE_BANNER_DISMISSED_PHASE_KEY) } catch { return null } }) if (!phase || !isAbnormalLifecyclePhase(phase)) return null if (variant === 'floating' && dismissedPhase === phase) return null const { titleKey, descKey } = resolveBannerCopy(phase) const tone = PHASE_TONE[phase] const days = lifecycle?.daysRemaining ?? 0 const handleRenew = () => { cloudAdapter.openPricing() } const handleDismiss = () => { try { window.localStorage.setItem(PICGO_GUI_LIFECYCLE_BANNER_DISMISSED_PHASE_KEY, phase) } catch { // ignore storage failure } setDismissedPhase(phase) } return ( ) } interface LifecycleBannerViewProps { variant: LifecycleBannerVariant phase: IPicGoCloudLifecyclePhase days: number tone: typeof PHASE_TONE[IPicGoCloudLifecyclePhase] titleKey: ILocalesKey descKey: ILocalesKey onRenew: () => void onDismiss: () => void } function LifecycleBannerView ({ variant, days, tone, titleKey, descKey, onRenew, onDismiss }: LifecycleBannerViewProps) { const { t } = useTranslation() const Icon = tone.Icon const containerClass = variant === 'floating' ? cn( 'fixed bottom-6 right-6 z-50 w-[22rem] max-w-[calc(100vw-3rem)] rounded-lg border shadow-lg', tone.container ) : cn('w-full rounded-lg border', tone.container) // floating 因宽度受限保持原上下布局;inline 走左右一行(按钮垂直居中右对齐) if (variant === 'floating') { return (

{t(titleKey)}

{t(descKey, { days: String(days) })}

) } return (

{t(titleKey)}

{t(descKey, { days: String(days) })}

) }