Skip to content

Comments

Fix-expiration-time-display#286

Merged
ding113 merged 2 commits intoding113:devfrom
NightYuYyy:fix-expiration-time-display
Dec 7, 2025
Merged

Fix-expiration-time-display#286
ding113 merged 2 commits intoding113:devfrom
NightYuYyy:fix-expiration-time-display

Conversation

@NightYuYyy
Copy link
Collaborator

@NightYuYyy NightYuYyy commented Dec 7, 2025

Summary

Enhanced user management UI with expiration date display and integrated edit/delete functionalities.

Problem

The user list and quota pages were missing critical expiration date information, making it difficult for administrators to track user account validity. Additionally, edit and delete user operations required navigating away from the user list, reducing efficiency.

Solution

This PR implements a comprehensive UI enhancement for user management:

  • Added expiration date display with relative time formatting and status badges
  • Integrated edit and delete user functionalities directly in the user list
  • Refactored dropdown menu to include all user management actions in one place
  • Enhanced user quota display with expiration information and status indicators

Changes

  • i18n: Added "expiresAtLabel" translations for English, Japanese, Russian, Simplified Chinese, and Traditional Chinese
  • User List UI:
    • Replaced separate "Renew" and "Enable/Disable" buttons with unified dropdown menu
    • Added edit (SquarePen) and delete (Trash) actions with confirmation dialogs
    • Integrated UserForm and DeleteUserConfirm components
  • User Quota Display:
    • Added expiration date display in user-quota-list-item.tsx with formatted date and relative time
    • Added status badges (Active/Expiring Soon/Expired/Disabled) based on expiration state
    • Implemented same changes in user-unlimited-item.tsx for consistency
  • Type Enhancement: Extended UserQuotaWithUsage interface to include isEnabled and expiresAt fields
  • List Component: Enhanced metadata display to support compact mode for better responsiveness

Testing

  • Manual testing performed
  • UI/UX improvements verified
  • i18n translations added for all supported languages
  • No breaking changes

Related Issues

Closes #285


🚀 This PR improves administrator experience by providing at-a-glance user expiration information and streamlined user management operations.

- Added "expiresAtLabel" translations for English, Japanese, Russian, Simplified Chinese, and Traditional Chinese.
- Enhanced user management UI by integrating edit and delete user functionalities with confirmation dialogs.
- Updated user quota handling to include expiration dates and status indicators for user activity.
- Improved user list display to show expiration information and status badges for better user experience.
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @NightYuYyy, 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 upgrades the user management and quota display features. It introduces comprehensive internationalization for expiration labels, integrates robust user editing and deletion capabilities with confirmation dialogs, and enhances the user interface to clearly show user expiration dates and activity statuses through intuitive badges. These changes aim to provide administrators with a more powerful and user-friendly tool for managing user accounts and their associated quotas.

Highlights

  • Internationalization (i18n) for Expiration Label: Added the 'expiresAtLabel' translation key across five languages (English, Japanese, Russian, Simplified Chinese, and Traditional Chinese) to support displaying user expiration information globally.
  • Enhanced User Management UI: The user list now includes a dropdown menu for each user, providing direct access to 'Edit' and 'Delete' actions, alongside existing 'Renew' options. These actions trigger new confirmation and edit dialogs for a more streamlined management experience.
  • User Quota Expiration and Status Display: User data models have been extended to include 'isEnabled' and 'expiresAt' fields. The user list items now prominently display the expiration date and a status badge (e.g., 'Active', 'Expiring Soon', 'Expired', 'Disabled') for better visibility of user account states.
  • Improved User List Item Presentation: The display of metadata within list items has been refined, particularly for compact views, to ensure that new expiration details and status badges are presented clearly and consistently.
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.

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

This pull request introduces significant enhancements to the user management UI, including the display of expiration times, status indicators, and the integration of edit and delete functionalities. The changes are well-structured, adding new translations and updating data types to support these features. My review focuses on improving code maintainability by standardizing comment language and addressing a potential bug related to the use of useMemo for time-sensitive data, which could lead to a stale UI. Overall, these changes improve the user experience and administrative capabilities.

