diff --git a/.gitignore b/.gitignore index de420fd..2937150 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ coverage # Build Outputs .next/ out/ +out-dev/ build dist diff --git a/apps/user/app/(main)/(content)/(user)/order/page.tsx b/apps/user/app/(main)/(content)/(user)/order/page.tsx index 096d175..10c16e2 100644 --- a/apps/user/app/(main)/(content)/(user)/order/page.tsx +++ b/apps/user/app/(main)/(content)/(user)/order/page.tsx @@ -31,7 +31,7 @@ export default function Page() { }} renderItem={(item) => { return ( - +
{t('orderNo')}
diff --git a/apps/user/app/(main)/(content)/(user)/sidebar-left.tsx b/apps/user/app/(main)/(content)/(user)/sidebar-left.tsx index d92a47b..0c18a44 100644 --- a/apps/user/app/(main)/(content)/(user)/sidebar-left.tsx +++ b/apps/user/app/(main)/(content)/(user)/sidebar-left.tsx @@ -83,8 +83,8 @@ export function SidebarLeft({ ...props }: React.ComponentProps) - -
+ +
diff --git a/apps/user/app/(main)/(content)/(user)/subscribe/page.tsx b/apps/user/app/(main)/(content)/(user)/subscribe/page.tsx index db1051e..0e5e14a 100644 --- a/apps/user/app/(main)/(content)/(user)/subscribe/page.tsx +++ b/apps/user/app/(main)/(content)/(user)/subscribe/page.tsx @@ -2,21 +2,23 @@ import { querySubscribeList } from '@/services/user/subscribe'; import { useQuery } from '@tanstack/react-query'; -import { Tabs, TabsList, TabsTrigger } from '@workspace/airo-ui/components/tabs'; import { useTranslations } from 'next-intl'; -import { useMemo, useRef, useState } from 'react'; +import { useState } from 'react'; import { LoginDialogProvider } from '@/app/auth/LoginDialogContext'; -import { TabContent } from '@/components/main/OfferDialog/TabContent'; -import { ProcessedPlanData } from '@/components/main/OfferDialog/types'; -import Purchase from '@/components/subscribe/purchase'; -import { unitConversion } from '@workspace/airo-ui/utils'; +import TabContent from '@/components/SubscribePlan/PlanContent/index'; +import PlanTabs from '@/components/SubscribePlan/PlanTabs/PlanTabs'; export default function Page() { const t = useTranslations('subscribe'); const [tabValue, setTabValue] = useState<'year' | 'month'>('year'); - const { data, isLoading, error, refetch } = useQuery({ + const { + data = [], + isLoading, + error, + refetch, + } = useQuery({ queryKey: ['querySubscribeList'], queryFn: async () => { const { data } = await querySubscribeList(); @@ -24,50 +26,6 @@ export default function Page() { }, }); - // 处理套餐数据的工具函数 - const processPlanData = (item: API.Subscribe, isYearly: boolean): ProcessedPlanData => { - if (isYearly) { - const discountItem = item.discount?.find((v) => v.quantity === 12); - return { - ...item, - origin_price: unitConversion('centsToDollars', item.unit_price).toString(), // 原价 - discount_price: unitConversion( - 'centsToDollars', - item.unit_price * ((discountItem?.discount || 100) / 100), - ).toString(), // 优惠价格 - }; - } else { - return { - ...item, - origin_price: '', // 月付没有原价 - discount_price: unitConversion('centsToDollars', item.unit_price).toString(), // 月付价格 - }; - } - }; - - // 使用 useMemo 优化数据处理性能 - const yearlyPlans: ProcessedPlanData[] = useMemo( - () => (data || []).map((item) => processPlanData(item, true)), - [data], - ); - - const monthlyPlans: ProcessedPlanData[] = useMemo( - () => (data || []).map((item) => processPlanData(item, false)), - [data], - ); - - // 处理订阅点击 - const handleSubscribe = (plan: ProcessedPlanData) => { - console.log('用户选择了套餐:', plan); - // 这里可以添加订阅逻辑,比如跳转到支付页面或显示确认对话框 - PurchaseRef.current.show(plan, tabValue); - }; - - const PurchaseRef = useRef<{ - show: (subscribe: API.Subscribe, tabValue: string) => void; - hide: () => void; - }>(null); - return ( <> @@ -86,52 +44,17 @@ export default function Page() { {t('description')}
- setTabValue(value as 'year' | 'month')} - > - - {tabValue === 'year' ? ( - - -20% - {/* 小三角箭头 */} - - - ) : null} - - {t('yearlyPlan')} - - - {t('monthlyPlan')} - - - +
+ +
- ); diff --git a/apps/user/components/Modal.tsx b/apps/user/components/Modal.tsx index d28369a..2f37996 100644 --- a/apps/user/components/Modal.tsx +++ b/apps/user/components/Modal.tsx @@ -11,10 +11,24 @@ import { AlertDialogTitle, } from '@workspace/airo-ui/components/alert-dialog'; import CloseSvg from '@workspace/airo-ui/components/close.svg'; -import { useImperativeHandle, useState } from 'react'; +import { Ref, useImperativeHandle, useState } from 'react'; + +interface AlertDialogComponentProps { + ref: Ref; + title: string; + description: string; + cancelText?: string; + confirmText?: string; + onConfirm?: () => void; +} + +// 定义ref可以访问的方法类型 +export interface AlertDialogRef { + show: () => void; +} // Defining the AlertDialogComponent with internal state and onShow prop -const AlertDialogComponent = ({ +const AlertDialogComponent: React.FC = ({ ref, title, description, @@ -28,9 +42,13 @@ const AlertDialogComponent = ({ setOpen(true); } - useImperativeHandle(ref, () => ({ - show, - })); + useImperativeHandle( + ref, + (): AlertDialogRef => ({ + show, + }), + [], + ); return ( diff --git a/apps/user/components/SubscribePlan/PlanContent/index.tsx b/apps/user/components/SubscribePlan/PlanContent/index.tsx new file mode 100644 index 0000000..2030f59 --- /dev/null +++ b/apps/user/components/SubscribePlan/PlanContent/index.tsx @@ -0,0 +1,351 @@ +// 加载状态组件 +import { unitConversion } from '@workspace/airo-ui/utils'; +import { useMemo, useRef } from 'react'; + +const LoadingState = () => { + const t = useTranslations('components.offerDialog'); + return ( +
+
+

{t('loading')}

+
+ ); +}; + +// 错误状态组件 +const ErrorState = ({ onRetry }: { onRetry: () => void }) => { + const t = useTranslations('components.offerDialog'); + return ( +
+

