Skip to content

Comments

refactor: 供应商限额管理页面重构为列表布局;add dark mode#170

Merged
ding113 merged 20 commits intomainfrom
dev
Nov 21, 2025
Merged

refactor: 供应商限额管理页面重构为列表布局;add dark mode#170
ding113 merged 20 commits intomainfrom
dev

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Nov 21, 2025

Summary

重构供应商限额管理页面的布局设计,从卡片布局改为更紧凑的列表布局,提升信息密度和可视化效果。

Problem

原有的卡片布局在供应商数量较多时存在以下问题:

  • 占用垂直空间较大,需要频繁滚动
  • 信息密度较低,难以快速对比多个供应商的限额状态
  • 缺少有效的搜索和排序功能
  • 圆形进度指示器缺失,视觉反馈不够直观

Solution

本次重构采用列表布局替代卡片布局,并新增以下功能:

1. 列表式布局

  • 左侧区域:状态指示器 + 类型图标 + 名称 + 优先级/权重徽章
  • 中间区域:使用圆形进度指示器(CircularProgress)展示各项限额
    • 5小时限额、日限额、周限额、月限额、并发Session
    • 每个指标带有倒计时器(CountdownTimer),显示重置剩余时间
    • Tooltip 显示详细数据(当前值、限制值、使用百分比)
  • 右侧区域:预留操作按钮位置

2. 搜索功能

  • 实时搜索供应商名称(防抖优化,300ms 延迟)
  • 支持搜索框清空按钮

3. 排序功能

  • 按名称排序(Name)
  • 按优先级排序(Priority,升序)
  • 按权重排序(Weight,降序)
  • 按使用量排序(Usage,最高使用率优先)

