's'
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)}
|
||||
|
||||
47
app/components/ShopCard.tsx
Normal file
47
app/components/ShopCard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
48
app/components/SocialPlatformIcon.tsx
Normal file
48
app/components/SocialPlatformIcon.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user