fix(community): ShareLink 按钮语义修正 + feed SSR 抗 CF 挑战#315
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
… SSR 抗 CF 挑战 按钮语义修正(用户反馈): - Hero ShareLink 主按钮 /feed → /feed/submit(语义:投稿动作,与 Contribute 平级) - 去掉 ShareLink 右上角 "+" 徽章(主按钮已经是投稿,徽章冗余) - Join the Resistance 卡片里"访问文章"按钮下方加同构"看看我们最近在读什么"→ /feed (阅读入口从 Hero 主 CTA 挪到 Join 区,避免与投稿动作混淆) Next 16 严格模式修正: - FeedAuthWrapper 之前收 getCategoryLabel 函数 prop 会触发 "Functions cannot be passed directly to Client Components" → /feed 500 - 改传 server 端预计算的 slug → 中文 map(纯数据),client 组件自己查表 生产 500 修复(生产症状:/feed 显示 "server error"): - fetchLinks 之前单次失败就抛错,Cloudflare Managed Challenge 403 时直接崩 - 加重试 + UA 头 + cf-ray 日志,对齐 fetchProfile 的防御策略 - 全败时返回 [] 而非抛错,页面降级展示空态不崩 i18n: - 新增 hero.cta.feed("看看我们最近在读什么" / "What we're reading lately") - 移除失效的 shareLink.submitAriaLabel(徽章已删)
2283663 to
f670a35
Compare
There was a problem hiding this comment.
Pull request overview
This PR adjusts the homepage/community CTA information architecture and introduces a new Community Feed feature set, while hardening /feed SSR fetching against Cloudflare challenge failures.
Changes:
- Fixes Hero ShareLink CTA semantics (primary action goes to
/feed/submit) and adds a separate “reading” entry to/feed. - Adds Community Feed pages/components:
/feed,/feed/submit,/share, user “my shares” pages under/u/[username]/shares, and an admin moderation screen. - Adds backend rewrites for the new community APIs and expands i18n strings for feed/share UI.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| next.config.mjs | Adds rewrites for community feed and admin community API proxying to backend. |
| messages/zh.json | Adds feed/share i18n strings and Hero CTA label for /feed. |
| messages/en.json | Adds English equivalents for feed/share i18n strings and Hero CTA label for /feed. |
| app/u/[username]/shares/page.tsx | New client page to show the signed-in user’s submitted links (“mine”) with status badges. |
| app/u/[username]/shares/layout.tsx | Adds a server layout to host Header/Footer above a client page. |
| app/u/[username]/page.tsx | Adds “My shares” entry point and profile-side embedded shares section. |
| app/u/[username]/SharesOnProfile.tsx | New embedded “My shares” module on the profile page (owner-only). |
| app/u/[username]/SharesLinkIfOwner.tsx | New owner-only navigation link to /u/{identifier}/shares. |
| app/share/page.tsx | New minimal “quick share” page with optional URL/text prefilling and login redirect. |
| app/feed/types.ts | Introduces typed DTOs for community shared links and categories. |
| app/feed/submit/page.tsx | New feed submission page that posts links to backend with auth. |
| app/feed/submit/layout.tsx | Server layout to keep Header/Footer as server components while page is client. |
| app/feed/page.tsx | New SSR feed listing page with CF-challenge-aware retries and server-precomputed category labels. |
| app/feed/components/ReportButton.tsx | New client report dialog/button to POST link reports. |
| app/feed/components/LinkCard.tsx | New link card UI used across feed and “my shares” views. |
| app/feed/components/FeedAuthWrapper.tsx | Client bridge to inject login status and category labels into LinkCard list rendering. |
| app/feed/components/CategoryTabs.tsx | Client category tabs that drive filtering via ?category=<slug>. |
| app/components/ShareLink.tsx | Updates Hero ShareLink CTA to point to /feed/submit and removes redundant badge behavior. |
| app/components/Hero.tsx | Updates Hero CTA layout to separate “contribute/share” vs “read” actions and adds /feed CTA label. |
| app/components/Contribute.tsx | Small layout tweak so outer container controls margin and positioning consistently with the new CTA row. |
| app/admin/community/page.tsx | New admin UI to approve/reject pending/flagged community links. |
| app/admin/community/lib.ts | Adds admin API client helper for community moderation endpoints. |
| app/admin/community/layout.tsx | Adds route segment layout placeholder for future admin community nav/sidebars. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| className="block w-full" | ||
| data-umami-event="navigation_click" | ||
| data-umami-event-region="hero_cta" | ||
| data-umami-event-label="Access Articles" | ||
| > | ||
| <button className="w-full py-3 border border-[var(--background)] font-sans text-xs uppercase tracking-widest hover:bg-[var(--background)] hover:text-[var(--foreground)] transition-all cursor-pointer"> | ||
| {t("cta.access")} | ||
| </button> | ||
| </Link> | ||
| <Link | ||
| href="/feed" | ||
| className="block w-full" | ||
| data-umami-event="navigation_click" | ||
| data-umami-event-region="hero_cta" | ||
| data-umami-event-label="Community Feed" | ||
| > | ||
| <button className="w-full py-3 border border-[var(--background)] font-sans text-xs uppercase tracking-widest hover:bg-[var(--background)] hover:text-[var(--foreground)] transition-all cursor-pointer"> | ||
| {t("cta.feed")} | ||
| </button> |
There was a problem hiding this comment.
These Link blocks wrap a native <button> inside an anchor, which is invalid HTML (nested interactive elements) and can cause accessibility / keyboard issues. Prefer rendering the link itself as the styled control (or use the shared <Button asChild> pattern used elsewhere, e.g. app/not-found.tsx:31) to avoid nested interactive elements.
| className="block w-full" | |
| data-umami-event="navigation_click" | |
| data-umami-event-region="hero_cta" | |
| data-umami-event-label="Access Articles" | |
| > | |
| <button className="w-full py-3 border border-[var(--background)] font-sans text-xs uppercase tracking-widest hover:bg-[var(--background)] hover:text-[var(--foreground)] transition-all cursor-pointer"> | |
| {t("cta.access")} | |
| </button> | |
| </Link> | |
| <Link | |
| href="/feed" | |
| className="block w-full" | |
| data-umami-event="navigation_click" | |
| data-umami-event-region="hero_cta" | |
| data-umami-event-label="Community Feed" | |
| > | |
| <button className="w-full py-3 border border-[var(--background)] font-sans text-xs uppercase tracking-widest hover:bg-[var(--background)] hover:text-[var(--foreground)] transition-all cursor-pointer"> | |
| {t("cta.feed")} | |
| </button> | |
| className="block w-full py-3 border border-[var(--background)] font-sans text-xs uppercase tracking-widest text-center hover:bg-[var(--background)] hover:text-[var(--foreground)] transition-all cursor-pointer" | |
| data-umami-event="navigation_click" | |
| data-umami-event-region="hero_cta" | |
| data-umami-event-label="Access Articles" | |
| > | |
| {t("cta.access")} | |
| </Link> | |
| <Link | |
| href="/feed" | |
| className="block w-full py-3 border border-[var(--background)] font-sans text-xs uppercase tracking-widest text-center hover:bg-[var(--background)] hover:text-[var(--foreground)] transition-all cursor-pointer" | |
| data-umami-event="navigation_click" | |
| data-umami-event-region="hero_cta" | |
| data-umami-event-label="Community Feed" | |
| > | |
| {t("cta.feed")} |
Copilot CR 指出的两条: PR #315 Hero.tsx:119 —— <a> 包 <button> 是嵌套交互元素(HTML 无效 + a11y 问题) 修法:把 <Link> 直接渲染成按钮样式,不再嵌套 <button> PR #316 LinkCard.tsx:52 / admin/community/page.tsx:143 —— OG 封面 URL 直接进 <img src> 没过白名单。 修法:用 lib/url-safety.ts 的 sanitizeMediaUrl 兜底,拦 javascript:/data: 协议 (后端 UrlNormalizer 是第一道防线,前端 sanitize 是 defense-in-depth)
Copilot CR 指出的两条: PR #315 Hero.tsx:119 —— <a> 包 <button> 是嵌套交互元素(HTML 无效 + a11y 问题) 修法:把 <Link> 直接渲染成按钮样式,不再嵌套 <button> PR #316 LinkCard.tsx:52 / admin/community/page.tsx:143 —— OG 封面 URL 直接进 <img src> 没过白名单。 修法:用 lib/url-safety.ts 的 sanitizeMediaUrl 兜底,拦 javascript:/data: 协议 (后端 UrlNormalizer 是第一道防线,前端 sanitize 是 defense-in-depth) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
背景
用户反馈:
改动
按钮语义修正
Next 16 严格模式修正
生产 /feed 500 修复
i18n
验证