{t('loadFailed')}

+ +
+ ); +}; + +// 空状态组件 +const EmptyState = ({ message }: { message: string }) => ( +
+

{message}

+
+); + +import { useLocale } from 'next-intl'; + +interface PriceDisplayProps { + plan: PlanProps; +} +interface PlanProps extends API.Subscribe { + origin_price: string; + discount_price: string; +} +// 价格显示组件 +const PriceDisplay: React.FC = ({ plan }) => { + const t = useTranslations('components.offerDialog'); + const locale = useLocale(); // 获取当前语言环境 + const { common } = useGlobalStore(); + const discountItem = plan.discount.find((v) => v.quantity === 12) ?? { discount: 0 }; + const discount = + locale === 'zh-CN' ? discountItem?.discount / 10 : `${100 - discountItem?.discount}%`; + return ( +
+
+ {plan.origin_price && ( + + {common?.currency?.currency_symbol} + {plan.origin_price} + + )} + + {common?.currency?.currency_symbol} + {plan.discount_price} + + + {t('perYear')} + +
+
+ {plan.origin_price && ( +

+ {t('yearlyDiscount', { discount })} +

+ )} +
+
+ ); +}; + +import { useLoginDialog } from '@/app/auth/LoginDialogContext'; +import { Display } from '@/components/display'; +import Modal, { AlertDialogRef } from '@/components/Modal'; +import Purchase from '@/components/subscribe/purchase'; +import useGlobalStore from '@/config/use-global'; +import { queryUserSubscribe } from '@/services/user/user'; +import { useTranslations } from 'next-intl'; +import React from 'react'; + +// 星级评分组件 +const StarRating = ({ rating, maxRating = 5 }: { rating: number; maxRating?: number }) => ( +
+ {Array.from({ length: Math.min(rating, maxRating) }, (_, i) => ( + + ✭ + + ))} +
+); + +// 功能列表组件 +const FeatureList = ({ plan }: { plan: API.Subscribe }) => { + const t = useTranslations('subscribe.detail'); + const tOffer = useTranslations('components.offerDialog'); + const features = [{ label: tOffer('availableNodes'), value: plan?.server_count }]; + + return ( +
+
    +
  • +
    + {t('availableTraffic')} + + + +
    +
  • +
  • +
    + {t('connectionSpeed')} + + + +
    +
  • +
  • +
    + {t('connectedDevices')} + + + +
    +
  • + {features.map((feature) => ( +
  • +
    + {feature.label}: + {feature.value} +
    +
  • + ))} +
  • +
    + + {tOffer('networkStabilityIndex')} + + +
    +
  • +
