Skip to content

Feature/award page refactor#81

Open
xiaohei644 wants to merge 4 commits into
Open-Source-Bazaar:mainfrom
xiaohei644:feature/award-page-refactor
Open

Feature/award page refactor#81
xiaohei644 wants to merge 4 commits into
Open-Source-Bazaar:mainfrom
xiaohei644:feature/award-page-refactor

Conversation

@xiaohei644

@xiaohei644 xiaohei644 commented Jun 30, 2026

Copy link
Copy Markdown

PR-81 PR-81 PR-81 Powered by Pull Request Badge

Checklist(清单):

  • Labels
  • Assignees
  • Reviewers

Closes #XXXXX

Summary by CodeRabbit

  • 新功能

    • 新增图书馆页面,支持按分类筛选和关键词搜索图书。
    • 新增图书详情页,可查看封面、标签、状态、基础信息和借阅信息。
    • 导航菜单新增“开放图书馆”入口,并更新奖项相关链接。
  • 页面优化

    • 奖项页面已完善,支持提名列表、视频介绍、提名步骤和流程说明展示。
    • 在无提名或无搜索结果时,提供更清晰的空状态提示。

Open-Source-Bazaar-Contributor added 2 commits June 30, 2026 16:40
- Add /library page with book grid, category filter and search
- Add /library/[id] book detail page with borrowing info and guide
- Add 12 open-source/tech books with borrowing status
- Add Book type definitions in types/library.ts
- Add 35 i18n keys for zh-CN, en-US, zh-TW
- Add Open Library link to MainNavigator

Resolves Open-Source-Bazaar#15
- Add hero section with award intro
- Display nominations from Feishu bi-table via AwardModel
- Add nomination cards with video embed, votes, and nominator info
- Add 4-step nomination guide section
- Add about award section
- Preserve Bilibili video intro from original page
- Add 20 i18n keys for zh-CN/en-US/zh-TW
- Update navigation to point to /award

Resolves Open-Source-Bazaar#52
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@xiaohei644, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2b1a8647-6a41-4900-bdcf-c272a2fbc552

📥 Commits

Reviewing files that changed from the base of the PR and between b05ec5c and 4f6c12a.

📒 Files selected for processing (2)
  • components/Navigator/MainNavigator.tsx
  • pages/award/index.tsx
📝 Walkthrough

概览

新增完整的 Open Library 功能模块(静态书库数据文件、带分类/搜索的列表页、带借阅信息的详情页),同时将原占位的 Award 页面替换为含提名卡片、步骤区块等完整实现,并更新导航菜单对应链接。

变更内容

Open Library 功能

Layer / File(s) 说明
libraryBooks 静态数据
pages/library/books.ts
导出 libraryBooks: Book[],包含多本书的完整元数据;borrowed 状态条目附带 borrower/borrowDate/returnDate 字段。
LibraryPage 列表页
pages/library/index.tsx
LibraryPage 通过 useMemolibraryBooks 进行分类与关键字过滤,渲染搜索框、分类下拉、书籍网格卡片及空状态提示;getServerSideProps 返回空 props。
BookDetailPage 详情页
pages/library/[id].tsx
getServerSidePropsparams.id 查找书籍,未找到返回 notFound: trueBookDetailPage 渲染封面、状态 Badge、标签、描述、字段 Card、借阅信息 Card 与借阅指南 Card。

Award 页面重写

Layer / File(s) 说明
NominationCard 与 AwardPage
pages/award/index.tsx
新增 NominationCard 组件,按 videoUrl/votes 条件渲染 iframe 与徽标;AwardPage 重写为含 PageHead、Hero 区块、提名列表(或空状态)、步骤区块与关于区块的完整页面。

导航菜单更新

Layer / File(s) 说明
MainNavigator 链接调整
components/Navigator/MainNavigator.tsx
join_us 子项 href/article/open-collaborator-award 改为 /award;主菜单在 /bounty 后新增 href: '/library't('open_library'))菜单项。

预估代码审查工作量

🎯 3 (Moderate) | ⏱️ ~25 minutes

可能相关的 PR

建议标签

feature

📚 图书上架啦,借阅有指南,
🏆 Award 提名卡,步骤皆齐全,
🔗 导航新链接,/library 登场,
🔍 搜索加分类,好书随手找,
observer 守护态,MobX 永不乱!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题与本次变更的核心“award page refactor”一致,能概括主要改动方向。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (7)
pages/award/index.tsx (2)

