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:
+51
-26
@@ -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>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
interface AnnouncementBarProps {
|
||||
text: string
|
||||
href: string
|
||||
locale: string
|
||||
}
|
||||
|
||||
export function AnnouncementBar({ text, href, locale }: AnnouncementBarProps) {
|
||||
const [isVisible, setIsVisible] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
// Check if user has closed the announcement before
|
||||
const hasClosed = localStorage.getItem('announcement-closed')
|
||||
if (hasClosed === 'true') {
|
||||
setIsVisible(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleClose = (e: React.MouseEvent) => {
|
||||
e.preventDefault() // Prevent link navigation
|
||||
e.stopPropagation()
|
||||
setIsVisible(false)
|
||||
localStorage.setItem('announcement-closed', 'true')
|
||||
}
|
||||
|
||||
if (!isVisible) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`/${locale}${href}`}
|
||||
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 hover:opacity-90 transition-opacity relative"
|
||||
>
|
||||
<span>{text}</span>
|
||||
<span className="material-icons text-sm align-middle ml-1">arrow_forward</span>
|
||||
|
||||
{/* Close button */}
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="absolute right-4 hover:bg-black/10 rounded-full p-1 transition-colors"
|
||||
aria-label="Close announcement"
|
||||
>
|
||||
<span className="material-icons text-sm">close</span>
|
||||
</button>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
+31
-4
@@ -3,15 +3,19 @@
|
||||
"search": "Search",
|
||||
"loading": "Loading...",
|
||||
"noResults": "No results found",
|
||||
"noProjects": "No projects yet",
|
||||
"viewMore": "View More",
|
||||
"backToHome": "Back to Home"
|
||||
"backToHome": "Back to Home",
|
||||
"viewDetails": "VIEW DETAILS"
|
||||
},
|
||||
"home": {
|
||||
"title": "AI Project Navigator",
|
||||
"subtitle": "Discover and explore AI projects",
|
||||
"featuredProjects": "Featured Projects",
|
||||
"browseByTag": "Browse by Tag",
|
||||
"searchPlaceholder": "Search AI projects..."
|
||||
"searchPlaceholder": "Search AI projects...",
|
||||
"metaTitle": "Agent Park - AI Project Navigator",
|
||||
"metaDescription": "Discover and explore quality AI projects from across the web"
|
||||
},
|
||||
"project": {
|
||||
"details": "Project Details",
|
||||
@@ -21,11 +25,34 @@
|
||||
"website": "Website",
|
||||
"github": "GitHub",
|
||||
"huggingface": "HuggingFace",
|
||||
"paper": "Paper"
|
||||
"paper": "Paper",
|
||||
"submitYourProject": "Submit Your Project",
|
||||
"submitProjectTitle": "Submit Your Project",
|
||||
"submitProjectDescription": "Have an AI tool worth sharing? Add it to Agent Park.",
|
||||
"submitNow": "SUBMIT NOW"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Home",
|
||||
"projects": "Projects",
|
||||
"about": "About"
|
||||
"blog": "Blog",
|
||||
"about": "About",
|
||||
"submitProject": "SUBMIT PROJECT"
|
||||
},
|
||||
"layout": {
|
||||
"announcement": "DISCOVER THE FUTURE OF AI AGENTS",
|
||||
"announcementHref": "/projects",
|
||||
"stayUpdated": "STAY UPDATED",
|
||||
"stayUpdatedDesc": "Get the latest AI tools and trends delivered straight to your inbox. No spam, just intelligence.",
|
||||
"email": "E-mail",
|
||||
"subscribe": "SUBSCRIBE",
|
||||
"subscribeConsent": "Subscribe to Agent Park updates",
|
||||
"footerDesc": "Discover and explore high-quality AI projects from across the web. Curated for developers and enthusiasts.",
|
||||
"resources": "Resources",
|
||||
"legal": "Legal",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"termsOfService": "Terms of Service",
|
||||
"followUs": "Follow Us",
|
||||
"copyright": "© 2025 Agent Park. All rights reserved.",
|
||||
"designedFor": "DESIGNED FOR AI BUILDERS"
|
||||
}
|
||||
}
|
||||
|
||||
+31
-4
@@ -3,15 +3,19 @@
|
||||
"search": "搜索",
|
||||
"loading": "加载中...",
|
||||
"noResults": "未找到结果",
|
||||
"noProjects": "暂无项目",
|
||||
"viewMore": "查看更多",
|
||||
"backToHome": "返回首页"
|
||||
"backToHome": "返回首页",
|
||||
"viewDetails": "查看详情"
|
||||
},
|
||||
"home": {
|
||||
"title": "AI 项目导航",
|
||||
"subtitle": "发现和探索全网优质 AI 项目",
|
||||
"featuredProjects": "精选项目",
|
||||
"browseByTag": "按标签浏览",
|
||||
"searchPlaceholder": "搜索 AI 项目..."
|
||||
"searchPlaceholder": "搜索 AI 项目...",
|
||||
"metaTitle": "Agent Park - AI 项目导航",
|
||||
"metaDescription": "发现和探索全网优质 AI 项目"
|
||||
},
|
||||
"project": {
|
||||
"details": "项目详情",
|
||||
@@ -21,11 +25,34 @@
|
||||
"website": "官网",
|
||||
"github": "GitHub",
|
||||
"huggingface": "HuggingFace",
|
||||
"paper": "论文"
|
||||
"paper": "论文",
|
||||
"submitYourProject": "提交你的项目",
|
||||
"submitProjectTitle": "提交你的项目",
|
||||
"submitProjectDescription": "有值得分享的 AI 工具吗?将其添加到 Agent Park。",
|
||||
"submitNow": "立即提交"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "首页",
|
||||
"projects": "项目列表",
|
||||
"about": "关于"
|
||||
"blog": "博客",
|
||||
"about": "关于",
|
||||
"submitProject": "提交项目"
|
||||
},
|
||||
"layout": {
|
||||
"announcement": "发现 AI 代理的未来",
|
||||
"announcementHref": "/projects",
|
||||
"stayUpdated": "保持更新",
|
||||
"stayUpdatedDesc": "获取最新的 AI 工具和趋势,直接发送到您的收件箱。没有垃圾邮件,只有智能。",
|
||||
"email": "电子邮箱",
|
||||
"subscribe": "订阅",
|
||||
"subscribeConsent": "订阅 Agent Park 更新",
|
||||
"footerDesc": "发现和探索来自全网的高质量 AI 项目。为开发者和爱好者精心策划。",
|
||||
"resources": "资源",
|
||||
"legal": "法律",
|
||||
"privacyPolicy": "隐私政策",
|
||||
"termsOfService": "服务条款",
|
||||
"followUs": "关注我们",
|
||||
"copyright": "© 2025 Agent Park. 保留所有权利。",
|
||||
"designedFor": "专为 AI 构建者设计"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user