Feature/faq (#546)

* feat(web): add FAQ page with search, sidebar, and i18n integration; update navigation and routes; include user feedback analysis docs (faq.md)
* docs: add filled frontend PR template for FAQ feature (PR_FRONTEND_FAQ.md)
* docs(web): add Contributing & Tasks FAQ category near top with guidance on using GitHub Projects and PR contribution standards
* feat(web,api): dynamically embed GitHub Projects roadmap in FAQ via /api/roadmap and RoadmapWidget; add env vars for GitHub token/org/project
* chore(docker): pass GitHub roadmap env vars into backend container
* docs(web): update FAQ with fork-based PR workflow, yellow links to roadmap/task dashboard, and contribution incentives; remove dynamic roadmap embed\n\nchore(api,docker): remove /api/roadmap endpoint and related env wiring
* chore: revert unintended changes (.env.example, api/server.go, docker-compose.yml); remove local-only files (PR_FRONTEND_FAQ.md, web/faq.md) from PR
* feat: 添加对重置密码页面的路由支持
This commit is contained in:
Ember
2025-11-05 22:39:42 +08:00
committed by GitHub
parent 22731189bd
commit c633a782ae
9 changed files with 1628 additions and 40 deletions

View File

@@ -0,0 +1,459 @@
import { useEffect, useRef } from 'react'
import { t, type Language } from '../../i18n/translations'
import type { FAQCategory } from '../../data/faqData'
// RoadmapWidget 移除动态嵌入,按需仅展示外部链接
interface FAQContentProps {
categories: FAQCategory[]
language: Language
onActiveItemChange: (itemId: string) => void
}
export function FAQContent({
categories,
language,
onActiveItemChange,
}: FAQContentProps) {
const sectionRefs = useRef<Map<string, HTMLElement>>(new Map())
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const itemId = entry.target.getAttribute('data-item-id')
if (itemId) {
onActiveItemChange(itemId)
}
}
})
},
{
rootMargin: '-100px 0px -80% 0px',
threshold: 0,
}
)
sectionRefs.current.forEach((ref) => {
if (ref) observer.observe(ref)
})
return () => {
sectionRefs.current.forEach((ref) => {
if (ref) observer.unobserve(ref)
})
}
}, [onActiveItemChange])
const setRef = (itemId: string, element: HTMLElement | null) => {
if (element) {
sectionRefs.current.set(itemId, element)
} else {
sectionRefs.current.delete(itemId)
}
}
return (
<div className="space-y-12">
{categories.map((category) => (
<div key={category.id}>
{/* Category Header */}
<div
className="flex items-center gap-3 mb-6 pb-3"
style={{ borderBottom: '2px solid #2B3139' }}
>
<category.icon className="w-7 h-7" style={{ color: '#F0B90B' }} />
<h2 className="text-2xl font-bold" style={{ color: '#EAECEF' }}>
{t(category.titleKey, language)}
</h2>
</div>
{/* FAQ Items */}
<div className="space-y-8">
{category.items.map((item) => (
<section
key={item.id}
id={item.id}
data-item-id={item.id}
ref={(el) => setRef(item.id, el)}
className="scroll-mt-24"
>
{/* Question */}
<h3
className="text-xl font-semibold mb-3"
style={{ color: '#EAECEF' }}
>
{t(item.questionKey, language)}
</h3>
{/* Answer */}
<div
className="prose prose-invert max-w-none"
style={{
color: '#B7BDC6',
lineHeight: '1.7',
}}
>
{item.id === 'github-projects-tasks' ? (
<div className="space-y-3">
<div className="text-base">
{language === 'zh' ? '链接:' : 'Links:'}{' '}
<a
href="https://github.com/orgs/NoFxAiOS/projects/3"
target="_blank"
rel="noreferrer"
style={{ color: '#F0B90B' }}
>
{language === 'zh' ? '路线图' : 'Roadmap'}
</a>
{' | '}
<a
href="https://github.com/orgs/NoFxAiOS/projects/5"
target="_blank"
rel="noreferrer"
style={{ color: '#F0B90B' }}
>
{language === 'zh' ? '任务看板' : 'Task Dashboard'}
</a>
</div>
<ol className="list-decimal pl-5 space-y-1 text-base">
{language === 'zh' ? (
<>
<li>
good first issue / help
wanted / frontend / backend
</li>
<li>
Acceptance
Criteria
</li>
<li>assign me</li>
<li>Fork GitHub </li>
<li>
fork <code>dev</code>{' '}
<code className="ml-2">
git remote add upstream
https://github.com/NoFxAiOS/nofx.git
</code>
<br />
<code>git fetch upstream</code>
<br />
<code>git checkout dev</code>
<br />
<code>git rebase upstream/dev</code>
<br />
<code>git push origin dev</code>
</li>
<li>
fork <code>dev</code>
<code className="ml-2">
git checkout -b feat/your-topic
</code>
</li>
<li>
fork
<code className="ml-2">
git push origin feat/your-topic
</code>
</li>
<li>
PRbase <code>NoFxAiOS/nofx:dev</code>{' '}
compare {' '}
<code>/nofx:feat/your-topic</code>
</li>
<li>
PR Issue
<code className="ml-1">Closes #123</code>
PR {' '}
<code>upstream/dev</code>{' '}
rebase
</li>
</>
) : (
<>
<li>
Open the links above and filter by labels (good
first issue / help wanted / frontend / backend).
</li>
<li>
Open the task and read the Description &
Acceptance Criteria.
</li>
<li>
Comment "assign me" or self-assign (if permitted).
</li>
<li>Fork the repository to your GitHub account.</li>
<li>
Sync your fork's <code>dev</code> with upstream:
<code className="ml-2">
git remote add upstream
https://github.com/NoFxAiOS/nofx.git
</code>
<br />
<code>git fetch upstream</code>
<br />
<code>git checkout dev</code>
<br />
<code>git rebase upstream/dev</code>
<br />
<code>git push origin dev</code>
</li>
<li>
Create a feature branch from your fork's{' '}
<code>dev</code>:
<code className="ml-2">
git checkout -b feat/your-topic
</code>
</li>
<li>
Push to your fork:
<code className="ml-2">
git push origin feat/your-topic
</code>
</li>
<li>
Open a PR: base <code>NoFxAiOS/nofx:dev</code>
compare{' '}
<code>your-username/nofx:feat/your-topic</code>.
</li>
<li>
In PR, reference the Issue (e.g.,{' '}
<code className="ml-1">Closes #123</code>) and
choose the proper PR template; rebase onto{' '}
<code>upstream/dev</code> as needed.
</li>
</>
)}
</ol>
<div
className="rounded p-3 mt-3"
style={{
background: 'rgba(240, 185, 11, 0.08)',
border: '1px solid rgba(240, 185, 11, 0.25)',
}}
>
{language === 'zh' ? (
<div className="text-sm">
<strong style={{ color: '#F0B90B' }}></strong>{' '}
Bounty/
Review/
<a
href="https://github.com/NoFxAiOS/nofx/labels/bounty"
target="_blank"
rel="noreferrer"
style={{ color: '#F0B90B' }}
>
bounty
</a>
<a
href="https://github.com/NoFxAiOS/nofx/blob/dev/.github/ISSUE_TEMPLATE/bounty_claim.md"
target="_blank"
rel="noreferrer"
style={{ color: '#F0B90B' }}
>
Bounty Claim
</a>
</div>
) : (
<div className="text-sm">
<strong style={{ color: '#F0B90B' }}>Note:</strong>{' '}
Contribution incentives are available (e.g., cash
bounties, badges & shout-outs, priority
review/merge, beta access). Prefer tasks with
<a
href="https://github.com/NoFxAiOS/nofx/labels/bounty"
target="_blank"
rel="noreferrer"
style={{ color: '#F0B90B' }}
>
bounty label
</a>
, or file a
<a
href="https://github.com/NoFxAiOS/nofx/blob/dev/.github/ISSUE_TEMPLATE/bounty_claim.md"
target="_blank"
rel="noreferrer"
style={{ color: '#F0B90B' }}
>
Bounty Claim
</a>
after completion.
</div>
)}
</div>
</div>
) : item.id === 'contribute-pr-guidelines' ? (
<div className="space-y-3">
<div className="text-base">
{language === 'zh' ? '参考文档:' : 'References:'}{' '}
<a
href="https://github.com/NoFxAiOS/nofx/blob/dev/CONTRIBUTING.md"
target="_blank"
rel="noreferrer"
style={{ color: '#F0B90B' }}
>
CONTRIBUTING.md
</a>
{' | '}
<a
href="https://github.com/NoFxAiOS/nofx/blob/dev/.github/PR_TITLE_GUIDE.md"
target="_blank"
rel="noreferrer"
style={{ color: '#F0B90B' }}
>
PR_TITLE_GUIDE.md
</a>
</div>
<ol className="list-decimal pl-5 space-y-1 text-base">
{language === 'zh' ? (
<>
<li>
Fork fork <code>dev</code>{' '}
<code>main</code>{' '}
</li>
<li>
feat/fix/docs/
Conventional Commits
</li>
<li>
<code className="ml-2">
npm --prefix web run lint && npm --prefix web
run build
</code>
</li>
<li> UI </li>
<li>
PR
frontend/backend/docs/general
</li>
<li>
PR Issue
<code className="ml-1">Closes #123</code>PR
<code>NoFxAiOS/nofx:dev</code>
</li>
<li>
<code>upstream/dev</code>{' '}
rebase CI PR
</li>
</>
) : (
<>
<li>
After forking, branch from your fork's{' '}
<code>dev</code>; avoid direct commits to upstream{' '}
<code>main</code>.
</li>
<li>
Branch naming: feat/…, fix/…, docs/…; commit
messages follow Conventional Commits.
</li>
<li>
Run checks before PR:
<code className="ml-2">
npm --prefix web run lint && npm --prefix web
run build
</code>
</li>
<li>
For UI changes, attach screenshots or a short
video.
</li>
<li>
Choose the proper PR template
(frontend/backend/docs/general).
</li>
<li>
Link the Issue in PR (e.g.,{' '}
<code className="ml-1">Closes #123</code>) and
target <code>NoFxAiOS/nofx:dev</code>.
</li>
<li>
Keep rebasing onto <code>upstream/dev</code>,
ensure CI passes; prefer small and focused PRs.
</li>
</>
)}
</ol>
<div
className="rounded p-3 mt-3"
style={{
background: 'rgba(240, 185, 11, 0.08)',
border: '1px solid rgba(240, 185, 11, 0.25)',
}}
>
{language === 'zh' ? (
<div className="text-sm">
<strong style={{ color: '#F0B90B' }}>提示:</strong>{' '}
我们为高质量贡献提供激励Bounty/奖金、荣誉徽章与鸣谢、优先
Review/合并与内测资格 等)。 详情可关注带
<a
href="https://github.com/NoFxAiOS/nofx/labels/bounty"
target="_blank"
rel="noreferrer"
style={{ color: '#F0B90B' }}
>
bounty 标签
</a>
的任务,或使用
<a
href="https://github.com/NoFxAiOS/nofx/blob/dev/.github/ISSUE_TEMPLATE/bounty_claim.md"
target="_blank"
rel="noreferrer"
style={{ color: '#F0B90B' }}
>
Bounty Claim 模板
</a>
提交申请。
</div>
) : (
<div className="text-sm">
<strong style={{ color: '#F0B90B' }}>Note:</strong>{' '}
We offer contribution incentives (bounties, badges,
shout-outs, priority review/merge, beta access).
Look for tasks with
<a
href="https://github.com/NoFxAiOS/nofx/labels/bounty"
target="_blank"
rel="noreferrer"
style={{ color: '#F0B90B' }}
>
bounty label
</a>
, or submit a
<a
href="https://github.com/NoFxAiOS/nofx/blob/dev/.github/ISSUE_TEMPLATE/bounty_claim.md"
target="_blank"
rel="noreferrer"
style={{ color: '#F0B90B' }}
>
Bounty Claim
</a>
when ready.
</div>
)}
</div>
</div>
) : (
<p className="text-base">{t(item.answerKey, language)}</p>
)}
</div>
{/* Divider */}
<div className="mt-6 h-px" style={{ background: '#2B3139' }} />
</section>
))}
</div>
</div>
))}
</div>
)
}

