feat: add search and toggle verify button to user page#16
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR enhances the admin Users page with debounced search and a UI/server flow to toggle a user’s verified status, plus end-to-end coverage for both behaviors.
Changes:
- Introduces a generic
useDebouncehook and aUserSearchcomponent, wiring them into the adminUserListto support debounced searching with empty-state messaging and pagination-aware queries. - Adds a
toggleUserVerifiedserver action and updatesUserRowto show a verified/unverified badge and a toggle item in the actions menu, with local state and parent callbacks to keep the list in sync. - Extends
getUsersto accept an optional search term, filters and paginates results accordingly, and adds Playwright E2E tests for user search and the verification toggle flow.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| hooks/use-debounce.ts | New reusable client hook to debounce arbitrary values, used for search input. |
| components/admin/user-search.tsx | New presentational search input with icon, loading spinner, and clear button for the admin users list. |
| components/admin/user-row.tsx | Adds local verified state, a toggle-verified dropdown item, and wiring to the new toggleUserVerified server action and parent callback. |
| components/admin/user-list.tsx | Integrates search UI and debounced queries, threads search into pagination, adjusts empty state messaging, and updates list state on verification changes. |
| actions/admin/users/toggle-verified.ts | New admin-only server action that flips a user’s emailVerified flag in the database and revalidates the users page. |
| actions/admin/users/list.ts | Extends users listing to support search (email, username, display name) while preserving cursor-based pagination and count semantics. |
| e2e/admin/users.spec.ts | Adds Playwright tests covering user search UX and the verified/unverified toggle in the admin users UI. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
e2e/admin/users.spec.ts
Outdated
| test("should update actions menu after toggle", async ({ page }) => { | ||
| // Find the test user's row (use last() to get desktop layout which is visible) | ||
| const userRow = page.locator(`[data-testid^="user-row-"]`).last(); | ||
| const userId = (await userRow.getAttribute("data-testid"))?.replace( | ||
| "user-row-", | ||
| "", | ||
| ); | ||
|
|
||
| // Check initial state (use exact match to avoid "Unverified" matching "Verified") | ||
| const verifiedBadge = userRow.getByText("Verified", { exact: true }); | ||
|
|
||
| // Accept the confirmation dialog before clicking | ||
| page.on("dialog", (dialog) => dialog.accept()); | ||
|
|
||
| // Open actions menu (use last() to get desktop layout which is visible) | ||
| await page.getByTestId(`user-actions-${userId}`).last().click(); | ||
|
|
||
| // Click the toggle action | ||
| await page.getByTestId(`unmark-verified-${userId}`).click(); | ||
|
|
||
| // Wait for the action to complete | ||
| await page.waitForTimeout(500); | ||
|
|
||
| // Open actions menu again (use last() to get desktop layout which is visible) | ||
| await page.getByTestId(`user-actions-${userId}`).last().click(); | ||
|
|
||
| // The toggle action should now show the opposite option | ||
| await expect(page.getByTestId(`mark-verified-${userId}`)).toBeVisible(); |
There was a problem hiding this comment.
This test both hardcodes a click on unmark-verified-${userId} and relies on the user starting in a verified state, but the same user is toggled in the previous test and there is no reset in beforeEach, so the outcome becomes dependent on test execution order and persisted DB state. Additionally, the verifiedBadge locator is created but never asserted, so the initial state mentioned in the comment is not actually being checked. It would be more robust to either give this test its own user or explicitly reset the verification status before running the assertions, and to assert the initial badge state while choosing the correct toggle action based on the current state.
| const handleVerificationChanged = (userId: string, verified: boolean) => { | ||
| setUsers( | ||
| users.map((u) => (u.id === userId ? { ...u, emailVerified: verified } : u)) | ||
| ); |
There was a problem hiding this comment.
When updating users in response to a verification change, this uses the users value from the outer closure, which can become stale if multiple updates are queued close together (for example, a delete and a verification toggle). Using a functional state update (deriving the new array from the previous state passed into the setter) would make this more robust against concurrent state updates.
| test("should toggle verification status", async ({ page }) => { | ||
| // Find the test user's row (use last() to get desktop layout which is visible) | ||
| const userRow = page.locator(`[data-testid^="user-row-"]`).last(); | ||
| const userId = (await userRow.getAttribute("data-testid"))?.replace( | ||
| "user-row-", | ||
| "", | ||
| ); | ||
|
|
||
| // Accept the confirmation dialog before clicking | ||
| page.on("dialog", (dialog) => dialog.accept()); | ||
|
|
||
| // Open actions menu (use last() to get desktop layout which is visible) | ||
| await page.getByTestId(`user-actions-${userId}`).last().click(); | ||
|
|
||
| // Click the toggle action | ||
|
|
||
| await page.getByTestId(`unmark-verified-${userId}`).click(); | ||
|
|
||
| // Wait for the action to complete | ||
| await page.waitForTimeout(500); | ||
|
|
||
| // Should now show Unverified badge | ||
| await expect(page.getByText("Unverified").nth(1)).toBeVisible(); |
There was a problem hiding this comment.
This test unconditionally clicks the unmark-verified-${userId} action, which assumes the user is currently verified. Because the verification status can change (and the same user is reused across tests), this makes the test order-dependent and it will fail if the current state is "Unverified" (where only the mark-verified-* action exists). Consider either resetting the user's verification state before this test or branching on which toggle action is currently visible so the locator matches the actual state under test.
6a67c2d to
96a65db
Compare
96a65db to
a3d04bb
Compare
No description provided.