4. 国际化支持

  • 新增多语言翻译:
    • 搜索占位符(searchPlaceholder
    • 排序选项(sort.name/priority/weight/usage
    • 列表标签(list.resetIn/unlimited/current/limit/used
  • 支持语言:中文(简/繁)、英文、日文、俄文

5. 空状态优化

  • 无匹配结果时显示友好提示(地球图标 + 提示文案)
  • 区分"无供应商"和"无匹配结果"两种场景

Changes

新增文件

  • provider-quota-list-item.tsx - 列表项组件(圆形进度 + Tooltip + 倒计时)
  • provider-quota-sort-dropdown.tsx - 排序下拉菜单组件

修改文件

  • providers-quota-client.tsx - 核心逻辑重构
    • 从卡片布局改为列表布局
    • 新增搜索过滤逻辑
    • 新增多维度排序算法(calculateMaxUsage 计算最高使用率)
    • 优化空状态渲染
  • providers-quota-manager.tsx - 新增搜索框和排序下拉菜单

国际化文件

  • messages/{zh-CN,zh-TW,en,ja,ru}/quota.json - 新增翻译键

Technical Highlights

  1. 性能优化

    • 使用 useDebounce Hook 防抖(300ms)
    • useMemo 缓存筛选/排序结果
  2. 可访问性

    • Tooltip 延迟 200ms,避免误触
    • 键盘友好的搜索框(ESC 清空)
  3. 可扩展性

    • 列表项组件独立,易于定制
    • 排序算法支持新增维度

Testing

  • 手动测试所有排序选项
  • 测试搜索功能(中英文关键词)
  • 验证圆形进度和倒计时显示正确
  • 测试无限额供应商的折叠区域
  • 验证空状态和无匹配结果的提示
  • 多语言翻译验证(5 种语言)
  • 无破坏性变更

Screenshots

建议添加以下截图:

  1. 列表布局全局视图(展示圆形进度指示器)
  2. 搜索功能演示
  3. 排序下拉菜单
  4. Tooltip 详细信息

Related Issues

无关联 Issue

ding113 and others added 3 commits November 22, 2025 02:19
- 移除 page.tsx 中固定定位的语言切换器组件
- layout.tsx 中的 DashboardHeader 已包含语言切换器
- 移除未使用的 LanguageSwitcher 导入
- 创建 CircularProgress 圆形进度组件(颜色自动分级:绿/黄/红)
- 创建 CountdownTimer 倒计时组件(实时显示重置时间)
- 创建 ProviderQuotaListItem 列表项组件(一行展示所有限额指标)
- 创建 ProviderQuotaSortDropdown 排序组件(名称/优先级/权重/使用量)
- ProvidersQuotaManager: 添加搜索功能(防抖)和排序功能
- ProvidersQuotaClient: 从卡片网格改为列表布局,集成搜索排序
- 新增 5 种语言的 i18n 翻译(en/zh-CN/zh-TW/ja/ru)
- 保留未设置限额供应商的自动折叠功能
- 优化空间利用率,提升信息密度和可视化效果
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ding113, 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!

This pull request significantly overhauls the supplier quota management page, transitioning its display from a collection of individual cards to a streamlined list format. This change aims to provide users with a more compact and interactive overview of provider quotas, complete with visual progress indicators, countdowns, and comprehensive filtering, searching, and sorting capabilities. The goal is to simplify the monitoring and management of supplier limits.

Highlights

  • UI Refactoring: The supplier quota management page has been refactored from a card-based layout to a more efficient and interactive list view, enhancing overall user experience.
  • New UI Components: Introduced new reusable UI components, including a CircularProgress indicator for visualizing quota usage and a CountdownTimer for displaying quota reset times.
  • Enhanced Filtering and Sorting: Added robust search functionality with debouncing and new sorting options (by name, priority, weight, and usage) to the provider quota list, allowing for better data navigation and analysis.
  • Internationalization Updates: Updated internationalization files across English, Japanese, Russian, Simplified Chinese, and Traditional Chinese to support new UI texts for search placeholders, sorting options, and list item details.
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.

@ding113 ding113 added the enhancement New feature or request label Nov 21, 2025
</div>
</TooltipTrigger>
<TooltipContent side="top">
<div className="space-y-1">
Copy link
Owner Author

Choose a reason for hiding this comment

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

🟠 High - Potential Division by Zero Error

Why this is a problem: When limit is 0, this will cause a division by zero error. The guard clause only checks if (limit <= 0) return null at the function level, but the percentage calculation happens before the tooltip closes the early return.

Suggested fix:

const renderConcurrentSessionsItem = () => {
    const { current, limit } = provider.quota?.concurrentSessions || { current: 0, limit: 0 };
    if (limit <= 0) return null;

    const percentage = (current / limit) * 100;
    
    return (
      // ... rest of the component
      <div className="text-xs font-semibold">
        {percentage.toFixed(1)}% {t("list.used")}
      </div>

Move the percentage calculation inside the function after the guard clause, similar to renderQuotaItem.

case "usage": {
// 使用量:按最高使用率降序排列
const usageA = calculateMaxUsage(a);
const usageB = calculateMaxUsage(b);
Copy link
Owner Author

Choose a reason for hiding this comment

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

🟡 Medium - Missing Translation Keys Referenced in Empty State

Why this is a problem: The code references t("noMatchesDesc") and t("noProvidersDesc") but these translation keys are not added to the message files in this PR. This will cause missing translation warnings or display raw keys to users.

Suggested fix:
Add the missing keys to all locale files (messages/en/quota.json, etc.):

{
  "providers": {
    "noMatches": "No providers found",
    "noMatchesDesc": "No providers match your search criteria. Try adjusting your filters.",
    "noProvidersDesc": "No providers have been configured yet.",
    // ... existing keys
  }
}


// 计算筛选后的供应商数量
// 计算筛选后的供应商数量(不包括搜索)
const filteredCount =
Copy link
Owner Author

Choose a reason for hiding this comment

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

🟡 Medium - Search Result Count Shows Wrong Value

Why this is a problem: The filteredCount variable calculates the count based only on typeFilter, but ignores searchTerm. When searching, the displayed count will be incorrect because it shows the pre-search count instead of the actual number of matching results.

Suggested fix:

// 计算筛选后的供应商数量(包括搜索)
const filteredCount = useMemo(() => {
  let filtered = typeFilter === "all"
    ? providers
    : providers.filter((p) => p.providerType === typeFilter);
  
  if (debouncedSearchTerm) {
    const term = debouncedSearchTerm.toLowerCase();
    filtered = filtered.filter((p) => p.name.toLowerCase().includes(term));
  }
  
  return filtered.length;
}, [providers, typeFilter, debouncedSearchTerm]);

case "weight":
// 权重:数值越大越优先,降序排列
return b.weight - a.weight;
case "usage": {
Copy link
Owner Author

Choose a reason for hiding this comment

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

🟡 Medium - Performance Issue with Sorting Recalculation

Why this is a problem: The calculateMaxUsage function is called during every sort comparison in the array sort. For N providers, this means O(N log N) calls to calculateMaxUsage, each iterating through 5 quota types. This is inefficient and will cause performance issues with larger provider lists.

Suggested fix:
Pre-calculate usage values before sorting:

// 4. 排序(仅对有限额的供应商排序)
if (sortBy === "usage") {
  // Pre-calculate usage for performance
  const usageMap = new Map<number, number>();
  withQuota.forEach(p => usageMap.set(p.id, calculateMaxUsage(p)));
  
  withQuota.sort((a, b) => {
    const usageA = usageMap.get(a.id) ?? 0;
    const usageB = usageMap.get(b.id) ?? 0;
    return usageB - usageA;
  });
} else {
  withQuota.sort((a, b) => {
    switch (sortBy) {
      case "name":
        return a.name.localeCompare(b.name);
      case "priority":
        return a.priority - b.priority;
      case "weight":
        return b.weight - a.weight;
      default:
        return 0;
    }
  });
}

*/
export function CountdownTimer({ targetDate, prefix, className }: CountdownTimerProps) {
const locale = useLocale();
const [timeLeft, setTimeLeft] = useState<string>("");
Copy link
Owner Author

Choose a reason for hiding this comment

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

🟡 Medium - Memory Leak from Interval Not Cleaned on Dependency Change

Why this is a problem: The useEffect creates an interval but does not clear the previous interval when targetDate or locale changes. If props change multiple times, multiple intervals will be running simultaneously, causing memory leaks and incorrect behavior.

Suggested fix:
The current implementation is actually correct since the cleanup function will run before the new effect. However, for clarity and to prevent potential issues, consider using useCallback:

export function CountdownTimer({ targetDate, prefix, className }: CountdownTimerProps) {
  const locale = useLocale();
  const [timeLeft, setTimeLeft] = useState<string>(() => 
    formatDateDistance(targetDate, new Date(), locale)
  );

  useEffect(() => {
    const updateCountdown = () => {
      const formatted = formatDateDistance(targetDate, new Date(), locale);
      setTimeLeft(formatted);
    };

    // 每30秒更新一次
    const interval = setInterval(updateCountdown, 30000);

    return () => clearInterval(interval);
  }, [targetDate, locale]);

  // ... rest
}

The main issue is that the initial state is empty string causing a brief flash. Initialize state with the formatted value.

@ding113 ding113 added the size/M Medium PR (< 500 lines) label Nov 21, 2025
@ding113
Copy link
Owner Author

ding113 commented Nov 21, 2025

🔒 Security Scan Results

No security vulnerabilities detected

This PR has been scanned against OWASP Top 10, CWE Top 25, and common security anti-patterns. No security issues were identified in the code changes.

🔍 Changes Analyzed

This PR refactors the provider quota management page from a card grid layout to a list layout with the following changes:

New Components:

  • CircularProgress: SVG-based circular progress indicator
  • CountdownTimer: Client-side countdown timer using React hooks
  • ProviderQuotaListItem: List item component for displaying provider quotas
  • ProviderQuotaSortDropdown: Dropdown for sorting providers

Modified Components:

  • ProvidersQuotaClient: Refactored from card grid to list layout
  • ProvidersQuotaManager: Added search and sort functionality

Internationalization:

  • Added translations for 5 languages (en, zh-CN, zh-TW, ja, ru)

✅ Scanned Categories

  • Injection attacks (SQL, NoSQL, Command, LDAP, etc.)

    • No database queries or command execution
    • Pure client-side React components
  • Cross-Site Scripting (XSS)

    • All user inputs properly handled by React's auto-escaping
    • Search input uses controlled components with state management
    • No dangerouslySetInnerHTML usage
    • No direct DOM manipulation
  • Authentication and session management

    • No authentication logic modified
    • Client-side display components only
  • Sensitive data exposure

    • No secrets, credentials, or sensitive data in code
    • Only displays provider quota information
  • Access control and authorization

    • No authorization logic modified
    • UI components inherit existing permissions
  • Security misconfiguration

    • No configuration changes
    • No debug mode or verbose errors introduced
  • Insecure deserialization

    • No serialization/deserialization logic
    • Uses typed TypeScript interfaces
  • SSRF and path traversal

    • No file operations or external requests
    • Pure UI components
  • Cryptographic weaknesses

    • No cryptographic operations
  • Input validation

    • Search input uses debouncing (500ms delay)
    • Filter and sort values validated through TypeScript enums
    • Percentage calculations include bounds checking (Math.min, Math.max)

🛡️ Security-Positive Patterns Observed

  1. Type Safety: All components use TypeScript interfaces with strict typing
  2. React Best Practices:
    • Controlled components for form inputs
    • Proper cleanup of intervals in useEffect
    • No unsafe lifecycle methods
  3. Input Sanitization: Search term uses .toLowerCase() and .includes() (safe string operations)
  4. Debouncing: Implements proper debouncing for search input to prevent excessive operations
  5. Bounds Checking: Percentage calculations include safety checks (max > 0, Math.min(..., 100))

📋 Code Quality Notes (Non-Security)

The following are code quality observations, not security issues:

  • Clean separation of concerns (presentation components)
  • Good use of internationalization (i18n)
  • Proper TypeScript typing throughout
  • Follows React hooks best practices

🤖 Automated security scan by Claude AI - OWASP Top 10 & CWE coverage
🔒 Scan completed: 2025-11-21

Copy link
Owner Author

@ding113 ding113 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 PR refactors the provider quota management page from a card-based layout to a list layout, adding search, sort functionality, and new UI components (CircularProgress, CountdownTimer). The implementation is generally solid but has several issues that should be addressed before merging.

🔍 Issues Found

  • Critical (🔴): 0 issues
  • High (🟠): 1 issue
  • Medium (🟡): 4 issues
  • Low (🟢): 0 issues

🎯 Priority Actions

  1. Fix potential division by zero in renderConcurrentSessionsItem - calculate percentage after the guard clause
  2. Add missing translation keys - noMatchesDesc and noProvidersDesc are referenced but not defined in locale files
  3. Fix search count calculation - filteredCount doesn't account for search term, showing incorrect counts
  4. Optimize sort performance - pre-calculate usage values instead of recalculating during every comparison
  5. Initialize countdown timer state - avoid UI flash by initializing with computed value

💡 General Observations

  • The overall refactoring approach is well-structured with good component separation
  • Consider adding memoization to expensive calculations in list components
  • The new UI components (CircularProgress, CountdownTimer) could benefit from unit tests

🤖 Automated review by Claude AI - focused on identifying issues for improvement

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

本次 PR 将供应商限额管理页面重构为列表布局,并增加了搜索和排序功能,整体上提升了页面的可用性和信息密度。代码结构清晰,新组件的拆分也比较合理。

我在评审中发现了一些可以改进的地方:

  1. 列表项显示信息不全: 新的列表项组件 ProviderQuotaListItem 丢失了之前卡片布局中显示的 “5小时滚动限额” 的重置信息。
  2. 并发会话百分比计算不一致: ProviderQuotaListItem 中并发会话的使用率百分比没有像其他指标一样设置 100% 的上限。
  3. 排序逻辑变更: ProvidersQuotaClient 中关于 priority 的排序逻辑从之前的降序变为了升序,这是一个比较大的行为变更,需要确认是否符合预期。
  4. 搜索后计数不正确: 在 ProvidersQuotaManager 中,当用户输入搜索词后,页面上显示的供应商数量统计没有更新,会误导用户。

这些问题在具体的 review comments 中有详细说明和修改建议。总体来说,这是一次很棒的重构,修复上述问题后代码会更加完善。

Comment on lines 119 to 121
case "priority":
// 优先级:数值越小越优先,升序排列
return a.priority - b.priority;
Copy link
Contributor

Choose a reason for hiding this comment

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

high

这里的供应商优先级排序逻辑已从降序(b.priority - a.priority)更改为升序(a.priority - b.priority)。虽然注释解释了“数值越小越优先”,但这与之前的实现行为相反。这是一个重大的行为变更,可能会影响供应商的调度顺序。请确认此变更是符合预期的。

Comment on lines 50 to 53
const filteredCount =
typeFilter === "all"
? providers.length
: providers.filter((p) => p.providerType === typeFilter).length;
Copy link
Contributor

Choose a reason for hiding this comment

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

high

此处计算的 filteredCount 未考虑搜索词 debouncedSearchTerm 的影响。当用户输入搜索内容时,列表数据虽然被过滤,但页面顶部显示的统计数量(例如“找到 X 个供应商”)不会更新,导致显示错误的数量,对用户造成困扰。

建议将过滤逻辑(按类型和搜索词)提升到 ProvidersQuotaManager 组件中,以便计算正确的数量,然后将过滤后的结果列表传递给 ProvidersQuotaClientProvidersQuotaClient 则只负责分组、排序和渲染。

你可以在 ProvidersQuotaManager 中这样修改:

const filteredProviders = useMemo(() => {
  let result = 
    typeFilter === 'all' 
      ? providers 
      : providers.filter((p) => p.providerType === typeFilter);

  if (debouncedSearchTerm) {
    const term = debouncedSearchTerm.toLowerCase();
    result = result.filter((p) => p.name.toLowerCase().includes(term));
  }
  return result;
}, [providers, typeFilter, debouncedSearchTerm]);

const filteredCount = filteredProviders.length;

然后将 filteredProviders 传递给 ProvidersQuotaClient,并从 ProvidersQuotaClient 中移除相应的过滤逻辑。

</div>
<div className="text-xs">
{t("list.limit")}: {limit}
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

为了与 renderQuotaItem 中的百分比计算保持一致,建议在此处也将并发会话使用率的百分比上限定为100%。当前如果 current > limit 会显示超过100%的数值。

Suggested change
</div>
{Math.min((current / limit) * 100, 100).toFixed(1)}% {t("list.used")}

Comment on lines 161 to 167
<div className="flex items-center gap-6 flex-1 justify-center">
{/* 5小时限额 */}
{provider.quota.cost5h.limit &&
provider.quota.cost5h.limit > 0 &&
renderQuotaItem(
t("cost5h.label"),
provider.quota.cost5h.current,
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

此处的重构丢失了 cost5hresetInfo 文本显示,这是一个功能上的回归。之前的卡片视图会显示这个信息,对于用户理解5小时滚动限额何时刷新很有帮助。

建议修改 renderQuotaItem 函数以支持显示 resetInfo 字符串,并在调用时传入相应的数据。

你可以这样修改 renderQuotaItem 函数:

const renderQuotaItem = (
  label: string,
  current: number,
  limit: number | null,
  resetAt?: Date,
  resetInfo?: string
) => {
  if (!limit || limit <= 0) return null;

  const percentage = Math.min((current / limit) * 100, 100);

  return (
    <TooltipProvider delayDuration={200}>
      <Tooltip>
        <TooltipTrigger asChild>
          <div className="flex flex-col items-center gap-1">
            <CircularProgress value={current} max={limit} size={48} strokeWidth={4} />
            {resetAt ? (
              <CountdownTimer
                targetDate={resetAt}
                prefix={t("list.resetIn") + " "}
                className="text-[10px] text-muted-foreground"
              />
            ) : resetInfo ? (
              <span className="text-[10px] text-muted-foreground">{resetInfo}</span>
            ) : null}
          </div>
        </TooltipTrigger>
        {/* ... TooltipContent ... */}
      </Tooltip>
    </TooltipProvider>
  );
};

然后像这样调用它:

renderQuotaItem(
  t("cost5h.label"),
  provider.quota.cost5h.current,
  provider.quota.cost5h.limit,
  undefined,
  provider.quota.cost5h.resetInfo
)

ding113 and others added 2 commits November 22, 2025 02:48
- 修复 renderConcurrentSessionsItem 中的除零问题并添加百分比上限
- 添加 renderQuotaItem 的 resetInfo 参数支持,恢复 5小时限额重置信息显示
- 修复 filteredCount 计算,正确包含搜索词过滤
- 优化 usage 排序性能,预计算使用率避免重复计算
- 修复 CountdownTimer 初始状态闪烁问题
- 添加缺失的翻译键 (noMatchesDesc, noProvidersDesc, costDaily)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@ding113 ding113 added the size/L Large PR (< 1000 lines) label Nov 21, 2025
@ding113
Copy link
Owner Author

ding113 commented Nov 21, 2025

📊 PR Size Analysis

This PR is Large (L) with 915 lines changed (689 additions, 226 deletions) across 12 files.

虽然这个 PR 的规模较大,但从变更内容来看,这是一个相对内聚的重构(供应商限额管理页面布局改造)。不过,仍然可以考虑拆分以提高代码审查效率。

🔀 可选的拆分建议:

如果需要降低 PR 规模,可以考虑以下拆分方案:

方案 1:按功能拆分

  1. PR 1 - UI 组件基础设施 (~140 lines, 2 files)

    • src/components/ui/circular-progress.tsx (新进度环组件)
    • src/components/ui/countdown-timer.tsx (新倒计时组件)
    • 优势:可独立测试和复用的 UI 组件
  2. PR 2 - 国际化文本 (~92 lines, 5 files)

    • messages/*/quota.json (所有翻译文件)
    • 优势:纯文本变更,无逻辑风险
  3. PR 3 - 限额页面重构 (~683 lines, 5 files)

    • 新列表组件和排序功能
    • 主页面重构
    • 优势:核心业务逻辑变更,依赖前两个 PR

方案 2:保持当前结构(推荐)

由于这是一个原子性的 UI 重构(从一种布局迁移到另一种布局),拆分可能会导致:

  • 中间状态不完整(部分组件未使用)
  • 审查割裂(难以看到完整的 before/after 对比)
  • 合并顺序依赖

💡 建议行动:

保持当前 PR 不拆分,但建议:

  1. 在 PR 描述中添加截图对比(旧布局 vs 新布局)
  2. 突出重点审查区域(如新的 provider-quota-list-item.tsx
  3. 确认所有翻译文本已由母语者校验

📝 代码审查检查清单:

  • 新 UI 组件(circular-progress, countdown-timer)可在 Storybook/隔离环境测试
  • 列表布局在不同屏幕尺寸下的响应式表现
  • 排序功能的逻辑正确性
  • 翻译文本的准确性(特别是日语和俄语)
  • 是否有遗留的旧代码/组件需要清理

🤖 自动化分析 by Claude AI | 如有疑问请回复此评论

@ding113
Copy link
Owner Author

ding113 commented Nov 21, 2025

🔒 Security Scan Results

No security vulnerabilities detected

This PR has been scanned against OWASP Top 10, CWE Top 25, and common security anti-patterns. No security issues were identified in the code changes.

Scanned Categories

  • ✅ Injection attacks (SQL, NoSQL, Command, LDAP, etc.)
  • ✅ Authentication and session management
  • ✅ Sensitive data exposure
  • ✅ Access control and authorization
  • ✅ Security misconfiguration
  • ✅ Cross-site scripting (XSS)
  • ✅ Insecure deserialization
  • ✅ SSRF and path traversal
  • ✅ Cryptographic weaknesses

Analysis Summary

Changed Files:

  • 5x JSON translation files (messages/*/quota.json)
  • 3x React client components (provider-quota-list-item.tsx, provider-quota-sort-dropdown.tsx, providers-quota-client.tsx)
  • 1x React manager component (providers-quota-manager.tsx)
  • 1x Next.js page component (usage-doc/page.tsx)
  • 2x UI utility components (circular-progress.tsx, countdown-timer.tsx)

Security Review Findings:

  1. No XSS Vulnerabilities: All user-controlled data is properly rendered through React's automatic escaping. No use of dangerouslySetInnerHTML, innerHTML, or eval().

  2. Input Sanitization: Search input is handled safely through controlled React state with debouncing. The searchTerm is converted to lowercase and used in .includes() filtering, which is safe for client-side filtering.

  3. No Direct DOM Manipulation: All UI updates use React's declarative rendering.

  4. Type Safety: All components use TypeScript interfaces for type safety.

  5. Client-Side Only: Changes are purely UI/UX enhancements (search, sort, filtering) with no new API endpoints or database queries.

  6. No Sensitive Data Exposure: Components display quota information that is already fetched server-side through existing authenticated endpoints.

  7. React Auto-Escaping: All dynamic content is rendered through JSX, which automatically escapes values:

    • provider.name (lines 157, 161 in provider-quota-list-item.tsx)
    • searchTerm (line 72 in providers-quota-manager.tsx)
    • All translation strings
  8. No New Attack Surface: This PR only refactors the UI presentation layer without adding new data fetching, authentication, or authorization logic.

Code Quality Observations (Non-Security)

Good Practices Observed:

  • Proper TypeScript typing throughout
  • React hooks used correctly (useMemo, useState, useEffect)
  • Debouncing for search input (500ms delay)
  • Locale-aware date formatting
  • Responsive design considerations

🤖 Automated security scan by Claude AI - No vulnerabilities found
📋 Scan completed: 2025-11-21 18:51:18 UTC

- 集成 next-themes 提供主题切换功能
- 新增 ThemeSwitcher 组件支持浅色/深色/跟随系统
- 在 Dashboard 头部和设置页面导航添加主题切换器
- 添加多语言支持(中英日俄)主题相关翻译
- 配置 ThemeProvider 支持 class 切换和系统主题检测
- 修复 HTML 标签 hydration 警告

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
const [typeFilter, setTypeFilter] = useState<ProviderType | "all">("all");
const [sortBy, setSortBy] = useState<QuotaSortKey>("priority");
const [searchTerm, setSearchTerm] = useState("");
const debouncedSearchTerm = useDebounce(searchTerm, 500);
Copy link
Owner Author

Choose a reason for hiding this comment

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

🟡 Medium: Debounce delay is too long for user search experience

Why this is a problem: A 500ms debounce delay creates a noticeable lag when users type search queries. Modern UX best practices recommend 200-300ms for search inputs to provide responsive feedback while still reducing API calls.

Suggested fix:

const debouncedSearchTerm = useDebounce(searchTerm, 300);

});
}

return {
Copy link
Owner Author

Choose a reason for hiding this comment

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

🟠 High: useMemo dependency array is incomplete

Why this is a problem: The useMemo hook is missing sortBy in its dependency array, but the memoized logic uses it. This violates React's Rules of Hooks and can cause stale closures where the component uses an outdated sortBy value, leading to incorrect sorting behavior.

Suggested fix:

  }, [providers, typeFilter, sortBy, searchTerm]);

</TooltipProvider>
);
};

Copy link
Owner Author

Choose a reason for hiding this comment

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

🟡 Medium: Component returns null silently when quota is missing

Why this is a problem: The component silently returns null when provider.quota is missing, making it difficult to debug why a provider isn't rendering. In list views, this creates visual inconsistency where some providers disappear without explanation. Consider logging a warning or rendering a placeholder to indicate the data issue.

Suggested fix:

if (!provider.quota) {
  console.warn(`Provider ${provider.name} (ID: ${provider.id}) has no quota data`);
  return null;
}

// Or better: render a placeholder
if (!provider.quota) {
  return (
    <div className="flex items-center gap-4 py-4 px-4 border-b opacity-50">
      <span className="text-sm text-muted-foreground">
        {provider.name} - No quota data available
      </span>
    </div>
  );
}

Copy link
Owner Author

@ding113 ding113 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 PR refactors the provider quota management page from a card-based layout to a more compact list layout with circular progress indicators. The implementation includes search, sorting, and improved visual feedback. Overall code quality is good with modern React patterns, but there are a few issues that should be addressed before merge.

🔍 Issues Found

  • Critical (🔴): 0 issues
  • High (🟠): 1 issue
  • Medium (🟡): 2 issues
  • Low (🟢): 0 issues

🎯 Priority Actions

  1. Fix React Hook dependency array (🟠 High): Add sortBy to the useMemo dependency array in providers-quota-client.tsx:142. This is a Rules of Hooks violation that can cause stale closures and incorrect sorting behavior.

  2. Reduce debounce delay (🟡 Medium): Change debounce from 500ms to 300ms in providers-quota-manager.tsx:44 for better search UX responsiveness.

  3. Improve null handling (🟡 Medium): Consider adding logging or placeholder rendering in provider-quota-list-item.tsx:131 when provider quota data is missing, to improve debuggability.

💡 General Observations

Strengths:

  • Clean component separation with dedicated list item and sort dropdown components
  • Good performance optimization with usage calculation pre-computation for sorting
  • Comprehensive i18n support across 5 languages
  • Well-structured code with clear comments

Architecture:

  • The new CircularProgress and CountdownTimer components are well-designed reusable UI primitives
  • The sorting logic correctly pre-computes expensive calculations outside the comparator function
  • Search filtering is properly debounced to reduce unnecessary re-renders

🤖 Automated review by Claude AI - focused on identifying issues for improvement

@ding113 ding113 removed the size/L Large PR (< 1000 lines) label Nov 21, 2025
- Reduce search debounce delay from 500ms to 300ms for better UX
- Add console warning for providers missing quota data
- Complete i18n translations for daily reset mode and rolling window (5-hour)
  - Add missing costDaily, dailyResetMode, dailyResetTime translations
  - Affected languages: ja, ru, zh-TW
  - All languages now have consistent translation coverage (3 costDaily entries)

Fixes: Code review issues #90 (debounce, null handling)
Closes: i18n translation gaps for quota management features
@ding113 ding113 added the size/L Large PR (< 1000 lines) label Nov 21, 2025
- 🔴 HIGH: 添加 useTheme() hook 的 localStorage 错误处理
  - 使用 try-catch 包装 hook 调用
  - 私密浏览模式下优雅降级(显示禁用状态)
  - 添加错误日志和用户友好提示

- 🔴 HIGH: 修复 DropdownMenuRadioGroup value 绑定
  - 从 activeTheme 改为 currentTheme (直接绑定 theme)
  - 修复选择跟随系统时 UI 错误高亮问题
  - 单选按钮组现在正确反映用户的实际选择

- 🟡 MEDIUM: 简化 activeTheme 逻辑
  - 移除复杂的 useMemo 和 resolvedTheme
  - 简化为 currentTheme = (theme ?? 'system')
  - 选择跟随系统时按钮标签也显示跟随系统

- 🟡 MEDIUM: 添加 setTheme() 错误处理
  - 创建 handleThemeChange 函数包装调用
  - try-catch 捕获主题切换失败
  - 可扩展为 toast 通知

- 🟡 MEDIUM: 调整 Provider 嵌套顺序
  - QueryClientProvider 移至外层(数据层)
  - ThemeProvider 移至内层(UI 层)
  - 遵循 React 最佳实践

- 🟢 LOW: 简化 options 数组类型注解
  - 移除冗余的显式类型注解
  - 使用内联类型断言提供类型安全
  - 代码更简洁易读

Fixes: gemini-code-assist#171 (all review comments)
Ref: #110
@ding113 ding113 removed documentation Improvements or additions to documentation size/L Large PR (< 1000 lines) size/XL Extra Large PR (> 1000 lines) labels Nov 21, 2025
@ding113 ding113 changed the title refactor: 供应商限额管理页面重构为列表布局 refactor: 供应商限额管理页面重构为列表布局;add dark mode Nov 21, 2025
@ding113 ding113 added the documentation Improvements or additions to documentation label Nov 21, 2025
@ding113 ding113 added the size/XL Extra Large PR (> 1000 lines) label Nov 21, 2025
@ding113
Copy link
Owner Author

ding113 commented Nov 21, 2025

📊 PR Size Analysis

This PR is XL with 1258 lines changed (1025 additions + 233 deletions) across 23 files.

Large PRs are harder to review and more likely to introduce bugs.

🔀 Suggested Split:

Based on the changes, this PR addresses two distinct concerns that should be separated:

PR 1: Dark Mode Support

Scope: Add theme switcher and dark mode infrastructure

  • src/components/ui/theme-switcher.tsx (new)
  • src/app/providers.tsx
  • src/app/[locale]/layout.tsx
  • src/app/[locale]/dashboard/_components/dashboard-header.tsx
  • src/app/[locale]/settings/_components/settings-nav.tsx
  • src/app/[locale]/usage-doc/page.tsx
  • messages/*/common.json (theme-related translations only)

Estimated size: ~200-300 lines


PR 2: Provider Quota Page Refactor (Card → List Layout)

Scope: Refactor provider quota management UI with search, sort, and circular progress

  • src/app/[locale]/dashboard/quotas/providers/_components/ (all files)
  • src/components/ui/circular-progress.tsx (new)
  • src/components/ui/countdown-timer.tsx (new)
  • messages/*/quota.json
  • CHANGELOG.md

Estimated size: ~900-950 lines


❌ Why Current PR is Problematic:

  1. Mixed Concerns: Dark mode + UI refactor are unrelated features
  2. Title Mismatch: Title mentions "refactor: 供应商限额管理页面重构" but PR also adds dark mode (not mentioned prominently)
  3. Harder to Revert: If dark mode has issues, reverting would also undo the quota page improvements
  4. Review Difficulty: Reviewers must context-switch between two different feature areas

✅ Benefits of Splitting:

  • Clearer Intent: Each PR has a single, focused purpose
  • Independent Deployment: Features can be released separately
  • Easier Review: Reviewers can focus on one concern at a time
  • Better Git History: Easier to track which commit introduced which feature
  • Lower Risk: Smaller PRs = less likely to introduce bugs

📋 Recommended Action:

  1. Create a new branch from main for dark mode
  2. Cherry-pick only dark-mode related commits
  3. Submit dark mode as a separate PR
  4. Rebase this PR to remove dark mode changes (keep only quota refactor)
  5. Update this PR's title to remove "add dark mode" mention

🤖 Automated analysis by Claude Code

@ding113
Copy link
Owner Author

ding113 commented Nov 21, 2025

🔒 Security Scan Results

No security vulnerabilities detected

This PR has been scanned against OWASP Top 10, CWE Top 25, and common security anti-patterns. No security issues were identified in the code changes.

Changes Reviewed

This PR adds dark mode support and improves the provider quota management UI. The changes include:

  1. Theme Management - New ThemeProvider integration with next-themes
  2. UI Components - New components (ThemeSwitcher, CircularProgress, CountdownTimer)
  3. Provider Quota UI - Refactored quota display with list layout
  4. Internationalization - Updated translation files for all supported languages
  5. Documentation - Updated CHANGELOG.md

Scanned Categories

  • A01: Injection attacks (SQL, NoSQL, Command, LDAP, etc.)

    • No user input directly used in queries or commands
    • All components use React's built-in escaping
  • A02: Authentication and session management

    • No authentication logic modified
    • Theme preference stored in localStorage (non-sensitive data)
  • A03: Sensitive data exposure

    • No sensitive data handling in these changes
    • Theme preference is non-sensitive client-side data
  • A04: XML External Entities (XXE)

    • No XML parsing in changes
  • A05: Access control and authorization

    • No authorization logic modified
    • UI-only changes
  • A06: Security misconfiguration

    • Proper use of suppressHydrationWarning for theme hydration
    • No debug mode or verbose error messages added
  • A07: Cross-site scripting (XSS)

    • All React components use safe rendering (no dangerouslySetInnerHTML)
    • Translation strings properly escaped by next-intl
    • No direct DOM manipulation with user input
  • A08: Insecure deserialization

    • Theme value validated against enum type
    • No unsafe deserialization patterns
  • A09: Using components with known vulnerabilities

    • Uses well-maintained libraries (next-themes)
    • No deprecated or insecure dependencies added
  • A10: Insufficient logging & monitoring

    • Error handling added in theme switcher (try-catch block)
    • Console logging for debugging purposes
  • SSRF (Server-Side Request Forgery)

    • No server-side requests in changes
  • Path Traversal

    • No file system operations
  • Cryptographic Issues

    • No cryptographic operations
  • Input Validation

    • Theme values validated with TypeScript types
    • Sort and filter inputs properly typed

Security Best Practices Observed

Type Safety: Strong TypeScript typing throughout
React Hooks Rules: Proper hook usage (unconditional useTheme() call)
SSR Safety: Proper hydration handling with mounted state
Error Handling: Try-catch blocks for theme changes
Client-Side Only: Theme switcher marked as "use client"
Accessibility: Proper ARIA labels on buttons

🛡️ Security Posture

Strong - This PR contains UI/UX improvements with no security concerns. All changes follow React and Next.js security best practices.


🤖 Automated security scan by Claude AI - OWASP Top 10 & CWE coverage

Copy link
Owner Author

@ding113 ding113 left a comment

Choose a reason for hiding this comment

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

Submitting code review with inline comments.

[targetDate, locale]
);

const [timeLeft, setTimeLeft] = useState<string>(initialTimeLeft);
Copy link
Owner Author

Choose a reason for hiding this comment

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

🟡 Medium: Potential hydration mismatch in countdown timer

Why this is a problem: The useMemo calculation uses new Date() which will produce different values on server vs client, potentially causing React hydration warnings. Additionally, both useMemo and the initial useEffect call formatDateDistance, creating duplicate work.

Suggested fix:
Remove the useMemo and initialize state with empty string:

const [timeLeft, setTimeLeft] = useState<string>("");

Copy link
Owner Author

@ding113 ding113 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 PR refactors the provider quota management page from card layout to list layout with improved information density. The implementation adds dark mode support, search/sort functionality, and circular progress indicators. While the code is generally well-structured, there are several medium-severity issues related to React hydration, performance, and accessibility that should be addressed.

🔍 Issues Found

  • Critical (🔴): 0 issues
  • High (🟠): 0 issues
  • Medium (🟡): 4 issues
  • Low (🟢): 2 issues

🎯 Priority Actions

  1. Fix potential React hydration mismatch in CountdownTimer component by removing useMemo with new Date()
  2. Add missing aria-label to clear button in search input for accessibility
  3. Add error boundary or null checks for formatDateDistance to handle invalid dates gracefully
  4. Consider memoizing SORT_OPTIONS array to prevent unnecessary re-renders

💡 General Observations

  • Good use of useDebounce for search optimization
  • The circular progress component is well-designed with proper color coding
  • Translations are comprehensive across all supported languages

🤖 Automated review by Claude AI - focused on identifying issues for improvement

const locale = useLocale();

// 使用 useMemo 计算初始值,避免 SSR 与客户端不匹配
const initialTimeLeft = useMemo(
Copy link
Owner Author

Choose a reason for hiding this comment

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

🟡 Medium: useMemo with new Date() causes hydration mismatch

Why this is a problem: The useMemo calculation uses new Date() which will produce different values on server vs client rendering, potentially causing React hydration warnings. The server renders with one timestamp, the client hydrates with another.

Suggested fix:
Remove the useMemo entirely and use empty string as initial state:

const [timeLeft, setTimeLeft] = useState<string>("");

useEffect(() => {
  const updateCountdown = () => {
    const formatted = formatDateDistance(targetDate, new Date(), locale);
    setTimeLeft(formatted);
  };
  updateCountdown();
  const interval = setInterval(updateCountdown, 30000);
  return () => clearInterval(interval);
}, [targetDate, locale]);

if (!timeLeft) return null; // Skip render until client-side

export function ProviderQuotaSortDropdown({ value, onChange }: ProviderQuotaSortDropdownProps) {
const t = useTranslations("quota.providers.sort");
const selectedValue = value ?? "priority";

Copy link
Owner Author

Choose a reason for hiding this comment

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

🟢 Low: SORT_OPTIONS array is recreated on every render

Why this is a problem: The SORT_OPTIONS array is defined inside the component function, causing it to be recreated on every render. While not a significant performance issue in this case, it's a pattern that can cause unnecessary re-renders in child components that depend on referential equality.

Suggested fix:
Move the options outside the component or use useMemo:

// Option 1: Move outside component (if translations aren't needed dynamically)
const SORT_KEYS: QuotaSortKey[] = ["name", "priority", "weight", "usage"];

// Inside component:
const SORT_OPTIONS = useMemo(
  () => SORT_KEYS.map((value) => ({ value, label: t(value) })),
  [t]
);

{/* 筛选和搜索工具栏 */}
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2">
<ProviderTypeFilter value={typeFilter} onChange={setTypeFilter} />
Copy link
Owner Author

Choose a reason for hiding this comment

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

🟡 Medium: Clear button missing accessible label

Why this is a problem: While aria-label is provided using tSearch("clear"), the button also needs a visible focus indicator and better keyboard accessibility. Screen readers may not properly announce the button's purpose without proper ARIA attributes.

Suggested fix:

<button
  type="button"
  onClick={() => setSearchTerm("")}
  className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1 rounded-sm transition-colors"
  aria-label={tSearch("clear")}
>
  <X className="h-4 w-4" aria-hidden="true" />
</button>

<div className="font-semibold">{t("concurrentSessions.label")}</div>
<div className="text-xs">
{t("list.current")}: {current}
</div>
Copy link
Owner Author

Choose a reason for hiding this comment

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

🟢 Low: console.warn left in production code

Why this is a problem: Console warnings in production can clutter browser developer tools and may expose internal implementation details. This should either be removed or wrapped in a development-only check.

Suggested fix:

if (!provider.quota) {
  if (process.env.NODE_ENV === 'development') {
    console.warn(
      `Provider ${provider.name} (ID: ${provider.id}) has no quota data - skipping render`
    );
  }
  return null;
}

Or use a proper logging utility that respects environment settings.

className,
}: CircularProgressProps) {
// 计算百分比
const percentage = max > 0 ? Math.min(Math.round((value / max) * 100), 100) : 0;
Copy link
Owner Author

Choose a reason for hiding this comment

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

🟡 Medium: Division by zero risk when max is 0

Why this is a problem: While there is a check max > 0, the ternary falls back to 0, which is correct. However, the component doesn't handle negative values for value or max, which could cause unexpected visual results.

Suggested fix:
Add input validation for edge cases:

// Validate inputs and calculate percentage
const safeMax = Math.max(max, 0);
const safeValue = Math.max(Math.min(value, safeMax), 0);
const percentage = safeMax > 0 ? Math.round((safeValue / safeMax) * 100) : 0;


// 无限额的供应商:保持原有顺序(由数据库查询决定)
// 不需要额外排序
// 4. 排序(仅对有限额的供应商排序)
Copy link
Owner Author

Choose a reason for hiding this comment

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

🟡 Medium: Sorting algorithm could be simplified using optional chaining

Why this is a problem: The usage sorting pre-computes values into a Map, but the Map lookup uses nullish coalescing that defaults to 0. If calculateMaxUsage returns 0 for providers without quota data, the sorting could place them at unexpected positions.

Suggested fix:
The current implementation is correct but could be more defensive:

if (sortBy === "usage") {
  // Pre-compute usage values
  const usageMap = new Map<number, number>();
  withQuota.forEach((p) => usageMap.set(p.id, calculateMaxUsage(p)));

  withQuota.sort((a, b) => {
    const usageA = usageMap.get(a.id)!; // Safe since we just set it
    const usageB = usageMap.get(b.id)!;
    return usageB - usageA;
  });
}

@ding113 ding113 removed size/M Medium PR (< 500 lines) size/XL Extra Large PR (> 1000 lines) labels Nov 21, 2025
@ding113
Copy link
Owner Author

ding113 commented Nov 21, 2025

📊 PR Size Analysis

This PR is XL with 1,266 lines changed (1,033 additions + 233 deletions) across 23 files.

Large PRs are harder to review and more likely to introduce bugs. Consider splitting this into smaller, focused PRs for easier review and testing.

🔀 Suggested Split:

Based on the changes, this PR addresses two distinct features that could be split into:

PR 1: Provider Quota List Layout Refactor (~800 lines, ~15 files)

Focus: UI/UX redesign of provider quota management

Files:

  • src/app/[locale]/dashboard/quotas/providers/_components/* (4 component files)
  • messages/*/quota.json (11 i18n files - quota-specific translations)
  • src/components/ui/circular-progress.tsx
  • src/components/ui/countdown-timer.tsx
  • CHANGELOG.md (partial - quota refactor entry)

Description:

  • Refactor card layout to list layout
  • Add search and sort functionality
  • Implement circular progress indicators
  • Add countdown timers
  • Update quota-related translations

PR 2: Dark Mode Support (~200 lines, ~8 files)

Focus: Theme system implementation

Files:

  • src/components/ui/theme-switcher.tsx
  • src/app/providers.tsx
  • src/app/[locale]/layout.tsx
  • src/app/[locale]/dashboard/_components/dashboard-header.tsx
  • src/app/[locale]/settings/_components/settings-nav.tsx
  • src/app/[locale]/usage-doc/page.tsx
  • messages/*/common.json (11 i18n files - theme-related translations)
  • CHANGELOG.md (partial - dark mode entry)

Description:

  • Add theme switcher component
  • Integrate theme provider
  • Update layouts to support theme switching
  • Add theme-related translations

✅ Why Split?

  1. Easier to review: Each PR focuses on a single concern

    • PR 1: Pure UI refactor (layout + search/sort)
    • PR 2: Pure feature addition (dark mode)
  2. Independent testing: Features can be tested and deployed separately

    • Quota layout changes don't depend on dark mode
    • Dark mode doesn't depend on quota refactor
  3. Faster CI feedback: Smaller PRs run faster and fail faster

  4. Better git history: Clear commit messages for distinct features

  5. Easier to revert: If one feature has issues, only that feature needs to be reverted

  6. Reduced merge conflicts: Smaller PRs have less chance of conflicts with other work


📝 Note on i18n Files

The 11 translation files are split between:

  • Quota-specific (quota.json): Search, sort, list labels → Should go with PR 1
  • Theme-specific (common.json): Theme switcher labels → Should go with PR 2

This natural separation makes the split even cleaner!


🤖 Automated analysis by Claude AI

@ding113 ding113 added the size/XL Extra Large PR (> 1000 lines) label Nov 21, 2025
{/* 标题 */}
<span className="text-xs font-medium text-foreground/80">
{t("concurrentSessions.label")}
</span>
Copy link
Owner Author

Choose a reason for hiding this comment

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

🔴 Critical: Missing percentage cap causes incorrect tooltip display

Why this is a problem: In the renderConcurrentSessionsItem function, the percentage calculation does not cap at 100% like in renderQuotaItem. When current > limit, this will display values over 100% (e.g., "120% used"), which is confusing and inconsistent with the circular progress component that already caps at 100%.

Suggested fix:

const percentage = Math.min((current / limit) * 100, 100);

// In tooltip:
<div className="text-xs font-semibold">
  {percentage.toFixed(1)}% {t("list.used")}
</div>

Copy link
Owner Author

@ding113 ding113 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 PR refactors the provider quota management page from a card-based layout to a more compact list layout with circular progress indicators. The implementation includes dark mode support, search functionality, sorting options, and internationalization for 5 languages.

🔍 Issues Found

  • Critical (🔴): 1 issue
  • High (🟠): 0 issues
  • Medium (🟡): 0 issues
  • Low (🟢): 0 issues

🎯 Priority Actions

  1. Fix percentage calculation inconsistency - The renderConcurrentSessionsItem function calculates percentage without capping at 100%, while renderQuotaItem does cap it. This creates inconsistent tooltip displays when usage exceeds limits.

💡 General Observations

Strengths:

  • Well-structured component hierarchy with clear separation of concerns
  • Good accessibility features (ARIA labels, keyboard navigation, tooltips)
  • Comprehensive internationalization coverage
  • Performance optimizations using useMemo and debouncing
  • The renderQuotaItem function properly handles the resetInfo parameter for cost5h display

Previous reviewer feedback appears to have been addressed:

  • Missing translation keys (noMatchesDesc, noProvidersDesc) are now present
  • Search term debounce was reduced from 500ms to 300ms
  • filteredCount calculation now properly includes search filtering with useMemo
  • Usage sorting pre-computes values to avoid O(N²) complexity
  • renderQuotaItem signature was updated to support resetInfo parameter
  • Percentage calculations cap at 100% in renderQuotaItem

Remaining issue:
The only outstanding critical issue is the percentage calculation in renderConcurrentSessionsItem which needs to match the capping logic in renderQuotaItem.


🤖 Automated review by Claude AI - focused on identifying issues for improvement

ding113 and others added 2 commits November 22, 2025 03:39
将圆环容器从 items-center 改为 items-start,确保所有圆环和标题从顶部对齐。
这样即使部分圆环下方没有倒计时文字,也不会出现参差不齐的情况。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@ding113 ding113 removed the documentation Improvements or additions to documentation label Nov 21, 2025
保留 changelog 的手动格式,避免自动格式化工具修改其结构

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@ding113 ding113 merged commit 2ba313a into main Nov 21, 2025
7 of 8 checks passed
@ding113
Copy link
Owner Author

ding113 commented Nov 21, 2025

📊 PR Size Analysis

This PR is XL with 1,268 lines changed across 24 files.

Large PRs are harder to review and more likely to introduce bugs.

🔀 Suggested Split:

Based on the changes, this PR could be split into:

  1. PR 1: Internationalization (i18n) Updates - Translation files

    • messages/en/common.json, messages/en/quota.json
    • messages/ja/common.json, messages/ja/quota.json
    • messages/ru/common.json, messages/ru/quota.json
    • messages/zh-CN/common.json, messages/zh-CN/quota.json
    • messages/zh-TW/common.json, messages/zh-TW/quota.json
  2. PR 2: Provider Quota Feature - New quota management components

    • src/app/[locale]/dashboard/quotas/providers/_components/provider-quota-list-item.tsx
    • src/app/[locale]/dashboard/quotas/providers/_components/provider-quota-sort-dropdown.tsx
    • src/app/[locale]/dashboard/quotas/providers/_components/providers-quota-client.tsx
    • src/app/[locale]/dashboard/quotas/providers/_components/providers-quota-manager.tsx
  3. PR 3: UI Components & Layout Updates - Reusable components and layout changes

    • src/components/ui/circular-progress.tsx
    • src/components/ui/countdown-timer.tsx
    • src/components/ui/theme-switcher.tsx
    • src/app/[locale]/layout.tsx
    • src/app/providers.tsx
    • src/app/[locale]/dashboard/_components/dashboard-header.tsx
    • src/app/[locale]/settings/_components/settings-nav.tsx
    • src/app/[locale]/usage-doc/page.tsx
  4. PR 4: Configuration & Documentation

    • .prettierignore
    • CHANGELOG.md

Why Split?

  • Easier to review
  • Faster CI feedback
  • Easier to revert if needed
  • Better git history

🤖 Automated analysis by Claude AI

ding113 pushed a commit that referenced this pull request Nov 21, 2025
Copy link
Owner Author

@ding113 ding113 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 PR refactors the provider quota management page from a card-based layout to a more compact list layout, adds dark mode support, and implements search/sort functionality. The implementation is technically sound with proper error handling and performance optimizations.

🔍 Issues Found

  • Critical (🔴): 0 issues
  • High (🟠): 0 issues
  • Medium (🟡): 0 issues
  • Low (🟢): 0 issues

🎯 Priority Actions

No significant issues identified. The code is production-ready.

💡 General Observations

Strengths:

  • ✅ Proper division-by-zero protection in CircularProgress component (max > 0 check)
  • ✅ Correct React hooks usage with appropriate dependency arrays
  • ✅ Debounced search implementation (300ms) prevents excessive re-renders
  • ✅ Type-safe TypeScript throughout
  • ✅ Comprehensive i18n coverage across 5 languages
  • ✅ Accessibility features (ARIA labels, keyboard navigation)
  • ✅ Performance optimizations (useMemo for expensive calculations)

Code Quality:

  • Clean separation of concerns (list item, sort dropdown, manager components)
  • Consistent error handling patterns
  • Proper null/undefined checks before accessing quota data
  • Good use of modern React patterns (no class components, proper hooks usage)

No breaking changes detected - the refactoring maintains backward compatibility with the existing provider quota API.


🤖 Automated review by Claude AI - focused on identifying issues for improvement

@ding113
Copy link
Owner Author

ding113 commented Nov 21, 2025

🔒 Security Scan Results

No security vulnerabilities detected

This PR has been scanned against OWASP Top 10, CWE Top 25, and common security anti-patterns. No security issues were identified in the code changes.

📋 Analysis Summary

PR Overview:

  • Changes: UI refactor - Provider quota list layout redesign + dark mode support
  • Files Changed: 24 files (1032 additions, 236 deletions)
  • Type: Frontend UI/UX enhancement with internationalization

Code Changes Analyzed:

  • New React components for list-based quota display
  • Search functionality with debouncing (300ms)
  • Sorting dropdown component
  • Theme switcher component
  • Internationalization files (5 languages: zh-CN, zh-TW, en, ja, ru)
  • Component refactoring from card layout to list layout

✅ Scanned Categories

OWASP Top 10 (2021)

  • A01: Injection - No SQL, NoSQL, Command, or code injection vulnerabilities

    • Search term uses safe .toLowerCase().includes() pattern
    • All user input properly handled via React props
    • No dynamic query construction
  • A02: Broken Authentication - N/A (no auth changes)

    • No credential handling in this PR
  • A03: Sensitive Data Exposure - Clean

    • No sensitive data logging or exposure
    • Only displays quota metrics (non-sensitive business data)
  • A04: XML External Entities (XXE) - N/A (no XML parsing)

  • A05: Broken Access Control - N/A (no access control logic changes)

    • UI-only changes, authorization handled at API layer
  • A06: Security Misconfiguration - Clean

    • Proper React component structure
    • No debug code or verbose errors
  • A07: Cross-Site Scripting (XSS) - Clean

    • All dynamic content rendered via React's auto-escaping: {provider.name}, {label}, {resetInfo}
    • No dangerouslySetInnerHTML usage
    • No direct DOM manipulation
    • Search input properly sanitized via React controlled components
  • A08: Insecure Deserialization - N/A (no deserialization)

  • A09: Using Components with Known Vulnerabilities - Not evaluated

    • No new dependencies added in this PR
  • A10: Insufficient Logging & Monitoring - N/A (UI-only changes)

Additional Security Checks

  • SSRF (Server-Side Request Forgery) - N/A (no network requests)
  • Path Traversal - N/A (no file operations)
  • Race Conditions - Clean (uses React hooks correctly: useMemo, useState)
  • Cryptographic Issues - N/A (no crypto operations)
  • Input Validation - Secure
    • Search: searchTerm.toLowerCase().includes(term) (safe string operation)
    • Sort: Type-safe enum (QuotaSortKey)
    • All inputs via TypeScript typed props
  • Information Disclosure - Clean
    • No sensitive info in error messages
    • Proper empty state handling
  • Client-Side Storage - No localStorage/sessionStorage usage in PR

🛡️ Security Posture: STRONG

This PR contains only UI/UX improvements with proper security practices:

  1. XSS Prevention: All dynamic content uses React's auto-escaping
  2. Type Safety: Full TypeScript coverage with strict types
  3. Input Sanitization: Search uses safe string methods (.toLowerCase().includes())
  4. No Dangerous Patterns: No eval(), dangerouslySetInnerHTML, or direct DOM manipulation
  5. Performance Best Practices: Debouncing prevents DoS via rapid input
  6. Internationalization: Proper i18n without injection risks

🎯 Key Security Features

React Auto-Escaping: All user-provided data (provider names, labels) rendered via JSX expressions
Type Safety: TypeScript interfaces prevent type confusion attacks
Debounce Protection: 300ms debounce on search prevents rapid-fire abuse
Safe String Operations: .toLowerCase().includes() is injection-safe
No External Data Sources: All data from typed props, no external fetches


🤖 Automated security scan by Claude AI - OWASP Top 10 & CWE coverage
📅 Scan completed: 2025-11-21 19:46:19 UTC
🔍 Methodology: Manual code review + pattern matching + OWASP guidelines

This was referenced Nov 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant