Skip to content

refactor: 拆分 unified-edit-dialog 为专用对话框组件#539

Merged
ding113 merged 2 commits intoding113:devfrom
NieiR:issue-413-split-unified-edit-dialog
Jan 6, 2026
Merged

refactor: 拆分 unified-edit-dialog 为专用对话框组件#539
ding113 merged 2 commits intoding113:devfrom
NieiR:issue-413-split-unified-edit-dialog

Conversation

@NieiR
Copy link
Contributor

@NieiR NieiR commented Jan 4, 2026

Summary

Closes #413

将 1120 行的 unified-edit-dialog.tsx 拆分为 4 个单一职责组件,提取共享逻辑到 hooks 和 utils,修复 expiresAt 清除时传参问题,并定义精确类型消除 as any

Changes

新增组件

组件 职责 行数
CreateUserDialog 创建用户 + 首个密钥 425
EditUserDialog 编辑用户信息 268
AddKeyDialog 添加新密钥 128
EditKeyDialog 编辑单个密钥 64

提取的共享代码

  • hooks/use-key-translations.ts - 密钥表单翻译
  • hooks/use-model-suggestions.ts - 模型建议列表
  • hooks/use-user-translations.ts - 用户表单翻译
  • utils/form-utils.ts - 表单工具函数
  • utils/provider-group.ts - Provider Group 处理

新增类型定义

  • KeyDialogUserContext (src/types/user.ts) - Key Dialog 所需的精简用户上下文类型

新增 Server Action

  • getModelSuggestionsByProviderGroup - 根据 Provider Group 获取模型建议

Bug 修复

类型安全改进

  • 定义 KeyDialogUserContext 类型,移除 4 处 as any 断言(响应 Code Review 反馈)

Test plan

  • TypeCheck 通过
  • Lint 通过
  • 新增单元测试 tests/unit/user-dialogs.test.tsx (18 tests)
  • 新增单元测试 tests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsx
  • 手动测试创建用户流程
  • 手动测试编辑用户流程
  • 手动测试添加/编辑密钥流程

Breaking Changes

⚠️ 删除 unified-edit-dialog.tsx,如有外部引用需更新为新组件

将 1120 行的 unified-edit-dialog.tsx 拆分为 4 个单一职责组件:
- CreateUserDialog: 创建用户 + 首个密钥
- EditUserDialog: 编辑用户信息
- AddKeyDialog: 添加新密钥
- EditKeyDialog: 编辑单个密钥

主要改动:
- 提取共享逻辑到 hooks/ (翻译、模型建议)
- 提取工具函数到 utils/ (表单处理、provider group)
- 新增 getModelSuggestionsByProviderGroup action
- 修复 expiresAt 清除时传参问题 (使用空字符串)
- 添加单元测试覆盖新组件

BREAKING CHANGE: 删除 unified-edit-dialog.tsx,引用需更新为新组件
@coderabbitai
Copy link

coderabbitai bot commented Jan 4, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

本 PR 将统一的用户/密钥编辑对话框拆分为独立的创建/编辑组件,新增密钥创建成功显示与模型建议 API,并在多语言消息、表单逻辑、UI 交互和单元测试中做相应调整与补充。

Changes