View File

@@ -0,0 +1,181 @@
import { useState, useMemo } from 'react'
import { HelpCircle } from 'lucide-react'
import { t, type Language } from '../../i18n/translations'
import { FAQSearchBar } from './FAQSearchBar'
import { FAQSidebar } from './FAQSidebar'
import { FAQContent } from './FAQContent'
import { faqCategories } from '../../data/faqData'
import type { FAQCategory } from '../../data/faqData'
interface FAQLayoutProps {
language: Language
}
export function FAQLayout({ language }: FAQLayoutProps) {
const [searchTerm, setSearchTerm] = useState('')
const [activeItemId, setActiveItemId] = useState<string | null>(null)
// Filter categories based on search term
const filteredCategories = useMemo(() => {
if (!searchTerm.trim()) {
return faqCategories
}
const term = searchTerm.toLowerCase()
const filtered: FAQCategory[] = []
faqCategories.forEach((category) => {
const matchingItems = category.items.filter((item) => {
const question = t(item.questionKey, language).toLowerCase()
const answer = t(item.answerKey, language).toLowerCase()
return question.includes(term) || answer.includes(term)
})
if (matchingItems.length > 0) {
filtered.push({
...category,
items: matchingItems,
})
}
})
return filtered
}, [searchTerm, language])
const handleItemClick = (_categoryId: string, itemId: string) => {
const element = document.getElementById(itemId)
if (element) {
const offset = 100
const elementPosition = element.getBoundingClientRect().top
const offsetPosition = elementPosition + window.pageYOffset - offset
window.scrollTo({
top: offsetPosition,
behavior: 'smooth',
})
}
}
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 pt-24">
{/* Page Header */}
<div className="text-center mb-12">
<div className="flex items-center justify-center gap-3 mb-4">
<div
className="w-16 h-16 rounded-full flex items-center justify-center"
style={{
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
boxShadow: '0 8px 24px rgba(240, 185, 11, 0.4)',
}}
>
<HelpCircle className="w-8 h-8" style={{ color: '#0B0E11' }} />
</div>
</div>
<h1 className="text-4xl font-bold mb-4" style={{ color: '#EAECEF' }}>
{t('faqTitle', language)}
</h1>
<p className="text-lg mb-8" style={{ color: '#848E9C' }}>
{t('faqSubtitle', language)}
</p>
{/* Search Bar */}
<div className="max-w-2xl mx-auto">
<FAQSearchBar
searchTerm={searchTerm}
onSearchChange={setSearchTerm}
placeholder={
language === 'zh' ? '搜索常见问题...' : 'Search FAQ...'
}
/>
</div>
</div>
{/* Main Content */}
<div className="flex gap-8">
{/* Sidebar - Hidden on mobile, visible on desktop */}
<aside className="hidden lg:block w-64 flex-shrink-0">
<FAQSidebar
categories={filteredCategories}
activeItemId={activeItemId}
language={language}
onItemClick={handleItemClick}
/>
</aside>
{/* Content Area */}
<main className="flex-1 min-w-0">
{filteredCategories.length > 0 ? (
<FAQContent
categories={filteredCategories}
language={language}
onActiveItemChange={setActiveItemId}
/>
) : (
<div className="text-center py-12">
<p className="text-lg" style={{ color: '#848E9C' }}>
{language === 'zh'
? '没有找到匹配的问题'
: 'No matching questions found'}
</p>
<button
onClick={() => setSearchTerm('')}
className="mt-4 px-6 py-2 rounded-lg font-semibold transition-all hover:opacity-90"
style={{
background:
'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
color: '#0B0E11',
}}
>
{language === 'zh' ? '清除搜索' : 'Clear Search'}
</button>
</div>
)}
</main>
</div>
{/* Contact Section */}
<div
className="mt-16 p-8 rounded-lg text-center"
style={{
background:
'linear-gradient(135deg, rgba(240, 185, 11, 0.1) 0%, rgba(252, 213, 53, 0.05) 100%)',
border: '1px solid rgba(240, 185, 11, 0.2)',
}}
>
<h3 className="text-xl font-bold mb-3" style={{ color: '#EAECEF' }}>
{t('faqStillHaveQuestions', language)}
</h3>
<p className="mb-6" style={{ color: '#848E9C' }}>
{t('faqContactUs', language)}
</p>
<div className="flex items-center justify-center gap-4">
<a
href="https://github.com/tinkle-community/nofx"
target="_blank"
rel="noopener noreferrer"
className="px-6 py-3 rounded-lg font-semibold transition-all hover:scale-105"
style={{
background: '#1E2329',
color: '#EAECEF',
border: '1px solid #2B3139',
}}
>
GitHub
</a>
<a
href="https://t.me/nofx_dev_community"
target="_blank"
rel="noopener noreferrer"
className="px-6 py-3 rounded-lg font-semibold transition-all hover:scale-105"
style={{
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
color: '#0B0E11',
}}
>
{t('community', language)}
</a>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,51 @@
import { Search, X } from 'lucide-react'
interface FAQSearchBarProps {
searchTerm: string
onSearchChange: (value: string) => void
placeholder?: string
}
export function FAQSearchBar({
searchTerm,
onSearchChange,
placeholder = 'Search FAQ...',
}: FAQSearchBarProps) {
return (
<div className="relative">
<Search
className="absolute left-4 top-1/2 transform -translate-y-1/2 w-5 h-5"
style={{ color: '#848E9C' }}
/>
<input
type="text"
value={searchTerm}
onChange={(e) => onSearchChange(e.target.value)}
placeholder={placeholder}
className="w-full pl-12 pr-12 py-3 rounded-lg text-base transition-all focus:outline-none focus:ring-2"
style={{
background: '#1E2329',
border: '1px solid #2B3139',
color: '#EAECEF',
}}
onFocus={(e) => {
e.target.style.borderColor = '#F0B90B'
e.target.style.boxShadow = '0 0 0 3px rgba(240, 185, 11, 0.1)'
}}
onBlur={(e) => {
e.target.style.borderColor = '#2B3139'
e.target.style.boxShadow = 'none'
}}
/>
{searchTerm && (
<button
onClick={() => onSearchChange('')}
className="absolute right-4 top-1/2 transform -translate-y-1/2 hover:opacity-70 transition-opacity"
style={{ color: '#848E9C' }}
>
<X className="w-5 h-5" />
</button>
)}
</div>
)
}

View File

@@ -0,0 +1,83 @@
import { t, type Language } from '../../i18n/translations'
import type { FAQCategory } from '../../data/faqData'
interface FAQSidebarProps {
categories: FAQCategory[]
activeItemId: string | null
language: Language
onItemClick: (categoryId: string, itemId: string) => void
}
export function FAQSidebar({
categories,
activeItemId,
language,
onItemClick,
}: FAQSidebarProps) {
return (
<nav
className="sticky top-24 h-[calc(100vh-120px)] overflow-y-auto pr-4"
style={{
scrollbarWidth: 'thin',
scrollbarColor: '#2B3139 #1E2329',
}}
>
<div className="space-y-6">
{categories.map((category) => (
<div key={category.id}>
{/* Category Title */}
<div className="flex items-center gap-2 mb-3 px-3">
<category.icon className="w-5 h-5" style={{ color: '#F0B90B' }} />
<h3
className="text-sm font-bold uppercase tracking-wide"
style={{ color: '#F0B90B' }}
>
{t(category.titleKey, language)}
</h3>
</div>
{/* Category Items */}
<ul className="space-y-1">
{category.items.map((item) => {
const isActive = activeItemId === item.id
return (
<li key={item.id}>
<button
onClick={() => onItemClick(category.id, item.id)}
className="w-full text-left px-3 py-2 rounded-lg text-sm transition-all"
style={{
background: isActive
? 'rgba(240, 185, 11, 0.1)'
: 'transparent',
color: isActive ? '#F0B90B' : '#848E9C',
borderLeft: isActive
? '3px solid #F0B90B'
: '3px solid transparent',
paddingLeft: isActive ? '9px' : '12px',
}}
onMouseEnter={(e) => {
if (!isActive) {
e.currentTarget.style.background =
'rgba(240, 185, 11, 0.05)'
e.currentTarget.style.color = '#EAECEF'
}
}}
onMouseLeave={(e) => {
if (!isActive) {
e.currentTarget.style.background = 'transparent'
e.currentTarget.style.color = '#848E9C'
}
}}
>
{t(item.questionKey, language)}
</button>
</li>
)
})}
</ul>
</div>
))}
</div>
</nav>
)
}

View File

@@ -215,45 +215,127 @@ export default function HeaderBar({
{t('dashboardNav', language)}
</button>
<button
onClick={() => {
console.log(
'FAQ button clicked, onPageChange:',
onPageChange
)
onPageChange?.('faq')
}}
className="text-sm font-bold transition-all duration-300 relative focus:outline-2 focus:outline-yellow-500"
style={{
color:
currentPage === 'faq'
? 'var(--brand-yellow)'
: 'var(--brand-light-gray)',
padding: '8px 16px',
borderRadius: '8px',
position: 'relative',
}}
onMouseEnter={(e) => {
if (currentPage !== 'faq') {
e.currentTarget.style.color = 'var(--brand-yellow)'
}
}}
onMouseLeave={(e) => {
if (currentPage !== 'faq') {
e.currentTarget.style.color = 'var(--brand-light-gray)'
}
}}
>
{/* Background for selected state */}
{currentPage === 'faq' && (
<span
className="absolute inset-0 rounded-lg"
style={{
background: 'rgba(240, 185, 11, 0.15)',
zIndex: -1,
}}
/>
)}
{t('faqNav', language)}
</button>
</>
) : (
// Landing page navigation when not logged in
<a
href="/competition"
className="text-sm font-bold transition-all duration-300 relative focus:outline-2 focus:outline-yellow-500"
style={{
color:
currentPage === 'competition'
? 'var(--brand-yellow)'
: 'var(--brand-light-gray)',
padding: '8px 16px',
borderRadius: '8px',
position: 'relative',
}}
onMouseEnter={(e) => {
if (currentPage !== 'competition') {
e.currentTarget.style.color = 'var(--brand-yellow)'
}
}}
onMouseLeave={(e) => {
if (currentPage !== 'competition') {
e.currentTarget.style.color = 'var(--brand-light-gray)'
}
}}
>
{/* Background for selected state */}
{currentPage === 'competition' && (
<span
className="absolute inset-0 rounded-lg"
style={{
background: 'rgba(240, 185, 11, 0.15)',
zIndex: -1,
}}
/>
)}
<>
<a
href="/competition"
className="text-sm font-bold transition-all duration-300 relative focus:outline-2 focus:outline-yellow-500"
style={{
color:
currentPage === 'competition'
? 'var(--brand-yellow)'
: 'var(--brand-light-gray)',
padding: '8px 16px',
borderRadius: '8px',
position: 'relative',
}}
onMouseEnter={(e) => {
if (currentPage !== 'competition') {
e.currentTarget.style.color = 'var(--brand-yellow)'
}
}}
onMouseLeave={(e) => {
if (currentPage !== 'competition') {
e.currentTarget.style.color = 'var(--brand-light-gray)'
}
}}
>
{/* Background for selected state */}
{currentPage === 'competition' && (
<span
className="absolute inset-0 rounded-lg"
style={{
background: 'rgba(240, 185, 11, 0.15)',
zIndex: -1,
}}
/>
)}
{t('realtimeNav', language)}
</a>
{t('realtimeNav', language)}
</a>
<a
href="/faq"
className="text-sm font-bold transition-all duration-300 relative focus:outline-2 focus:outline-yellow-500"
style={{
color:
currentPage === 'faq'
? 'var(--brand-yellow)'
: 'var(--brand-light-gray)',
padding: '8px 16px',
borderRadius: '8px',
position: 'relative',
}}
onMouseEnter={(e) => {
if (currentPage !== 'faq') {
e.currentTarget.style.color = 'var(--brand-yellow)'
}
}}
onMouseLeave={(e) => {
if (currentPage !== 'faq') {
e.currentTarget.style.color = 'var(--brand-light-gray)'
}
}}
>
{/* Background for selected state */}
{currentPage === 'faq' && (
<span
className="absolute inset-0 rounded-lg"
style={{
background: 'rgba(240, 185, 11, 0.15)',
zIndex: -1,
}}
/>
)}
{t('faqNav', language)}
</a>
</>
)}
</div>
@@ -650,6 +732,41 @@ export default function HeaderBar({
{t('dashboardNav', language)}
</button>
<button
onClick={() => {
console.log(
'移动端 FAQ button clicked, onPageChange:',
onPageChange
)
onPageChange?.('faq')
setMobileMenuOpen(false)
}}
className="block text-sm font-bold transition-all duration-300 relative focus:outline-2 focus:outline-yellow-500 hover:text-yellow-500"
style={{
color:
currentPage === 'faq'
? 'var(--brand-yellow)'
: 'var(--brand-light-gray)',
padding: '12px 16px',
borderRadius: '8px',
position: 'relative',
width: '100%',
textAlign: 'left',
}}
>
{/* Background for selected state */}
{currentPage === 'faq' && (
<span
className="absolute inset-0 rounded-lg"
style={{
background: 'rgba(240, 185, 11, 0.15)',
zIndex: -1,
}}
/>
)}
{t('faqNav', language)}
</button>
</>
)}