From 7bd993139ece94c29fefc36d9b6ae4ae981c5f1b Mon Sep 17 00:00:00 2001 From: ComputelessComputer <63365510+ComputelessComputer@users.noreply.github.com> Date: Mon, 29 Jun 2026 18:25:28 +0900 Subject: [PATCH] Expose legal pages in footer Add root-level privacy and terms routes, reuse the existing legal document renderer, and update public legal links. --- apps/web/content/legal/terms.mdx | 2 +- apps/web/netlify.toml | 38 +++++++++-- apps/web/public/robots.txt | 3 +- .../legal-document.tsx} | 48 ++++++-------- apps/web/src/components/site-footer.tsx | 12 +--- apps/web/src/routeTree.gen.ts | 63 ++++++++++++------- apps/web/src/routes/auth.tsx | 4 +- apps/web/src/routes/privacy.tsx | 26 ++++++++ apps/web/src/routes/terms.tsx | 26 ++++++++ 9 files changed, 153 insertions(+), 69 deletions(-) rename apps/web/src/{routes/legal/$slug.tsx => components/legal-document.tsx} (50%) create mode 100644 apps/web/src/routes/privacy.tsx create mode 100644 apps/web/src/routes/terms.tsx diff --git a/apps/web/content/legal/terms.mdx b/apps/web/content/legal/terms.mdx index a95b2466b1..cf10bfcdee 100644 --- a/apps/web/content/legal/terms.mdx +++ b/apps/web/content/legal/terms.mdx @@ -74,7 +74,7 @@ You may opt out of marketing communications at any time by clicking the "unsubsc ## 9. Privacy and Third-Party Services -Your use of the Service is also governed by our [Privacy Policy](/legal/privacy). Please review it to understand our practices. +Your use of the Service is also governed by our [Privacy Policy](/privacy). Please review it to understand our practices. The Service may integrate with third-party services to provide certain features, including cloud-based transcription, AI-powered summarization, payment processing, and analytics. When you enable cloud-based features, your data may be processed by our sub-processors. You acknowledge and agree that your use of these features is subject to the terms and privacy practices of such third-party services. diff --git a/apps/web/netlify.toml b/apps/web/netlify.toml index 5444ea5d73..fd7a9fcba7 100644 --- a/apps/web/netlify.toml +++ b/apps/web/netlify.toml @@ -66,7 +66,7 @@ force = true [[redirects]] from = "https://hyprnote.com/legal/*" -to = "https://char.com/legal/:splat" +to = "https://anarlog.so/:splat" status = 301 force = true @@ -203,7 +203,7 @@ status = 301 force = true # Domain migration: char.com -> anarlog.so (301 for SEO) -# Char is shut down except for /blog/* and /legal/* which redirect to anarlog.so +# Char is shut down except for /blog/* and root legal pages which redirect to anarlog.so [[redirects]] from = "https://char.com/blog/*" @@ -217,14 +217,44 @@ to = "https://anarlog.so/blog/:splat" status = 301 force = true +[[redirects]] +from = "/legal/*" +to = "/:splat" +status = 301 +force = true + [[redirects]] from = "https://char.com/legal/*" -to = "https://anarlog.so/legal/:splat" +to = "https://anarlog.so/:splat" status = 301 force = true [[redirects]] from = "https://www.char.com/legal/*" -to = "https://anarlog.so/legal/:splat" +to = "https://anarlog.so/:splat" +status = 301 +force = true + +[[redirects]] +from = "https://char.com/privacy" +to = "https://anarlog.so/privacy" +status = 301 +force = true + +[[redirects]] +from = "https://www.char.com/privacy" +to = "https://anarlog.so/privacy" +status = 301 +force = true + +[[redirects]] +from = "https://char.com/terms" +to = "https://anarlog.so/terms" +status = 301 +force = true + +[[redirects]] +from = "https://www.char.com/terms" +to = "https://anarlog.so/terms" status = 301 force = true diff --git a/apps/web/public/robots.txt b/apps/web/public/robots.txt index 19f0183fea..f1bfed9483 100644 --- a/apps/web/public/robots.txt +++ b/apps/web/public/robots.txt @@ -24,7 +24,8 @@ Allow: /docs Allow: /changelog Allow: /pricing Allow: /enterprise -Allow: /legal/ +Allow: /privacy +Allow: /terms Allow: /download Allow: /faq Allow: /about diff --git a/apps/web/src/routes/legal/$slug.tsx b/apps/web/src/components/legal-document.tsx similarity index 50% rename from apps/web/src/routes/legal/$slug.tsx rename to apps/web/src/components/legal-document.tsx index b2d81c08fa..41fe2581e1 100644 --- a/apps/web/src/routes/legal/$slug.tsx +++ b/apps/web/src/components/legal-document.tsx @@ -1,39 +1,27 @@ import { MDXContent } from "@content-collections/mdx/react"; -import { createFileRoute, Link, notFound } from "@tanstack/react-router"; -import { allLegals } from "content-collections"; +import { Link } from "@tanstack/react-router"; +import type { Legal } from "content-collections"; -import { mdxComponents } from "@/components/mdx-components"; import { ANARLOG_SITE_URL } from "@/lib/seo"; -export const Route = createFileRoute("/legal/$slug")({ - component: Component, - loader: async ({ params }) => { - const doc = allLegals.find((d) => d.slug === params.slug); - if (!doc) { - throw notFound(); - } - return { doc }; - }, - head: ({ loaderData }) => { - const doc = loaderData?.doc; - if (!doc) return {}; - const url = `${ANARLOG_SITE_URL}/legal/${doc.slug}`; - return { - links: [{ rel: "canonical", href: url }], - meta: [ - { title: `${doc.title} — Anarlog` }, - { name: "description", content: doc.summary || doc.title }, - { property: "og:title", content: doc.title }, - { property: "og:description", content: doc.summary || doc.title }, - { property: "og:url", content: url }, - ], - }; - }, -}); +import { mdxComponents } from "./mdx-components"; -function Component() { - const { doc } = Route.useLoaderData(); +export function legalHead(doc: Legal, path: "/privacy" | "/terms") { + const url = `${ANARLOG_SITE_URL}${path}`; + return { + links: [{ rel: "canonical", href: url }], + meta: [ + { title: `${doc.title} — Anarlog` }, + { name: "description", content: doc.summary || doc.title }, + { property: "og:title", content: doc.title }, + { property: "og:description", content: doc.summary || doc.title }, + { property: "og:url", content: url }, + ], + }; +} + +export function LegalDocument({ doc }: { doc: Legal }) { return (
Changelog - + Privacy - + Terms diff --git a/apps/web/src/routeTree.gen.ts b/apps/web/src/routeTree.gen.ts index 2185fa0520..63bce15fab 100644 --- a/apps/web/src/routeTree.gen.ts +++ b/apps/web/src/routeTree.gen.ts @@ -10,14 +10,15 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as UpdatePasswordRouteImport } from './routes/update-password' +import { Route as TermsRouteImport } from './routes/terms' import { Route as ResetPasswordRouteImport } from './routes/reset-password' +import { Route as PrivacyRouteImport } from './routes/privacy' import { Route as FoundersRouteImport } from './routes/founders' import { Route as AuthRouteImport } from './routes/auth' import { Route as ViewRouteRouteImport } from './routes/_view/route' import { Route as IndexRouteImport } from './routes/index' import { Route as ChangelogIndexRouteImport } from './routes/changelog/index' import { Route as BlogIndexRouteImport } from './routes/blog/index' -import { Route as LegalSlugRouteImport } from './routes/legal/$slug' import { Route as ChangelogVersionRouteImport } from './routes/changelog/$version' import { Route as BlogSlugRouteImport } from './routes/blog/$slug' import { Route as ApiTemplatesRouteImport } from './routes/api/templates' @@ -80,11 +81,21 @@ const UpdatePasswordRoute = UpdatePasswordRouteImport.update({ path: '/update-password', getParentRoute: () => rootRouteImport, } as any) +const TermsRoute = TermsRouteImport.update({ + id: '/terms', + path: '/terms', + getParentRoute: () => rootRouteImport, +} as any) const ResetPasswordRoute = ResetPasswordRouteImport.update({ id: '/reset-password', path: '/reset-password', getParentRoute: () => rootRouteImport, } as any) +const PrivacyRoute = PrivacyRouteImport.update({ + id: '/privacy', + path: '/privacy', + getParentRoute: () => rootRouteImport, +} as any) const FoundersRoute = FoundersRouteImport.update({ id: '/founders', path: '/founders', @@ -114,11 +125,6 @@ const BlogIndexRoute = BlogIndexRouteImport.update({ path: '/blog/', getParentRoute: () => rootRouteImport, } as any) -const LegalSlugRoute = LegalSlugRouteImport.update({ - id: '/legal/$slug', - path: '/legal/$slug', - getParentRoute: () => rootRouteImport, -} as any) const ChangelogVersionRoute = ChangelogVersionRouteImport.update({ id: '/changelog/$version', path: '/changelog/$version', @@ -412,7 +418,9 @@ export interface FileRoutesByFullPath { '/': typeof IndexRoute '/auth': typeof AuthRoute '/founders': typeof FoundersRoute + '/privacy': typeof PrivacyRoute '/reset-password': typeof ResetPasswordRoute + '/terms': typeof TermsRoute '/update-password': typeof UpdatePasswordRoute '/app': typeof ViewAppRouteRouteWithChildren '/pricing': typeof ViewPricingRoute @@ -421,7 +429,6 @@ export interface FileRoutesByFullPath { '/api/templates': typeof ApiTemplatesRoute '/blog/$slug': typeof BlogSlugRoute '/changelog/$version': typeof ChangelogVersionRoute - '/legal/$slug': typeof LegalSlugRoute '/blog/': typeof BlogIndexRoute '/changelog/': typeof ChangelogIndexRoute '/app/account': typeof ViewAppAccountRoute @@ -478,7 +485,9 @@ export interface FileRoutesByTo { '/': typeof IndexRoute '/auth': typeof AuthRoute '/founders': typeof FoundersRoute + '/privacy': typeof PrivacyRoute '/reset-password': typeof ResetPasswordRoute + '/terms': typeof TermsRoute '/update-password': typeof UpdatePasswordRoute '/pricing': typeof ViewPricingRoute '/api/media-upload': typeof ApiMediaUploadRoute @@ -486,7 +495,6 @@ export interface FileRoutesByTo { '/api/templates': typeof ApiTemplatesRoute '/blog/$slug': typeof BlogSlugRoute '/changelog/$version': typeof ChangelogVersionRoute - '/legal/$slug': typeof LegalSlugRoute '/blog': typeof BlogIndexRoute '/changelog': typeof ChangelogIndexRoute '/app/account': typeof ViewAppAccountRoute @@ -545,7 +553,9 @@ export interface FileRoutesById { '/_view': typeof ViewRouteRouteWithChildren '/auth': typeof AuthRoute '/founders': typeof FoundersRoute + '/privacy': typeof PrivacyRoute '/reset-password': typeof ResetPasswordRoute + '/terms': typeof TermsRoute '/update-password': typeof UpdatePasswordRoute '/_view/app': typeof ViewAppRouteRouteWithChildren '/_view/pricing': typeof ViewPricingRoute @@ -554,7 +564,6 @@ export interface FileRoutesById { '/api/templates': typeof ApiTemplatesRoute '/blog/$slug': typeof BlogSlugRoute '/changelog/$version': typeof ChangelogVersionRoute - '/legal/$slug': typeof LegalSlugRoute '/blog/': typeof BlogIndexRoute '/changelog/': typeof ChangelogIndexRoute '/_view/app/account': typeof ViewAppAccountRoute @@ -613,7 +622,9 @@ export interface FileRouteTypes { | '/' | '/auth' | '/founders' + | '/privacy' | '/reset-password' + | '/terms' | '/update-password' | '/app' | '/pricing' @@ -622,7 +633,6 @@ export interface FileRouteTypes { | '/api/templates' | '/blog/$slug' | '/changelog/$version' - | '/legal/$slug' | '/blog/' | '/changelog/' | '/app/account' @@ -679,7 +689,9 @@ export interface FileRouteTypes { | '/' | '/auth' | '/founders' + | '/privacy' | '/reset-password' + | '/terms' | '/update-password' | '/pricing' | '/api/media-upload' @@ -687,7 +699,6 @@ export interface FileRouteTypes { | '/api/templates' | '/blog/$slug' | '/changelog/$version' - | '/legal/$slug' | '/blog' | '/changelog' | '/app/account' @@ -745,7 +756,9 @@ export interface FileRouteTypes { | '/_view' | '/auth' | '/founders' + | '/privacy' | '/reset-password' + | '/terms' | '/update-password' | '/_view/app' | '/_view/pricing' @@ -754,7 +767,6 @@ export interface FileRouteTypes { | '/api/templates' | '/blog/$slug' | '/changelog/$version' - | '/legal/$slug' | '/blog/' | '/changelog/' | '/_view/app/account' @@ -813,14 +825,15 @@ export interface RootRouteChildren { ViewRouteRoute: typeof ViewRouteRouteWithChildren AuthRoute: typeof AuthRoute FoundersRoute: typeof FoundersRoute + PrivacyRoute: typeof PrivacyRoute ResetPasswordRoute: typeof ResetPasswordRoute + TermsRoute: typeof TermsRoute UpdatePasswordRoute: typeof UpdatePasswordRoute ApiMediaUploadRoute: typeof ApiMediaUploadRoute ApiShortcutsRoute: typeof ApiShortcutsRoute ApiTemplatesRoute: typeof ApiTemplatesRoute BlogSlugRoute: typeof BlogSlugRoute ChangelogVersionRoute: typeof ChangelogVersionRoute - LegalSlugRoute: typeof LegalSlugRoute BlogIndexRoute: typeof BlogIndexRoute ChangelogIndexRoute: typeof ChangelogIndexRoute ApiAssetsSplatRoute: typeof ApiAssetsSplatRoute @@ -867,6 +880,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof UpdatePasswordRouteImport parentRoute: typeof rootRouteImport } + '/terms': { + id: '/terms' + path: '/terms' + fullPath: '/terms' + preLoaderRoute: typeof TermsRouteImport + parentRoute: typeof rootRouteImport + } '/reset-password': { id: '/reset-password' path: '/reset-password' @@ -874,6 +894,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ResetPasswordRouteImport parentRoute: typeof rootRouteImport } + '/privacy': { + id: '/privacy' + path: '/privacy' + fullPath: '/privacy' + preLoaderRoute: typeof PrivacyRouteImport + parentRoute: typeof rootRouteImport + } '/founders': { id: '/founders' path: '/founders' @@ -916,13 +943,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof BlogIndexRouteImport parentRoute: typeof rootRouteImport } - '/legal/$slug': { - id: '/legal/$slug' - path: '/legal/$slug' - fullPath: '/legal/$slug' - preLoaderRoute: typeof LegalSlugRouteImport - parentRoute: typeof rootRouteImport - } '/changelog/$version': { id: '/changelog/$version' path: '/changelog/$version' @@ -1379,14 +1399,15 @@ const rootRouteChildren: RootRouteChildren = { ViewRouteRoute: ViewRouteRouteWithChildren, AuthRoute: AuthRoute, FoundersRoute: FoundersRoute, + PrivacyRoute: PrivacyRoute, ResetPasswordRoute: ResetPasswordRoute, + TermsRoute: TermsRoute, UpdatePasswordRoute: UpdatePasswordRoute, ApiMediaUploadRoute: ApiMediaUploadRoute, ApiShortcutsRoute: ApiShortcutsRoute, ApiTemplatesRoute: ApiTemplatesRoute, BlogSlugRoute: BlogSlugRoute, ChangelogVersionRoute: ChangelogVersionRoute, - LegalSlugRoute: LegalSlugRoute, BlogIndexRoute: BlogIndexRoute, ChangelogIndexRoute: ChangelogIndexRoute, ApiAssetsSplatRoute: ApiAssetsSplatRoute, diff --git a/apps/web/src/routes/auth.tsx b/apps/web/src/routes/auth.tsx index 5311ba3da1..b629cdeec5 100644 --- a/apps/web/src/routes/auth.tsx +++ b/apps/web/src/routes/auth.tsx @@ -276,14 +276,14 @@ function LegalText() {

By signing up, you agree to our{" "} Terms of Service {" "} and{" "} Privacy Policy diff --git a/apps/web/src/routes/privacy.tsx b/apps/web/src/routes/privacy.tsx new file mode 100644 index 0000000000..ae0a2a2b7e --- /dev/null +++ b/apps/web/src/routes/privacy.tsx @@ -0,0 +1,26 @@ +import { createFileRoute, notFound } from "@tanstack/react-router"; +import { allLegals } from "content-collections"; + +import { LegalDocument, legalHead } from "@/components/legal-document"; + +export const Route = createFileRoute("/privacy")({ + component: Component, + loader: async () => { + const doc = allLegals.find((legal) => legal.slug === "privacy"); + if (!doc) { + throw notFound(); + } + return { doc }; + }, + head: ({ loaderData }) => { + const doc = loaderData?.doc; + if (!doc) return {}; + return legalHead(doc, "/privacy"); + }, +}); + +function Component() { + const { doc } = Route.useLoaderData(); + + return ; +} diff --git a/apps/web/src/routes/terms.tsx b/apps/web/src/routes/terms.tsx new file mode 100644 index 0000000000..5a472e077e --- /dev/null +++ b/apps/web/src/routes/terms.tsx @@ -0,0 +1,26 @@ +import { createFileRoute, notFound } from "@tanstack/react-router"; +import { allLegals } from "content-collections"; + +import { LegalDocument, legalHead } from "@/components/legal-document"; + +export const Route = createFileRoute("/terms")({ + component: Component, + loader: async () => { + const doc = allLegals.find((legal) => legal.slug === "terms"); + if (!doc) { + throw notFound(); + } + return { doc }; + }, + head: ({ loaderData }) => { + const doc = loaderData?.doc; + if (!doc) return {}; + return legalHead(doc, "/terms"); + }, +}); + +function Component() { + const { doc } = Route.useLoaderData(); + + return ; +}