This commit is contained in:
eric
2026-03-08 12:30:50 -05:00
parent 0e75fb2e6e
commit 57a788006f
12 changed files with 536 additions and 17 deletions

View File

@@ -0,0 +1,120 @@
"use client";
import { useTranslation } from "@/i18n/navigation";
import { getThemeConfig } from "@/config";
import SocialPlatformIcon from "../../components/SocialPlatformIcon";
export default function AboutContent() {
const { t } = useTranslation("about");
const config = getThemeConfig();
const linktreeUrl = config.linktreeUrl;
const socialLinks = config.socialLinks ?? [];
const teamMembers = config.teamMembers ?? [];
return (
<div className="min-h-screen bg-slate-50 dark:bg-slate-950">
<section className="py-20 sm:py-28">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<div className="text-center">
<h1 className="text-4xl font-bold tracking-tight text-slate-900 dark:text-slate-100 sm:text-5xl">
{t("title")}
</h1>
<p className="mx-auto mt-4 max-w-2xl text-lg text-slate-600 dark:text-slate-400">
{t("subtitle")}
</p>
</div>
{/* 团队成员 */}
{teamMembers.length > 0 && (
<div className="mt-16">
<h2 className="mb-2 text-center text-2xl font-bold text-slate-900 dark:text-slate-100 sm:text-3xl">
{t("teamTitle")}
</h2>
<p className="mb-10 text-center text-slate-600 dark:text-slate-400">
{t("teamDesc")}
</p>
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{teamMembers.map((member) => {
const content = (
<div className="card-hover flex flex-col items-center rounded-2xl border border-slate-100 bg-white p-6 text-center shadow-sm dark:border-slate-800 dark:bg-slate-900">
<div className="mb-4 flex h-20 w-20 items-center justify-center overflow-hidden rounded-full bg-gradient-to-br from-sky-100 to-cyan-50 text-4xl dark:from-sky-900/50 dark:to-cyan-900/50">
{member.avatar ?? "👤"}
</div>
<h3 className="text-lg font-bold text-slate-900 dark:text-slate-100">
{member.name}
</h3>
<p className="mt-1 text-sm text-slate-500 dark:text-slate-400">
{member.role}
</p>
</div>
);
if (member.link) {
return (
<a
key={member.name}
href={member.link}
target="_blank"
rel="noopener noreferrer"
className="block"
>
{content}
</a>
);
}
return <div key={member.name}>{content}</div>;
})}
</div>
</div>
)}
{/* 社交平台 Linktree */}
{(linktreeUrl || socialLinks.length > 0) && (
<div className="mt-16">
<h2 className="mb-2 text-center text-2xl font-bold text-slate-900 dark:text-slate-100 sm:text-3xl">
{t("linktreeTitle")}
</h2>
<p className="mb-10 text-center text-slate-600 dark:text-slate-400">
{t("linktreeDesc")}
</p>
{(["domestic", "overseas"] as const).map((group) => {
const items = socialLinks.filter((l) => l.group === group);
if (items.length === 0) return null;
return (
<div key={group} className="mb-10 last:mb-0">
<h3 className="mb-4 text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">
{group === "domestic" ? t("groupDomestic") : t("groupOverseas")}
</h3>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{items.map((item) => (
<a
key={item.platform}
href={item.url}
target="_blank"
rel="noopener noreferrer"
className="card-hover group flex items-center gap-4 rounded-2xl border border-slate-100 bg-white p-5 shadow-sm transition-all dark:border-slate-800 dark:bg-slate-900"
>
<SocialPlatformIcon platform={item.platform} />
<div className="min-w-0 flex-1">
<div className="font-bold text-slate-900 dark:text-slate-100">
{item.label}
</div>
<div className="mt-0.5 text-sm text-slate-500 dark:text-slate-400">
{t("linktreeCta")}
</div>
</div>
<span className="shrink-0 text-slate-400 transition-transform group-hover:translate-x-1">
</span>
</a>
))}
</div>
</div>
);
})}
</div>
)}
</div>
</section>
</div>
);
}

