瀏覽代碼

feat: 隐私协议和服务条款页面

BaiLuoYan 1 月之前
父節點
當前提交
be4c7c586e

+ 3 - 1
package.json

@@ -55,7 +55,9 @@
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
     "react-i18next": "^14.0.5",
-    "react-router-dom": "^7.4.0"
+    "react-markdown": "10.1.0",
+    "react-router-dom": "^7.4.0",
+    "remark-gfm": "4.0.1"
   },
   "devDependencies": {
     "@commitlint/cli": "^19.8.0",

File diff suppressed because it is too large
+ 746 - 0
pnpm-lock.yaml


+ 76 - 0
src/assets/md/privacyPolicy_en-US.md

@@ -0,0 +1,76 @@
+# Privacy Policy
+
+Last updated on: [Insert Date]
+
+## Introduction
+
+By using NOMO VPN, you agree to be bound by the terms and conditions hereinafter set forth and applicable laws and regulations. The terms and conditions herein shall take effect upon your use of NOMO VPN. If you do not agree to any of the following terms and conditions, please stop using NOMO VPN immediately.
+NOMO VPN is committed to protecting your privacy. This Privacy Policy explains how we collect, use, and safeguard your information when you use our VPN application and services.
+
+### 1. General Information
+
+In this Privacy Policy document, we address the information collected and processed during the use of the NOMO VPN application and related services.
+If you have questions or concerns about the use of your personal data, or for privacy-specific inquiries, please contact us at <[email protected]>.
+This Privacy Policy applies globally, though specific provisions may vary to comply with local regulations in your jurisdiction.
+
+### 2. About the Collected Data and Its Use
+
+NOMO VPN prioritizes user privacy. We follow a strict "No-Logs" policy regarding your online activities. We do not track, store, or share your browsing history, destination IPs, or DNS queries.
+To operate our service effectively, we collect limited data as follows:
+
+1. **User Account Data**: If you choose to register an account, we collect your email address and password. This is used solely for account management and authentication.
+2. **Technical & Device Data**: We may collect non-identifiable technical information such as your device model, OS version, app version, and generic regional settings (e.g., country). This helps us analyze compatibility issues, fix bugs, and improve service stability. This data is retained for up to 90 days.
+3. **Service Usage Data**: We collect minimal, anonymized aggregated data regarding the use of our VPN servers, such as total bandwidth consumed and connection duration. **We do not associate this data with your specific browsing activity.** This data is used solely to optimize server load balancing and performance.
+4. **Contact Data**: If you contact our support team, we will use your email address to communicate with you regarding your inquiry.
+
+**Payment Information**: As NOMO VPN is currently provided free of charge, we do not collect payment details or credit card information. We do not currently display third-party advertisements.
+
+### 3. Data Security and Storage
+
+We implement robust technical and organizational measures to protect the data collected by NOMO VPN.
+
+1. **Data Storage**: The minimal data we collect is stored securely and retained only for as long as necessary to provide and improve our services.
+2. **Encryption**: All traffic between your device and our VPN servers is encrypted to protect your online activities from prying eyes.
+3. **Data Deletion**: You can request the deletion of your account and associated personal data by contacting our support team via <[email protected]>. We will respond to and process your request within 30 days.
+
+### 4. Legal Bases for Data Processing
+
+The processing of personal data is based on the following legal grounds:
+
+1. **Contractual Necessity**: To provide you with the VPN service and functionality.
+2. **Legitimate Interests**: To improve our application, ensure security, and prevent fraud.
+3. **Consent**: When you voluntarily provide information (e.g., contacting support).
+4. **Legal Compliance**: To comply with applicable legal obligations.
+
+### 5. Data Sharing
+
+**We do not sell, rent, or trade your personal data to any third parties.**
+We may share limited data only in the following circumstances:
+
+1. **Service Providers**: We may share anonymized technical data with trusted third-party service providers who assist us in server hosting or infrastructure maintenance. They are contractually obligated to protect data.
+2. **Legal Requirements**: We may disclose information if required by law, court order, or governmental regulation. However, since we do not store user activity logs, we cannot provide information about your browsing history.
+3. **Business Transfers**: In the event of a merger, acquisition, or sale of assets, user information may be transferred as part of the transaction, subject to the same privacy commitments.
+
+### 6. User Rights
+
+As a data subject, you have the right to:
+
+1. Access the personal information we hold about you.
+2. Request correction of inaccurate information.
+3. Request deletion of your account and personal data.
+4. Withdraw consent where processing is based on consent.
+5. Lodge a complaint with a data protection authority.
+
+To exercise these rights, please contact our Support Team via <[email protected]>.
+
+### 7. Minors' Data Protection
+
+NOMO VPN does not knowingly collect personal information from individuals under the age of 16. If we become aware that we have inadvertently collected personal data from a minor, we will take steps to delete such information immediately.
+
+### 8. Updates and Amendments
+
+We may update this Privacy Policy to reflect changes in our services (such as the introduction of new features) or legal requirements. Any updates will be posted within the NOMO VPN application. Your continued use of NOMO VPN constitutes your acceptance of the updated policy.
+
+### 9. Contact Information
+
+If you have any questions about this Privacy Policy, please contact us at <[email protected]>.

+ 73 - 0
src/assets/md/termsOfService_en-US.md

@@ -0,0 +1,73 @@
+# Terms of Use Agreement
+
+Last updated on: [Insert Date]
+By using the services provided by NOMO VPN ("Service"), you agree to be bound by the terms and conditions hereinafter set forth. This Agreement shall take effect upon your download, installation, or use of NOMO VPN. If you do not agree to any of the following terms, you must immediately uninstall and cease using NOMO VPN.
+
+## 1. General Information
+
+This legally binding Agreement governs the access and use of NOMO VPN, a service designed to provide secure, encrypted internet connectivity and privacy protection.
+NOMO VPN allows users to route their data traffic through our servers to secure their connection and mask their IP address.
+By using NOMO VPN, you agree to comply with all applicable local, state, national, and international laws and regulations.
+
+## 2. Allowable Use and Prohibited Activities
+
+NOMO VPN allows you to browse the internet securely and privately. However, you must use the Service responsibly.
+**Prohibited activities include but are not limited to:**
+
+1. **Illegal Activities**: Using the Service for any criminal or illegal activities, including but not limited to fraud, identity theft, or the distribution of child pornography.
+2. **Harmful Conduct**: Transmitting malware, viruses, worms, or any code of a destructive nature.
+3. **Network Abuse**: engaging in denial-of-service (DoS) attacks, port scanning, or spamming (sending unsolicited bulk emails).
+4. **Intellectual Property Violation**: Downloading or distributing copyrighted material in violation of applicable copyright laws.
+5. **Unauthorized Access**: Attempting to access, probe, or scan unauthorized systems or networks.
+
+We reserve the right to terminate your account or restrict access to the Service immediately, without notice, if you violate these terms.
+
+## 3. Service Availability and Billing
+
+**Current Status**: NOMO VPN is currently provided free of charge. We do not currently display third-party ads.
+**Future Changes**: We reserve the right to introduce subscription fees, premium features, or advertisements in future versions of the Service. Any such changes will be communicated clearly to users.
+**Service Reliability**: While we strive to provide a reliable service, we do not guarantee uninterrupted availability. We may perform maintenance or updates that could temporarily affect connectivity.
+
+## 4. Errors and Updates
+
+The NOMO VPN software and related information may contain technical errors or inaccuracies. We reserve the right to correct such errors and update the software to add features, fix bugs, or improve security without prior notice. By using NOMO VPN, you consent to receive such updates.
+
+## 5. External Links
+
+NOMO VPN may contain links to external websites. We are not responsible for the content, privacy policies, or practices of any third-party websites. Accessing external links is at your own risk.
+
+## 6. Disclaimers and Limitations of Liability
+
+NOMO VPN is provided on an "AS IS" and "AS AVAILABLE" basis without warranties of any kind, either express or implied.
+To the maximum extent permitted by applicable law, NOMO VPN and its operators shall not be liable for:
+
+1. Any indirect, incidental, special, consequential, or punitive damages.
+2. Loss of data, profits, or business opportunities.
+3. Failures in connectivity or speed resulting from your internet service provider (ISP) or network environment.
+4. Any actions taken against you by third parties (e.g., copyright holders) due to your misuse of the Service.
+
+We do not control and are not responsible for the data you transmit or receive via the Service.
+
+## 7. User Responsibilities
+
+You are responsible for:
+
+1. Maintaining the confidentiality of your account credentials (if applicable).
+2. Ensuring that your use of the Service complies with the laws of your jurisdiction.
+3. Any activity that occurs under your account or device.
+
+## 8. Privacy Policy
+
+Your use of NOMO VPN is also governed by our Privacy Policy, which outlines how we collect and process data. By using the Service, you consent to the practices described in the Privacy Policy.
+
+## 9. Dispute Resolution
+
+Any disputes arising from this Agreement shall be resolved through good-faith negotiations. If a resolution cannot be reached, the dispute shall be submitted to the competent courts in the jurisdiction where NOMO VPN's operators are headquartered.
+
+## 10. Updates to this Agreement
+
+We may update this Agreement from time to time. The most current version will always be available within the application or on our official website. Your continued use of NOMO VPN after any changes constitutes your acceptance of the new Terms.
+
+## 11. Contact Information
+
+If you have any questions regarding this Agreement, please contact us at <[email protected]>.

+ 2 - 0
src/defines/docLastUpdated.ts

@@ -0,0 +1,2 @@
+/** 服务条款 / 隐私政策等文档的「最后更新」日期,用于替换 MD 中的 [Insert Date] */
+export const DOC_LAST_UPDATED = '2025-01-28';

+ 1 - 0
src/defines/index.ts

@@ -1,3 +1,4 @@
+export * from './docLastUpdated';
 export * from './errorShowType';
 export * from './planTagType';
 export * from './payMethodType';

+ 2 - 0
src/locales/en-US/menus.ts

@@ -4,6 +4,8 @@ export default {
     ['404']: '404',
     ['home']: 'Home',
     ['pricing']: 'Pricing',
+    ['privacyPolicy']: 'Privacy Policy',
+    ['termsOfService']: 'Terms of Service',
     // 以下是 框架功能演示 页面的路由名称
     ['featureDemo']: 'Feature Demo',
     ['routeDemo']: 'Route Demo',

+ 8 - 1
src/locales/en-US/pages.ts

@@ -17,7 +17,6 @@ export default {
         },
     },
 
-    // 以下是 Pricing 页面的翻译
     pricing: {
         title: 'Purchase NOMO VPN Plan',
         selecPlan: 'Select a plan that suits you',
@@ -44,6 +43,14 @@ export default {
         },
     },
 
+    privacyPolicy: {
+        title: 'Privacy Policy',
+    },
+
+    termsOfService: {
+        title: 'Terms of Service',
+    },
+
     // 以下是 框架功能演示 页面的翻译
     featureDemo: {
         title: 'Feature Demo',

+ 2 - 0
src/locales/fa-IR/menus.ts

@@ -4,6 +4,8 @@ export default {
     ['404']: '404',
     ['home']: 'صفحه اصلی',
     ['pricing']: 'قیمت ها',
+    ['privacyPolicy']: 'سیاست حریم خصوصی',
+    ['termsOfService']: 'شرایط استفاده',
     // 以下是 框架功能演示 页面的路由名称
     ['featureDemo']: 'نمایش ویژگی',
     ['routeDemo']: 'نمایش مسیر',

+ 8 - 1
src/locales/fa-IR/pages.ts

@@ -17,7 +17,6 @@ export default {
         },
     },
 
-    // 以下是 Pricing 页面的翻译
     pricing: {
         title: 'خرید پلن NOMO VPN',
         selecPlan: 'یک پلن را انتخاب کنید که به شما مناسب است',
@@ -44,6 +43,14 @@ export default {
         },
     },
 
+    privacyPolicy: {
+        title: 'سیاست حریم خصوصی',
+    },
+
+    termsOfService: {
+        title: 'شرایط استفاده',
+    },
+
     // 以下是 框架功能演示 页面的翻译
     featureDemo: {
         title: 'نمایش ویژگی',

+ 2 - 0
src/locales/zh-CN/menus.ts

@@ -4,6 +4,8 @@ export default {
     ['404']: '404',
     ['home']: '首页',
     ['pricing']: '价格',
+    ['privacyPolicy']: '隐私政策',
+    ['termsOfService']: '服务条款',
     // 以下是 框架功能演示 页面的路由名称
     ['featureDemo']: '功能演示',
     ['routeDemo']: '路由演示',

+ 8 - 1
src/locales/zh-CN/pages.ts

@@ -17,7 +17,6 @@ export default {
         },
     },
 
-    // 以下是 Pricing 页面的翻译
     pricing: {
         title: '购买 NOMO VPN 套餐',
         selecPlan: '选择一个适合您的套餐',
@@ -44,6 +43,14 @@ export default {
         },
     },
 
+    privacyPolicy: {
+        title: '隐私政策',
+    },
+
+    termsOfService: {
+        title: '服务条款',
+    },
+
     // 以下是 框架功能演示 页面的翻译
     featureDemo: {
         title: '功能演示',

+ 62 - 0
src/pages/privacyPolicy/index.tsx

@@ -0,0 +1,62 @@
+import React, { useEffect, useState } from 'react';
+
+import { useTranslation } from 'react-i18next';
+import ReactMarkdown from 'react-markdown';
+import remarkGfm from 'remark-gfm';
+
+import { DOC_LAST_UPDATED } from '@/defines';
+import { useResponsive } from '@/hooks/useResponsive';
+import { getDirByLang, loadMdByLang } from '@/utils/mdLoader';
+
+const PrivacyPolicy: React.FC = () => {
+    const { i18n, t } = useTranslation();
+    const { isMobile } = useResponsive();
+    const [mdContent, setMdContent] = useState<string>('');
+    const [mdLang, setMdLang] = useState<string>('en-US');
+    const [dir, setDir] = useState<'ltr' | 'rtl'>('ltr');
+
+    useEffect(() => {
+        loadMdByLang('privacyPolicy', i18n.language, {
+            replacements: { '[Insert Date]': DOC_LAST_UPDATED }, // 替换 MD 中的 [Insert Date]
+        }).then(({ content, usedLang }) => {
+            setMdLang(usedLang);
+            setMdContent(content);
+        });
+    }, [i18n.language]);
+
+    useEffect(() => {
+        setDir(getDirByLang(mdLang));
+    }, [mdLang]);
+
+    return (
+        <div className="flex items-start justify-center">
+            <div className={`max-w-[1440px] w-full ${isMobile ? 'px-0' : 'px-[30px]'}`}>
+                <div
+                    className={`bg-[#0F1116] px-5 sm:px-5 lg:px-[100px] py-[30px] flex flex-col ${isMobile ? 'gap-5 mt-0 pb-[30px]' : 'gap-10 my-[50px] rounded-[12px] pb-[100px]'}`}
+                >
+                    <span
+                        className={`w-full text-white font-semibold leading-[1.43] text-start uppercase ${isMobile ? 'text-[22px]' : 'text-[35px]'}`}
+                    >
+                        {t('pages.privacyPolicy.title')}
+                    </span>
+                    <div
+                        className={`flex flex-col bg-[#1B1D22] text-white rounded-xl shadow-[0px_4px_10px_0px_rgba(0,0,0,0.05)] ${isMobile ? 'p-[14px] gap-2' : 'p-[25px_30px_25px_25px] gap-5'}`}
+                        dir={dir}
+                        style={{
+                            direction: dir,
+                            textAlign: dir === 'ltr' ? 'left' : 'right',
+                        }}
+                    >
+                        {mdContent && (
+                            <ReactMarkdown remarkPlugins={[remarkGfm]} components={{}}>
+                                {mdContent}
+                            </ReactMarkdown>
+                        )}
+                    </div>
+                </div>
+            </div>
+        </div>
+    );
+};
+
+export default PrivacyPolicy;

+ 62 - 0
src/pages/termsOfService/index.tsx

@@ -0,0 +1,62 @@
+import React, { useEffect, useState } from 'react';
+
+import { useTranslation } from 'react-i18next';
+import ReactMarkdown from 'react-markdown';
+import remarkGfm from 'remark-gfm';
+
+import { DOC_LAST_UPDATED } from '@/defines';
+import { useResponsive } from '@/hooks/useResponsive';
+import { getDirByLang, loadMdByLang } from '@/utils/mdLoader';
+
+const TermsOfService: React.FC = () => {
+    const { i18n, t } = useTranslation();
+    const { isMobile } = useResponsive();
+    const [mdContent, setMdContent] = useState<string>('');
+    const [mdLang, setMdLang] = useState<string>('en-US');
+    const [dir, setDir] = useState<'ltr' | 'rtl'>('ltr');
+
+    useEffect(() => {
+        loadMdByLang('termsOfService', i18n.language, {
+            replacements: { '[Insert Date]': DOC_LAST_UPDATED }, // 替换 MD 中的 [Insert Date]
+        }).then(({ content, usedLang }) => {
+            setMdLang(usedLang);
+            setMdContent(content);
+        });
+    }, [i18n.language]);
+
+    useEffect(() => {
+        setDir(getDirByLang(mdLang));
+    }, [mdLang]);
+
+    return (
+        <div className="flex items-start justify-center">
+            <div className={`max-w-[1440px] w-full ${isMobile ? 'px-0' : 'px-[30px]'}`}>
+                <div
+                    className={`bg-[#0F1116] px-5 sm:px-5 lg:px-[100px] py-[30px] flex flex-col ${isMobile ? 'gap-5 mt-0 pb-[30px]' : 'gap-10 my-[50px] rounded-[12px] pb-[100px]'}`}
+                >
+                    <span
+                        className={`w-full text-white font-semibold leading-[1.43] text-start uppercase ${isMobile ? 'text-[22px]' : 'text-[35px]'}`}
+                    >
+                        {t('pages.termsOfService.title')}
+                    </span>
+                    <div
+                        className={`flex flex-col bg-[#1B1D22] text-white/80 rounded-xl shadow-[0px_4px_10px_0px_rgba(0,0,0,0.05)] ${isMobile ? 'p-[14px] gap-2' : 'p-[25px_30px_25px_25px] gap-5'}`}
+                        dir={dir}
+                        style={{
+                            direction: dir,
+                            textAlign: dir === 'ltr' ? 'left' : 'right',
+                        }}
+                    >
+                        {mdContent && (
+                            <ReactMarkdown remarkPlugins={[remarkGfm]} components={{}}>
+                                {mdContent}
+                            </ReactMarkdown>
+                        )}
+                    </div>
+                </div>
+            </div>
+        </div>
+    );
+};
+
+export default TermsOfService;

+ 12 - 0
src/router/routes.tsx

@@ -11,6 +11,8 @@ import Home from '@/pages/home';
 import RouteDemo from '@/pages/routeDemo';
 import FeatureDemo from '@/pages/featureDemo';
 import Pricing from '@/pages/pricing';
+import PrivacyPolicy from '@/pages/privacyPolicy';
+import TermsOfService from '@/pages/termsOfService';
 
 const routes: AppRouteObject[] = [
     {
@@ -31,6 +33,16 @@ const routes: AppRouteObject[] = [
                 path: '/pricing',
                 element: <Pricing />,
             },
+            {
+                name: 'privacyPolicy',
+                path: '/privacy',
+                element: <PrivacyPolicy />,
+            },
+            {
+                name: 'termsOfService',
+                path: '/terms-of-service',
+                element: <TermsOfService />,
+            },
             {
                 name: 'featureDemo',
                 path: '/feature-demo',

+ 75 - 0
src/utils/mdLoader.ts

@@ -0,0 +1,75 @@
+/**
+ * 按页面类型与语言动态加载 Markdown 文案(如服务条款、隐私政策)
+ */
+
+type MdPageType = 'termsOfService' | 'privacyPolicy';
+
+const DEFAULT_LANG = 'en-US';
+
+// 匹配 @/assets/md/{pageName}_{lang}.md,如 termsOfService_en-US.md、privacyPolicy_zh-CN.md
+const rawMdModules = import.meta.glob<string>('@/assets/md/*_*.md', {
+    query: '?raw',
+    import: 'default',
+});
+
+const mdLoaders: Record<string, Record<string, () => Promise<string>>> = {};
+Object.keys(rawMdModules).forEach((key) => {
+    const match = key.match(/\/([^/]+)_([\w-]+)\.md$/);
+    if (match) {
+        const [, pageName, lang] = match;
+        if (!mdLoaders[pageName!]) mdLoaders[pageName!] = {};
+        mdLoaders[pageName!]![lang!] = () => (rawMdModules[key] as () => Promise<string>)();
+    }
+});
+
+const localeModules = import.meta.glob<{ default?: { DIR?: string } }>('@/locales/*.ts', {
+    eager: true,
+});
+const dirByLang: Record<string, 'ltr' | 'rtl'> = {};
+Object.keys(localeModules).forEach((key) => {
+    const match = key.match(/\/([\w-]+)\.ts$/);
+    if (match) {
+        const lang = match[1]!;
+        const dir = localeModules[key]?.default?.DIR;
+        if (dir === 'ltr' || dir === 'rtl') dirByLang[lang] = dir;
+    }
+});
+
+export interface LoadMdByLangOptions {
+    /** 占位符替换:key 为 MD 中的占位符(如 [Insert Date]),value 为替换后的字符串 */
+    replacements?: Record<string, string>;
+}
+
+/**
+ * 根据当前语言加载 Markdown 内容;无该语言时回退到英文。返回内容及实际使用的语言。
+ * 可通过 options.replacements 替换 MD 中的占位符(如 [Insert Date])。
+ */
+export async function loadMdByLang(
+    page: MdPageType,
+    lang: string,
+    options?: LoadMdByLangOptions
+): Promise<{ content: string; usedLang: string }> {
+    const loaders = mdLoaders[page];
+    const hasLang = Boolean(loaders?.[lang]);
+    const usedLang = hasLang ? lang : DEFAULT_LANG;
+    const loader = loaders?.[usedLang] ?? loaders?.[DEFAULT_LANG];
+    if (!loader) return { content: '', usedLang };
+    let content = await loader();
+    const replacements = options?.replacements;
+    if (replacements && Object.keys(replacements).length > 0) {
+        content = Object.entries(replacements).reduce(
+            (acc, [placeholder, value]) => acc.replaceAll(placeholder, value),
+            content
+        );
+    }
+    return { content, usedLang };
+}
+
+/**
+ * 根据语言获取文案方向(ltr/rtl),用于 dir 与样式
+ */
+export function getDirByLang(lang: string): 'ltr' | 'rtl' {
+    return dirByLang[lang] ?? 'ltr';
+}
+
+export type { MdPageType };

Some files were not shown because too many files changed in this diff