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>
)
}