Comment on lines 53 to 72
const expiryText = useMemo(() => {
if (!expiresAtDate) return tUsersCommon("neverExpires");
return `${formatDateDistance(expiresAtDate, new Date(), locale, { addSuffix: true })} · ${formatDate(expiresAtDate, "yyyy-MM-dd", locale)}`;
}, [expiresAtDate, locale, tUsersCommon]);

const expiryStatus = useMemo(() => {
const now = Date.now();
const expTs = expiresAtDate?.getTime() ?? null;

if (!user.isEnabled) {
return { label: tStatus("disabled"), variant: "secondary" as const };
}
if (expTs && expTs <= now) {
return { label: tStatus("expired"), variant: "destructive" as const };
}
if (expTs && expTs - now <= EXPIRING_SOON_MS) {
return { label: tStatus("expiringSoon"), variant: "outline" as const };
}
return { label: tStatus("active"), variant: "default" as const };
}, [expiresAtDate, tStatus, user.isEnabled]);
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Using useMemo with new Date() and Date.now() can lead to a stale UI. The calculated expiryText and expiryStatus are based on the current time, but they will only be re-evaluated when the component's props in the dependency array change, not as time passes. This can result in displaying incorrect information, for example, a user's status might not update from 'expiring soon' to 'expired' automatically. Since these calculations are not computationally expensive, it's better to remove useMemo to ensure the UI always shows fresh data.

Suggested change
const expiryText = useMemo(() => {
if (!expiresAtDate) return tUsersCommon("neverExpires");
return `${formatDateDistance(expiresAtDate, new Date(), locale, { addSuffix: true })} · ${formatDate(expiresAtDate, "yyyy-MM-dd", locale)}`;
}, [expiresAtDate, locale, tUsersCommon]);
const expiryStatus = useMemo(() => {
const now = Date.now();
const expTs = expiresAtDate?.getTime() ?? null;
if (!user.isEnabled) {
return { label: tStatus("disabled"), variant: "secondary" as const };
}
if (expTs && expTs <= now) {
return { label: tStatus("expired"), variant: "destructive" as const };
}
if (expTs && expTs - now <= EXPIRING_SOON_MS) {
return { label: tStatus("expiringSoon"), variant: "outline" as const };
}
return { label: tStatus("active"), variant: "default" as const };
}, [expiresAtDate, tStatus, user.isEnabled]);
const expiryText = (() => {
if (!expiresAtDate) return tUsersCommon("neverExpires");
return `${formatDateDistance(expiresAtDate, new Date(), locale, { addSuffix: true })} · ${formatDate(expiresAtDate, "yyyy-MM-dd", locale)}`;
})();
const expiryStatus = (() => {
const now = Date.now();
const expTs = expiresAtDate?.getTime() ?? null;
if (!user.isEnabled) {
return { label: tStatus("disabled"), variant: "secondary" as const };
}
if (expTs && expTs <= now) {
return { label: tStatus("expired"), variant: "destructive" as const };
}
if (expTs && expTs - now <= EXPIRING_SOON_MS) {
return { label: tStatus("expiringSoon"), variant: "outline" as const };
}
return { label: tStatus("active"), variant: "default" as const };
})();

Comment on lines 41 to 60
const expiryText = useMemo(() => {
if (!expiresAtDate) return tUsersCommon("neverExpires");
return `${formatDateDistance(expiresAtDate, new Date(), locale, { addSuffix: true })} · ${formatDate(expiresAtDate, "yyyy-MM-dd", locale)}`;
}, [expiresAtDate, locale, tUsersCommon]);