133-134: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

移除这里的 as any,改为静态翻译 key 映射 t() 已经受 I18nKey 约束,字符串拼接再强转会绕过翻译键检查;用 as const 的 key 表直接传入,避免后续拼错 key。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/award/index.tsx` around lines 133 - 134, The translation calls in the
nomination step labels are bypassing I18nKey safety by using string
concatenation with as any. Update the award page rendering to replace the
dynamic key casting in the heading and description with a static key map (using
as const) and pass the mapped key directly to t(), so the identifiers for each
step stay type-checked and typos are caught.

Source: Coding guidelines


78-80: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

把站内链接统一成 React Bootstrap 导航组件
pages/award/index.tsx(78-80、148-150)里 /article/open-collaborator-award 的两处 CTA 不要继续用裸 <a>;改成 Button as={Link}(或仓库现有统一导航组件)更符合站内跳转和 pages/**.tsx 的 React Bootstrap 约定。#nominations 这种页内锚点可保留原样。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/award/index.tsx` around lines 78 - 80, The
`/article/open-collaborator-award` CTA in `AwardPage` should use the app’s React
Bootstrap navigation pattern instead of a raw anchor. Update both occurrences in
`pages/award/index.tsx` to use `Button as={Link}` or the repository’s existing
internal link component for that route, while leaving the `#nominations` in-page
anchor unchanged. Make the change in the `AwardPage` JSX where the `learn_more`
and second award CTA are rendered.

Source: Coding guidelines

pages/library/index.tsx (3)

12-14: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

静态数据无需 getServerSideProps,建议改用 getStaticProps

libraryBooks 为编译期静态导入,且此处 props 为空对象。使用 getServerSideProps(含 cache())会让该页面每次请求走 SSR,毫无收益。改为 getStaticProps(或直接不导出数据获取函数)可获得静态生成与更优性能。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/library/index.tsx` around lines 12 - 14, The page is using
getServerSideProps via compose(cache(), errorLogger, ...) even though the data
is static and props are empty, so switch this export to getStaticProps or remove
the data-fetching function entirely. Update the library/index.tsx page to use
static generation semantics instead of SSR, keeping the existing
compose/errorLogger setup only if it still applies to the new data-fetching
approach.

82-88: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

封面图建议使用 next/image 以优化加载性能。

Card.Img 渲染为原生 <img>,外链大图未做尺寸优化/懒加载。可考虑用 next/image(指定 width/height,配置 remotePatterns)减少布局抖动并启用懒加载与格式优化。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/library/index.tsx` around lines 82 - 88, The book cover image rendering
in the library page currently uses Card.Img, which leaves external images
unoptimized. Replace this usage in the library page component with next/image in
the same book card rendering path, supplying explicit width and height and
preserving the rounded/cover styling, and make sure the remote image host is
allowed via next.config remotePatterns so lazy loading and image optimization
can work correctly.

69-69: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

消除 as any,并修正三元表达式中错位的类型断言。

两处问题:

  • 第 95 行 ... ? 'library_status_available' : 'library_status_borrowed' as any 中,as any 因运算符优先级只作用于 false 分支,类型保护并不覆盖整个三元结果,意图不清晰。
  • t(labelKey as any)as any 等绕过了 t() 的键类型校验,违反强类型要求。建议为这些 i18n key 提供受约束的联合类型(如 keyof typeof messages)或集中映射,避免 any

Also applies to: 94-96

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/library/index.tsx` at line 69, The i18n key handling in the library
page is bypassing type safety with `as any`, and the ternary-based label
selection has a misplaced assertion that only applies to one branch. Update the
`option` label logic and the status key expression to use a properly constrained
union type or a typed mapping for the `t()` keys, so `t(labelKey)` no longer
needs `as any` and the entire ternary result is typed consistently.

Source: Coding guidelines

pages/library/[id].tsx (1)

36-36: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

消除 as any 并修正断言作用域。

第 36 行三元中的 as any 同样因优先级只作用于一个分支;第 74 行 t("library_category_" + book.category as any)as any 作用于 book.category,使整段拼接退化为 any。建议为分类/状态 key 建立受类型约束的映射,避免向 t() 传入 any,以满足强类型要求。

Also applies to: 74-74

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/library/`[id].tsx at line 36, Remove the `as any` casts in the `t()`
calls and fix the precedence issue in the status ternary so the entire
translated key is type-safe. In `pages/library/[id].tsx`, update the status and
category lookup logic around the inline `t(...)` usages by introducing properly
typed key mappings or narrow string unions for `book.status` and
`book.category`, then pass those typed keys directly to `t()` instead of relying
on `any`.

