Jelajahi Sumber

feat: 购买页面

BaiLuoYan 1 bulan lalu
induk
melakukan
3e2c8a5f40

+ 9 - 10
src/locales/en-US/pages.ts

@@ -19,19 +19,18 @@ export default {
 
     // 以下是 Pricing 页面的翻译
     pricing: {
-        title: 'Choose your NOMO VPN Plan',
-        step3: 'Step 3',
-        step3Title: 'Select a plan that works for you',
-        description:
-            'All plans include all NOMO VPN Apps,24/7 customer support,and high-speed unlimited bandwidth',
-        currencyNote: 'All amounts shown are in USD.',
+        title: 'Purchase NOMO VPN Plan',
+        subTitle: 'Choose a plan that suits you',
         orderSummary: {
             account: 'Your Account',
-            paymentMethod: 'Payment method',
-            currentSubscription: 'Current Subscription',
+            paymentMethod: 'Payment Method',
             orderTotal: 'Order Total',
-            terms: 'By continuing to pay, you agree to our Terms of Service by default.',
-            goPayNow: 'Go Pay Now',
+            terms: 'Continue to pay, you agree to our service terms',
+            goPayNow: 'Pay Now',
+        },
+        userInfo: {
+            account: 'Your Account',
+            planExpireDate: 'Plan Expire Date',
         },
     },
 

+ 6 - 7
src/locales/fa-IR/pages.ts

@@ -19,20 +19,19 @@ export default {
 
     // 以下是 Pricing 页面的翻译
     pricing: {
-        title: 'انتخاب پلان NOMO VPN',
-        step3: 'مرحله ۳',
-        step3Title: 'پلانی را انتخاب کنید که برای شما مناسب است',
-        description:
-            'همه پلان‌ها شامل تمام اپلیکیشن‌های NOMO VPN، پشتیبانی ۲۴/۷ مشتری و پهنای باند نامحدود با سرعت بالا می‌شوند',
-        currencyNote: 'تمام مبالغ به دلار آمریکا نمایش داده می‌شوند.',
+        title: 'خرید پلن NOMO VPN',
+        subTitle: 'یک پلن را انتخاب کنید که به شما مناسب است',
         orderSummary: {
             account: 'حساب شما',
             paymentMethod: 'روش پرداخت',
-            currentSubscription: 'اشتراک فعلی',
             orderTotal: 'مجموع سفارش',
             terms: 'با ادامه پرداخت، شما به طور پیش‌فرض با شرایط خدمات ما موافقت می‌کنید.',
             goPayNow: 'پرداخت کن',
         },
+        userInfo: {
+            account: 'حساب شما',
+            planExpireDate: 'تاریخ انقضا پلن',
+        },
     },
 
     // 以下是 框架功能演示 页面的翻译

+ 6 - 7
src/locales/zh-CN/pages.ts

@@ -19,20 +19,19 @@ export default {
 
     // 以下是 Pricing 页面的翻译
     pricing: {
-        title: '选择您的NOMO VPN计划',
-        step3: 'Step 3',
-        step3Title: '选择一个适合您的计划',
-        description:
-            '所有计划都包括所有NOMO VPN应用程序,24/7客户支持,以及高速无限带宽',
-        currencyNote: '所有金额均以美元显示。',
+        title: '购买 NOMO VPN 套餐',
+        subTitle: '选择一个适合您的套餐',
         orderSummary: {
             account: '您的账户',
             paymentMethod: '支付方式',
-            currentSubscription: '当前订阅',
             orderTotal: '订单总额',
             terms: '继续支付,您同意我们的服务条款',
             goPayNow: '立即支付',
         },
+        userInfo: {
+            account: '您的账户',
+            planExpireDate: '套餐到期日期',
+        },
     },
 
     // 以下是 框架功能演示 页面的翻译

+ 4 - 4
src/pages/pricing/components/OrderSummaryItem/index.tsx → src/pages/pricing/components/LabelValueItem/index.tsx

@@ -1,12 +1,12 @@
 import { memo } from 'react';
 
-export interface OrderSummaryItemProps {
+export interface LabelValueItemProps {
     label: string;
     value: string;
     valueColor?: string;
 }
 
-const OrderSummaryItem = memo(({ label, value, valueColor = 'text-white' }: OrderSummaryItemProps) => {
+const LabelValueItem = memo(({ label, value, valueColor = 'text-white' }: LabelValueItemProps) => {
     return (
         <div className="flex gap-2.5">
             <span className="text-white text-sm font-semibold leading-[1.4]">{label}:</span>
@@ -15,6 +15,6 @@ const OrderSummaryItem = memo(({ label, value, valueColor = 'text-white' }: Orde
     );
 });
 
-OrderSummaryItem.displayName = 'OrderSummaryItem';
+LabelValueItem.displayName = 'LabelValueItem';
 
-export default OrderSummaryItem;
+export default LabelValueItem;

+ 3 - 8
src/pages/pricing/components/OrderSummary/index.tsx

@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
 
 import { useResponsive } from '@/hooks/useResponsive';
 
-import OrderSummaryItem from '../OrderSummaryItem';
+import LabelValueItem from '../LabelValueItem';
 import type { Plan } from '../PricingContent/useService';
 import { useService } from './useService';
 
@@ -16,17 +16,12 @@ export interface OrderSummaryProps {
 const OrderSummary = memo(({ selectedPlan }: OrderSummaryProps) => {
     const { t } = useTranslation();
     const { isMobile } = useResponsive();
-    const { userAccount, currentSubscription, orderTotal } = useService({ selectedPlan });
+    const { orderTotal } = useService({ selectedPlan });
 
     return (
         <div className="flex flex-col gap-5 bg-[#1B1D22] rounded-xl p-[25px_30px_25px_25px] shadow-[0px_4px_10px_0px_rgba(0,0,0,0.05)]">
             <div className="flex flex-col gap-5">
-                <OrderSummaryItem label={t('pages.pricing.orderSummary.account')} value={userAccount} />
-                <OrderSummaryItem
-                    label={t('pages.pricing.orderSummary.currentSubscription')}
-                    value={currentSubscription}
-                />
-                <OrderSummaryItem
+                <LabelValueItem
                     label={t('pages.pricing.orderSummary.orderTotal')}
                     value={orderTotal}
                     valueColor="text-[#0EA5E9]"

+ 7 - 7
src/pages/pricing/components/PlanCard/index.tsx

@@ -34,14 +34,14 @@ const PlanCard = memo(
         return (
             <div
                 ref={containerRef}
-                className={`relative flex gap-8 box-border border cursor-pointer transition-all ${
+                className={`relative box-border cursor-pointer transition-all bg-white/10 border-2 ${
                     isMobile
-                        ? 'py-3 px-4 w-full flex-row items-center justify-between rounded-lg'
-                        : 'flex-col p-[33px] w-[362.66px] rounded-2xl'
+                        ? 'flex-row-bc py-3 px-4 w-full rounded-lg'
+                        : 'flex-col-c p-[33px] w-[362.66px] rounded-2xl gap-4'
                 } ${
                     isSelected
-                        ? 'bg-white/10 border-[#0FA4E9] border-2'
-                        : `bg-white/10 ${isMobile ? 'border-transparent' : 'border-white/10'} border-2`
+                        ? 'border-[#0FA4E9]'
+                        : `${isMobile ? 'border-transparent' : 'border-white/10'}`
                 }`}
                 onClick={onClick}
             >
@@ -70,7 +70,7 @@ const PlanCard = memo(
                         />
                     </>
                 ) : (
-                    <div className="flex flex-col gap-4 items-center">
+                    <>
                         <h3
                             ref={titleRef}
                             className="text-white font-normal leading-[1.5] text-center"
@@ -92,7 +92,7 @@ const PlanCard = memo(
                         >
                             {introduce}
                         </span>
-                    </div>
+                    </>
                 )}
             </div>
         );

+ 13 - 22
src/pages/pricing/components/PricingContent/index.tsx

@@ -8,6 +8,7 @@ import PlanCard from '../PlanCard';
 import OrderSummary from '../OrderSummary';
 import { useAction } from './useAction';
 import { useService } from './useService';
+import UserInfo from '../UserInfo';
 
 const PricingContent = memo(() => {
     const { t } = useTranslation();
@@ -19,29 +20,19 @@ const PricingContent = memo(() => {
 
     return (
         <div className="max-w-[1440px] w-full px-[30px]">
-            <div className="bg-[#0F1116] px-5 sm:px-6 lg:px-[100px] py-[30px] pb-[100px] my-[50px] rounded-[12px] flex flex-col gap-10">
-                <div className="flex flex-col gap-5">
-                    <h1 className="text-white text-[35px] font-semibold leading-[1.43] text-center uppercase">
-                        {t('pages.pricing.title')}
-                    </h1>
-                </div>
+            <div className={`bg-[#0F1116] px-5 sm:px-6 lg:px-[100px] py-[30px] pb-[100px] my-[50px] rounded-[12px] flex flex-col gap-10 ${isMobile ? 'gap-5' : 'gap-10'}`}>
+                <span
+                    className={`text-white font-semibold leading-[1.43] text-center uppercase ${isMobile ? 'text-[22px]' : 'text-[35px]'}`}
+                >
+                    {t('pages.pricing.title')}
+                </span>
+                <UserInfo />
+                <span
+                    className={`text-white font-semibold leading-[1.43] uppercase ${isMobile ? 'text-[16px]' : 'text-[22px]'}`}
+                >
+                    {t('pages.pricing.subTitle')}
+                </span>
                 <div className="flex flex-col gap-10">
-                    <div className="flex flex-col gap-2.5">
-                        <div className="flex gap-2.5">
-                            <span className="text-[#0EA5E9] text-[22px] font-semibold leading-[1.4]">
-                                {t('pages.pricing.step3')}
-                            </span>
-                            <span className="text-white text-[22px] font-semibold leading-[1.4] uppercase">
-                                {t('pages.pricing.step3Title')}
-                            </span>
-                        </div>
-                        <p className="text-[#646776] text-sm font-medium leading-[1.4]">
-                            {t('pages.pricing.description')}
-                        </p>
-                        <p className="text-[#646776] text-sm font-medium leading-[1.4]">
-                            {t('pages.pricing.currencyNote')}
-                        </p>
-                    </div>
                     <div
                         className={`flex ${
                             isMobile

+ 26 - 0
src/pages/pricing/components/UserInfo/index.tsx

@@ -0,0 +1,26 @@
+import { memo } from 'react';
+
+import { useTranslation } from 'react-i18next';
+
+import { useResponsive } from '@/hooks/useResponsive';
+import LabelValueItem from '../LabelValueItem';
+import { useService } from './useService';
+import { unixTimeFormat } from '@/utils/timeUtils';
+
+const UserInfo = memo(() => {
+    const { t } = useTranslation();
+    const { isMobile } = useResponsive();
+    const { userAccount, planExpireDate } = useService();
+
+    return (
+        <div className={`${isMobile ? 'flex flex-col gap-2.5' : 'flex-row-bc'}`}>
+            <LabelValueItem label={t('pages.pricing.userInfo.account')} value={userAccount} />
+            <LabelValueItem
+                label={t('pages.pricing.userInfo.planExpireDate')}
+                value={planExpireDate > 0 ? unixTimeFormat(planExpireDate) : '-'}
+            />
+        </div>
+    );
+});
+
+export default UserInfo;

+ 36 - 0
src/pages/pricing/components/UserInfo/useService.ts

@@ -0,0 +1,36 @@
+import { useMemo } from 'react';
+
+import { createLocalTools } from '@/utils/localUtils';
+import { userKey } from '@/utils/authUtils';
+
+const ls = createLocalTools();
+
+export interface UseServiceReturn {
+    userAccount: string;
+    planExpireDate: number;
+}
+
+export function useService(): UseServiceReturn {
+    const userInfo = ls.getLocal<API.UserInfo>(userKey);
+
+    const userAccount = useMemo(() => {
+        if (!userInfo) return '';
+        if (userInfo.email) return userInfo.email;
+        if (userInfo.phone) return userInfo.phone;
+        if (userInfo.username) return userInfo.username;
+        if (userInfo.userId) return userInfo.userId;
+        if (userInfo.deviceId) return userInfo.deviceId;
+        return '';
+    }, [userInfo]);
+
+    const planExpireDate = useMemo(() => {
+        if (!userInfo) return 0;
+        // TODO: 调用 api 获取用户当前套餐信息,返回套餐到期时间
+        return 0;
+    }, []);
+
+    return {
+        userAccount,
+        planExpireDate,
+    };
+}

+ 48 - 0
src/styles/tailwind.css

@@ -7,6 +7,14 @@
         @apply flex justify-center items-center;
     }
 
+    .flex-sc {
+        @apply flex justify-start items-center;
+    }
+
+    .flex-ec {
+        @apply flex justify-end items-center;
+    }
+
     .flex-ac {
         @apply flex justify-around items-center;
     }
@@ -15,6 +23,46 @@
         @apply flex justify-between items-center;
     }
 
+    .flex-row-c {
+        @apply flex flex-row justify-center items-center;
+    }
+
+    .flex-row-sc {
+        @apply flex flex-row justify-start items-center;
+    }
+
+    .flex-row-ec {
+        @apply flex flex-row justify-end items-center;
+    }
+
+    .flex-row-ac {
+        @apply flex flex-row justify-around items-center;
+    }
+
+    .flex-row-bc {
+        @apply flex flex-row justify-between items-center;
+    }
+
+    .flex-col-c {
+        @apply flex flex-col justify-center items-center;
+    }
+
+    .flex-col-sc {
+        @apply flex flex-col justify-start items-center;
+    }
+
+    .flex-col-ec {
+        @apply flex flex-col justify-end items-center;
+    }
+
+    .flex-col-ac {
+        @apply flex flex-col justify-around items-center;
+    }
+
+    .flex-col-bc {
+        @apply flex flex-col justify-between items-center;
+    }
+
     .navbar-bg-hover {
         @apply dark:text-white dark:hover:!bg-[#242424];
     }