feat: add legal pages for Terms of Service and Privacy Policy with localization support

This commit is contained in:
mlogclub
2026-05-30 20:38:14 +08:00
parent b44bb666c9
commit c2b6d595aa
7 changed files with 298 additions and 2 deletions
+97
View File
@@ -0,0 +1,97 @@
"use client"
import Image from "next/image"
import Link from "next/link"
import { LocaleSwitcher } from "@/components/locale-switcher"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { useAppLocale, useI18n } from "@/i18n/provider"
import enUSMessages from "@/messages/en-US.json"
import zhCNMessages from "@/messages/zh-CN.json"
type LegalPageType = "terms" | "privacy"
type LegalSection = {
title: string
body: string
}
type LegalDocument = {
title: string
description: string
updatedAt: string
relatedLabel: string
relatedLink: string
sections: LegalSection[]
}
const messages = {
"zh-CN": zhCNMessages,
"en-US": enUSMessages,
}
export function LegalDocumentPage({ type }: { type: LegalPageType }) {
const t = useI18n()
const { locale } = useAppLocale()
const document = messages[locale].legal[type] as LegalDocument
const relatedHref = type === "terms" ? "/legal/privacy" : "/legal/terms"
return (
<main className="min-h-svh bg-muted px-6 py-8 md:px-10">
<div className="mx-auto flex w-full max-w-4xl flex-col gap-6">
<header className="flex items-center justify-between gap-4">
<div className="flex items-center gap-2 font-medium">
<Image
src="/images/logo.svg"
alt={t("app.brand")}
width={32}
height={32}
className="size-8 object-contain"
priority
/>
<span>{t("app.brand")}</span>
</div>
<LocaleSwitcher />
</header>
<Card className="bg-card/95">
<CardHeader className="gap-3 border-b px-6 py-6 md:px-8">
<div className="flex flex-col gap-2">
<CardTitle className="text-3xl font-semibold tracking-tight">
{document.title}
</CardTitle>
<p className="text-sm leading-6 text-muted-foreground">
{document.description}
</p>
</div>
<p className="text-xs text-muted-foreground">{document.updatedAt}</p>
</CardHeader>
<CardContent className="space-y-8 px-6 py-7 md:px-8">
{document.sections.map((section) => (
<section key={section.title} className="space-y-2">
<h2 className="text-lg font-semibold tracking-tight">
{section.title}
</h2>
<p className="text-sm leading-7 text-muted-foreground">
{section.body}
</p>
</section>
))}
</CardContent>
</Card>
<footer className="flex justify-end text-sm text-muted-foreground">
<p>
{document.relatedLabel}{" "}
<Link
href={relatedHref}
className="font-medium text-foreground underline-offset-4 hover:underline"
>
{document.relatedLink}
</Link>
</p>
</footer>
</div>
</main>
)
}
+10 -2
View File
@@ -1,6 +1,7 @@
"use client"
import Image from "next/image"
import Link from "next/link"
import { useRouter, useSearchParams } from "next/navigation"
import { startTransition, useEffect, useState } from "react"
import { toast } from "sonner"
@@ -229,8 +230,15 @@ export function LoginForm({
</CardContent>
</Card>
<FieldDescription className="px-6 text-center">
{t("auth.termsPrefix")} <a href="#">{t("auth.termsOfService")}</a>{" "}
{t("auth.termsJoiner")} <a href="#">{t("auth.privacyPolicy")}</a>.
{t("auth.termsPrefix")}{" "}
<Link href="/legal/terms" target="_blank" rel="noreferrer">
{t("auth.termsOfService")}
</Link>{" "}
{t("auth.termsJoiner")}{" "}
<Link href="/legal/privacy" target="_blank" rel="noreferrer">
{t("auth.privacyPolicy")}
</Link>
.
</FieldDescription>
</div>
)