+
+ ); +}; + +// 套餐卡片组件 +const PlanCard: React.FC<{ + plan: PlanProps; + tabValue: string; + onSubscribe?: (plan: API.Subscribe) => void; + isFirstCard?: boolean; +}> = ({ plan, onSubscribe }) => { + const { user } = useGlobalStore(); + const { openLoginDialog } = useLoginDialog(); + const t = useTranslations('components.offerDialog'); + const ModalRef = useRef(null); + async function handleSubscribe() { + if (!user) { + // 强制登陆 + openLoginDialog(false); + return; + } + + // 有生效套餐进行弹窗提示 + const { data } = await queryUserSubscribe(); + if (data?.data?.list?.[0]?.status === 1) { + ModalRef.current?.show(); + return; + } + + onSubscribe?.(plan); + } + + return ( +
+ {/* 套餐名称 */} +

{plan.name}

+ + {/* 价格区域 */} + + + {/* 订阅按钮 */} + + + {/* 功能列表 */} + + + onSubscribe?.(plan)} + /> +
+ ); +}; + +PlanCard.displayName = 'PlanCard'; + +// 套餐列表组件 +export const PlanList = ({ + plans, + tabValue, + isLoading, + error, + onRetry, + emptyMessage, + onSubscribe, +}: { + plans: PlanProps[]; + tabValue: string; + isLoading: boolean; + error: any; + onRetry: () => void; + emptyMessage: string; + onSubscribe?: (plan: API.Subscribe) => void; +}) => { + if (isLoading) return ; + if (error) return ; + if (plans.length === 0) return ; + + return ( +
+
+ {plans.map((plan, index) => ( + + ))} +
+
+ ); +}; + +interface TabContentProps { + tabValue: string; + subscribeData: API.Subscribe[]; + isLoading: boolean; + error: Error | null; + onRetry: () => void; + firstPlanCardRef?: React.RefObject; +} + +const TabContent: React.FC = ({ + tabValue, + subscribeData, + isLoading, + error, + onRetry, +}) => { + const t = useTranslations('components.offerDialog'); + + // 处理套餐数据的工具函数 + const processPlanData = (item: API.Subscribe, isYearly: boolean): PlanProps => { + if (isYearly) { + const discountItem = item.discount?.find((v) => v.quantity === 12); + return { + ...item, + origin_price: unitConversion('centsToDollars', item.unit_price).toString(), // 原价 + discount_price: unitConversion( + 'centsToDollars', + item.unit_price * ((discountItem?.discount || 100) / 100), + ).toString(), // 优惠价格 + }; + } else { + return { + ...item, + origin_price: '', // 月付没有原价 + discount_price: unitConversion('centsToDollars', item.unit_price).toString(), // 月付价格 + }; + } + }; + + // 使用 useMemo 优化数据处理性能 + const yearlyPlans: PlanProps[] = useMemo( + () => (subscribeData || []).map((item) => processPlanData(item, true)), + [subscribeData], + ); + + const monthlyPlans: PlanProps[] = useMemo( + () => (subscribeData || []).map((item) => processPlanData(item, false)), + [subscribeData], + ); + + const PurchaseRef = useRef<{ + show: (subscribe: API.Subscribe, tabValue: string) => void; + hide: () => void; + }>(null); + // 处理订阅点击 + const handleSubscribe = (plan: API.Subscribe) => { + // 这里可以添加订阅逻辑,比如跳转到支付页面或显示确认对话框 + PurchaseRef.current?.show(plan, tabValue); + }; + + return ( +
+ {tabValue === 'year' && ( + + )} + {tabValue === 'month' && ( + + )} + +
+ ); +}; + +export default TabContent; diff --git a/apps/user/components/SubscribePlan/PlanTabs/PlanTabs.tsx b/apps/user/components/SubscribePlan/PlanTabs/PlanTabs.tsx new file mode 100644 index 0000000..9d7b149 --- /dev/null +++ b/apps/user/components/SubscribePlan/PlanTabs/PlanTabs.tsx @@ -0,0 +1,60 @@ +import { Tabs, TabsList, TabsTrigger } from '@workspace/airo-ui/components/tabs'; +import { cn } from '@workspace/airo-ui/lib/utils'; +import { useTranslations } from 'next-intl'; +import React from 'react'; + +export type TabValueType = 'year' | 'month'; +interface PlanTabProps { + tabValue: TabValueType; + setTabValue: (val: TabValueType) => void; + discount: number; + className?: string; +} + +const PlanTabs: React.FC = (props) => { + const t = useTranslations('components.offerDialog'); + const { tabValue, className } = props; + const TAB_LIST: { label: string; value: TabValueType }[] = [ + { + label: t('yearlyPlan'), + value: 'year', + }, + { + label: t('monthlyPlan'), + value: 'month', + }, + ]; + + return ( + props.setTabValue(value as 'year' | 'month')} + > + + {tabValue === 'year' ? ( + + -{props.discount}%{/* 小三角箭头 */} + + + ) : null} + {TAB_LIST.map((val, key) => ( + + {val.label} + + ))} + + + ); +}; +export default PlanTabs; diff --git a/apps/user/components/display.tsx b/apps/user/components/display.tsx index 2e419be..fc8a01b 100644 --- a/apps/user/components/display.tsx +++ b/apps/user/components/display.tsx @@ -19,10 +19,10 @@ export function Display({ }: DisplayProps): string { const t = useTranslations('common'); const { common } = useGlobalStore(); - // const { currency } = common; + const { currency } = common; if (type === 'currency') { - const formattedValue = `$ ${unitConversion('centsToDollars', value as number)?.toFixed(2) ?? '0.00'}`; + const formattedValue = `${currency?.currency_symbol ?? ''}${unitConversion('centsToDollars', value as number)?.toFixed(2) ?? '0.00'}`; return formattedValue; } diff --git a/apps/user/components/main/OfferDialog/TabContent.tsx b/apps/user/components/main/OfferDialog/TabContent.tsx deleted file mode 100644 index 152c776..0000000 --- a/apps/user/components/main/OfferDialog/TabContent.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { useTranslations } from 'next-intl'; -import React from 'react'; -import { PlanList } from './index'; -import { ProcessedPlanData } from './types'; - -interface TabContentProps { - tabValue: string; - yearlyPlans: ProcessedPlanData[]; - monthlyPlans: ProcessedPlanData[]; - isLoading: boolean; - error: Error | null; - onRetry: () => void; - onSubscribe: (plan: ProcessedPlanData) => void; - firstPlanCardRef?: React.RefObject; -} - -export const TabContent: React.FC = ({ - tabValue, - yearlyPlans, - monthlyPlans, - isLoading, - error, - onRetry, - onSubscribe, - firstPlanCardRef, -}) => { - const t = useTranslations('components.offerDialog'); - return ( -
- {tabValue === 'year' && ( - - )} - {tabValue === 'month' && ( - - )} -
- ); -}; diff --git a/apps/user/components/main/OfferDialog/index.tsx b/apps/user/components/main/OfferDialog/index.tsx index 734abe0..23f94fc 100644 --- a/apps/user/components/main/OfferDialog/index.tsx +++ b/apps/user/components/main/OfferDialog/index.tsx @@ -1,258 +1,12 @@ import { getSubscription } from '@/services/user/portal'; import { useQuery } from '@tanstack/react-query'; import { Dialog, DialogContent, DialogTitle } from '@workspace/airo-ui/components/dialog'; -import { Tabs, TabsList, TabsTrigger } from '@workspace/airo-ui/components/tabs'; -import { unitConversion } from '@workspace/airo-ui/utils'; -import { forwardRef, useImperativeHandle, useMemo, useRef, useState } from 'react'; +import { forwardRef, useImperativeHandle, useRef, useState } from 'react'; -import { TabContent } from './TabContent'; -import { ProcessedPlanData } from './types'; - -// 加载状态组件 -const LoadingState = () => { - const t = useTranslations('components.offerDialog'); - return ( -
-
-