View File

@@ -0,0 +1,20 @@
import type { Metadata } from "next";
import Header from "../../components/Header";
import Footer from "../../components/Footer";
import AboutContent from "./AboutContent";
export const metadata: Metadata = {
title: "关于我们 | About Us",
description: "认识数字游民指南背后的团队,关注我们的社交平台。",
};
export default function AboutPage() {
return (
<>
<Header />
<main>
<AboutContent />
</main>
<Footer />
</>
);
}

View File

@@ -0,0 +1,182 @@
"use client";
import { useLocale } from "@/i18n/navigation";
function renderWithLinks(text: string) {
const urlRegex = /(https?:\/\/[^\s)]+)/g;
const parts = text.split(urlRegex);
return parts.map((part, i) =>
part.match(urlRegex) ? (
<a
key={`${i}-${part}`}
href={part}
target="_blank"
rel="noopener noreferrer"
className="text-sky-600 underline hover:text-sky-700 dark:text-sky-400 dark:hover:text-sky-300"
>
{part}
</a>
) : (
part
)
);
}
const PRIVACY_ZH = {
title: "隐私政策",
lastUpdated: "最后更新",
sections: [
{
title: "一、适用范围",
content: `本隐私政策适用于「数字游民指南」(以下简称本网站)在您使用网站服务时收集的个人信息。本政策不适用于本网站以外的第三方链接网站,也不适用于非本网站委托或参与管理的人员。`,
},
{
title: "二、个人资料的收集、处理及利用",
content: `当您访问本网站或使用本网站提供的功能服务时,我们将根据服务性质请您提供必要的个人资料,并在特定目的范围内处理及利用;未经您书面同意,本网站不会将个人资料用于其他用途。
本网站在您使用申请表单、问卷、支付等互动功能时,会保留您提供的姓名、电子邮箱、联系方式及使用时间等。
在一般浏览时,服务器会自动记录相关行为,包括您使用设备的 IP 地址、使用时间、浏览器类型、浏览及点击记录等,作为我们改进网站服务的参考,此记录仅供内部使用,不会对外公布。
您可以随时向我们提出请求,以更正或删除您的账户或本网站所收集的个人资料。联系方式请见文末。`,
},
{
title: "三、支付处理与 Stripe",
content: `本网站使用 Stripe 作为支付服务提供商处理付款。在您进行支付时:
• 数据控制者本网站为个人数据的控制者Stripe 作为数据处理者代表我们处理支付相关数据。
• Stripe 隐私政策:您可查阅 Stripe 的隐私政策 https://stripe.com/privacy
• 收集的数据类别Stripe 直接收集的数据包括卡号、有效期等支付信息及设备数据;我们向 Stripe 共享的数据包括邮箱、姓名、账单地址等完成交易所需信息。
• 国际数据传输支付数据可能传输至美国等地区Stripe 采用标准合同条款及欧盟-美国数据隐私框架等机制保障合规。
• 法律依据:支付处理基于合同履行(完成支付义务)、法律义务(税务与会计要求)及合法利益(欺诈防范)。
• 数据保留:交易记录通常保留 710 年以满足税务及会计要求;支付失败日志可能保留 90 天至 1 年。`,
},
{
title: "四、资料保护",
content: `本网站主机设有防火墙、防毒系统等安全设备及必要防护措施,保护网站及您的个人资料。个人资料仅由经授权人员接触,相关处理人员均签署保密协议,违反者将承担法律责任。
如因业务需要委托其他单位提供服务,本网站将严格要求其遵守保密义务,并采取必要检查程序确保其切实遵守。`,
},
{
title: "五、外部链接",
content: `本网站提供其他网站的链接,您可经由本网站提供的链接进入其他网站。该等链接网站不适用本网站的隐私政策,您须参考该链接网站自身的隐私政策。`,
},
{
title: "六、与第三人共用个人资料",
content: `本网站不会提供、交换、出租或出售您的个人资料给其他个人、团体、私人企业或公务机关,但有法律依据或合约义务者不在此限,包括但不限于:经您书面同意、法律明文规定、为免除您生命身体自由或财产上之危险、与公务机关或学术研究机构基于公共利益进行统计或学术研究且资料经处理后无法识别特定当事人、或为辨识联络或采取法律行动所必要者。
本网站委托厂商协助收集、处理或利用您的个人资料时,将对委外厂商善尽监督管理之责。`,
},
{
title: "七、Cookie 使用",
content: `为提供您最佳服务,本网站会在您的设备中放置并读取 Cookie。若您不愿接受 Cookie可在浏览器中设定较高的隐私等级以拒绝 Cookie但可能导致网站部分功能无法正常执行。`,
},
{
title: "八、政策修订",
content: `本隐私政策将因应需求随时修正,修正后的条款将刊登于本网站。`,
},
{
title: "九、联系管道",
content: `若对本隐私政策有任何疑问,或欲提出变更、删除个人资料之请求,请前往本网站「联系我们」页面提交表单,或通过以下方式联系我们:
• 网站:数字游民指南
• 邮箱:请通过网站联系我们页面获取
• 地址:请通过网站联系我们页面获取`,
},
],
};
const PRIVACY_EN = {
title: "Privacy Policy",
lastUpdated: "Last updated",
sections: [
{
title: "1. Scope",
content: `This Privacy Policy applies to personal information collected by "Digital Nomad Guide" (the "Site") when you use our services. This policy does not apply to third-party websites linked from this Site, nor to persons not entrusted or involved in the management of this Site.`,
},
{
title: "2. Collection, Processing and Use of Personal Data",
content: `When you visit the Site or use our services, we may ask you to provide necessary personal data for specific purposes. We will not use your personal data for other purposes without your written consent.
When you use application forms, surveys, payments, or other interactive features, we retain information you provide, such as name, email, contact details, and usage time.
During general browsing, our servers automatically record relevant activity, including your IP address, access time, browser type, and browsing/click records, for improving our services. Such records are for internal use only and are not disclosed to the public.
You may request correction or deletion of your account or personal data collected by the Site at any time. Contact details are provided at the end of this policy.`,
},
{
title: "3. Payment Processing and Stripe",
content: `We use Stripe as our payment service provider. When you make a payment:
• Data Controller: We are the data controller; Stripe acts as a data processor on our behalf.
• Stripe Privacy Policy: https://stripe.com/privacy
• Data Categories: Stripe directly collects payment card details and device data; we share email, name, billing address, and other information necessary to complete the transaction.
• International Transfers: Payment data may be transferred to the US and other regions. Stripe uses Standard Contractual Clauses and the EU-US Data Privacy Framework to ensure compliance.
• Legal Basis: Payment processing is based on contract performance (fulfilling payment obligations), legal obligation (tax and accounting requirements), and legitimate interest (fraud prevention).
• Data Retention: Transaction records are typically retained for 710 years for tax and accounting purposes; payment failure logs may be retained for 90 days to 1 year.`,
},
{
title: "4. Data Protection",
content: `Our servers are protected by firewalls, antivirus systems, and other security measures. Personal data is accessible only to authorized personnel who have signed confidentiality agreements. Violations will result in legal liability.
When we entrust third parties to process personal data, we require them to comply with confidentiality obligations and conduct necessary audits.`,
},
{
title: "5. External Links",
content: `The Site provides links to other websites. Those linked sites are not governed by this Privacy Policy. Please refer to the privacy policy of each linked site.`,
},
{
title: "6. Sharing Personal Data with Third Parties",
content: `We will not provide, exchange, rent, or sell your personal data to other individuals, groups, or entities, except where required by law or contract, including but not limited to: your written consent, legal requirements, protection of life or property, statistical or academic research with anonymized data, or identification/contact/legal action as necessary.
When we entrust third parties to collect, process, or use your personal data, we will supervise them appropriately.`,
},
{
title: "7. Use of Cookies",
content: `To provide you with the best service, we place and read cookies on your device. You may set a higher privacy level in your browser to refuse cookies, but some features of the Site may not function properly.`,
},
{
title: "8. Policy Amendments",
content: `We may amend this Privacy Policy from time to time. Amended terms will be posted on the Site.`,
},
{
title: "9. Contact",
content: `If you have questions about this Privacy Policy or wish to request correction or deletion of your personal data, please submit a form via our "Contact Us" page, or contact us through:
• Site: Digital Nomad Guide
• Email: Available via the Contact Us page
• Address: Available via the Contact Us page`,
},
],
};
export default function PrivacyContent() {
const locale = useLocale();
const content = locale === "zh" ? PRIVACY_ZH : PRIVACY_EN;
return (
<div className="min-h-screen bg-slate-50 py-16 dark:bg-slate-950 sm:py-20">
<div className="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
<h1 className="text-3xl font-bold tracking-tight text-slate-900 dark:text-slate-100 sm:text-4xl">
{content.title}
</h1>
<p className="mt-2 text-sm text-slate-500 dark:text-slate-400">
{content.lastUpdated}: 2025-03
</p>
<div className="mt-10 space-y-10">
{content.sections.map((section) => (
<section key={section.title}>
<h2 className="mb-4 text-lg font-bold text-slate-900 dark:text-slate-100">
{section.title}
</h2>
<div className="whitespace-pre-line text-slate-600 dark:text-slate-400">
{renderWithLinks(section.content)}
</div>
</section>
))}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,21 @@
import type { Metadata } from "next";
import Header from "../../components/Header";
import Footer from "../../components/Footer";
import PrivacyContent from "./PrivacyContent";
export const metadata: Metadata = {
title: "隐私政策 | Privacy Policy",
description: "数字游民指南隐私政策,说明我们如何收集、使用和保护您的个人信息。",
};
export default function PrivacyPage() {
return (
<>
<Header />
<main>
<PrivacyContent />
</main>
<Footer />
</>
);
}

View File

@@ -1,6 +1,7 @@
"use client";
import { Link, useTranslation } from "@/i18n/navigation";
import ShopCard from "./ShopCard";
const linkKeys = ["guide", "discord", "tools", "github", "feishu", "destinations"] as const;
const linkIcons: Record<(typeof linkKeys)[number], string> = {
@@ -17,6 +18,11 @@ export default function Community() {
return (
<section id="community" className="py-20 sm:py-28 dark:bg-slate-950">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
{/* 电商独立站卡片 */}
<div id="shop">
<ShopCard />
</div>
{/* 数字游民社区 - 优化布局 */}
<div className="mb-20">
<Link

View File

@@ -32,9 +32,9 @@ const footerSections = [
{
titleKey: "about" as const,
links: [
{ labelKey: "aboutUs" as const, href: "#" },
{ labelKey: "aboutUs" as const, href: "/about", internal: true },
{ labelKey: "contribute" as const, href: "#" },
{ labelKey: "privacy" as const, href: "#" },
{ labelKey: "privacy" as const, href: "/privacy", internal: true, openInNewTab: true },
{ labelKey: "contact" as const, href: "#" },
],
},
@@ -66,12 +66,23 @@ export default function Footer() {
<ul className="space-y-2.5">
{section.links.map((link) => (
<li key={link.labelKey}>
<a
href={link.href}
className="text-sm text-slate-500 transition-colors hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
>
{t(`links.${link.labelKey}`)}
</a>
{"internal" in link && link.internal ? (
<Link
href={link.href}
target={"openInNewTab" in link && link.openInNewTab ? "_blank" : undefined}
rel={"openInNewTab" in link && link.openInNewTab ? "noopener noreferrer" : undefined}
className="text-sm text-slate-500 transition-colors hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
>
{t(`links.${link.labelKey}`)}
</Link>
) : (
<a
href={link.href}
className="text-sm text-slate-500 transition-colors hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
>
{t(`links.${link.labelKey}`)}
</a>
)}
</li>
))}
</ul>

View File

@@ -10,6 +10,7 @@ const navLinks = [
{ labelKey: "roadmap" as const, href: "#roadmap" },
{ labelKey: "tools" as const, href: "#tools" },
{ labelKey: "community" as const, href: "#community" },
{ labelKey: "shop" as const, href: "#shop" },
];
export default function Header() {
@@ -57,13 +58,6 @@ export default function Header() {
{tNav(link.labelKey)}
</a>
))}
<button
type="button"
onClick={() => router.replace(pathname, { locale: locale === "zh" ? "en" : "zh" })}
className="rounded-lg px-3 py-2 text-sm font-medium text-slate-600 transition-colors hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-100"
>
{locale === "zh" ? "EN" : "中文"}
</button>
</nav>
<div className="flex shrink-0 items-center gap-1 sm:gap-3">
@@ -87,6 +81,15 @@ export default function Header() {
{t("loginRegister")}
</button>
)}
<button
type="button"
onClick={() => router.replace(pathname, { locale: locale === "zh" ? "en" : "zh" })}
className="inline-flex shrink-0 items-center justify-center rounded-lg p-2 text-sm font-medium text-slate-600 transition-colors hover:bg-slate-100 dark:text-slate-400 dark:hover:bg-slate-800"
aria-label={locale === "zh" ? "Switch to English" : "切换到中文"}
title={locale === "zh" ? "EN" : "中文"}
>
{locale === "zh" ? "EN" : "中文"}
</button>
<ThemeToggle />
<button
onClick={() => setMobileOpen(!mobileOpen)}

View File

@@ -0,0 +1,47 @@
"use client";
import { useTranslation } from "@/i18n/navigation";
import { getThemeConfig } from "@/config";
export default function ShopCard() {
const { t } = useTranslation("shop");
const config = getThemeConfig();
const shopUrl = config.services?.shopUrl;
if (!shopUrl) return null;
return (
<div className="mb-12">
<a
href={shopUrl}
target="_blank"
rel="noopener noreferrer"
className="group block overflow-hidden rounded-2xl border border-emerald-200/80 bg-gradient-to-br from-emerald-50 via-white to-teal-50/60 p-8 shadow-sm transition-all duration-300 hover:border-emerald-300 hover:shadow-xl hover:shadow-emerald-100/50 dark:border-emerald-800/80 dark:from-emerald-900/30 dark:via-slate-900 dark:to-teal-900/20 dark:hover:border-emerald-700 dark:hover:shadow-emerald-900/30 sm:p-10"
>
<div className="flex flex-col gap-6 sm:flex-row sm:items-center sm:justify-between">
<div className="flex-1">
<div className="mb-3 inline-flex items-center gap-2 rounded-full bg-emerald-100 px-4 py-1.5 text-sm font-semibold text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-300">
<span className="text-lg">🛒</span>
{t("badge")}
</div>
<h3 className="mb-3 text-2xl font-bold tracking-tight text-slate-900 dark:text-slate-100 sm:text-3xl">
{t("title")}
</h3>
<p className="mb-4 max-w-xl text-base leading-relaxed text-slate-600 dark:text-slate-400">
{t("desc")}
</p>
</div>
<div className="flex shrink-0 items-center gap-4 rounded-2xl bg-white/90 p-6 shadow-md backdrop-blur-sm transition-transform dark:bg-slate-800/90 group-hover:scale-[1.02] sm:p-8">
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-br from-emerald-400 to-teal-500 text-3xl shadow-lg shadow-emerald-200/50">
🛍
</div>
<div className="text-left">
<div className="font-bold text-slate-800 dark:text-slate-200">{t("cta")}</div>
<div className="mt-1 text-sm text-slate-500 dark:text-slate-400">{t("ctaHint")}</div>
</div>
</div>
</div>
</a>
</div>
);
}

View File

@@ -0,0 +1,48 @@
"use client";
const PLATFORM_ICONS: Record<string, React.ReactNode> = {
// 国内平台
douyin: (
<svg viewBox="0 0 24 24" className="h-6 w-6" fill="currentColor">
<path d="M19.59 6.69a4.83 4.83 0 0 1-3.77-4.25V2h-3.45v13.67a2.89 2.89 0 0 1-5.2 1.74 2.89 2.89 0 0 1 2.31-4.64 2.93 2.93 0 0 1 .88.13V9.4a6.84 6.84 0 0 0-1-.05A6.33 6.33 0 0 0 5 20.1a6.34 6.34 0 0 0 10.86-4.43v-7a8.16 8.16 0 0 0 4.77 1.52v-3.4a4.85 4.85 0 0 1-1-.1z" />
</svg>
),
"wechat-official": (
<svg viewBox="0 0 24 24" className="h-6 w-6" fill="currentColor">
<path d="M8.5 6C5.46 6 3 8.24 3 11c0 1.64.72 3.14 1.87 4.17L3 17l2.13-1.13A6.5 6.5 0 0 0 8.5 16c3.04 0 5.5-2.24 5.5-5s-2.46-5-5.5-5zm0 8c-1.38 0-2.63-.56-3.54-1.46C4.43 11.54 4 10.31 4 9s.43-2.54 1.46-3.54C6.37 4.56 7.62 4 9 4s2.63.56 3.54 1.46c.93.93 1.46 2.16 1.46 3.54s-.53 2.61-1.46 3.54A4.99 4.99 0 0 1 8.5 14z" />
</svg>
),
xiaohongshu: (
<svg viewBox="0 0 24 24" className="h-6 w-6" fill="currentColor">
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z" />
</svg>
),
// 海外平台
tiktok: (
<svg viewBox="0 0 24 24" className="h-6 w-6" fill="currentColor">
<path d="M19.59 6.69a4.83 4.83 0 0 1-3.77-4.25V2h-3.45v13.67a2.89 2.89 0 0 1-5.2 1.74 2.89 2.89 0 0 1 2.31-4.64 2.93 2.93 0 0 1 .88.13V9.4a6.84 6.84 0 0 0-1-.05A6.33 6.33 0 0 0 5 20.1a6.34 6.34 0 0 0 10.86-4.43v-7a8.16 8.16 0 0 0 4.77 1.52v-3.4a4.85 4.85 0 0 1-1-.1z" />
</svg>
),
youtube: (
<svg viewBox="0 0 24 24" className="h-6 w-6" fill="currentColor">
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
</svg>
),
twitter: (
<svg viewBox="0 0 24 24" className="h-6 w-6" fill="currentColor">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
),
};
export default function SocialPlatformIcon({ platform }: { platform: string }) {
return (
<span className="flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-300">
{PLATFORM_ICONS[platform] ?? (
<svg viewBox="0 0 24 24" className="h-6 w-6" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93z" />
</svg>
)}
</span>
);
}

View File

@@ -54,7 +54,15 @@ export interface ThemeConfig {
bucket?: string;
uploadPrefix?: string;
};
/** 电商独立站域名(可选,配置后显示电商卡片) */
shopUrl?: string;
};
/** 关于我们Linktree 链接(可选) */
linktreeUrl?: string;
/** 社交平台链接(用于关于页展示) */
socialLinks?: Array<{ platform: string; label: string; url: string; group?: "domestic" | "overseas" }>;
/** 团队成员(可选) */
teamMembers?: Array<{ name: string; role: string; avatar?: string; link?: string }>;
}
export const THEME_CONFIGS: Record<ThemeId, ThemeConfig> = {
@@ -84,7 +92,22 @@ export const THEME_CONFIGS: Record<ThemeId, ThemeConfig> = {
services: {
join: { amount: 80, type: "meetup" },
pocketbaseJoinCollection: "solan",
shopUrl: process.env.NEXT_PUBLIC_SHOP_URL || "https://store.hackrobot.cn/", // 独立站域名
},
linktreeUrl: process.env.NEXT_PUBLIC_LINKTREE_URL || "https://linktr.ee/digitalnomad",
socialLinks: [
{ platform: "douyin", label: "抖音", url: "https://www.douyin.com", group: "domestic" },
{ platform: "wechat-official", label: "微信公众号", url: "https://mp.weixin.qq.com", group: "domestic" },
{ platform: "xiaohongshu", label: "小红书", url: "https://www.xiaohongshu.com", group: "domestic" },
{ platform: "tiktok", label: "TikTok", url: "https://www.tiktok.com", group: "overseas" },
{ platform: "youtube", label: "YouTube 频道", url: "https://www.youtube.com", group: "overseas" },
{ platform: "twitter", label: "X", url: "https://x.com", group: "overseas" },
],
teamMembers: [
{ name: "成员 A", role: "发起人", link: "#" },
{ name: "成员 B", role: "内容运营", link: "#" },
{ name: "成员 C", role: "技术开发", link: "#" },
],
},
"solo-company": {
id: "solo-company",

View File

@@ -10,7 +10,8 @@
"nav": {
"roadmap": "Learning Path",
"tools": "Tools",
"community": "Community"
"community": "Community",
"shop": "Shop"
},
"hero": {
"badge": "Open source · 200+ curated resources",
@@ -145,6 +146,24 @@
"madeBy": "Made with 🌍 by Digital Nomad Community | DN Guide",
"openSource": "Open source · Community driven"
},
"shop": {
"badge": "🛒 Curated Shop",
"title": "Digital Nomad Store",
"desc": "Curated products and services for digital nomads—from gear to courses, all in one place.",
"cta": "Visit Store →",
"ctaHint": "Opens in new tab"
},
"about": {
"title": "About Us",
"subtitle": "Meet the team behind Digital Nomad Guide",
"linktreeTitle": "Social Links",
"linktreeDesc": "Follow us for updates and resources",
"linktreeCta": "View all links →",
"groupDomestic": "Domestic",
"groupOverseas": "Overseas",
"teamTitle": "Team Members",
"teamDesc": "We are digital nomads who love remote work"
},
"auth": {
"title": "Login / Sign up",
"email": "Email",

View File

@@ -10,7 +10,8 @@
"nav": {
"roadmap": "学习路径",
"tools": "工具推荐",
"community": "社区"
"community": "社区",
"shop": "商店"
},
"hero": {
"badge": "开源免费 · 收录 200+ 篇实用资源",
@@ -145,6 +146,24 @@
"madeBy": "Made with 🌍 by 数字游民社区 | Digital Nomad Guide",
"openSource": "开源共享 · 社区驱动"
},
"shop": {
"badge": "🛒 精选好物",
"title": "数字游民好物商城",
"desc": "精选适合数字游民的产品与服务,从装备到课程,一站式满足你的需求。",
"cta": "前往商城 →",
"ctaHint": "新窗口打开独立站"
},
"about": {
"title": "关于我们",
"subtitle": "认识数字游民指南背后的团队",
"linktreeTitle": "社交平台",
"linktreeDesc": "关注我们,获取最新动态与资源",
"linktreeCta": "查看全部链接 →",
"groupDomestic": "国内平台",
"groupOverseas": "海外平台",
"teamTitle": "团队成员",
"teamDesc": "我们是一群热爱自由工作的数字游民"
},
"auth": {
"title": "登录 / 注册",
"email": "邮箱",