const expiryStatus = useMemo(() => {
const now = Date.now();
const expTs = expiresAtDate?.getTime() ?? null;

if (!user.isEnabled) {
return { label: tStatus("disabled"), variant: "secondary" as const };
}
if (expTs && expTs <= now) {
return { label: tStatus("expired"), variant: "destructive" as const };
}
if (expTs && expTs - now <= EXPIRING_SOON_MS) {
return { label: tStatus("expiringSoon"), variant: "outline" as const };
}
return { label: tStatus("active"), variant: "default" as const };
}, [expiresAtDate, tStatus, user.isEnabled]);
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Using useMemo with new Date() and Date.now() can lead to a stale UI. The calculated expiryText and expiryStatus are based on the current time, but they will only be re-evaluated when the component's props in the dependency array change, not as time passes. This can result in displaying incorrect information. Since these calculations are not computationally expensive, consider removing useMemo to ensure the UI always shows fresh data.

Suggested change
const expiryText = useMemo(() => {
if (!expiresAtDate) return tUsersCommon("neverExpires");
return `${formatDateDistance(expiresAtDate, new Date(), locale, { addSuffix: true })} · ${formatDate(expiresAtDate, "yyyy-MM-dd", locale)}`;
}, [expiresAtDate, locale, tUsersCommon]);
const expiryStatus = useMemo(() => {
const now = Date.now();
const expTs = expiresAtDate?.getTime() ?? null;
if (!user.isEnabled) {
return { label: tStatus("disabled"), variant: "secondary" as const };
}
if (expTs && expTs <= now) {
return { label: tStatus("expired"), variant: "destructive" as const };
}
if (expTs && expTs - now <= EXPIRING_SOON_MS) {
return { label: tStatus("expiringSoon"), variant: "outline" as const };
}
return { label: tStatus("active"), variant: "default" as const };
}, [expiresAtDate, tStatus, user.isEnabled]);
const expiryText = (() => {
if (!expiresAtDate) return tUsersCommon("neverExpires");
return `${formatDateDistance(expiresAtDate, new Date(), locale, { addSuffix: true })} · ${formatDate(expiresAtDate, "yyyy-MM-dd", locale)}`;
})();
const expiryStatus = (() => {
const now = Date.now();
const expTs = expiresAtDate?.getTime() ?? null;
if (!user.isEnabled) {
return { label: tStatus("disabled"), variant: "secondary" as const };
}
if (expTs && expTs <= now) {
return { label: tStatus("expired"), variant: "destructive" as const };
}
if (expTs && expTs - now <= EXPIRING_SOON_MS) {
return { label: tStatus("expiringSoon"), variant: "outline" as const };
}
return { label: tStatus("active",), variant: "default" as const };
})();

@ding113 ding113 added size/S Small PR (< 200 lines) enhancement New feature or request size/XS Extra Small PR (< 50 lines) labels Dec 7, 2025
Copy link
Owner

@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 - Critical Issues Found

1. CRITICAL Missing Import Statement (user-list.tsx:3)

Problem: The code uses MoreVertical, SquarePen, and Trash icons from lucide-react, but the import statement only includes Loader2 and Users. This will cause runtime errors.

Fix Required:

import { Loader2, MoreVertical, SquarePen, Trash, Users } from "lucide-react";

2. CRITICAL Missing Import Statements (user-quota-list-item.tsx:14, user-unlimited-item.tsx:8)

Problem: Both files import formatDateDistance but user-quota-list-item.tsx also uses formatDate which is added in this PR but was already present in the old code. Similarly, user-unlimited-item.tsx adds imports for formatDate and formatDateDistance. Need to verify these imports are complete.

Looking at the diff more carefully:

  • user-quota-list-item.tsx: line 14 changes from formatDateDistance only to formatDate, formatDateDistance ✅ CORRECT
  • user-unlimited-item.tsx: line 8 adds formatDate, formatDateDistance ✅ CORRECT

Actually these are correct. Removing this issue.


3. HIGH Missing Imports for Form Components (user-list.tsx:7-30)

Problem: The code uses FormErrorBoundary, DeleteUserConfirm, and UserForm components but the diff shows the imports are added. Let me verify:

Line 7 shows: import { FormErrorBoundary } from "@/components/form-error-boundary";
Lines 31-32 show: import { DeleteUserConfirm } from "./forms/delete-user-confirm"; and import { UserForm } from "./forms/user-form";

These are correctly added.


After careful re-review of the diff, I found the actual issue: The diff shows the NEW code has the imports removed!

Looking at line 3 in the diff:

-import { Loader2, Users } from "lucide-react";
+import { Loader2, MoreVertical, SquarePen, Trash, Users } from "lucide-react";

Wait, the + means this IS the added line. So the imports ARE correct in the new code.

Let me re-read the diff more carefully...

Copy link
Owner

@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

After thorough analysis of this PR, I have identified NO CRITICAL BLOCKERS. The code changes are generally well-implemented with proper imports, type safety, and i18n support.

PR Size: S

  • Lines changed: 188 (158 additions + 30 deletions)
  • Files changed: 11

Issues Found

Category Critical High Medium Low
Code Quality 0 1 2 1
Security 0 0 0 0
Dependencies 0 0 0 0
Documentation 0 0 0 0

Priority Actions

  1. HIGH: User list may not refresh after edit/delete operations. The UserForm and DeleteUserConfirm components call onSuccess() callback but there's no data refetch mechanism. Verify that these components internally trigger a router refresh or emit an event that causes the user list to re-fetch. If not, this could lead to stale UI state.

  2. MEDIUM: The EXPIRING_SOON_MS = 72 * 60 * 60 * 1000 constant is duplicated across three files:

    • src/app/[locale]/dashboard/_components/user/user-list.tsx:55
    • src/app/[locale]/dashboard/quotas/users/_components/user-quota-list-item.tsx:24 (new)
    • src/app/[locale]/dashboard/quotas/users/_components/user-unlimited-item.tsx:16 (new)

    Recommendation: Extract to a shared constant file (e.g., src/lib/constants/time.ts) to maintain DRY principle.

  3. MEDIUM: Status calculation logic (checking isEnabled, expired, expiringSoon) is duplicated in:

    • user-list.tsx: getStatusInfo() function
    • user-quota-list-item.tsx: inline expiryStatus useMemo
    • user-unlimited-item.tsx: inline expiryStatus useMemo

    Recommendation: Extract to a shared utility function (e.g., src/lib/utils/user-status.ts) for consistency and maintainability.

  4. LOW: The onClick vs onSelect inconsistency in DropdownMenuItem. The new code correctly uses onSelect (lines 267-319 in the diff), but ensure this aligns with the DropdownMenu component's expected API.

Positive Observations

  • ✅ All necessary imports are correctly added
  • ✅ i18n translations added for all 5 supported languages
  • ✅ Type safety maintained with proper interface extensions
  • ✅ Consistent use of useMemo for derived state
  • ✅ Proper accessibility with form error boundaries
  • ✅ No security vulnerabilities detected (OWASP Top 10 checked)

Review Coverage

  • Code quality and correctness - 1 High, 2 Medium, 1 Low issues
  • Security (OWASP Top 10) - Clean
  • PR size assessment - S (well-scoped)
  • Dependency changes - None
  • Documentation changes - i18n only (verified correct)

Recommendation: ✅ APPROVE with suggested improvements. The high-priority issue should be verified but is likely already handled by the form components. The medium-priority refactoring suggestions can be addressed in a follow-up PR for better code organization.


Automated review by Claude AI

- Translated user dialog comments from Chinese to English for clarity.
- Refactored memoization in UserQuotaListItem and UserUnlimitedItem components to use inline functions instead of useMemo for improved readability and performance.
@ding113 ding113 merged commit fedd96b into ding113:dev Dec 7, 2025
2 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Dec 7, 2025
@NightYuYyy NightYuYyy deleted the fix-expiration-time-display branch December 8, 2025 17:05
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/S Small PR (< 200 lines) size/XS Extra Small PR (< 50 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants