Move support into React Router

This commit is contained in:
rosetta
2026-07-30 16:47:54 -07:00
parent c81c3a1189
commit 650d73830f
11 changed files with 378 additions and 838 deletions

View File

@@ -13,7 +13,7 @@ export function Footer() {
© {currentYear} {LEGAL_NAME}. All rights reserved.
</Text>
<nav className={classes.links} aria-label="Legal information">
<Anchor href="/support/" size="sm" c="dimmed">
<Anchor component={Link} to="/support" size="sm" c="dimmed">
Support
</Anchor>
<Anchor component={Link} to="/privacy" size="sm" c="dimmed">

View File

@@ -13,12 +13,13 @@ import { Link } from 'react-router-dom';
import { RosettaLogo } from '../RosettaLogo/RosettaLogo';
import classes from './LegalPage.module.css';
type LegalRoute = 'privacy' | 'terms';
type ContentRoute = 'support' | 'privacy' | 'terms';
interface LegalPageProps {
active: LegalRoute;
active: ContentRoute;
title: string;
updated: string;
updated?: string;
eyebrow?: string;
children: ReactNode;
}
@@ -27,7 +28,19 @@ interface LegalSectionProps {
children: ReactNode;
}
export function LegalPage({ active, title, updated, children }: LegalPageProps) {
const NAV_LINKS = [
{ route: 'support', label: 'Support' },
{ route: 'privacy', label: 'Privacy' },
{ route: 'terms', label: 'Terms' },
] as const;
export function LegalPage({
active,
title,
updated,
eyebrow = 'Rosetta Legal',
children,
}: LegalPageProps) {
const currentYear = new Date().getFullYear();
return (
@@ -38,7 +51,7 @@ export function LegalPage({ active, title, updated, children }: LegalPageProps)
component={Link}
to="/"
className={classes.brand}
aria-label="Rosetta home"
aria-label="Rosetta"
>
<RosettaLogo size={34} />
<Text component="span" fw={700} fz="lg">
@@ -46,44 +59,36 @@ export function LegalPage({ active, title, updated, children }: LegalPageProps)
</Text>
</Anchor>
<Group component="nav" gap="lg" aria-label="Legal pages">
<Anchor href="/support/" c="dimmed" size="sm">
Support
</Anchor>
<Anchor
component={Link}
to="/privacy"
c={active === 'privacy' ? 'blue' : 'dimmed'}
fw={active === 'privacy' ? 600 : 400}
size="sm"
aria-current={active === 'privacy' ? 'page' : undefined}
>
Privacy
</Anchor>
<Anchor
component={Link}
to="/terms"
c={active === 'terms' ? 'blue' : 'dimmed'}
fw={active === 'terms' ? 600 : 400}
size="sm"
aria-current={active === 'terms' ? 'page' : undefined}
>
Terms
</Anchor>
<Group component="nav" gap="lg" aria-label="Rosetta pages">
{NAV_LINKS.map(({ route, label }) => (
<Anchor
key={route}
component={Link}
to={`/${route}`}
c={active === route ? 'blue' : 'dimmed'}
fw={active === route ? 600 : 400}
size="sm"
aria-current={active === route ? 'page' : undefined}
>
{label}
</Anchor>
))}
</Group>
</Group>
<Box component="main" className={classes.main}>
<Box className={classes.intro}>
<Text tt="uppercase" fw={700} fz="xs" c="blue" lts="0.08em">
Rosetta legal
{eyebrow}
</Text>
<Title order={1} className={classes.title}>
{title}
</Title>
<Text c="dimmed" mt="md">
Last updated: {updated}
</Text>
{updated && (
<Text c="dimmed" mt="md">
Last updated: {updated}
</Text>
)}
</Box>
<Paper
@@ -103,18 +108,16 @@ export function LegalPage({ active, title, updated, children }: LegalPageProps)
</Text>
<Group component="nav" gap="md" aria-label="Footer links">
<Anchor component={Link} to="/" c="dimmed" size="xs">
Rosetta home
Rosetta
</Anchor>
<Anchor href="/support/" c="dimmed" size="xs">
<Anchor component={Link} to="/support" c="dimmed" size="xs">
Support
</Anchor>
<Anchor
component={Link}
to={active === 'privacy' ? '/terms' : '/privacy'}
c="dimmed"
size="xs"
>
{active === 'privacy' ? 'Terms of Use' : 'Privacy Policy'}
<Anchor component={Link} to="/privacy" c="dimmed" size="xs">
Privacy Policy
</Anchor>
<Anchor component={Link} to="/terms" c="dimmed" size="xs">
Terms of Use
</Anchor>
</Group>
</Group>

View File

@@ -3,7 +3,6 @@ import { useEffect } from 'react';
interface SEOProps {
title?: string;
description?: string;
ogImage?: string;
keywords?: string;
canonical?: string;
}
@@ -11,7 +10,6 @@ interface SEOProps {
export function SEO({
title = 'Rosetta - Secure Messaging for Everyone',
description = 'End-to-end encrypted messaging app with military-grade security. Download for Windows, Linux, and Android.',
ogImage = '/og.png',
keywords = 'secure messaging, encrypted chat, private messenger',
canonical = 'https://rosetta.im/'
}: SEOProps) {
@@ -22,14 +20,14 @@ export function SEO({
updateMetaTag('name', 'keywords', keywords);
updateMetaTag('property', 'og:title', title);
updateMetaTag('property', 'og:description', description);
updateMetaTag('property', 'og:image', ogImage);
updateMetaTag('property', 'og:url', canonical);
updateMetaTag('name', 'twitter:title', title);
updateMetaTag('name', 'twitter:description', description);
updateMetaTag('name', 'twitter:image', ogImage);
updateMetaTag('name', 'twitter:url', canonical);
removeMetaTag('property', 'og:image');
removeMetaTag('name', 'twitter:image');
updateCanonical(canonical);
}, [title, description, ogImage, keywords, canonical]);
}, [title, description, keywords, canonical]);
return null;
}
@@ -46,6 +44,10 @@ function updateMetaTag(attr: string, name: string, content: string) {
element.setAttribute('content', content);
}
function removeMetaTag(attr: string, name: string) {
document.querySelector(`meta[${attr}="${name}"]`)?.remove();
}
function updateCanonical(href: string) {
let element = document.querySelector<HTMLLinkElement>('link[rel="canonical"]');