Files
skillhub/apps/web/app/[locale]/privacy/page.tsx
airano 3f599a23fb sync: SEO canonical URLs, curation improvements, batch scripts, stats fix
- Dynamic canonical URLs and alternates for all pages (SEO fix)
- Shared seo.ts utility for canonical path generation
- Improved duplicate detection: standalone > aggregator priority, forks/created_at tie-breakers
- browseReadyFilter now includes unique aggregator skills
- Stats aligned with browseReadyFilter (was showing 16k instead of 62k+)
- Re-review mechanism: content_hash changes trigger needs-re-review
- review_status field added to skills schema
- Batch security scan and quality score scripts
- Indexer Dockerfile updated for curation deps
- Removed roadmap from README

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 23:40:03 +03:30

105 lines
3.5 KiB
TypeScript

import { getTranslations, setRequestLocale } from 'next-intl/server';
import Link from 'next/link';
import { Header } from '@/components/Header';
import { Footer } from '@/components/Footer';
import { Shield, Database, Cookie, Users, Lock, Mail, ArrowRight, ArrowLeft } from 'lucide-react';
import { getPageAlternates } from '@/lib/seo';
export const dynamic = 'force-dynamic';
const sectionIcons = {
dataCollected: Database,
cookies: Cookie,
thirdParty: Users,
retention: Lock,
rights: Shield,
contact: Mail,
};
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
return {
alternates: getPageAlternates(locale, '/privacy'),
};
}
export default async function PrivacyPage({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslations('privacy');
const isRTL = locale === 'fa';
const ArrowIcon = isRTL ? ArrowLeft : ArrowRight;
const sections = [
'dataCollected',
'cookies',
'thirdParty',
'retention',
'rights',
'contact',
] as const;
return (
<div className="min-h-screen flex flex-col">
<Header />
<main className="flex-1">
<section className="section-header bg-gradient-subtle">
<div className="container-main text-center">
<h1 className="hero-title mb-4">{t('title')}</h1>
<p className="hero-subtitle max-w-2xl mx-auto">{t('subtitle')}</p>
<p className="text-sm text-text-muted mt-4">{t('lastUpdated')}</p>
</div>
</section>
<section className="section bg-surface">
<div className="container-main max-w-4xl">
<div className="space-y-8">
{sections.map((sectionKey) => {
const Icon = sectionIcons[sectionKey];
const showClaimLink = sectionKey === 'rights';
return (
<div key={sectionKey} className="card p-6">
<div className="flex items-start gap-4">
<div className="flex-shrink-0 w-10 h-10 rounded-lg bg-primary-50 dark:bg-primary-900/20 flex items-center justify-center">
<Icon className="w-5 h-5 text-primary-600 dark:text-primary-400" />
</div>
<div className="flex-1">
<h2 className="text-lg font-semibold text-text-primary mb-2">
{t(`sections.${sectionKey}.title`)}
</h2>
<p className="text-text-secondary" dir={locale === 'fa' ? 'rtl' : 'ltr'}>
{t(`sections.${sectionKey}.description`)}
</p>
{showClaimLink && (
<Link
href={`/${locale}/claim`}
className="inline-flex items-center gap-1 mt-3 text-primary-600 hover:text-primary-700 text-sm font-medium transition-colors"
>
{locale === 'fa' ? 'صفحه مدیریت مهارت‌ها' : 'Manage Skills Page'}
<ArrowIcon className="w-3 h-3" />
</Link>
)}
</div>
</div>
</div>
);
})}
</div>
</div>
</section>
</main>
<Footer />
</div>
);
}