{t('loading')}

-
- ); -}; - -// 错误状态组件 -const ErrorState = ({ onRetry }: { onRetry: () => void }) => { - const t = useTranslations('components.offerDialog'); - return ( -
-

{t('loadFailed')}

- -
- ); -}; - -// 空状态组件 -const EmptyState = ({ message }: { message: string }) => ( -
-

{message}

-
-); - -// 价格显示组件 -const PriceDisplay = ({ plan }: { plan: ProcessedPlanData }) => { - const t = useTranslations('components.offerDialog'); - return ( -
-
- {plan.origin_price && ( - - ${plan.origin_price} - - )} - - ${plan.discount_price} - - - {t('perYear')} - -
-
- {plan.origin_price && ( -

{t('yearlyDiscount')}

- )} -
-
- ); -}; - -import { useLoginDialog } from '@/app/auth/LoginDialogContext'; -import { Display } from '@/components/display'; -import Modal from '@/components/Modal'; -import Purchase from '@/components/subscribe/purchase'; -import useGlobalStore from '@/config/use-global'; -import { queryUserSubscribe } from '@/services/user/user'; +import TabContent from '@/components/SubscribePlan/PlanContent/index'; +import PlanTabs from '@/components/SubscribePlan/PlanTabs/PlanTabs'; import { useTranslations } from 'next-intl'; -// 星级评分组件 -const StarRating = ({ rating, maxRating = 5 }: { rating: number; maxRating?: number }) => ( -
- {Array.from({ length: Math.min(rating, maxRating) }, (_, i) => ( - - ✭ - - ))} -
-); - -// 功能列表组件 -const FeatureList = ({ plan }: { plan: ProcessedPlanData }) => { - const t = useTranslations('subscribe.detail'); - const tOffer = useTranslations('components.offerDialog'); - const features = [{ label: tOffer('availableNodes'), value: plan?.server_count }]; - - return ( -
-
    -
  • -
    - {t('availableTraffic')} - - - -
    -
  • -
  • -
    - {t('connectionSpeed')} - - - -
    -
  • -
  • -
    - {t('connectedDevices')} - - - -
    -
  • - {features.map((feature) => ( -
  • -
    - {feature.label}: - {feature.value} -
    -
  • - ))} -
  • -
    - - {tOffer('networkStabilityIndex')} - - -
    -
  • -
