Skip to content

[refactor] redesign & rewrite Hackathon home page#59

Merged
TechQuery merged 14 commits intoOpen-Source-Bazaar:mainfrom
lichang333:codex/static-hackathon-niuma-2026
Mar 30, 2026
Merged

[refactor] redesign & rewrite Hackathon home page#59
TechQuery merged 14 commits intoOpen-Source-Bazaar:mainfrom
lichang333:codex/static-hackathon-niuma-2026

Conversation

@lichang333
Copy link
Copy Markdown
Contributor

@lichang333 lichang333 commented Mar 30, 2026

PR-59 PR-59 PR-59 Powered by Pull Request Badge

What changed

  • remove the niuma-specific static profile registry and dedicated static detail component
  • fold the new hero and action-hub design into the existing /hackathon/[id] page (closes 重新设计并开发【开源黑客松】飞书版主页 #60)
  • generate the forms section generically from activity.databaseSchema.forms, so different activities can reuse the same layout without route-specific branches
  • keep the existing Lark-backed data flow and nested team route behavior intact

Why

TechQuery asked that the redesign be made generic instead of adding a one-off page for a single event. This update keeps the current data model and applies the new presentation layer to the shared hackathon detail page.

Impact

  • existing hackathon routes continue to load from Activity and its configured tables
  • activities with public form entries get a structured action hub automatically
  • no activity-specific route exceptions remain in the codebase

Validation

  • git diff --cached --check
  • bundled pages/hackathon/[id].tsx with esbuild
  • bundled pages/hackathon/[id]/team/[tid].tsx with esbuild
  • full dependency install and project tsc remain blocked locally because @open-source-bazaar/china-ngo-database could not be fetched from GitHub Packages with the current token permissions

Summary by CodeRabbit

  • 新功能

    • 英雄区改为主/次表单显式按钮(主表单优先:个人→项目→首项;次表单优先项目且不同于主表单),CTA 链接至对应共享链接或入口锚点。
    • 新增“入口中心”(id="entry-hub"),按步骤卡列出表单并提供查看全部表单入口。
    • 活动详情页改为徽章显示类型/主办方/日期区间、统计芯片、可选封面、议程预览与分阶段/天样式卡片;奖项/组织/模板/项目/参与者改为新卡片布局并在为空时隐藏。
  • 修复

    • 成员列表缺失时显示“—”而非连接错误。
    • 若活动元数据不完整则返回未找到页面,避免错误展示。

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

重构并类型化 hackathon 详情页:建立 FormGroup 模型、重做 hero 与入口(主/次表单 CTA 与 #entry-hub)、议程/奖项/项目/参与者卡片化并修复 members 空值;在服务端加入 schema 必要表校验;同时修正 Lark 文档复制接口读取复制后 token 字段为 node_token

Changes

Cohort / File(s) Summary
页面与表单模型
pages/hackathon/[id].tsx
引入 FormGroupKey/FormGroupMeta 类型化模型,从 databaseSchema.forms 派生有序 formGroups 并以 shared_limit === "anyone_editable" 过滤。增加 TypeScript 防护(将 schema 视为可选/Partial),并调整数据获取位置以配合 pages router。请注意类型/导入与 MobX observer(如适用)以及所有 UI 文本是否已用 t() 包裹。
Hero 与入口交互
pages/hackathon/[id].tsx
移除原有 Dropdown/ButtonGroup UI,改为计算 primaryForm(优先 Person->Project)与 secondaryForm,渲染最多两个 CTA(链接到各自 shared_url 或回退到 #entry-hub)。Hero 改用 badge、统计 chips、可选 hero 图片与议程预览,关注 next/image 使用与渲染成本。
Action Hub(entry-hub)与表单卡片
pages/hackathon/[id].tsx
新增 id="entry-hub" 区块:按步骤渲染每个 formGroup 的入口卡片并列出匹配公开表单(使用 shared_url);引入/使用 TableFormView 或等效组件。处理 projects[].members 时避免对 undefined 调用 .join(),用 '-' 作为回退显示。
议程/奖项/组织/项目/参与者渲染
pages/hackathon/[id].tsx
将 Agenda 改为 phase/day 风格卡片,新增 tone/class 映射与类型标签翻译;调整 prizes/organizations/templates/projects/participants 的条件渲染与卡片布局。注意性能、避免不必要重渲染和确保文本国际化。
清理与依赖调整
pages/hackathon/[id].tsx, package.json
移除不再使用的 react-bootstrap 组件导入(如 ButtonGroupDropdownDropdownButton)并清理相关逻辑。确认是否需统一保留 React Bootstrap 及样式一致性。
服务端校验(getServerSideProps)
pages/hackathon/[id].tsx
在 SSR 中新增对 databaseSchema.appId 与必需表(PersonOrganizationAgendaPrizeTemplateProject)的存在性校验,缺失时返回 { notFound: true };表数据获取改用受 Guard 的 schema.appId
Lark 文档复制接口
pages/api/Lark/document/copy/[...slug].ts
修正复制后文件 ID 的读取:当 lark.copyFile 返回对象不含 token 字段时,改为读取 node_token(之前为 obj_token),并用该 newId 进行后续所有权转移逻辑。

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

表单重整主次分,Hero 亮相议程亲,
入口汇聚成一处,成员空值不再惊,
复制接口改新钥,前后端合奏成章。 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The pull request title accurately summarizes the main change: a comprehensive redesign and rewrite of the hackathon home page, which aligns with the substantial refactoring of pages/hackathon/[id].tsx (982 lines added) and the objectives to generalize the design across activities.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

Copy link
Copy Markdown
Member

@TechQuery TechQuery left a comment

Choose a reason for hiding this comment

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

做就做通用的,把你的新设计融入到现有页面。我不接受为某次活动做专页,那样整个代码库就乱了去了。

@lichang333 lichang333 changed the title [codex] Add static fallback for niuma hackathon page [codex] Generalize hackathon page redesign Mar 30, 2026
@lichang333 lichang333 marked this pull request as ready for review March 30, 2026 12:58
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
pages/hackathon/[id].tsx (1)

70-90: 别把“泛化”又收回成四个硬编码分组

FormGroupKeyFormButtonBar 仍然把可展示表单白名单锁死为这四类。只要 databaseSchema.forms 里出现新的公开分组,这里就会被静默忽略,页面入口也不会显示。既然这次目标是按 schema 驱动页面,建议按实际 forms key 迭代,再让 FormSectionMeta 只做已知分组的展示覆写。

Also applies to: 132-139

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pages/hackathon/`[id].tsx around lines 70 - 90, FormButtonBar and
FormGroupKey are hardcoded to four groups, so change the rendering to derive
available form groups from the runtime schema (databaseSchema.forms) instead of
the fixed FormGroupKey/FormButtonBar list: iterate
Object.keys(databaseSchema.forms) (or the schema's forms map) to build the list
of FormGroup entries used by the UI and the button bar, and keep FormSectionMeta
only as an optional override map for known keys (use it when a key matches,
otherwise use schema-provided title/description). Update any code referencing
FormButtonBar/FormGroupKey (including the code that builds form sections and the
logic currently relying on those constants) to consume the dynamically generated
keys so new public groups appear automatically.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pages/hackathon/`[id].tsx:
- Around line 92-113: FormSectionMeta and other hardcoded user-facing strings
(e.g., CTA texts, "Agenda Preview", "Action Hub · Forms", step labels) bypass
i18n; replace the literal strings with translation keys in the data objects
(keep keys like FormSectionMeta.Person.eyebrow, .title, .description) and update
the rendering components to call t() when displaying these fields (e.g., map
over FormSectionMeta entries and pass t(meta.title) / t(meta.description) to the
UI), and similarly change CTA, Agenda Preview, Action Hub · Forms and Step label
sources to store keys and use t() in their components so all UI text goes
through i18n.
- Around line 164-171: 将页面中原生 span/div 切换为 React Bootstrap 组件以保持样式一致:在使用
styles.heroTag 的位置(activityType 和 hostTags 渲染处)用 <Badge> 替代 <span
className={styles.heroTag}> 并保留 key 和内容;将渲染 stat chip 的区域(使用 styles.statChip
的地方)改用 <Stack>(或用 React Bootstrap 的水平 Stack)包裹多个 <Badge> 并保持原有类名/样式映射;将
heroVisualList/Item 列表(styles.heroVisualList 和 styles.heroVisualListItem)改用
<ListGroup> 和 <ListGroup.Item>(或等价的 Card/List 组件)来渲染每一项。别忘了在文件顶部从
react-bootstrap 导入 Badge、Stack、ListGroup,并保留现有 key/props 和可访问性属性。
- Around line 273-297: Change the step cards container from a plain Row/Col
mapping to an ordered list by rendering the Row as an <ol> with className
"list-unstyled" and rendering each Col as an <li> so screen readers get proper
counting; specifically, update the JSX where formGroups is mapped (symbols:
formGroups, Row, Col, Card) to use Row as="ol" className="list-unstyled mt-4
g-3" (preserve md={2} xl={4}) and Col as="li" (keep key={key}), leaving Card and
its children unchanged so layout and styles remain identical while providing
correct <ol>/<li> semantics.

---

Nitpick comments:
In `@pages/hackathon/`[id].tsx:
- Around line 70-90: FormButtonBar and FormGroupKey are hardcoded to four
groups, so change the rendering to derive available form groups from the runtime
schema (databaseSchema.forms) instead of the fixed FormGroupKey/FormButtonBar
list: iterate Object.keys(databaseSchema.forms) (or the schema's forms map) to
build the list of FormGroup entries used by the UI and the button bar, and keep
FormSectionMeta only as an optional override map for known keys (use it when a
key matches, otherwise use schema-provided title/description). Update any code
referencing FormButtonBar/FormGroupKey (including the code that builds form
sections and the logic currently relying on those constants) to consume the
dynamically generated keys so new public groups appear automatically.
🪄 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: 70bd4d76-de5f-4139-869d-775b6b32051d

📥 Commits

Reviewing files that changed from the base of the PR and between babc91a and fec9d0a.

⛔ Files ignored due to path filters (1)
  • styles/Hackathon.module.less is excluded by none and included by none
📒 Files selected for processing (1)
  • pages/hackathon/[id].tsx

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pages/hackathon/[id].tsx (1)

401-404: ⚠️ Potential issue | 🟡 Minor

members 可能为 undefined,需添加空值保护

根据 ProjectModel.extractFields 的实现,members 字段经过 ?.map() 处理后可能返回 undefined。当前代码直接断言为 string[] 并调用 .join(),若 members 为空会导致运行时错误。

🐛 建议修改
                   <div className="text-white-50 small">
-                    <strong>{t('members')}:</strong> {(members as string[]).join(', ')}
+                    <strong>{t('members')}:</strong> {(members as string[] | undefined)?.join(', ') || '-'}
                   </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pages/hackathon/`[id].tsx around lines 401 - 404, The code renders members
with a direct cast and .join() which can throw if members is undefined; update
the render in the component that uses the members variable (from
ProjectModel.extractFields) to guard against undefined by using a safe default
or conditional rendering—e.g., treat members as (members ?? []) before calling
.join(', ') or render a fallback like "—" when members is falsy; locate the
usage where members is cast to string[] and replace it with a null-safe
expression or an if-check to prevent runtime errors.
🧹 Nitpick comments (1)
pages/hackathon/[id].tsx (1)

73-73: 移除冗余类型别名,直接使用 TableFormView

FormLink 只是 TableFormView 的别名,增加了不必要的间接层。按照之前的 review 建议,应直接使用 TableFormView

♻️ 建议修改
-type FormLink = TableFormView;
-
 interface FormGroupMeta {
   description: I18nKey;
   eyebrow: I18nKey;
   title: I18nKey;
 }

 interface FormGroup {
   key: FormGroupKey;
-  list: FormLink[];
+  list: TableFormView[];
   meta: FormGroupMeta;
 }

同时更新 isPublicForm 函数签名:

-const isPublicForm = ({ shared, shared_limit }: FormLink) =>
+const isPublicForm = ({ shared, shared_limit }: TableFormView) =>
   shared || ['tenant_editable', 'anyone_editable'].includes(shared_limit as string);

以及 forms 变量类型:

 const forms = (((databaseSchema as BiTableSchema).forms || {}) as Partial<
-    Record<FormGroupKey, FormLink[]>
+    Record<FormGroupKey, TableFormView[]>
   >)!;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pages/hackathon/`[id].tsx at line 73, Remove the redundant type alias
FormLink and use TableFormView directly: delete the declaration "type FormLink =
TableFormView" and update all references — including the isPublicForm function
signature (replace any parameter or return types that use FormLink with
TableFormView) and the forms variable type (use TableFormView[] or
ReadonlyArray<TableFormView> as appropriate). Ensure imports/types still resolve
and run type-checking to catch any remaining FormLink occurrences to replace.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@pages/hackathon/`[id].tsx:
- Around line 401-404: The code renders members with a direct cast and .join()
which can throw if members is undefined; update the render in the component that
uses the members variable (from ProjectModel.extractFields) to guard against
undefined by using a safe default or conditional rendering—e.g., treat members
as (members ?? []) before calling .join(', ') or render a fallback like "—" when
members is falsy; locate the usage where members is cast to string[] and replace
it with a null-safe expression or an if-check to prevent runtime errors.

---

Nitpick comments:
In `@pages/hackathon/`[id].tsx:
- Line 73: Remove the redundant type alias FormLink and use TableFormView
directly: delete the declaration "type FormLink = TableFormView" and update all
references — including the isPublicForm function signature (replace any
parameter or return types that use FormLink with TableFormView) and the forms
variable type (use TableFormView[] or ReadonlyArray<TableFormView> as
appropriate). Ensure imports/types still resolve and run type-checking to catch
any remaining FormLink occurrences to replace.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1c4562ce-9328-4863-9cc3-2c9fc2d78fed

📥 Commits

Reviewing files that changed from the base of the PR and between fec9d0a and 6c5d0fa.

⛔ Files ignored due to path filters (4)
  • styles/Hackathon.module.less is excluded by none and included by none
  • 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
📒 Files selected for processing (1)
  • pages/hackathon/[id].tsx

@TechQuery TechQuery added the feature New feature or request label Mar 30, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pages/hackathon/`[id].tsx:
- Around line 460-462: The JSX renders {score as number} inside the score circle
but score can be undefined; change the rendering in the component that uses
score (the element with className={styles.scoreCircle}) to use a safe fallback
like (score ?? 0) or a placeholder string (e.g., "—") so it never renders
undefined, and ensure any numeric formatting (e.g., Math.round or toFixed) is
applied after the nullish coalescing.
- Around line 138-140: The code accesses databaseSchema.forms directly which can
throw if databaseSchema is undefined; update the extraction of forms (the line
using (databaseSchema as BiTableSchema).forms) to use optional chaining and a
safe default (e.g., treat forms as ((databaseSchema as BiTableSchema)?.forms ??
{}) cast to Partial<Record<FormGroupKey, TableFormView[]>>) so forms is always a
defined object; ensure this change references the same identifiers
(databaseSchema, BiTableSchema, forms, FormGroupKey, TableFormView) so
downstream code still compiles.
- Line 43: The code unconditionally destructures const { appId, tableIdMap } =
activity.databaseSchema as BiTableSchema; but activity.databaseSchema can be
null/undefined; update the handler in pages/hackathon/[id].tsx to defensively
check activity.databaseSchema (or use optional chaining) before destructuring—if
it's missing, return a 404 response (or render the not-found path) instead of
proceeding; reference the activity.databaseSchema and BiTableSchema usage and
ensure any downstream code that expects appId/tableIdMap only runs after the
existence check.
- Line 369: The Agenda.type field is currently a generic TableCellValue and may
produce unexpected class names; update the Agenda type in models/Hackathon.ts to
a string union of the five allowed values ("formation" | "enrollment" |
"competition" | "break" | "evaluation") so TypeScript enforces valid values,
then replace the direct indexing in pages/hackathon/[id].tsx (the expression
styles[type?.toString().toLowerCase() || "break"]) with a defensive mapping or
type-guard: create a const record/map of allowedClassNames keyed by the union
and look up the normalized type via a narrow function (or check against the
union keys) and fall back to "break" for any out-of-spec value, ensuring no
undefined className is applied.
🪄 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: 07f1f8a4-4e6b-459a-a14d-00892aa27867

📥 Commits

Reviewing files that changed from the base of the PR and between 6c5d0fa and 1674037.

⛔ Files ignored due to path filters (1)
  • styles/Hackathon.module.less is excluded by none and included by none
📒 Files selected for processing (1)
  • pages/hackathon/[id].tsx

{agenda.map(({ name, type, summary, startedAt, endedAt }) => (
<li
key={name as string}
className={`${styles.agendaItem} ${styles[type?.toString().toLowerCase() || "break"]}`}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 检查 Hackathon.module.less 中定义的 agendaItem 相关样式类
rg -n "agendaItem|\.break|\.session|\.keynote|\.workshop" styles/Hackathon.module.less

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

Length of output: 124


🏁 Script executed:

# 查看 Hackathon.module.less 中 agendaItem 及其嵌套样式的完整定义
sed -n '238,300p' styles/Hackathon.module.less

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

Length of output: 1477


🏁 Script executed:

# 在页面文件中查找 type 的数据来源和可能的值
rg -n "type" pages/hackathon/\[id\].tsx | head -20

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

Length of output: 574


🏁 Script executed:

# 查看整个样式模块中所有的嵌套选择器结构
grep -n "^\s*&\." styles/Hackathon.module.less

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

Length of output: 190


🏁 Script executed:

# 查看 pages/hackathon/[id].tsx 的数据获取逻辑和 type 定义
grep -n "getStaticProps\|agenda\|interface\|type.*=" pages/hackathon/\[id\].tsx | head -30

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

Length of output: 1137


🏁 Script executed:

# 查找 agenda 相关的类型定义
rg -n "agenda|ActivityType" pages/hackathon/\[id\].tsx -A 3 -B 1

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

Length of output: 2536


🏁 Script executed:

# 查找是否有其他地方定义了所有可能的 type 值
rg -n "formation|enrollment|competition|evaluation" --type tsx --type ts | grep -E "(type|enum|const.*=)" | head -20

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

Length of output: 118


🏁 Script executed:

# 查找 Agenda 接口的定义位置
rg -n "interface Agenda|type Agenda" --type ts --type tsx

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

Length of output: 118


🏁 Script executed:

# 查找可能的 ActivityModel 或相关类型定义
rg -n "ActivityModel|Activity.*type" --type ts --type tsx | head -20

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

Length of output: 118


🏁 Script executed:

# 搜索所有导入,找到 Agenda 的来源
rg -n "^import.*Agenda|from.*agenda" pages/hackathon/\[id\].tsx

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

Length of output: 73


🏁 Script executed:

# 不使用 --type 标志,直接搜索 Agenda 接口定义
rg -n "interface Agenda|type Agenda|export.*Agenda" pages/ lib/ models/ types/ 2>/dev/null | head -20

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

Length of output: 225


🏁 Script executed:

# 查找页面文件的导入语句
head -50 pages/hackathon/\[id\].tsx | grep -n "import"

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

Length of output: 905


🏁 Script executed:

# 搜索 ActivityModel 相关的类型
rg -n "ActivityModel|class Activity" . 2>/dev/null | head -10

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

Length of output: 1020


🏁 Script executed:

# 在整个仓库中搜索 type 字段定义,包含所有可能值
rg -n "formation|enrollment|competition|evaluation" --max-count 30

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

Length of output: 1316


🏁 Script executed:

# 查看 models/Hackathon.ts 中 Agenda 接口的完整定义
cat -n models/Hackathon.ts | head -40

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

Length of output: 1247


🏁 Script executed:

# 搜索 Agenda 类型定义的具体内容
rg -n -A 10 "export type Agenda" models/Hackathon.ts

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

Length of output: 500


动态样式类名缺少类型约束和防御

Agenda 类型定义中,type 字段为通用 TableCellValue 类型,未使用联合类型约束可能的值。代码在 line 369 使用 styles[type?.toString().toLowerCase() || "break"] 时,仅为 falsy 值提供了 "break" 的兜底,但无法处理后端返回超出预期的类型值

现有 CSS 类仅涵盖 5 种:formationenrollmentcompetitionbreakevaluation。若后端引入新类型而样式未同步,会导致 undefined 类名被应用。

建议:

  1. models/Hackathon.ts 中为 Agenda.type 定义联合类型,明确列出所有可能值,利用 TypeScript 强类型
  2. 增加防御性兜底:使用完整的类型检查或 CSS 类名映射表,确保任何超出预期的值都有默认样式处理
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pages/hackathon/`[id].tsx at line 369, The Agenda.type field is currently a
generic TableCellValue and may produce unexpected class names; update the Agenda
type in models/Hackathon.ts to a string union of the five allowed values
("formation" | "enrollment" | "competition" | "break" | "evaluation") so
TypeScript enforces valid values, then replace the direct indexing in
pages/hackathon/[id].tsx (the expression styles[type?.toString().toLowerCase()
|| "break"]) with a defensive mapping or type-guard: create a const record/map
of allowedClassNames keyed by the union and look up the normalized type via a
narrow function (or check against the union keys) and fall back to "break" for
any out-of-spec value, ensuring no undefined className is applied.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
pages/hackathon/[id].tsx (2)

38-46: ⚠️ Potential issue | 🔴 Critical

databaseSchema 防御性检查不完整,仍会导致运行时错误

当前 (activity.databaseSchema || {}) 的写法在 databaseSchema 为空时会得到 {},解构后 appIdtableIdMap 均为 undefined,传入后续 Model 构造函数会抛出异常。

建议在访问前校验并返回 404:

🛡️ 建议修复
   const activity = await new ActivityModel().getOne(params!.id);

-  const { appId, tableIdMap } = (activity.databaseSchema || {}) as BiTableSchema;
+  const schema = activity.databaseSchema as BiTableSchema | undefined;
+  if (!schema?.appId || !schema?.tableIdMap) {
+    return { notFound: true };
+  }
+  const { appId, tableIdMap } = schema;

   const [people, organizations, agenda, prizes, templates, projects] = await Promise.all([
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pages/hackathon/`[id].tsx around lines 38 - 46, The code destructures appId
and tableIdMap from activity.databaseSchema without validating it so
appId/tableIdMap can be undefined and will throw when used to construct models
(PersonModel, OrganizationModel, AgendaModel, PrizeModel, TemplateModel,
ProjectModel) and call getAll(); fix by checking activity.databaseSchema is
present and matches BiTableSchema (has appId and tableIdMap) before creating the
models, and if missing return a 404 response (or throw a NotFound) instead of
proceeding.

318-321: ⚠️ Potential issue | 🟡 Minor

动态样式类名缺少防御性兜底

type 值不在预定义的 CSS 类名集合(formationenrollmentcompetitionbreakevaluation)中时,styles[...] 返回 undefined,导致无效的 className

建议使用类名映射表确保安全:

🛡️ 建议修复
+const AgendaTypeStyles = ['formation', 'enrollment', 'competition', 'break', 'evaluation'] as const;
+type AgendaStyleKey = (typeof AgendaTypeStyles)[number];
+
+const getAgendaStyle = (type: unknown): AgendaStyleKey => {
+  const normalized = type?.toString().toLowerCase() as AgendaStyleKey;
+  return AgendaTypeStyles.includes(normalized) ? normalized : 'break';
+};

// 在 render 中使用:
-className={`${styles.agendaItem} ${styles[type?.toString().toLowerCase() || 'break']}`}
+className={`${styles.agendaItem} ${styles[getAgendaStyle(type)]}`}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pages/hackathon/`[id].tsx around lines 318 - 321, 在渲染 agenda
列表时动态访问样式(styles[type?.toString().toLowerCase() || 'break'])在 type 不在预期集合时会返回
undefined;为修复,在使用 agenda.map 的渲染处引入一个安全的类名映射(例如 const TYPE_CLASS_MAP = {
formation: styles.formation, enrollment: styles.enrollment, competition:
styles.competition, break: styles.break, evaluation: styles.evaluation }),然后用
lookup = TYPE_CLASS_MAP[(type?.toString().toLowerCase() ?? '')] ||
styles.break(或其他默认类)代替直接索引,保持现有 styles.agendaItem 串联,从而保证 className 始终有效且有兜底样式。
♻️ Duplicate comments (1)
pages/hackathon/[id].tsx (1)

389-389: ⚠️ Potential issue | 🟡 Minor

score 可能为 undefined,需要增加兜底显示

当项目没有评分时,直接渲染 {score as number} 会显示空白或 undefined。建议增加兜底值:

-<div className={styles.scoreCircle}>{score as number}</div>
+<div className={styles.scoreCircle}>{(score as number) ?? '-'}</div>

,

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pages/hackathon/`[id].tsx at line 389, The score value can be undefined and
currently renders as `{score as number}`; update the rendering inside the div
with className `styles.scoreCircle` to use a safe fallback (e.g. `score ?? 0` or
a display string like `'—'`) so an explicit default is shown when score is
missing; change the expression from casting to a nullish-coalescing or
conditional expression (refer to the `score` variable and the
`styles.scoreCircle` div) to ensure a predictable fallback display.
🧹 Nitpick comments (2)
pages/api/Lark/document/copy/[...slug].ts (2)

16-19: 可选:简化冗余的类型断言

当前三元表达式中 type === 'wiki' 的判断与类型断言 as 'wiki' / as LarkDocumentPathType 重复。由于 copyFile 方法签名可能已经处理了这两种情况,可以考虑简化:

♻️ 简化建议
-  const copiedFile =
-    type === 'wiki'
-      ? await lark.copyFile(`${type as 'wiki'}/${id}`, name, parentToken)
-      : await lark.copyFile(`${type as LarkDocumentPathType}/${id}`, name, parentToken);
+  const copiedFile = await lark.copyFile(
+    `${type as LarkDocumentPathType | 'wiki'}/${id}`,
+    name,
+    parentToken,
+  );

如果 mobx-lark 的类型定义要求区分处理,则保持现状即可。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pages/api/Lark/document/copy/`[...slug].ts around lines 16 - 19, The ternary
uses redundant type assertions when calling lark.copyFile: remove the duplicated
casts and pass the same constructed path string for both branches (use
`${type}/${id}`) or, if the library requires a specific union type, narrow
`type` once before the call (e.g., ensure `type` has the correct
union/LarkDocumentPathType) and then call lark.copyFile(path, name, parentToken)
without per-branch `as` assertions; update the code around the copiedFile
assignment and lark.copyFile invocation to use the simplified single call and
keep id, name, parentToken unchanged.

23-31: 所有权转移失败处理方式可接受,但建议增强日志

当前 transferOwner 失败时仅打印错误日志,不影响主流程返回。这种"尽力而为"的模式是合理的,但建议在日志中增加上下文信息(如 typenewId),便于后续排查:

♻️ 增强日志建议
     } catch (error) {
-      console.error(JSON.stringify(error, null, 2));
+      console.error('transferOwner failed:', { type, newId, ownerType, ownerId, error });
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pages/api/Lark/document/copy/`[...slug].ts around lines 23 - 31, When
transferOwner fails in the block calling lark.driveFileStore.transferOwner(type,
newId, { member_type: ownerType, member_id: ownerId }), enhance the catch to log
contextual info (type and newId and ownerType/ownerId) along with the error so
failures are diagnosable; update the catch for the transferOwner call to include
a descriptive message and the relevant identifiers (type, newId, ownerType,
ownerId) plus the error object instead of only JSON.stringify(error).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pages/api/Lark/document/copy/`[...slug].ts:
- Line 21: newId currently falls back to a non-existent copiedFile.node_token;
change logic to use copiedFile.token directly (remove the 'token' in copiedFile
? ... : copiedFile.node_token fallback) because CopiedFile only exposes token;
if you actually need a WikiNode (node_token) instead, first call
WikiNodeModel.moveDocument({ obj_type, obj_token }, ...) to mount the doc into
Wiki and obtain a WikiNode, then use that node's node_token—update references to
copiedFile and newId accordingly and remove any assumptions about node_token on
CopiedFile.

In `@pages/hackathon/`[id].tsx:
- Around line 122-125: The destructuring const { forms } = (databaseSchema ||
{}) as BiTableSchema can leave forms undefined and cause forms[key] to throw;
update the code to provide a safe default (e.g., const { forms = {} } =
(databaseSchema || {}) as BiTableSchema) or use optional chaining/fallback when
accessing forms (e.g., (forms?.[key] || []) ) inside the FormButtonBar.flatMap
callback so FormButtonBar, formGroups and isPublicForm logic never attempt to
index into undefined.

---

Outside diff comments:
In `@pages/hackathon/`[id].tsx:
- Around line 38-46: The code destructures appId and tableIdMap from
activity.databaseSchema without validating it so appId/tableIdMap can be
undefined and will throw when used to construct models (PersonModel,
OrganizationModel, AgendaModel, PrizeModel, TemplateModel, ProjectModel) and
call getAll(); fix by checking activity.databaseSchema is present and matches
BiTableSchema (has appId and tableIdMap) before creating the models, and if
missing return a 404 response (or throw a NotFound) instead of proceeding.
- Around line 318-321: 在渲染 agenda
列表时动态访问样式(styles[type?.toString().toLowerCase() || 'break'])在 type 不在预期集合时会返回
undefined;为修复,在使用 agenda.map 的渲染处引入一个安全的类名映射(例如 const TYPE_CLASS_MAP = {
formation: styles.formation, enrollment: styles.enrollment, competition:
styles.competition, break: styles.break, evaluation: styles.evaluation }),然后用
lookup = TYPE_CLASS_MAP[(type?.toString().toLowerCase() ?? '')] ||
styles.break(或其他默认类)代替直接索引,保持现有 styles.agendaItem 串联,从而保证 className 始终有效且有兜底样式。

---

Duplicate comments:
In `@pages/hackathon/`[id].tsx:
- Line 389: The score value can be undefined and currently renders as `{score as
number}`; update the rendering inside the div with className
`styles.scoreCircle` to use a safe fallback (e.g. `score ?? 0` or a display
string like `'—'`) so an explicit default is shown when score is missing; change
the expression from casting to a nullish-coalescing or conditional expression
(refer to the `score` variable and the `styles.scoreCircle` div) to ensure a
predictable fallback display.

---

Nitpick comments:
In `@pages/api/Lark/document/copy/`[...slug].ts:
- Around line 16-19: The ternary uses redundant type assertions when calling
lark.copyFile: remove the duplicated casts and pass the same constructed path
string for both branches (use `${type}/${id}`) or, if the library requires a
specific union type, narrow `type` once before the call (e.g., ensure `type` has
the correct union/LarkDocumentPathType) and then call lark.copyFile(path, name,
parentToken) without per-branch `as` assertions; update the code around the
copiedFile assignment and lark.copyFile invocation to use the simplified single
call and keep id, name, parentToken unchanged.
- Around line 23-31: When transferOwner fails in the block calling
lark.driveFileStore.transferOwner(type, newId, { member_type: ownerType,
member_id: ownerId }), enhance the catch to log contextual info (type and newId
and ownerType/ownerId) along with the error so failures are diagnosable; update
the catch for the transferOwner call to include a descriptive message and the
relevant identifiers (type, newId, ownerType, ownerId) plus the error object
instead of only JSON.stringify(error).
🪄 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: a38f5fa5-e9d2-46d5-ae97-5eee6d660871

📥 Commits

Reviewing files that changed from the base of the PR and between 1674037 and ae8eed3.

⛔ Files ignored due to path filters (1)
  • styles/Hackathon.module.less is excluded by none and included by none
📒 Files selected for processing (2)
  • pages/api/Lark/document/copy/[...slug].ts
  • pages/hackathon/[id].tsx

Copy link
Copy Markdown
Member

@TechQuery TechQuery left a comment

Choose a reason for hiding this comment

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

重写 pages/hackathon/[id].tsx 的 JSX 结构:

  1. 结构与样式:https://github.com/lichang333/niuma-hackthon-2026/blob/3e8004241167ae4ed5d4d4151cea0e1a32c87760/docs/index.html
  2. 数据沿用页面现有数据
  3. 文案尽量沿用现有 i18n 数据,没有的再翻译

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pages/hackathon/`[id].tsx:
- Around line 648-650: The string "PHASE" is hard-coded in the span with
className styles.dayNo; replace it with the i18n translator call (e.g.,
t('hackathon_phase')) so the label is localized, ensuring the component
imports/uses the t() from your i18n hook/context (where applicable) and preserve
the numeric formatting String(index + 1).padStart(2, "0"); also add the key
hackathon_phase to translation files (zh-CN.ts, en-US.ts, zh-TW.ts) with the
suggested values.
- Around line 879-890: The rendering assumes languages and tags are always
defined; to prevent runtime errors replace the direct casts and .map calls with
defensive checks (e.g., use nullish coalescing or optional chaining) so the
component maps over an empty array when values are undefined; update the JSX
around languages and tags (the expressions producing the list items using
languages and tags, and the ul with styles.topicList/topicChip/topicChipMuted)
to use (languages ?? []) and (tags ?? []) or languages?.map(...) || [] so
mapping only happens on arrays.
🪄 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: 18a63c55-e30e-4474-9d8c-5d53992ffcaf

📥 Commits

Reviewing files that changed from the base of the PR and between ae8eed3 and d6c6f97.

⛔ Files ignored due to path filters (1)
  • styles/Hackathon.module.less is excluded by none and included by none
📒 Files selected for processing (1)
  • pages/hackathon/[id].tsx

Copy link
Copy Markdown
Member

@TechQuery TechQuery left a comment

Choose a reason for hiding this comment

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

同类问题通篇都该,CSS 要跟着组件创建同名 .module.less

Copy link
Copy Markdown
Member

@TechQuery TechQuery left a comment

Choose a reason for hiding this comment

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

同类问题通篇都改!

@TechQuery TechQuery added the enhancement Some improvements label Mar 30, 2026
@TechQuery TechQuery changed the title [codex] Generalize hackathon page redesign [refactor] redesign & rewrite Hackathon home page Mar 30, 2026
@TechQuery TechQuery merged commit 9b76329 into Open-Source-Bazaar:main Mar 30, 2026
1 of 2 checks passed
@github-project-automation github-project-automation bot moved this from In review to Done in 开放会务系统 Mar 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Some improvements feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

重新设计并开发【开源黑客松】飞书版主页

3 participants