Cohort / File(s) 变更摘要
国际化消息
messages/en/dashboard.json, messages/ja/dashboard.json, messages/ru/dashboard.json, messages/zh-CN/dashboard.json, messages/zh-TW/dashboard.json
增加密钥创建成功文案(successTitlesuccessDescriptiongeneratedKey 等);新增 actions.addKey 标签;修改用户编辑对话框标题/描述及 balance 页面禁用说明等字符串一致性调整。
后端 / 提供者动作
src/actions/providers.ts
新增并导出 `getModelSuggestionsByProviderGroup(providerGroup?: string
新增对话框组件
src/app/[locale]/dashboard/_components/user/add-key-dialog.tsx, src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx, src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx, src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
分别新增 AddKeyDialogEditKeyDialogCreateUserDialogEditUserDialog 组件及其 props 接口,提供密钥创建、单键编辑、用户创建与用户编辑的独立对话流与成功视图(含复制键行为)。
移除旧的统一对话框
src/app/[locale]/dashboard/_components/user/unified-edit-dialog.tsx
完全删除原有 UnifiedEditDialog 文件及其相关类型、工具和内部逻辑,功能被拆分到上述独立组件。
表单与字段逻辑调整
src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx, src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx, src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx
expiresAt 提交改为使用 ?? 保留空串以显式清除;反转 canLoginWebUi 开关的呈现逻辑;KeyEditSection 新增可选 props showLimitRulesshowExpireTime 控制条件渲染。
翻译 Hook 与模型建议 Hook
src/app/[locale]/dashboard/_components/user/hooks/use-key-translations.ts, src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.ts, src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts
新增 useKeyTranslationsuseUserTranslations(集中翻译映射)和 useModelSuggestions(基于 providerGroup 获取模型建议)。
用户/密钥列表与表格变更
src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx, src/app/[locale]/dashboard/_components/user/user-management-table.tsx, src/app/[locale]/dashboard/_components/user/key-list-header.tsx
在行组件中集成 EditKeyDialog、添加“添加密钥”按钮及 actions.addKey 文案;UserManagementTable 替换为使用 EditUserDialog,新增并传递 onAddKey prop;key-list-header 调整传递给 AddKeyForm 的精简 user 对象(引入 limitTotalUsd、providerGroup 可空)。
页面客户端集成
src/app/[locale]/dashboard/users/users-page-client.tsx
引入并使用 CreateUserDialogAddKeyDialog,使用 useQueryClient 在密钥创建后使 users 查询失效并刷新数据,新增对应对话状态与回调。
工具函数与类型
src/app/[locale]/dashboard/_components/user/utils/form-utils.ts, src/app/[locale]/dashboard/_components/user/utils/provider-group.ts, src/types/user.ts
新增 getFirstErrorMessagenormalizeProviderGroup 工具;新增导出类型 KeyDialogUserContext(供密钥对话相关组件使用)。
测试
tests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsx, tests/unit/user-dialogs.test.tsx
新增单元测试,覆盖 AddKeyForm expiresAt 清空行为与各类用户/密钥对话框的渲染与交互。

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.47% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 标题清晰准确地总结了主要变更:将 unified-edit-dialog 拆分为专用对话框组件,与变更集高度相关。
Linked Issues check ✅ Passed PR 完整实现了 issue #413 的所有目标:拆分 unified-edit-dialog、创建 EditKeyDialog、优化密钥编辑流程。
Out of Scope Changes check ✅ Passed 所有变更均在 issue #413 和 bug #534(expiresAt 处理)的范围内,包括国际化文本更新以支持新组件。
Description check ✅ Passed PR 描述清晰地总结了关闭的 issue (#413)、核心变化(将 1120 行的 unified-edit-dialog.tsx 拆分为 4 个单一职责组件)、新增的 hooks 和 utils、类型定义、Server Action、bug 修复以及测试计划。
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @NieiR, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

本次拉取请求旨在通过将一个庞大且功能集中的对话框组件拆分为多个职责明确、易于维护的独立组件,显著提升用户和密钥管理界面的模块化和可维护性。此重构不仅优化了代码结构,还通过引入新的 hooks 和工具函数增强了逻辑复用,并添加了一个新的服务器端动作以支持模型建议功能。同时,还解决了一个关于过期时间参数传递的关键 bug,确保了数据处理的准确性。

Highlights

  • 组件重构: 将大型 unified-edit-dialog.tsx 组件拆分为四个职责单一的专用对话框组件,包括 CreateUserDialogEditUserDialogAddKeyDialogEditKeyDialog
  • 代码复用性提升: 提取了共享逻辑到新的 hooks (use-key-translations.ts, use-model-suggestions.ts, use-user-translations.ts) 和工具函数 (form-utils.ts, provider-group.ts) 中,提高了代码复用性。
  • 新增 Server Action: 新增了一个名为 getModelSuggestionsByProviderGroup 的 Server Action,用于根据供应商分组获取模型建议列表。
  • Bug 修复: 修复了 expiresAt 字段在清除时参数传递不正确的问题,确保了过期时间处理的准确性。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions bot added enhancement New feature or request area:UI area:i18n size/XL Extra Large PR (> 1000 lines) labels Jan 4, 2026
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

这次重构做得非常出色,成功地将一个庞大的 unified-edit-dialog.tsx 组件拆分成了四个职责单一的专用对话框组件。这极大地提升了代码的可读性和可维护性。将共享逻辑提取到自定义 Hooks 和工具函数中也是一个很好的实践。此外,本次提交还修复了 expiresAt 清除时的传参问题,并增加了单元测试,这对于保证代码质量至关重要。整体来看,这是一次高质量的重构。我发现了几处可以使用更严格的类型来代替 as any 的地方,以增强类型安全,具体请看我的评论。

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Code Review Summary

This is a well-structured refactoring PR that splits a large 1120-line unified dialog component into focused, single-responsibility components. The refactoring improves maintainability and includes comprehensive test coverage (18 tests). After thorough analysis across all review perspectives, no significant issues were identified that warrant inline comments.

PR Size: XL

  • Lines changed: 3783 (2559 additions + 1224 deletions)
  • Files changed: 24

Split Suggestions for XL PR:

While the PR is large, the changes are cohesive and splitting would be impractical as:

  1. The new components depend on each other and shared utilities
  2. Removing the old unified-edit-dialog.tsx requires all new components to be present
  3. The integration changes in users-page-client.tsx and user-management-table.tsx depend on the new components

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Review Notes

Positive observations (not for praise, but for context):

  1. Error Handling: All catch blocks properly log errors via console.error() with descriptive prefixes (e.g., [CreateUserDialog]) and show user feedback via toast.error(). No silent failures detected.

  2. Rollback Logic: The CreateUserDialog includes proper rollback when key creation fails after user creation (lines 186-192), preventing orphaned users.

  3. Type Safety: New Zod schemas (CreateUserSchema, CreateKeySchema, EditUserSchema) properly extend base schemas and handle edge cases like the expiresAt transformation.

  4. Model Suggestions Hook: The useModelSuggestions hook has an intentionally silent .catch() (line 21-23 in use-model-suggestions.ts) which is appropriate here since model suggestions are an optional enhancement - the form works without them.

  5. Test Coverage: New tests cover dialog rendering, prop passing, success callbacks, and accessibility (dialog titles).

Potential Future Considerations

  1. Hardcoded Chinese Strings: Lines 201-202, 218-219 in add-key-form.tsx contain hardcoded Chinese strings for Cache TTL labels. These should use i18n translations for consistency, but this is pre-existing code not modified in this PR.

  2. Console logging in production: The console.error calls are appropriate for debugging but could be replaced with a proper logger in production. This is consistent with existing patterns in the codebase.

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate (18 new tests)
  • Code clarity - Good

Automated review by Claude AI

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Code Review Summary

This is a well-structured refactoring PR that splits a large 1120-line unified dialog component into focused, single-responsibility components. The refactoring improves maintainability and includes comprehensive test coverage (18 tests). After thorough analysis across all review perspectives, no significant issues were identified that warrant inline comments.

PR Size: XL

  • Lines changed: 3783 (2559 additions + 1224 deletions)
  • Files changed: 24

Split Suggestions for XL PR:

While the PR is large, the changes are cohesive and splitting would be impractical as:

  1. The new components depend on each other and shared utilities
  2. Removing the old unified-edit-dialog.tsx requires all new components to be present
  3. The integration changes in users-page-client.tsx and user-management-table.tsx depend on the new components

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Review Notes

Positive observations (not for praise, but for context):

  1. Error Handling: All catch blocks properly log errors via console.error() with descriptive prefixes (e.g., [CreateUserDialog]) and show user feedback via toast.error(). No silent failures detected.

  2. Rollback Logic: The CreateUserDialog includes proper rollback when key creation fails after user creation (lines 186-192), preventing orphaned users.

  3. Type Safety: New Zod schemas (CreateUserSchema, CreateKeySchema, EditUserSchema) properly extend base schemas and handle edge cases like the expiresAt transformation.

  4. Model Suggestions Hook: The useModelSuggestions hook has an intentionally silent .catch() (line 21-23 in use-model-suggestions.ts) which is appropriate here since model suggestions are an optional enhancement - the form works without them.

  5. Test Coverage: New tests cover dialog rendering, prop passing, success callbacks, and accessibility (dialog titles).

Potential Future Considerations

  1. Hardcoded Chinese Strings: Lines 201-202, 218-219 in add-key-form.tsx contain hardcoded Chinese strings for Cache TTL labels. These should use i18n translations for consistency, but this is pre-existing code not modified in this PR.

  2. Console logging in production: The console.error calls are appropriate for debugging but could be replaced with a proper logger in production. This is consistent with existing patterns in the codebase.

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate (18 new tests)
  • Code clarity - Good

Automated review by Claude AI

Copy link

@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 (1)
src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx (1)

201-221: Cache TTL 部分存在硬编码的中文字符串

此处的 Label、SelectItem 和描述文本使用了硬编码的中文字符串,违反了项目的国际化规范。根据 coding guidelines,应使用 next-intl 进行国际化处理。

🔎 建议的修复方案
+  const tKeyEdit = useTranslations("dashboard.userManagement.keyEditSection.fields");

   <div className="space-y-2">
-    <Label>Cache TTL 覆写</Label>
+    <Label>{tKeyEdit("cacheTtl.label")}</Label>
     <Select
       value={form.values.cacheTtlPreference}
       onValueChange={(val) =>
         form.setValue("cacheTtlPreference", val as "inherit" | "5m" | "1h")
       }
     >
       <SelectTrigger>
         <SelectValue placeholder="inherit" />
       </SelectTrigger>
       <SelectContent>
-        <SelectItem value="inherit">不覆写(跟随供应商/客户端)</SelectItem>
-        <SelectItem value="5m">5m</SelectItem>
-        <SelectItem value="1h">1h</SelectItem>
+        <SelectItem value="inherit">{tKeyEdit("cacheTtl.options.inherit")}</SelectItem>
+        <SelectItem value="5m">{tKeyEdit("cacheTtl.options.5m")}</SelectItem>
+        <SelectItem value="1h">{tKeyEdit("cacheTtl.options.1h")}</SelectItem>
       </SelectContent>
     </Select>
-    <p className="text-xs text-muted-foreground">
-      强制为包含 cache_control 的请求设置 Anthropic prompt cache TTL。
-    </p>
+    <p className="text-xs text-muted-foreground">{tKeyEdit("cacheTtl.description")}</p>
   </div>

根据 coding guidelines,使用 next-intl 进行国际化,支持 5 种语言 (en, ja, ru, zh-CN, zh-TW)。

🧹 Nitpick comments (12)
src/app/[locale]/dashboard/_components/user/utils/form-utils.ts (1)

1-9: 代码逻辑正确,实现清晰!

该工具函数正确处理了表单错误的优先级和边界情况:

  • 优先返回 _form 错误
  • 正确过滤空字符串(通过真值检查)
  • 适当使用可选链和空值回退
可选改进:增强类型不可变性

为了强化函数的不可变语义,可以考虑将参数类型标记为只读:

-export function getFirstErrorMessage(errors: Record<string, string>): string | null {
+export function getFirstErrorMessage(errors: Readonly<Record<string, string>>): string | null {

这样可以在类型层面保证函数不会修改输入对象,但这是一个可选的改进。

tests/unit/user-dialogs.test.tsx (1)

187-345: Mock 消息对象结构完整

Mock messages 对象涵盖了测试所需的所有翻译路径,与实际的 i18n 消息文件结构一致。

可选优化:如果未来有更多组件测试需要类似的 mock messages,可以考虑将其提取为共享的测试工具函数,以减少重复。

src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts (1)

14-24: 考虑添加竞态条件处理

providerGroup 快速变化时,可能会出现竞态条件:旧的请求返回后覆盖新请求的结果。建议使用 cleanup 函数或 abort 标志来忽略过时的响应。

🔎 建议的修复方案
 useEffect(() => {
+  let isCancelled = false;
   getModelSuggestionsByProviderGroup(providerGroup)
     .then((res) => {
-      if (res.ok && res.data) {
+      if (!isCancelled && res.ok && res.data) {
         setModelSuggestions(res.data);
       }
     })
     .catch(() => {
       // Silently fail - model suggestions are optional enhancement
     });
+  return () => {
+    isCancelled = true;
+  };
 }, [providerGroup]);
src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx (2)

153-185: 异步处理函数中的错误处理可以改进

handleDisableUserhandleEnableUserhandleDeleteUser 函数在失败时抛出错误,但调用方(如 Line 220-225 的 onToggleEnabled)没有 try-catch 包装。这可能导致未捕获的 Promise rejection。

🔎 建议的修复方案
 const handleDisableUser = async () => {
-  const res = await toggleUserEnabled(user.id, false);
-  if (!res.ok) {
-    throw new Error(res.error || t("editDialog.operationFailed"));
+  try {
+    const res = await toggleUserEnabled(user.id, false);
+    if (!res.ok) {
+      toast.error(res.error || t("editDialog.operationFailed"));
+      return;
+    }
+    toast.success(t("editDialog.userDisabled"));
+    onSuccess?.();
+    queryClient.invalidateQueries({ queryKey: ["users"] });
+    router.refresh();
+  } catch (error) {
+    console.error("[EditUserDialog] disable user failed", error);
+    toast.error(t("editDialog.operationFailed"));
   }
-  toast.success(t("editDialog.userDisabled"));
-  onSuccess?.();
-  queryClient.invalidateQueries({ queryKey: ["users"] });
-  router.refresh();
 };

handleEnableUserhandleDeleteUser 也应用相同的模式。


128-151: 字段映射逻辑较为复杂,建议简化

handleUserChange 函数中 "description" 到 "note" 的映射逻辑分散在多处,增加了维护成本。考虑提取一个独立的映射函数来集中处理字段名转换。

🔎 建议的简化方案
+const mapFieldName = (field: string): string => (field === "description" ? "note" : field);
+
 const handleUserChange = (field: string | Record<string, any>, value?: any) => {
   const prev = form.values || defaultValues;
   const next = { ...prev } as EditUserValues;

   if (typeof field === "object") {
     Object.entries(field).forEach(([key, val]) => {
-      const mappedField = key === "description" ? "note" : key;
+      const mappedField = mapFieldName(key);
       (next as any)[mappedField] = mappedField === "expiresAt" ? (val ?? undefined) : val;
     });
   } else {
-    const mappedField = field === "description" ? "note" : field;
+    const mappedField = mapFieldName(field);
     if (mappedField === "expiresAt") {
       (next as any)[mappedField] = value ?? undefined;
     } else {
       (next as any)[mappedField] = value;
     }
   }
   // ...
 };
tests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsx (1)

91-98: 元素选择器较为脆弱

使用 input[placeholder*="key"] 作为选择器依赖于 placeholder 文本内容,当翻译文件更改时可能会导致测试失败。建议使用更稳定的选择器,如 data-testid 或基于 name 属性。

src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx (1)

51-55: 按需隐藏到期时间/限额区域的改造合理,但可以懒计算 limitRules 和到期 UI

新增的 showExpireTime / showLimitRules 默认值为 true,对现有调用是后向兼容的;CreateUserDialog 这类场景也能按需隐藏子区域,这点很好。

如果后续发现该组件在大列表中频繁渲染、性能吃紧,可以考虑在 showLimitRules === false 时跳过 limitRules / existingLimitTypes 的计算,在 showExpireTime === false 时延迟相关 UI 的计算与渲染,进一步减少不必要的 work。

Also applies to: 143-145, 379-431

src/actions/providers.ts (1)

3094-3168: 按分组聚合 allowedModels 的实现正确,但参数目前未绑定到会话权限

  • 分组解析 & 匹配逻辑(parseGroupString + checkProviderGroupMatch)和 PROVIDER_GROUP.DEFAULT / PROVIDER_GROUP.ALL 语义是一致的,默认走 default 分组也合理。
  • 使用 findAllProviders() 一次性拉取供应商,再在内存中过滤、去重模型列表,避免了 N+1,结构上没问题。

一点可以考虑的加强点:getModelSuggestionsByProviderGroup 目前直接信任调用方传入的 providerGroup,只要 session 存在,就可以传入 "*" 拿到所有分组的模型名。模型名通常不是敏感信息,但如果希望这个接口严格反映“当前用户可用的模型”,可以改为从 session / 用户记录中读取其真实 providerGroup,再据此计算建议列表,而不是简单使用参数。

src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx (1)

4-4: 行内 Key 编辑弹窗与“添加 Key” 按钮整体设计合理,有两点类型/可空性可以微调

  • 基于 editingKeyId 条件渲染 EditKeyDialog 的逻辑很清晰:既支持行内「编辑」/「查看详情」入口,又在未找到 key 时安全返回 null,刷新后也会自动关闭,用户体验不错。
  • 展开区域右下角的 “Add Key” 按钮通过 onAddKey? 控制是否显示,事件里 stopPropagation() 也避免了误触展开/收起,交互上考虑周全。

可以考虑的小改动:

  1. EditKeyDialoguser prop 目前通过内联对象再 as any 传入,后续如果 EditKeyDialog 的类型签名调整,很难在编译期发现问题。建议抽出一个明确的 EditKeyUser 类型(例如从 UserDisplay 派生 Pick),让两边都用这个类型,去掉 as any
  2. translations.actions.addKey 在 JSX 中直接渲染,但类型上是可选的;既然已经在 messages 各 locale 中新增了对应 key,可以把它改为必填字段,避免某些 locale 漏配时按钮文案渲染为 undefined 的隐蔽问题。

Also applies to: 20-21, 35-37, 143-145, 465-469, 480-495, 499-545

src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx (1)

120-160: CreateUser 调用目前忽略 providerGroup / isEnabled 字段,需确认是否为刻意简化

在提交 createUserOnly 时,只传了:

  • name / note / tags / expiresAt / rpm / 各种 limit 字段 / allowedClients / allowedModels,

但没有传:

  • providerGroup(尽管 schema 和默认值里都存在);
  • isEnabledUpdateUserSchema 通常包含此字段)。

目前 UI 里 UserEditSection 是以 showProviderGroup={false} + isEnabled={true} 的形式使用的,因此:

  • providerGroup 在创建对话框里确实不可编辑,缺省走后端默认 default,忽略也不会影响体验;
  • isEnabled 若在 UserEditSection 中有可见的开关,则该开关的值目前不会反映到后端,用户可能误以为可以创建“初始即禁用”的用户。

建议:

  • 如果设计上创建用户时不允许修改启用状态/分组,可以在 UserEditSection 中隐藏或禁用对应控件,避免产生“可以改但其实不生效”的错觉;
  • 如果预期后续会放开这两个字段的编辑,则最好现在就把 providerGroup / isEnabled 一并传给 createUserOnly,保持与 EditUser 流程行为一致。
src/app/[locale]/dashboard/users/users-page-client.tsx (2)

676-686: 避免使用 as any 类型断言。

这里使用 as any 绕过了 TypeScript 类型检查,降低了类型安全性。建议定义一个明确的类型或使用 AddKeyDialog 组件期望的 user prop 类型。

此外,相同的对象构造模式在 Lines 699-709 重复出现,可以提取为辅助函数以减少重复。

🔎 建议的重构方案
+ // 在组件顶部定义辅助函数
+ const buildAddKeyUser = (user: UserDisplay) => ({
+   id: user.id,
+   name: user.name,
+   providerGroup: user.providerGroup ?? null,
+   limit5hUsd: user.limit5hUsd ?? undefined,
+   limitWeeklyUsd: user.limitWeeklyUsd ?? undefined,
+   limitMonthlyUsd: user.limitMonthlyUsd ?? undefined,
+   limitTotalUsd: user.limitTotalUsd ?? undefined,
+   limitConcurrentSessions: user.limitConcurrentSessions ?? undefined,
+ });

  {/* 使用辅助函数 */}
  <AddKeyDialog
    open={showCreateDialog}
    onOpenChange={handleCreateDialogClose}
    userId={selfUser.id}
-   user={
-     {
-       id: selfUser.id,
-       name: selfUser.name,
-       providerGroup: selfUser.providerGroup ?? null,
-       limit5hUsd: selfUser.limit5hUsd ?? undefined,
-       limitWeeklyUsd: selfUser.limitWeeklyUsd ?? undefined,
-       limitMonthlyUsd: selfUser.limitMonthlyUsd ?? undefined,
-       limitTotalUsd: selfUser.limitTotalUsd ?? undefined,
-       limitConcurrentSessions: selfUser.limitConcurrentSessions ?? undefined,
-     } as any
-   }
+   user={buildAddKeyUser(selfUser)}
    isAdmin={false}
    onSuccess={handleKeyCreated}
  />

668-714: 对话框渲染逻辑清晰。

管理员与非管理员的对话框流程分离合理:

  • 管理员使用 CreateUserDialog 创建用户
  • 非管理员使用 AddKeyDialog 创建密钥
  • 额外的 AddKeyDialog(从用户列表触发)支持管理员为特定用户添加密钥

建议添加注释说明两个 AddKeyDialog 的不同用途,以提高代码可读性。

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

📥 Commits

Reviewing files that changed from the base of the PR and between 7c13cc6 and 3291848.

📒 Files selected for processing (24)
  • messages/en/dashboard.json
  • messages/ja/dashboard.json
  • messages/ru/dashboard.json
  • messages/zh-CN/dashboard.json
  • messages/zh-TW/dashboard.json
  • src/actions/providers.ts
  • src/app/[locale]/dashboard/_components/user/add-key-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx
  • src/app/[locale]/dashboard/_components/user/hooks/use-key-translations.ts
  • src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts
  • src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.ts
  • src/app/[locale]/dashboard/_components/user/unified-edit-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
  • src/app/[locale]/dashboard/_components/user/user-management-table.tsx
  • src/app/[locale]/dashboard/_components/user/utils/form-utils.ts
  • src/app/[locale]/dashboard/_components/user/utils/provider-group.ts
  • src/app/[locale]/dashboard/users/users-page-client.tsx
  • tests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsx
  • tests/unit/user-dialogs.test.tsx
💤 Files with no reviewable changes (1)
  • src/app/[locale]/dashboard/_components/user/unified-edit-dialog.tsx
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use 2-space indentation in all code files

Files:

  • src/app/[locale]/dashboard/_components/user/utils/form-utils.ts
  • src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.ts
  • src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts
  • src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx
  • src/actions/providers.ts
  • tests/unit/user-dialogs.test.tsx
  • src/app/[locale]/dashboard/_components/user/add-key-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/hooks/use-key-translations.ts
  • tests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsx
  • src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx
  • messages/zh-TW/dashboard.json
  • messages/ja/dashboard.json
  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
  • src/app/[locale]/dashboard/users/users-page-client.tsx
  • messages/ru/dashboard.json
  • src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx
  • messages/zh-CN/dashboard.json
  • src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/user-management-table.tsx
  • messages/en/dashboard.json
  • src/app/[locale]/dashboard/_components/user/utils/provider-group.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use double quotes for strings instead of single quotes
Use trailing commas in multi-line structures
Enforce maximum line length of 100 characters
Use path alias @/* to reference files from ./src/* directory

**/*.{ts,tsx,js,jsx}: Use Biome for linting and formatting with 2-space indent, double quotes, trailing commas, and 100 character max line length
Use path alias @/* to reference files in ./src/* directory

Files:

  • src/app/[locale]/dashboard/_components/user/utils/form-utils.ts
  • src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.ts
  • src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts
  • src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx
  • src/actions/providers.ts
  • tests/unit/user-dialogs.test.tsx
  • src/app/[locale]/dashboard/_components/user/add-key-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/hooks/use-key-translations.ts
  • tests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsx
  • src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
  • src/app/[locale]/dashboard/users/users-page-client.tsx
  • src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx
  • src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/user-management-table.tsx
  • src/app/[locale]/dashboard/_components/user/utils/provider-group.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript strict mode for type safety
Use readonly or const assertions for immutable data structures

Files:

  • src/app/[locale]/dashboard/_components/user/utils/form-utils.ts
  • src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.ts
  • src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts
  • src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx
  • src/actions/providers.ts
  • tests/unit/user-dialogs.test.tsx
  • src/app/[locale]/dashboard/_components/user/add-key-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/hooks/use-key-translations.ts
  • tests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsx
  • src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
  • src/app/[locale]/dashboard/users/users-page-client.tsx
  • src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx
  • src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/user-management-table.tsx
  • src/app/[locale]/dashboard/_components/user/utils/provider-group.ts
src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.ts: Hash API keys using SHA-256 before storing in database, never store plaintext keys
Mask API keys and sensitive data in application logs
Validate required environment variables at startup with clear error messages

Files:

  • src/app/[locale]/dashboard/_components/user/utils/form-utils.ts
  • src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.ts
  • src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts
  • src/actions/providers.ts
  • src/app/[locale]/dashboard/_components/user/hooks/use-key-translations.ts
  • src/app/[locale]/dashboard/_components/user/utils/provider-group.ts
src/app/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Implement Content-Security-Policy headers for XSS prevention

Files:

  • src/app/[locale]/dashboard/_components/user/utils/form-utils.ts
  • src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.ts
  • src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts
  • src/app/[locale]/dashboard/_components/user/hooks/use-key-translations.ts
  • src/app/[locale]/dashboard/_components/user/utils/provider-group.ts
src/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.{tsx,jsx}: Use lucide-react for icons, no custom SVGs
Use React's automatic escaping to prevent XSS vulnerabilities

Files:

  • src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/add-key-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
  • src/app/[locale]/dashboard/users/users-page-client.tsx
  • src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx
  • src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/user-management-table.tsx
**/*.{tsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Use next-intl for internationalization with 5 locales: en, ja, ru, zh-CN, zh-TW

Files:

  • src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx
  • tests/unit/user-dialogs.test.tsx
  • src/app/[locale]/dashboard/_components/user/add-key-dialog.tsx
  • tests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsx
  • src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx
  • messages/zh-TW/dashboard.json
  • messages/ja/dashboard.json
  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
  • src/app/[locale]/dashboard/users/users-page-client.tsx
  • messages/ru/dashboard.json
  • src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx
  • messages/zh-CN/dashboard.json
  • src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/user-management-table.tsx
  • messages/en/dashboard.json
src/actions/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/actions/**/*.ts: Validate all user inputs with Zod schemas before processing
Use Server Actions in next-safe-action with OpenAPI generation for admin API endpoints
Use Next.js API Routes and Server Actions for admin operations and REST endpoints

Files:

  • src/actions/providers.ts
src/**/*provider*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Set provider circuit breaker failure threshold, open duration, and half-open success threshold in configuration

Files:

  • src/actions/providers.ts
  • src/app/[locale]/dashboard/_components/user/utils/provider-group.ts
src/{repository,actions}/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Avoid N+1 queries by using eager loading and batch queries for statistics

Files:

  • src/actions/providers.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Vitest for unit testing with Node environment, coverage thresholds: 50% lines/functions, 40% branches

Files:

  • tests/unit/user-dialogs.test.tsx
  • tests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsx
messages/**/*.json

📄 CodeRabbit inference engine (CLAUDE.md)

Support 5 locales via next-intl: en, ja, ru, zh-CN, zh-TW with messages in messages/{locale}/*.json

Store message translations in messages/{locale}/*.json files

Files:

  • messages/zh-TW/dashboard.json
  • messages/ja/dashboard.json
  • messages/ru/dashboard.json
  • messages/zh-CN/dashboard.json
  • messages/en/dashboard.json
🧠 Learnings (7)
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to **/*.{tsx,json} : Use next-intl for internationalization with 5 locales: en, ja, ru, zh-CN, zh-TW

Applied to files:

  • src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.ts
  • src/app/[locale]/dashboard/_components/user/hooks/use-key-translations.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/i18n/**/*.ts : Configure i18n routing in `src/i18n/`

Applied to files:

  • src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.ts
  • src/app/[locale]/dashboard/_components/user/hooks/use-key-translations.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/actions/**/*.ts : Validate all user inputs with Zod schemas before processing

Applied to files:

  • src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to **/*.test.{ts,tsx} : Use Vitest for unit testing with Node environment, coverage thresholds: 50% lines/functions, 40% branches

Applied to files:

  • tests/unit/user-dialogs.test.tsx
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/components/**/*.{ts,tsx} : Use `lucide-react` for icons instead of custom SVGs

Applied to files:

  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/**/*.{tsx,jsx} : Use `lucide-react` for icons, no custom SVGs

Applied to files:

  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/components/**/*.{tsx,jsx} : Use shadcn/ui component library for high-quality, accessible UI components

Applied to files:

  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
  • src/app/[locale]/dashboard/users/users-page-client.tsx
🧬 Code graph analysis (11)
src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.ts (1)
scripts/sync-settings-keys.js (1)
  • t (72-72)
src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts (1)
src/actions/providers.ts (1)
  • getModelSuggestionsByProviderGroup (3128-3168)
src/actions/providers.ts (4)
src/lib/constants/provider.constants.ts (1)
  • PROVIDER_GROUP (25-30)
src/actions/types.ts (1)
  • ActionResult (31-31)
src/lib/auth.ts (1)
  • getSession (116-128)
src/repository/provider.ts (1)
  • findAllProviders (197-261)
tests/unit/user-dialogs.test.tsx (5)
src/components/ui/dialog.tsx (6)
  • Dialog (119-119)
  • DialogContent (121-121)
  • DialogHeader (124-124)
  • DialogTitle (127-127)
  • DialogDescription (122-122)
  • DialogFooter (123-123)
src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx (1)
  • EditUserDialog (262-268)
src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx (1)
  • EditKeyDialog (38-64)
src/app/[locale]/dashboard/_components/user/add-key-dialog.tsx (1)
  • AddKeyDialog (34-128)
src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx (1)
  • CreateUserDialog (419-425)
tests/unit/dashboard/add-key-form-expiry-clear-ui.test.tsx (1)
src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx (1)
  • AddKeyForm (33-348)
src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx (6)
src/lib/validation/schemas.ts (3)
  • CreateUserSchema (30-156)
  • UpdateUserSchema (161-279)
  • KeyFormSchema (284-339)
src/lib/constants/provider.constants.ts (1)
  • PROVIDER_GROUP (25-30)
src/actions/users.ts (2)
  • createUserOnly (899-1063)
  • removeUser (1205-1228)
src/actions/keys.ts (1)
  • addKey (84-278)
src/app/[locale]/dashboard/_components/user/utils/provider-group.ts (1)
  • normalizeProviderGroup (10-23)
src/app/[locale]/dashboard/_components/user/utils/form-utils.ts (1)
  • getFirstErrorMessage (5-9)
src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx (2)
src/components/ui/label.tsx (1)
  • Label (21-21)
src/components/ui/switch.tsx (1)
  • Switch (28-28)
src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx (1)
src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx (1)
  • EditKeyDialog (38-64)
src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx (6)
src/components/ui/calendar.tsx (1)
  • Calendar (179-179)
src/components/form/date-picker-field.tsx (1)
  • DatePickerField (55-164)
src/app/[locale]/dashboard/_components/user/forms/quick-expire-picker.tsx (1)
  • QuickExpirePicker (38-67)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/app/[locale]/dashboard/_components/user/forms/limit-rules-display.tsx (1)
  • LimitRulesDisplay (45-90)
src/app/[locale]/dashboard/_components/user/forms/limit-rule-picker.tsx (1)
  • LimitRulePicker (85-303)
src/app/[locale]/dashboard/_components/user/user-management-table.tsx (3)
src/types/user.ts (1)
  • UserDisplay (130-156)
src/app/[locale]/dashboard/_components/user/user-key-manager.tsx (1)
  • UserDisplay (129-129)
src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx (1)
  • EditUserDialog (262-268)
src/app/[locale]/dashboard/_components/user/utils/provider-group.ts (1)
src/lib/constants/provider.constants.ts (1)
  • PROVIDER_GROUP (25-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: pr-review
  • GitHub Check: Greptile Review
  • GitHub Check: 🌐 API Tests
  • GitHub Check: pr-label
  • GitHub Check: Docker Build Test
  • GitHub Check: pr-description
🔇 Additional comments (29)
src/app/[locale]/dashboard/_components/user/utils/provider-group.ts (1)

10-23: 实现正确,逻辑清晰

函数正确处理了各种边界情况:

  • null/undefined/非字符串输入返回默认值
  • 空字符串和仅包含空白的输入返回默认值
  • 正确地去重、排序和规范化有效输入

实现符合 PR 目标中提到的 provider group 规范化需求。

messages/zh-CN/dashboard.json (1)

773-778: 翻译新增内容符合 UI 需求

新增的翻译条目支持了拆分后的对话框组件:

  • addKeyForm.successTitle/successDescription/generatedKey: 密钥创建成功提示
  • userManagement.table.actions.addKey: 新增密钥操作标签
  • editDialog.title/description: 简化为仅关注用户编辑
  • createDialog.successTitle/successDescription/generatedKey/keyHint: 用户创建成功流程
  • keyEditSection.balanceQueryPage.descriptionDisabled: 更新为反映受限 Web UI 的使用场景

措辞清晰,与 PR 目标中的新对话框组件结构一致。

Also applies to: 1153-1155, 1223-1227, 1323-1327, 1544-1544

messages/zh-TW/dashboard.json (1)

753-758: 繁体中文翻译与简体中文保持一致

新增的翻译条目与 messages/zh-CN/dashboard.json 中的结构完全对应:

  • 密钥创建成功提示
  • 新增密钥操作
  • 编辑对话框简化
  • 用户创建成功流程
  • 余额查询页面描述更新

建议人工验证繁体中文措辞是否符合目标用户的语言习惯。

Also applies to: 1128-1130, 1193-1194, 1293-1297, 1514-1514

tests/unit/user-dialogs.test.tsx (3)

1-12: 测试环境配置正确

使用 happy-dom 环境进行 React 组件测试是正确的选择。虽然代码规范中提到使用 Node 环境,但该建议主要针对纯逻辑单元测试。对于需要 DOM 操作的 UI 组件测试,happy-domjsdom 是必需的。


20-118: Mock 结构完善,测试隔离良好

测试文件的 mock 策略合理:

  • 完整模拟了 next/navigation、路由、server actions、toast 等依赖
  • Dialog 和 Form 组件使用简化的 mock 实现,保留了 data-testid 用于查询
  • Server action mocks 返回适当的响应数据,支持成功和失败场景

这种结构确保了测试的独立性和可维护性。


390-752: 测试覆盖全面,场景合理

测试套件涵盖了 4 个对话框组件的关键场景:

  • EditUserDialog: 开启/关闭渲染、用户 ID 传递、按钮存在性、prop 变化重渲染
  • EditKeyDialog: 密钥数据传递、关闭回调
  • AddKeyDialog: userId 传递、成功回调和 UI 更新
  • CreateUserDialog: 开启/关闭渲染、按钮存在性
  • 集成测试: 可访问性标题验证

测试实践良好:

  • 使用 act() 包裹状态更新
  • 每个测试后正确清理(unmount 和 DOM 清空)
  • 通过 data-testid 而非实现细节进行断言

与 PR 目标中的新组件结构对应,测试充分验证了组件拆分后的行为。

messages/ru/dashboard.json (1)

755-760: 俄语翻译结构与其他语言版本一致

新增的翻译条目在结构上与中文版本完全对应:

  • 密钥创建成功消息
  • 新增密钥操作标签
  • 编辑对话框标题简化
  • 用户创建成功流程
  • 余额查询页面描述更新

翻译内容与 PR 中的新对话框组件功能匹配。建议由母语使用者验证俄语措辞的准确性。

Also applies to: 1130-1132, 1195-1196, 1295-1298, 1393-1393

src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx (1)

174-193: LGTM!

反转开关逻辑实现正确,注释清晰地解释了设计意图。checked={!form.values.canLoginWebUi}onCheckedChange={(checked) => form.setValue("canLoginWebUi", !checked)} 的组合确保了正确的状态管理。动态描述文本根据当前状态显示,提升了用户体验。

src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx (1)

155-170: LGTM!

反转开关逻辑与 edit-key-form.tsx 保持一致,注释清晰地说明了设计意图。默认值从 true 改为 false 符合新的业务逻辑。

src/app/[locale]/dashboard/_components/user/hooks/use-key-translations.ts (1)

1-136: LGTM!

该 hook 的实现良好:

  • 使用 useMemo 正确优化了翻译对象的创建
  • KeyEditTranslations 接口提供了清晰的类型定义
  • 翻译键结构与 UI 组件的使用模式一致
  • 遵循了项目的 next-intl 国际化规范
src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx (1)

262-268: LGTM! 条件渲染优化

使用 {props.open ? <EditUserDialogInner key={props.user.id} {...props} /> : null} 确保对话框关闭时内部组件被卸载,结合 key={props.user.id} 确保切换用户时组件状态被重置。这是一个良好的性能和状态管理实践。

src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx (1)

53-63: LGTM!

组件实现简洁清晰:

  • 职责单一,仅负责对话框的展示和关闭逻辑
  • sr-only 类确保了对屏幕阅读器的可访问性
  • handleSuccess 正确处理了成功回调和对话框关闭
  • Props 接口定义完善,与 EditKeyForm 的接口保持一致
src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.ts (1)

6-190: 集中管理用户编辑文案的结构清晰,可复用性好

UserEditTranslations 的层级设计和 useUserTranslations 的封装都很干净,showProviderGroup 通过 optional 字段控制也比较安全,默认值和依赖数组处理正确,看不到明显问题。

src/app/[locale]/dashboard/_components/user/add-key-dialog.tsx (1)

20-127: 新增 AddKeyDialog 流程清晰,状态管理和复制逻辑都比较健壮

  • 通过 generatedKey 切换表单态/成功态的方式简单直接,关闭时统一重置本地状态也避免了下次打开时的残留。
  • navigator.clipboard 错误路径有 toast 和 console.error,对 HTTP / 权限不足等场景也有比较好的降级体验。
  • Dialog 目前由外部 open 控制,内部 onOpenChange={handleClose} 专门处理关闭即可,和现有用法是兼容的。

整体实现看起来可以直接合入。

src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx (1)

331-395: 复用 UserEditSection / KeyEditSection 的方式不错,隐藏 Key 的限额与到期设置符合“创建时简化表单”的目标

  • User 部分通过 UserEditSection 复用已有编辑 UI,利用 modelSuggestionsuseUserTranslations 保持体验一致,这样后续文案或字段调整时也更易维护。
  • Key 部分用 KeyEditSection 且传入 showLimitRules={false}showExpireTime={false},只保留基础字段(名称、Provider 分组、缓存 TTL 等),让首次创建用户的表单更精简,这个拆分是合理的。

后续如果产品上希望在创建阶段就设置 Key 的限额/到期时间,也可以只通过这两个布尔开关打开对应 UI,无需再拆新组件。

messages/ja/dashboard.json (1)

753-758: 日文文案与新增对话框/按钮结构匹配,多语言键已同步完成

日文翻译的结构和内容正确:

  • addKeyForm.successTitle/successDescription/generatedKey.*AddKeyDialog 成功态展示对应
  • userManagement.table.actions.addKeyUserKeyTableRow 新增按钮相呼应
  • createDialog.success*/generatedKey/keyHint 对应 CreateUserDialog 成功态提示
  • keyEditSection.balanceQueryPage.descriptionDisabled 的表达清晰简洁

验证确认:所有 5 个语言包(en/ja/ru/zh-CN/zh-TW)均已包含这些键,多语言同步完整无缺失。

src/app/[locale]/dashboard/_components/user/user-management-table.tsx (5)

15-15: LGTM!

导入 EditUserDialog 组件,使用了正确的 @/ 路径别名,符合代码规范。


29-29: LGTM!

新增 onAddKey 可选属性,类型定义正确,与 UserDisplay 类型匹配。在组件内正确解构使用。

Also applies to: 114-114


208-211: LGTM!

正确地将 addKey 翻译键添加到 rowTranslations.actions 中,使用了展开运算符合并现有翻译。


329-332: LGTM!

openEditDialog 简化为仅接受 userId,符合 PR 目标中将用户编辑与密钥编辑流程解耦的设计。onAddKey 正确创建闭包传递用户上下文。

Also applies to: 572-573


597-603: LGTM!

EditUserDialog 的条件渲染逻辑正确,仅在 editingUser 存在且为管理员时显示。Props 与 EditUserDialogProps 接口匹配。

src/app/[locale]/dashboard/users/users-page-client.tsx (3)

8-10: LGTM!

导入变更正确,添加了 useQueryClient 用于缓存失效,移除了未使用的 Key 图标导入。


230-260: LGTM!

新增的 AddKeyDialog 状态管理和处理函数设计合理:

  • handleAddKeyDialogClose 在关闭时正确清理 addKeyUser 状态
  • handleKeyCreated 正确使用 queryClient.invalidateQueries 刷新缓存
  • useCallback 依赖项设置正确

499-505: LGTM!

"创建用户"按钮正确限制为仅管理员可见,符合权限控制要求。

messages/en/dashboard.json (5)

1151-1154: LGTM!

新增 table.actions.addKey 翻译键,与 user-management-table.tsx 中的 tUserMgmt("table.actions.addKey") 调用匹配。


1222-1223: LGTM!

editDialog 的标题和描述更新反映了重构后的单一职责设计 —— EditUserDialog 现在仅专注于用户信息编辑,不再包含密钥设置。


1322-1325: LGTM!

createDialog 下新增的成功消息与 addKeyForm 中的模式保持一致,支持密钥创建成功后的用户反馈流程。


1420-1420: LGTM!

descriptionDisabled 的文案更新移除了关于修改 provider group 的描述,简化了用户理解。两处相同文案分别位于不同的上下文路径中,属于合理的结构设计。

Also applies to: 1545-1545


772-777: 新增的国际化键已正确同步到所有5个语言文件。

已验证 addKeyForm.successTitleaddKeyForm.successDescriptionaddKeyForm.generatedKey 在所有语言版本 (en, ja, ru, zh-CN, zh-TW) 中都存在且结构一致。消息键的命名规范清晰,符合现有约定,完全满足 next-intl 国际化需求。

- 新增 KeyDialogUserContext 类型,替代过于宽泛的 User 类型
- 移除 4 处 as any 类型断言
- 清理 key-list-header.tsx 中伪造的字段

Addresses Gemini Code Review feedback on type safety
@NieiR
Copy link
Contributor Author

NieiR commented Jan 5, 2026

Type Safety Fix for as any Usage

Addressed the Gemini Code Review feedback on type safety. Changes in commit 3fc8585d:

What was done:

  1. Created KeyDialogUserContext type in src/types/user.ts - a minimal interface with only the 7 fields actually used by Key Dialogs, replacing the overly broad User type (30+ fields)

  2. Removed 4 as any assertions from:

    • user-key-table-row.tsx (EditKeyDialog user prop)
    • users-page-client.tsx (AddKeyDialog user prop × 2)
  3. Cleaned up fake fields in key-list-header.tsx:

    • Removed fabricated createdAt: new Date(), updatedAt: new Date(), description, role, rpm, etc.
    • Now only passes the fields actually needed

Verification:

  • bun run typecheck passes
  • bun run lint passes
  • Net code reduction: -5 lines (51 added, 56 removed)

The remaining as any usages (dynamic property access in handleUserChange/handleKeyChange) are TypeScript limitations for dynamic key access, not type safety issues.

Copy link

@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

🧹 Nitpick comments (3)
src/app/[locale]/dashboard/users/users-page-client.tsx (1)

668-708: 消除重复的用户对象构造逻辑

在两处(lines 676-684 和 696-704)使用了相同的用户对象构造逻辑。这种重复增加了维护成本,如果 KeyDialogUserContext 的字段发生变化,需要在多处更新。

建议提取为辅助函数以消除重复。

🔎 提取辅助函数的建议

在文件顶部或独立的 utils 文件中添加:

function mapUserDisplayToKeyDialogContext(user: UserDisplay): KeyDialogUserContext {
  return {
    id: user.id,
    providerGroup: user.providerGroup ?? null,
    limit5hUsd: user.limit5hUsd ?? undefined,
    limitWeeklyUsd: user.limitWeeklyUsd ?? undefined,
    limitMonthlyUsd: user.limitMonthlyUsd ?? undefined,
    limitTotalUsd: user.limitTotalUsd ?? undefined,
    limitConcurrentSessions: user.limitConcurrentSessions ?? undefined,
  };
}

然后在两处使用:

       <AddKeyDialog
         open={showCreateDialog}
         onOpenChange={handleCreateDialogClose}
         userId={selfUser.id}
-        user={{
-          id: selfUser.id,
-          providerGroup: selfUser.providerGroup ?? null,
-          limit5hUsd: selfUser.limit5hUsd ?? undefined,
-          limitWeeklyUsd: selfUser.limitWeeklyUsd ?? undefined,
-          limitMonthlyUsd: selfUser.limitMonthlyUsd ?? undefined,
-          limitTotalUsd: selfUser.limitTotalUsd ?? undefined,
-          limitConcurrentSessions: selfUser.limitConcurrentSessions ?? undefined,
-        }}
+        user={mapUserDisplayToKeyDialogContext(selfUser)}
         isAdmin={false}
         onSuccess={handleKeyCreated}
       />
       <AddKeyDialog
         open={showAddKeyDialog}
         onOpenChange={handleAddKeyDialogClose}
         userId={addKeyUser.id}
-        user={{
-          id: addKeyUser.id,
-          providerGroup: addKeyUser.providerGroup ?? null,
-          limit5hUsd: addKeyUser.limit5hUsd ?? undefined,
-          limitWeeklyUsd: addKeyUser.limitWeeklyUsd ?? undefined,
-          limitMonthlyUsd: addKeyUser.limitMonthlyUsd ?? undefined,
-          limitTotalUsd: addKeyUser.limitTotalUsd ?? undefined,
-          limitConcurrentSessions: addKeyUser.limitConcurrentSessions ?? undefined,
-        }}
+        user={mapUserDisplayToKeyDialogContext(addKeyUser)}
         isAdmin={isAdmin}
         onSuccess={handleKeyCreated}
       />
src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx (2)

480-495: 添加密钥按钮缺少无障碍标签。

"添加密钥"按钮应该添加 aria-label 属性以提升可访问性,特别是对于使用屏幕阅读器的用户。

🔎 建议的改进
             <Button
               type="button"
               variant="outline"
               size="sm"
+              aria-label={translations.actions.addKey ?? "Add Key"}
               onClick={(e) => {
                 e.stopPropagation();
                 onAddKey();
               }}
             >

499-542: 密钥编辑对话框渲染逻辑可优化。

当前实现使用 IIFE 模式在每次渲染时查找编辑中的密钥。虽然功能正确,但可以通过以下方式提升可读性和性能:

  1. 使用 useMemo 缓存密钥查找结果,避免在每次渲染时重复查找。
  2. 提取为独立的渲染函数或组件,提升代码可维护性。

另外,根据提交信息,已消除 as any 类型断言并使用了精确的 KeyDialogUserContext 类型(第 525-533 行),这是很好的改进。

🔎 可选的重构建议
+  const editingKey = useMemo(
+    () => (editingKeyId !== null ? user.keys.find((k) => k.id === editingKeyId) : null),
+    [editingKeyId, user.keys]
+  );
+
   return (
     <div className="border-b">
       {/* ... 现有代码 ... */}

       {/* Key 编辑 Dialog */}
-      {editingKeyId !== null &&
-        (() => {
-          const editingKey = user.keys.find((k) => k.id === editingKeyId);
-          if (!editingKey) return null;
-          return (
+      {editingKey && (
             <EditKeyDialog
               open={true}
               onOpenChange={(open) => {
                 if (!open) setEditingKeyId(null);
               }}
               {/* ... 其余 props ... */}
             />
-          );
-        })()}
+      )}
     </div>
   );
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

📥 Commits

Reviewing files that changed from the base of the PR and between 3291848 and 3fc8585.

📒 Files selected for processing (8)
  • src/app/[locale]/dashboard/_components/user/add-key-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/key-list-header.tsx
  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
  • src/app/[locale]/dashboard/users/users-page-client.tsx
  • src/types/user.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/app/[locale]/dashboard/_components/user/add-key-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use 2-space indentation in all code files

Files:

  • src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx
  • src/types/user.ts
  • src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/key-list-header.tsx
  • src/app/[locale]/dashboard/users/users-page-client.tsx
  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use double quotes for strings instead of single quotes
Use trailing commas in multi-line structures
Enforce maximum line length of 100 characters
Use path alias @/* to reference files from ./src/* directory

**/*.{ts,tsx,js,jsx}: Use Biome for linting and formatting with 2-space indent, double quotes, trailing commas, and 100 character max line length
Use path alias @/* to reference files in ./src/* directory

Files:

  • src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx
  • src/types/user.ts
  • src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/key-list-header.tsx
  • src/app/[locale]/dashboard/users/users-page-client.tsx
  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
src/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.{tsx,jsx}: Use lucide-react for icons, no custom SVGs
Use React's automatic escaping to prevent XSS vulnerabilities

Files:

  • src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/key-list-header.tsx
  • src/app/[locale]/dashboard/users/users-page-client.tsx
  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript strict mode for type safety
Use readonly or const assertions for immutable data structures

Files:

  • src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx
  • src/types/user.ts
  • src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/key-list-header.tsx
  • src/app/[locale]/dashboard/users/users-page-client.tsx
  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
**/*.{tsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Use next-intl for internationalization with 5 locales: en, ja, ru, zh-CN, zh-TW

Files:

  • src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx
  • src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx
  • src/app/[locale]/dashboard/_components/user/key-list-header.tsx
  • src/app/[locale]/dashboard/users/users-page-client.tsx
  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.ts: Hash API keys using SHA-256 before storing in database, never store plaintext keys
Mask API keys and sensitive data in application logs
Validate required environment variables at startup with clear error messages

Files:

  • src/types/user.ts
🧠 Learnings (4)
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/actions/**/*.ts : Validate all user inputs with Zod schemas before processing

Applied to files:

  • src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/components/**/*.{tsx,jsx} : Use shadcn/ui component library for high-quality, accessible UI components

Applied to files:

  • src/app/[locale]/dashboard/users/users-page-client.tsx
  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/components/**/*.{ts,tsx} : Use `lucide-react` for icons instead of custom SVGs

Applied to files:

  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/**/*.{tsx,jsx} : Use `lucide-react` for icons, no custom SVGs

Applied to files:

  • src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx
🧬 Code graph analysis (4)
src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx (3)
src/types/user.ts (1)
  • KeyDialogUserContext (162-170)
src/components/ui/dialog.tsx (5)
  • Dialog (119-119)
  • DialogContent (121-121)
  • DialogHeader (124-124)
  • DialogTitle (127-127)
  • DialogDescription (122-122)
src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx (1)
  • EditKeyForm (48-370)
src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx (1)
src/types/user.ts (1)
  • KeyDialogUserContext (162-170)
src/app/[locale]/dashboard/users/users-page-client.tsx (4)
src/types/user.ts (1)
  • UserDisplay (130-156)
src/app/[locale]/dashboard/_components/user/user-key-manager.tsx (1)
  • UserDisplay (129-129)
src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx (1)
  • CreateUserDialog (419-425)
src/app/[locale]/dashboard/_components/user/add-key-dialog.tsx (1)
  • AddKeyDialog (34-128)
src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx (1)
src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx (1)
  • EditKeyDialog (38-64)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: 🌐 API Tests
  • GitHub Check: Docker Build Test
🔇 Additional comments (10)
src/app/[locale]/dashboard/_components/user/edit-key-dialog.tsx (1)

1-64: 实现简洁且类型安全

该组件的实现质量很高:

  • 使用 KeyDialogUserContext 类型提高了类型安全性
  • handleSuccess 正确地链接了回调并关闭对话框
  • 使用 sr-only 类提供了良好的无障碍支持
src/app/[locale]/dashboard/_components/user/key-list-header.tsx (1)

318-336: 用户对象构造符合新类型契约

传递给 AddKeyForm 的用户对象现在正确地符合 KeyDialogUserContext 类型。providerGroup 的空值合并到 nulllimitTotalUsd 的空值合并到 undefined 都是合理的。

这解决了之前审查中提到的 as any 类型问题,提高了类型安全性。

src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx (3)

56-56: 确认 canLoginWebUi 默认值变更

canLoginWebUi 的默认值从 true 更改为 false。这是一个行为变更,意味着新创建的密钥默认情况下将使用独立的余额查询页面,而不是完整的 Web UI。

请确认此更改是有意为之,并且与产品需求一致。


155-171: 反转逻辑设计合理且文档完善

余额查询页面切换的反转逻辑(canLoginWebUi 的反向绑定)设计清晰:

  • 注释清楚地解释了 canLoginWebUi=true 表示完整 Web UI 访问(开关关闭)
  • canLoginWebUi=false 表示使用独立余额页面(开关打开)
  • UI 标签和行为与用户期望一致

虽然反转逻辑可能带来维护复杂性,但完善的注释有助于理解。


77-78: 后端正确处理了空字符串作为"无过期时间"的表示

验证发现,src/lib/validation/schemas.ts 中的 KeyFormSchemaexpiresAt 字段的定义为:

expiresAt: z
  .string()
  .optional()
  .default("")
  .transform((val) => (val === "" ? undefined : val)),

该方案正确地将前端发送的空字符串("")转换为 undefined,避免了 Server Action 序列化时 undefined 丢失的问题。后端接收到的实际数据为 undefined,数据库处理无需修改。此实现模式是处理 Server Action 序列化限制的标准做法。

src/app/[locale]/dashboard/users/users-page-client.tsx (2)

258-260: 缓存失效处理得当

使用 queryClient.invalidateQueries 在密钥创建后刷新数据是正确的做法,确保 UI 显示最新状态。


668-708: 解决了之前审查中的类型安全问题

根据之前的审查意见,此处代码通过使用 KeyDialogUserContext 类型替代 as any 断言,成功提高了类型安全性。内联对象构造现在具有完整的类型检查。

基于 learnings,这正是之前建议的改进方向。

src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx (3)

4-4: 导入的图标和组件符合规范。

使用 lucide-react 提供的 Plus 图标以及正确使用路径别名导入 EditKeyDialog 组件,符合项目编码规范。

Also applies to: 20-20


143-144: 编辑密钥状态管理正确。

使用 editingKeyId 状态跟踪当前正在编辑的密钥,类型定义准确(number | null),初始化合理。


465-465: 密钥编辑和查看详情统一使用 EditKeyDialog。

onEditonViewDetails 回调统一为打开 EditKeyDialog,符合 PR 目标:为单个密钥提供专用编辑对话框。实现简洁且一致。

Also applies to: 468-468

@ding113 ding113 merged commit d774c5d into ding113:dev Jan 6, 2026
19 of 20 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Jan 6, 2026
@NieiR NieiR deleted the issue-413-split-unified-edit-dialog branch January 6, 2026 13:21
@github-actions github-actions bot mentioned this pull request Jan 6, 2026
4 tasks
NieiR added a commit to NieiR/claude-code-hub that referenced this pull request Jan 7, 2026
- Always show providerGroup field in edit mode (was hidden when user had no providerGroup)
- Replace read-only Badge display with editable ProviderGroupSelect component
- Move modelSuggestions hook after form declaration to support dynamic updates

Regression from ding113#539
@github-actions github-actions bot mentioned this pull request Jan 7, 2026
5 tasks
NieiR added a commit to NieiR/claude-code-hub that referenced this pull request Jan 8, 2026
- 在 add-key-form.tsx 和 edit-key-form.tsx 中添加 handleProviderGroupChange
- 当选择多个分组时自动移除 default 分组
- 修复 DialogFormLayout 中输入框 focus ring 左边被裁剪的样式问题

此问题由 PR ding113#539 重构引入,原有逻辑在 key-edit-section.tsx 中存在但未迁移到新表单组件
NieiR added a commit to NieiR/claude-code-hub that referenced this pull request Jan 9, 2026
- Always show providerGroup field in edit mode (was hidden when user had no providerGroup)
- Replace read-only Badge display with editable ProviderGroupSelect component
- Move modelSuggestions hook after form declaration to support dynamic updates

Regression from ding113#539
NieiR added a commit to NieiR/claude-code-hub that referenced this pull request Jan 10, 2026
- Always show providerGroup field in edit mode (was hidden when user had no providerGroup)
- Replace read-only Badge display with editable ProviderGroupSelect component
- Move modelSuggestions hook after form declaration to support dynamic updates

Regression from ding113#539
ding113 pushed a commit that referenced this pull request Jan 10, 2026
* fix: enable provider group editing in edit user dialog

- Always show providerGroup field in edit mode (was hidden when user had no providerGroup)
- Replace read-only Badge display with editable ProviderGroupSelect component
- Move modelSuggestions hook after form declaration to support dynamic updates

Regression from #539

* fix: add complete translations for ProviderGroupSelect in edit user dialog

Pass full translations object to ProviderGroupSelect including:
- tagInputErrors for validation messages (empty, duplicate, too_long, etc.)
- errors.loadFailed for API error handling
- providersSuffix for provider count display

This fixes untranslated error messages when users input invalid provider group tags.

* feat(prices): 添加手动模型价格管理功能

- 新增 source 字段区分 litellm/manual 来源
- 支持手动添加、编辑、删除模型价格
- LiteLLM 同步时自动跳过手动价格,避免覆盖
- 添加冲突检测和解决 UI,支持批量处理
- 完整的单元测试覆盖

closes #405

* fix: 修复 CI 检查问题

- 移除未使用的 ModelPriceSource 导入
- 修复 useEffect 依赖数组 (fetchPrices)
- 修复 fetchPrices 声明前使用问题
- 添加价格非负数验证
- 格式化代码

* fix: wrap upsertModelPrice in transaction for data integrity
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:i18n area:UI enhancement New feature or request size/XL Extra Large PR (> 1000 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants