feat: 添加顶部公告栏跳转链接和关闭按钮功能

- 新增 AnnouncementBar 客户端组件支持点击跳转
- 添加关闭按钮,状态保存至 localStorage
- 刷新页面后记住用户关闭状态
- 支持 i18n 配置公告内容和链接

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-29 19:57:47 +08:00
co-authored by Claude
parent a8a847beb2
commit 3ab2d2057c
4 changed files with 165 additions and 34 deletions
+51 -26
View File
@@ -1,7 +1,9 @@
import { notFound } from "next/navigation"
import { setRequestLocale } from 'next-intl/server'
import { setRequestLocale, getTranslations } from 'next-intl/server'
import Link from "next/link"
import { LocaleSwitcher } from "@/components/locale/LocaleSwitcher"
import { AnnouncementBar } from "@/components/layout/AnnouncementBar"
import type { Metadata } from "next"
const locales = ['zh', 'en']
@@ -9,6 +11,20 @@ export async function generateStaticParams() {
return locales.map((locale) => ({ locale }))
}
export async function generateMetadata({
params
}: {
params: Promise<{ locale: string }>
}): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations('home')
return {
title: t('metaTitle'),
description: t('metaDescription'),
}
}
export default async function LocaleLayout({
children,
params
@@ -23,13 +39,15 @@ export default async function LocaleLayout({
setRequestLocale(locale)
// Get translations
const t = await getTranslations('layout')
const tNav = await getTranslations('navigation')
const tHome = await getTranslations('home')
return (
<div className="min-h-screen flex flex-col">
{/* Top announcement bar */}
<div className="bg-primary w-full py-2 px-4 border-b-2 border-black dark:border-gray-600 flex justify-center items-center text-xs font-bold font-display tracking-wide text-black">
<span>DISCOVER THE FUTURE OF AI AGENTS</span>
<span className="material-icons text-sm align-middle ml-1">arrow_forward</span>
</div>
<AnnouncementBar text={t('announcement')} href={t('announcementHref')} locale={locale} />
{/* Navigation */}
<header className="w-full border-b-2 border-black dark:border-gray-600 bg-background-light dark:bg-background-dark sticky top-0 z-50">
@@ -49,36 +67,39 @@ export default async function LocaleLayout({
className="font-display text-sm font-bold hover:text-secondary dark:hover:text-primary transition-colors"
href={`/${locale}`}
>
HOME
{tNav('home')}
</Link>
<Link
className="font-display text-sm font-bold hover:text-secondary dark:hover:text-primary transition-colors"
href={`/${locale}/projects`}
>
PROJECTS
{tNav('projects')}
</Link>
<Link
className="font-display text-sm font-bold hover:text-secondary dark:hover:text-primary transition-colors"
href="#"
>
BLOG
{tNav('blog')}
</Link>
<Link
className="font-display text-sm font-bold hover:text-secondary dark:hover:text-primary transition-colors"
href="#"
>
ABOUT
{tNav('about')}
</Link>
</nav>
{/* Right side */}
<div className="hidden md:flex items-center space-x-4">
<LocaleSwitcher currentLocale={locale} />
<LocaleSwitcher
currentLocale={locale}
switchUrl={`/${locale === 'zh' ? 'en' : 'zh'}`}
/>
<Link
className="bg-white dark:bg-surface-dark border-2 border-black dark:border-white px-4 py-2 font-display text-sm font-bold shadow-neo-sm hover:shadow-none hover:translate-x-[2px] hover:translate-y-[2px] transition-all"
href="#"
>
SUBMIT PROJECT
{tNav('submitProject')}
</Link>
</div>
@@ -98,27 +119,31 @@ export default async function LocaleLayout({
<section className="border-t-2 border-black dark:border-gray-600">
<div className="grid grid-cols-1 md:grid-cols-2">
<div className="bg-primary dark:bg-yellow-600 p-12 md:p-20 border-b-2 md:border-b-0 md:border-r-2 border-black dark:border-gray-600 flex flex-col justify-center">
<h2 className="font-display text-4xl font-bold text-black mb-4">STAY UPDATED</h2>
<h2 className="font-display text-4xl font-bold text-black mb-4">{t('stayUpdated')}</h2>
<p className="font-sans text-black mb-8 max-w-md">
Get the latest AI tools and trends delivered straight to your inbox. No spam, just intelligence.
{t('stayUpdatedDesc')}
</p>
<form className="space-y-4 max-w-md">
<input
className="w-full bg-background-light border-2 border-black p-3 font-display text-sm placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-black"
placeholder="E-mail"
placeholder={t('email')}
type="email"
id="newsletter-email"
name="email"
autoComplete="email"
required
/>
<div className="flex items-center gap-2">
<input className="w-4 h-4 border-2 border-black text-black focus:ring-0" id="consent" type="checkbox" />
<input className="w-4 h-4 border-2 border-black text-black focus:ring-0" id="consent" name="consent" type="checkbox" required />
<label className="text-xs font-bold font-display text-black" htmlFor="consent">
Subscribe to Agent Park updates
{t('subscribeConsent')}
</label>
</div>
<button
className="bg-white text-black font-display font-bold py-3 px-8 border-2 border-black shadow-neo hover:shadow-none hover:translate-x-[2px] hover:translate-y-[2px] transition-all"
type="submit"
>
SUBSCRIBE
{t('subscribe')}
</button>
</form>
</div>
@@ -144,26 +169,26 @@ export default async function LocaleLayout({
<span className="font-display font-bold text-lg tracking-tight">Agent Park</span>
</Link>
<p className="text-sm text-gray-600 dark:text-gray-400">
Discover and explore high-quality AI projects from across the web. Curated for developers and enthusiasts.
{t('footerDesc')}
</p>
</div>
<div>
<h4 className="font-display font-bold text-lg mb-4">Resources</h4>
<h4 className="font-display font-bold text-lg mb-4">{t('resources')}</h4>
<ul className="space-y-2 text-sm font-sans text-gray-600 dark:text-gray-400">
<li><Link className="hover:text-black dark:hover:text-white" href="#">Blog</Link></li>
<li><Link className="hover:text-black dark:hover:text-white" href="#">{tNav('blog')}</Link></li>
<li><Link className="hover:text-black dark:hover:text-white" href="#">Newsletter</Link></li>
<li><Link className="hover:text-black dark:hover:text-white" href="#">Documentation</Link></li>
</ul>
</div>
<div>
<h4 className="font-display font-bold text-lg mb-4">Legal</h4>
<h4 className="font-display font-bold text-lg mb-4">{t('legal')}</h4>
<ul className="space-y-2 text-sm font-sans text-gray-600 dark:text-gray-400">
<li><Link className="hover:text-black dark:hover:text-white" href="#">Privacy Policy</Link></li>
<li><Link className="hover:text-black dark:hover:text-white" href="#">Terms of Service</Link></li>
<li><Link className="hover:text-black dark:hover:text-white" href="#">{t('privacyPolicy')}</Link></li>
<li><Link className="hover:text-black dark:hover:text-white" href="#">{t('termsOfService')}</Link></li>
</ul>
</div>
<div>
<h4 className="font-display font-bold text-lg mb-4">Follow Us</h4>
<h4 className="font-display font-bold text-lg mb-4">{t('followUs')}</h4>
<div className="flex space-x-4">
<Link className="w-10 h-10 border-2 border-black dark:border-gray-500 flex items-center justify-center hover:bg-primary hover:text-black transition-colors font-display font-bold" href="#">X</Link>
<Link className="w-10 h-10 border-2 border-black dark:border-gray-500 flex items-center justify-center hover:bg-primary hover:text-black transition-colors font-display font-bold" href="#">Li</Link>
@@ -172,9 +197,9 @@ export default async function LocaleLayout({
</div>
</div>
<div className="border-t border-black dark:border-gray-700 pt-8 flex flex-col md:flex-row justify-between items-center">
<p className="text-sm text-gray-500 font-display">© 2025 Agent Park. All rights reserved.</p>
<p className="text-sm text-gray-500 font-display">{t('copyright')}</p>
<div className="mt-4 md:mt-0 font-display text-sm font-bold">
DESIGNED FOR AI BUILDERS
{t('designedFor')}
</div>
</div>
</div>