-
- ); -}; - -// 套餐卡片组件 -const PlanCard = forwardRef< - HTMLDivElement, - { - plan: ProcessedPlanData; - tabValue: string; - onSubscribe?: (plan: ProcessedPlanData) => void; - isFirstCard?: boolean; - } ->(({ plan, onSubscribe }, ref) => { - const { user } = useGlobalStore(); - const { openLoginDialog } = useLoginDialog(); - const t = useTranslations('components.offerDialog'); - const ModalRef = useRef(null); - async function handleSubscribe() { - if (!user) { - // 强制登陆 - openLoginDialog(false); - return; - } - - // 有生效套餐进行弹窗提示 - const { data } = await queryUserSubscribe(); - if (data?.data?.list?.[0]?.status === 1) { - ModalRef.current.show(); - return; - } - - onSubscribe?.(plan); - } - - return ( -
- {/* 套餐名称 */} -

{plan.name}

- - {/* 价格区域 */} - - - {/* 订阅按钮 */} - - - {/* 功能列表 */} - - - onSubscribe?.(plan)} - /> -
- ); -}); - -PlanCard.displayName = 'PlanCard'; - -// 套餐列表组件 -export const PlanList = ({ - plans, - tabValue, - isLoading, - error, - onRetry, - emptyMessage, - onSubscribe, -}: { - plans: ProcessedPlanData[]; - tabValue: string; - isLoading: boolean; - error: any; - onRetry: () => void; - emptyMessage: string; - onSubscribe?: (plan: ProcessedPlanData) => void; -}) => { - if (isLoading) return ; - if (error) return ; - if (plans.length === 0) return ; - - return ( -
-
- {plans.map((plan, index) => ( - - ))} -
-
- ); -}; - export interface OfferDialogRef { show: () => void; hide: () => void; @@ -261,7 +15,7 @@ export interface OfferDialogRef { const OfferDialog = forwardRef((props, ref) => { const t = useTranslations('components.offerDialog'); const [open, setOpen] = useState(false); - const [tabValue, setTabValue] = useState('year'); + const [tabValue, setTabValue] = useState<'year' | 'month'>('year'); const dialogRef = useRef(null); // 使用 useQuery 来管理请求 const { @@ -295,44 +49,6 @@ const OfferDialog = forwardRef((props, ref) => { }, hide: () => setOpen(false), })); - const PurchaseRef = useRef<{ show: (subscribe: API.Subscribe) => void; hide: () => void }>(null); - // 处理订阅点击 - const handleSubscribe = (plan: ProcessedPlanData) => { - // 这里可以添加订阅逻辑,比如跳转到支付页面或显示确认对话框 - PurchaseRef.current.show(plan, tabValue); - }; - - // 处理套餐数据的工具函数 - const processPlanData = (item: API.Subscribe, isYearly: boolean): ProcessedPlanData => { - if (isYearly) { - const discountItem = item.discount?.find((v) => v.quantity === 12); - return { - ...item, - origin_price: unitConversion('centsToDollars', item.unit_price).toString(), // 原价 - discount_price: unitConversion( - 'centsToDollars', - item.unit_price * ((discountItem?.discount || 100) / 100), - ).toString(), // 优惠价格 - }; - } else { - return { - ...item, - origin_price: '', // 月付没有原价 - discount_price: unitConversion('centsToDollars', item.unit_price).toString(), // 月付价格 - }; - } - }; - - // 使用 useMemo 优化数据处理性能 - const yearlyPlans: ProcessedPlanData[] = useMemo( - () => (data || []).map((item) => processPlanData(item, true)), - [data], - ); - - const monthlyPlans: ProcessedPlanData[] = useMemo( - () => (data || []).map((item) => processPlanData(item, false)), - [data], - ); return ( @@ -350,52 +66,17 @@ const OfferDialog = forwardRef((props, ref) => {
- - - {tabValue === 'year' ? ( - - -20% - {/* 小三角箭头 */} - - - ) : null} - - {t('yearlyPlan')} - - - {t('monthlyPlan')} - - - +
+ +
- ); diff --git a/apps/user/components/main/OfferDialog/types.ts b/apps/user/components/main/OfferDialog/types.ts deleted file mode 100644 index 99b9a82..0000000 --- a/apps/user/components/main/OfferDialog/types.ts +++ /dev/null @@ -1,65 +0,0 @@ -export interface SubscriptionData { - id: string; - name: string; - price: number; - originalPrice: number; - duration: string; - unit_price: number; - discount: Array<{ - quantity: number; - discount: number; - }>; - features: { - traffic: string; - duration: string; - onlineIPs: string; - connections: string; - bandwidth: string; - nodes: string; - stability: number; // 星级 1-5 - }; -} - -// 以 API.Subscribe 为准的类型定义 -export interface ProcessedPlanData { - id: number; - name: string; - description: string; - unit_price: number; - unit_time: string; - discount: Array<{ - quantity: number; - discount: number; - }>; - replacement: number; - inventory: number; - traffic: number; - speed_limit: number; - device_limit: number; - quota: number; - group_id: number; - server_group: number[]; - server: number[]; - show: boolean; - sell: boolean; - sort: number; - deduction_ratio: number; - allow_deduction: boolean; - reset_cycle: number; - renewal_reset: boolean; - created_at: number; - updated_at: number; - // 处理后的价格字段 - origin_price: string; - discount_price: string; - // 添加features属性以兼容现有代码 - features?: { - traffic: string; - duration: string; - onlineIPs: string; - connections: string; - bandwidth: string; - nodes: string; - stability: number; - }; -} diff --git a/apps/user/components/subscribe/purchase.tsx b/apps/user/components/subscribe/purchase.tsx index 79315d9..8d75985 100644 --- a/apps/user/components/subscribe/purchase.tsx +++ b/apps/user/components/subscribe/purchase.tsx @@ -1,6 +1,7 @@ 'use client'; import PaymentMethods from '@/components/subscribe/payment-methods'; +import PlanTabs, { TabValueType } from '@/components/SubscribePlan/PlanTabs/PlanTabs'; import useGlobalStore from '@/config/use-global'; import { preCreateOrder, purchase } from '@/services/user/order'; import { useQuery } from '@tanstack/react-query'; @@ -12,7 +13,6 @@ import { DialogTitle, } from '@workspace/airo-ui/components/dialog'; import { Separator } from '@workspace/airo-ui/components/separator'; -import { Tabs, TabsList, TabsTrigger } from '@workspace/airo-ui/components/tabs'; import { LoaderCircle } from 'lucide-react'; import { useTranslations } from 'next-intl'; import { useRouter } from 'next/navigation'; @@ -32,7 +32,7 @@ interface PurchaseProps { } interface PurchaseDialogRef { - show: (subscribe: API.Subscribe, tabValue: string) => void; + show: (subscribe: API.Subscribe, tabValue: TabValueType) => void; hide: () => void; } @@ -50,10 +50,10 @@ const Purchase = forwardRef((props, ref) => { const [loading, startTransition] = useTransition(); const [open, setOpen] = useState(false); const lastSuccessOrderRef = useRef(null); - const [tabValue, setTabValue] = useState('year'); + const [tabValue, setTabValue] = useState<'year' | 'month'>('year'); useImperativeHandle(ref, () => ({ - show: (newSubscribe: API.Subscribe, tabValue: string) => { + show: (newSubscribe: API.Subscribe, tabValue: TabValueType) => { setSubscribe(newSubscribe); setParams((prev) => ({ ...prev, @@ -127,46 +127,19 @@ const Purchase = forwardRef((props, ref) => {
{t('purchaseTitle')}
-
- { - if (val === 'year') { - handleChange('quantity', 12); - } else if (val === 'month') { - handleChange('quantity', 1); - } - setTabValue(val); - }} - > - - {tabValue === 'year' ? ( - - {t('discount20')} - {/* 小三角箭头 */} - - - ) : null} - - {t('yearlyPlan')} - - - {t('monthlyPlan')} - - - -
+ { + if (val === 'year') { + handleChange('quantity', 12); + } else if (val === 'month') { + handleChange('quantity', 1); + } + setTabValue(val); + }} + discount={20} + className={'sm:mt-6'} + />
{from === 'profile' ? ( -
+
- - - - - - - - - - - - - + + diff --git a/apps/user/services/common/typings.d.ts b/apps/user/services/common/typings.d.ts index 963ab1e..89478ac 100644 --- a/apps/user/services/common/typings.d.ts +++ b/apps/user/services/common/typings.d.ts @@ -756,6 +756,7 @@ declare namespace API { quota: number; group_id: number; server_group: number[]; + server_count: number; server: number[]; show: boolean; sell: boolean; diff --git a/packages/airo-ui/src/components/dialog.tsx b/packages/airo-ui/src/components/dialog.tsx index 2c748d0..4aef616 100644 --- a/packages/airo-ui/src/components/dialog.tsx +++ b/packages/airo-ui/src/components/dialog.tsx @@ -41,15 +41,16 @@ const DialogContent = React.forwardRef< - {children} +
{children}
+
diff --git a/packages/airo-ui/src/custom-components/pro-list/pagination.tsx b/packages/airo-ui/src/custom-components/pro-list/pagination.tsx index 4d12a4e..30f523e 100644 --- a/packages/airo-ui/src/custom-components/pro-list/pagination.tsx +++ b/packages/airo-ui/src/custom-components/pro-list/pagination.tsx @@ -26,12 +26,12 @@ interface PaginationProps { export function Pagination({ table, text }: PaginationProps) { return ( -
+
{text?.textPageOf?.(table.getState().pagination.pageIndex + 1, table.getPageCount()) || `Page ${table.getState().pagination.pageIndex + 1} of ${table.getPageCount()}`}
-
+
{/*

{text?.textRowsPerPage || 'Rows per page'}