Source: Coding guidelines

pages/library/books.ts (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

文件首部存在 BOM 字符。

第 1 行 import 前含 UTF-8 BOM(\uFEFF)。虽然 TS 编译器可容忍,但建议去除以保持与仓库其余文件一致,避免个别工具链解析异常。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/library/books.ts` at line 1, The file header contains a UTF-8 BOM
before the first import, so remove the leading BOM from the top of
pages/library/books.ts and keep the file starting directly with the existing
import; this is a formatting cleanup only and no logic changes are needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/Navigator/MainNavigator.tsx`:
- Around line 65-66: Add the missing comma between the two array entries in
MainNavigator’s navigation items list so the array parses correctly; update the
item sequence around the existing '/bounty' and '/library' entries in
MainNavigator to keep the array literal valid and allow the component to
compile.

In `@pages/award/index.tsx`:
- Line 89: The SectionTitle usage for award_intro_video is rendering an
undefined badge because SectionTitle always shows its count Badge even when no
count is passed. Update the award page to pass an explicit count, or change
SectionTitle in components/Layout/SectionTitle.tsx so the Badge is rendered only
when count != null. Use the SectionTitle component and its count prop to locate
and fix the issue.
- Around line 27-34: The iframe used for award videos is loading immediately,
which can trigger multiple player requests during initial render. Update the
iframe render in the award page’s video card and top intro video sections to
include loading="lazy" so offscreen embeds are deferred. Use the existing
videoUrl-driven iframe markup in this component and apply the same change
wherever the award page renders iframe players.
- Around line 27-34: The iframe in the award page is using BiTable’s videoUrl
directly as src, which allows arbitrary remote URLs to be embedded; update the
award rendering logic in the component that builds this iframe to validate
videoUrl against a strict host allowlist before using it, and reject or omit
anything else. Also harden the iframe itself by adding sandbox restrictions and
a referrerPolicy, keeping the existing title/name wiring intact while only
allowing approved video providers to load.

In `@pages/library/`[id].tsx:
- Around line 22-24: The page still uses native `<a>` and `<img>` elements,
which violates the UI component guideline. In `pages/library/[id].tsx`, replace
the back-link and any other internal navigation anchors with `next/link` wrapped
around a React Bootstrap `Button`, and replace the cover image rendering with
`next/image` (or `Card.Img`) instead of a raw `<img>`. Make the same
UI-component-only update anywhere else in this component that uses the native
anchor/button pattern, using the existing `t(...)` text and the library detail
render flow as your guide.
- Around line 10-14: `getServerSideProps` is relying on an implicit `params`
shape, so `params.id` should be narrowed explicitly before using it in the
`libraryBooks.find` lookup. Update the `compose` call in `getServerSideProps` to
carry the `{ id: string }` generic, and then guard access with `params?.id` so
the dynamic route handling matches the rest of the Pages Router patterns and
avoids assuming `params` is always present.

In `@pages/library/books.ts`:
- Line 8: The book cover URLs referenced in books data are using external links
that may return 404, so update the affected entries in the books list to use
stable assets from the repository or a controlled CDN, and add an image
load-fallback placeholder for the list/detail rendering path. Locate the cover
definitions in books.ts and the components that render those covers, and make
sure the fallback is applied when the image fails to load so missing external
images do not show as broken thumbnails.

In `@pages/library/index.tsx`:
- Around line 101-105: Replace the native anchor used in the book card with
React Bootstrap + Next.js client navigation: in pages/library/index.tsx, update
the view_details link inside the book item render to use next/link together with
a React Bootstrap Button instead of an <a className="btn">. Keep the same
destination built from book.id, preserve the existing styling/size/width, and
ensure the component imports and uses Link and Button consistently with the rest
of the UI.

---

Nitpick comments:
In `@pages/award/index.tsx`:
- Around line 133-134: The translation calls in the nomination step labels are
bypassing I18nKey safety by using string concatenation with as any. Update the
award page rendering to replace the dynamic key casting in the heading and
description with a static key map (using as const) and pass the mapped key
directly to t(), so the identifiers for each step stay type-checked and typos
are caught.
- Around line 78-80: The `/article/open-collaborator-award` CTA in `AwardPage`
should use the app’s React Bootstrap navigation pattern instead of a raw anchor.
Update both occurrences in `pages/award/index.tsx` to use `Button as={Link}` or
the repository’s existing internal link component for that route, while leaving
the `#nominations` in-page anchor unchanged. Make the change in the `AwardPage`
JSX where the `learn_more` and second award CTA are rendered.

In `@pages/library/`[id].tsx:
- Line 36: Remove the `as any` casts in the `t()` calls and fix the precedence
issue in the status ternary so the entire translated key is type-safe. In
`pages/library/[id].tsx`, update the status and category lookup logic around the
inline `t(...)` usages by introducing properly typed key mappings or narrow
string unions for `book.status` and `book.category`, then pass those typed keys
directly to `t()` instead of relying on `any`.

In `@pages/library/books.ts`:
- Line 1: The file header contains a UTF-8 BOM before the first import, so
remove the leading BOM from the top of pages/library/books.ts and keep the file
starting directly with the existing import; this is a formatting cleanup only
and no logic changes are needed.

In `@pages/library/index.tsx`:
- Around line 12-14: The page is using getServerSideProps via compose(cache(),
errorLogger, ...) even though the data is static and props are empty, so switch
this export to getStaticProps or remove the data-fetching function entirely.
Update the library/index.tsx page to use static generation semantics instead of
SSR, keeping the existing compose/errorLogger setup only if it still applies to
the new data-fetching approach.
- Around line 82-88: The book cover image rendering in the library page
currently uses Card.Img, which leaves external images unoptimized. Replace this
usage in the library page component with next/image in the same book card
rendering path, supplying explicit width and height and preserving the
rounded/cover styling, and make sure the remote image host is allowed via
next.config remotePatterns so lazy loading and image optimization can work
correctly.
- Line 69: The i18n key handling in the library page is bypassing type safety
with `as any`, and the ternary-based label selection has a misplaced assertion
that only applies to one branch. Update the `option` label logic and the status
key expression to use a properly constrained union type or a typed mapping for
the `t()` keys, so `t(labelKey)` no longer needs `as any` and the entire ternary
result is typed consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7a40df31-6f01-4670-8fc4-482855fb5713

📥 Commits

Reviewing files that changed from the base of the PR and between 58126ca and b05ec5c.

⛔ Files ignored due to path filters (4)
  • translation/en-US.ts is excluded by none and included by none
  • translation/zh-CN.ts is excluded by none and included by none
  • translation/zh-TW.ts is excluded by none and included by none
  • types/library.ts is excluded by none and included by none
📒 Files selected for processing (5)
  • components/Navigator/MainNavigator.tsx
  • pages/award/index.tsx
  • pages/library/[id].tsx
  • pages/library/books.ts
  • pages/library/index.tsx

Comment thread components/Navigator/MainNavigator.tsx Outdated
Comment thread pages/award/index.tsx
Comment thread pages/award/index.tsx

{/* Video Intro */}
<section className="mb-5">
<SectionTitle>{t('award_intro_video')}</SectionTitle>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

这里会渲染出 undefined 徽标。

components/Layout/SectionTitle.tsx:8-15 里会无条件渲染 countBadge。这里没传 count,页面上会直接出现 undefined。要么传一个显式值,要么先让 SectionTitlecount != null 条件渲染徽标。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/award/index.tsx` at line 89, The SectionTitle usage for
award_intro_video is rendering an undefined badge because SectionTitle always
shows its count Badge even when no count is passed. Update the award page to
pass an explicit count, or change SectionTitle in
components/Layout/SectionTitle.tsx so the Badge is rendered only when count !=
null. Use the SectionTitle component and its count prop to locate and fix the
issue.

Comment thread pages/library/[id].tsx
Comment on lines +10 to +14
export const getServerSideProps = compose(cache(), errorLogger, async ({ params }) => {
const book = libraryBooks.find((b) => b.id === params.id);
if (!book) return { notFound: true };
return { props: { book } };
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 确认 next-ssr-middleware compose 回调中 params 的类型定义
fd -t f 'index.d.ts|index.ts' node_modules/next-ssr-middleware 2>/dev/null | xargs -r rg -n 'params' -C2 || echo "查看 next-ssr-middleware 类型以确认 params 是否可能为 undefined"

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 185


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) 定位相关页面与 compose 用法
git ls-files 'pages/library/[id].tsx' 'pages/**/*.tsx' 'package.json' 'tsconfig.json' | sed -n '1,120p'

echo '--- pages/library/[id].tsx ---'
wc -l pages/library/[id].tsx
cat -n pages/library/[id].tsx | sed -n '1,180p'

echo '--- search compose/cache/errorLogger/getServerSideProps ---'
rg -n "compose\\(|cache\\(|errorLogger|getServerSideProps|params\\.id|notFound" pages components lib . -g '!node_modules' -g '!dist' -g '!build'

# 2) 若存在相关类型/实现,查看定义
echo '--- possible middleware package references ---'
rg -n "next-ssr-middleware|compose\\s*\\(" . -g '!node_modules' -g '!dist' -g '!build'

echo '--- package.json ---'
cat -n package.json | sed -n '1,220p'

echo '--- tsconfig.json ---'
cat -n tsconfig.json | sed -n '1,220p'

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 20949


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 3) 精准检查该页面的动态路由数据获取写法
ast-grep outline pages/library/[id].tsx --view expanded || true

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 391


🌐 Web query:

next-ssr-middleware compose async callback params type params undefined string string[] GetServerSideProps

💡 Result:

In Next.js, the params object within GetServerSidePropsContext is typed as ParsedUrlQuery | undefined [1][2]. Because dynamic route parameters in Next.js can resolve to either a string or a string[] (in the case of catch-all routes), accessing them directly requires narrowing the type or using optional chaining [3][4]. When using the next-ssr-middleware library's compose function, you can provide a generic type argument to define the expected structure of params [5][6]. This allows you to treat the parameters as a specific object type within your async callback [5][7]. Example implementation with next-ssr-middleware: import { compose } from 'next-ssr-middleware'; // Define the shape of your route params type MyParams = { id: string }; export const getServerSideProps = compose<MyParams, MyPageProps>( async ({ params }) => { // params is typed as MyParams | undefined // Use optional chaining or a null check to safely access it const id = params?.id; if (!id) { return { notFound: true }; } return { props: { data: await fetchData(id) } }; }); Standard Next.js Type Handling: If you are not using the middleware utility, context.params may be undefined if the page is not dynamic [4]. To safely access dynamic parameters, perform a type check or cast the value after verifying the expected route structure [3]: export const getServerSideProps: GetServerSideProps = async (context) => { const { params } = context; // Narrow the type to ensure params exists if (!params) return { props: {} }; const id = params.id as string; // Cast if you are certain of the format //... }; The type string | string[] | undefined arises because Next.js treats all query and path parameters as ParsedUrlQuery, which is essentially an object mapping keys to string | string[] [3][1]. When accessing a specific key, you must account for the possibility that the parameter is an array (from a catch-all route) or undefined (if the route does not match) [3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 查看 libraryBooks 的 id 类型,判断 params.id 的比较是否存在实际类型不匹配风险
ast-grep outline pages/library/books.ts --view expanded || true
echo '--- pages/library/books.ts ---'
wc -l pages/library/books.ts
cat -n pages/library/books.ts | sed -n '1,220p'

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 6429


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 仅读取与 book 数据结构相关的定义
rg -n "export const libraryBooks|id:|interface .*Book|type .*Book" pages/library -g '!node_modules'

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 1099


params.id 补上显式类型收窄。
params 在 Pages Router 里可能为空,这里直接用于 find 会把路由契约留在隐式类型上;改成 compose<{ id: string }>(...) 后再用 params?.id 判空更稳,也和同类动态路由页保持一致。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/library/`[id].tsx around lines 10 - 14, `getServerSideProps` is relying
on an implicit `params` shape, so `params.id` should be narrowed explicitly
before using it in the `libraryBooks.find` lookup. Update the `compose` call in
`getServerSideProps` to carry the `{ id: string }` generic, and then guard
access with `params?.id` so the dynamic route handling matches the rest of the
Pages Router patterns and avoids assuming `params` is always present.

Comment thread pages/library/[id].tsx
Comment on lines +22 to +24
<a href="/library" className="btn btn-sm btn-outline-secondary mb-4">
&larr; {t("back_to_library")}
</a>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

封面图与返回链接应使用 React Bootstrap 组件 / next/imagenext/link

第 28-33 行的原生 <img>、第 22-24 与 105 行的原生 <a class="btn"> 均违反“UI 必须使用 React Bootstrap 组件”的规范,且内部跳转应走 next/link。建议:图片用 next/image(或 Card.Img),返回按钮用 Button + next/link

As per coding guidelines: "ALWAYS use React Bootstrap components instead of custom HTML elements in UI code".

Also applies to: 28-33, 105-105

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/library/`[id].tsx around lines 22 - 24, The page still uses native
`<a>` and `<img>` elements, which violates the UI component guideline. In
`pages/library/[id].tsx`, replace the back-link and any other internal
navigation anchors with `next/link` wrapped around a React Bootstrap `Button`,
and replace the cover image rendering with `next/image` (or `Card.Img`) instead
of a raw `<img>`. Make the same UI-component-only update anywhere else in this
component that uses the native anchor/button pattern, using the existing
`t(...)` text and the library detail render flow as your guide.

Source: Coding guidelines

Comment thread pages/library/books.ts
id: 'the-cathedral-and-the-bazaar',
title: 'The Cathedral and the Bazaar',
author: 'Eric S. Raymond',
cover: 'https://images-na.ssl-images-amazon.com/images/I/51G9NqRf1-L._SX331_BO1,204,203,200_.jpg',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 检查封面 URL 的可达性
urls=(
"https://images-na.ssl-images-amazon.com/images/I/51G9NqRf1-L._SX331_BO1,204,203,200_.jpg"
"https://producingoss.com/images/producingoss-cover-small.gif"
"https://www.theopensourceway.org/images/cover.png"
)
for u in "${urls[@]}"; do
  echo -n "$u -> "
  curl -s -o /dev/null -w "%{http_code}\n" -L "$u"
done

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 406


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="pages/library/books.ts"

echo "== outline =="
ast-grep outline "$file" --view expanded || true

echo
echo "== cover URLs =="
python3 - <<'PY'
from pathlib import Path
import re
text = Path("pages/library/books.ts").read_text(encoding="utf-8-sig")
for i, line in enumerate(text.splitlines(), 1):
    if "cover:" in line or "images-na.ssl-images-amazon.com" in line or "producingoss.com" in line or "theopensourceway.org" in line:
        print(f"{i}: {line}")
PY

Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io

Length of output: 1471


替换失效的封面链接,并加占位图兜底。

pages/library/books.ts 里这几处外链已失效:83245(Amazon / theopensourceway 返回 404;producingoss 目前可用)。建议把失效封面迁到仓库静态资源或可控 CDN,并在列表/详情页加加载失败兜底,避免直接缺图。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/library/books.ts` at line 8, The book cover URLs referenced in books
data are using external links that may return 404, so update the affected
entries in the books list to use stable assets from the repository or a
controlled CDN, and add an image load-fallback placeholder for the list/detail
rendering path. Locate the cover definitions in books.ts and the components that
render those covers, and make sure the fallback is applied when the image fails
to load so missing external images do not show as broken thumbnails.

Comment thread pages/library/index.tsx
Comment on lines +101 to +105
<div className="mt-auto">
<a href={`/library/${book.id}`} className="btn btn-outline-primary btn-sm w-100 stretched-link">
{t('view_details')}
</a>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

应使用 React Bootstrap 组件 + 客户端导航,而非原生 <a class="btn">

按编码规范,UI 中应始终使用 React Bootstrap 组件而非自定义 HTML 元素;同时内部跳转应使用 next/link 以获得客户端导航与预取能力,而非整页刷新的原生 <a>。建议用 Button 配合 next/link(如 as={Link}Link 包裹 Button)。

As per coding guidelines: "ALWAYS use React Bootstrap components instead of custom HTML elements in UI code".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pages/library/index.tsx` around lines 101 - 105, Replace the native anchor
used in the book card with React Bootstrap + Next.js client navigation: in
pages/library/index.tsx, update the view_details link inside the book item
render to use next/link together with a React Bootstrap Button instead of an <a
className="btn">. Keep the same destination built from book.id, preserve the
existing styling/size/width, and ensure the component imports and uses Link and
Button consistently with the rest of the UI.

Source: Coding guidelines

Open-Source-Bazaar-Contributor added 2 commits June 30, 2026 17:05
- Fix syntax: add comma after /bounty nav entry
- Add ALLOWED_EMBED_HOSTS whitelist for iframe URL security
- Add getSafeEmbedUrl helper for validated embed URLs
- Add loading=lazy to all award page iframes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant