Skip to content

feat: setup admin management flow with backend endpoints#25

Merged
mrcoded merged 4 commits into
devfrom
feat/admin-management
Jul 9, 2026
Merged

feat: setup admin management flow with backend endpoints#25
mrcoded merged 4 commits into
devfrom
feat/admin-management

Conversation

@ayomikun-ade

@ayomikun-ade ayomikun-ade commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR wires up the six admin management REST endpoints (replacing mock data) and fixes a production login error caused by Next.js 15 error sanitization in server actions..

Changes Proposed

What were you told to do?

  1. Connect GET /admin/admins, POST /admin/admins/invite, POST /admin/admins/{id}/reset-password, PATCH /admin/admins/{id}/email, PATCH /admin/admins/{id}/role, PATCH /admin/admins/{id}/deactivate, and PATCH /admin/admins/{id}/reactivate following the existing actions/hooks/types pattern.

What did you do?

Admin management API integration:

  • Updated src/types/api/admin-management.ts — aligned AdminAccountStatus to API values ("active", "pending_setup", "deactivated"), renamed lastLoginlast_login, added AdminAccountsQueryParams, AdminAccountsPage, and GetAdminAccountsResponse types. Added confirm_downgrade?: boolean to ChangeAdminRoleInput and a ADMIN_STATUS_LABELS map for display formatting.
  • Replaced all mock implementations in src/actions/admin-management.ts with real authApi calls. All mutation actions catch errors via toApiError and return { ok: false, message } — they never throw, keeping the existing result-based error handling in the table component intact. getAdminAccounts handles the double-wrapped API envelope (data.data.items).
  • Updated src/hooks/api/keys.ts to add adminManagementKeys.list(params) and src/hooks/api/use-admin-management.ts to accept AdminAccountsQueryParams with keepPreviousData.
  • Updated columns.tsx (status variant map, last_login cell, isDeactivated check) and admin-accounts-table.tsx (data?.items ?? [], passes confirm_downgrade on super admin downgrade).

Production login fix:

  • Root cause: Next.js 15 sanitizes errors thrown from server actions in production, replacing them with a generic message before they reach the client.
  • src/actions/auth.ts: changed login() from throwing to returning LoginResult = { success: true; data } | { success: false; error: string }. All error paths (API failure, missing cookies) now return, never throw.
  • src/components/auth/admin-login-form.tsx: updated to check result.success. signIn() is now wrapped in its own try/catch since it can throw on NextAuth endpoint failure.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Chore (changes that do not relate to a fix or feature and don't modify src or test files)

Check List

  • My code follows the code style of this project.
  • This PR does not contain plagiarized content.
  • The title and description of the PR are clear and explain the approach.
  • I am making a pull request against the dev branch (left side).
  • My commit message style matches our requested structure.
  • My code additions will not fail code linting checks or unit tests.
  • I am only making changes to files I was requested to.

Summary by CodeRabbit

  • New Features
    • Admin account management is now powered by live backend data (viewing, inviting, editing, activating, and deactivating accounts).
    • Added dedicated dialogs for password reset, email change, role change, and activation toggling, improving focus and flow.
  • Bug Fixes
    • Admin login now provides clearer success/failure results and more consistent error messaging.
    • Admin table display is improved with standardized status labels and better “last login” formatting when missing.
    • Activation/deactivation actions now align correctly with the current account status.

@ayomikun-ade ayomikun-ade self-assigned this Jul 8, 2026
@ayomikun-ade ayomikun-ade requested a review from mrcoded July 8, 2026 17:05
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Admin management now uses server-backed API actions, parameterized query hooks, extracted dialogs, and normalized status/last_login fields. Login now returns a typed success/failure result, and the login form handles that result plus session-start errors.

Changes

Admin management API migration

Layer / File(s) Summary
Types and status normalization
src/types/api/admin-management.ts
AdminAccountStatus and ManagedAdminAccount use lowercase/snake-case shapes, ADMIN_STATUS_LABELS is added, and admin list response/query types are added.
Server actions call real API
src/actions/admin-management.ts
Admin list and mutation actions now call authApi endpoints and return standardized success/failure results.
Query keys and mutation hooks
src/hooks/api/keys.ts, src/hooks/api/use-admin-management.ts
Adds parameterized admin list keys, parameterized account queries, and shared mutation hooks with toast feedback and cache invalidation.
Extracted admin action dialogs
src/components/admin-management/admin-action-dialogs.tsx, src/components/admin-management/admin-toggle-activation-modal.tsx
Adds reusable dialogs for reset password, email change, role change, and activation toggling.
Table wiring and column normalization
src/components/admin-management/admin-accounts-table.tsx, src/components/admin-management/columns.tsx
The table delegates dialog UI to extracted components and the columns now read normalized status and last_login fields.
Mock data field renaming
src/mocks/admin-management.ts
Mock admin records now use last_login and lowercase status values.

Login result handling

Layer / File(s) Summary
LoginResult type and login action
src/actions/auth.ts
login now returns a discriminated success/failure result and converts API or session-cookie failures into error strings.
Login form consumption
src/components/auth/admin-login-form.tsx
The form branches on result.success and wraps login/session-start calls in try/catch blocks.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AdminManagementHooks
  participant AdminManagementActions
  participant authApi
  participant AdminManagementUI

  AdminManagementHooks->>AdminManagementActions: invoke admin action or list query
  AdminManagementActions->>authApi: GET/POST/PATCH /admin/admins
  authApi-->>AdminManagementActions: response or error
  AdminManagementActions-->>AdminManagementHooks: AdminManagementResult / AdminAccountsPage
  AdminManagementHooks-->>AdminManagementUI: data, toast updates, invalidation
Loading
sequenceDiagram
  participant AdminLoginForm
  participant loginAction
  participant publicApi
  participant signIn

  AdminLoginForm->>loginAction: login(body)
  loginAction->>publicApi: post credentials
  publicApi-->>loginAction: response or error
  loginAction-->>AdminLoginForm: LoginResult
  AdminLoginForm->>signIn: signIn("credentials") if success
  signIn-->>AdminLoginForm: signInResult
Loading

Possibly related PRs

Suggested reviewers: mrcoded

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: moving admin management to backend endpoints.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/admin-management

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
src/actions/auth.ts (1)

23-52: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Keep the full login flow inside the LoginResult boundary.

Line 50 and Line 52 can still throw/reject outside the try/catch, which reintroduces thrown server-action errors instead of returning { success: false }. Wrap cookie persistence and response unwrapping too; unwrap before persisting cookies so invalid envelopes do not leave cookies behind.

Proposed fix
 export async function login(body: LoginInput): Promise<LoginResult> {
-  let res: Awaited<
-    ReturnType<typeof publicApi.post<ApiEnvelope<LoginResponseData>>>
-  >;
-
   try {
-    res = await publicApi.post<ApiEnvelope<LoginResponseData>>(
+    const res = await publicApi.post<ApiEnvelope<LoginResponseData>>(
       "/admin/auth/login",
       body,
     );
-  } catch (err) {
-    return { success: false, error: toApiError(err).message };
-  }
 
-  // Forward API auth cookies to the browser so subsequent server-action API
-  // calls can read and proxy them via getServerCookieHeader().
-  const cookies = setCookieHeadersFrom(res.headers)
-    .map(parseSetCookieHeader)
-    .filter((c): c is NonNullable<typeof c> => c != null);
+    const data = unwrapData(res);
 
-  if (cookies.length === 0) {
-    return {
-      success: false,
-      error: "Authentication failed: no session cookies returned by the API.",
-    };
-  }
+    // Forward API auth cookies to the browser so subsequent server-action API
+    // calls can read and proxy them via getServerCookieHeader().
+    const cookies = setCookieHeadersFrom(res.headers)
+      .map(parseSetCookieHeader)
+      .filter((c): c is NonNullable<typeof c> => c != null);
 
-  await persistServerCookies(cookies);
+    if (cookies.length === 0) {
+      return {
+        success: false,
+        error: "Authentication failed: no session cookies returned by the API.",
+      };
+    }
 
-  return { success: true, data: unwrapData(res) };
+    await persistServerCookies(cookies);
+
+    return { success: true, data };
+  } catch (err) {
+    return { success: false, error: toApiError(err).message };
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/actions/auth.ts` around lines 23 - 52, The `login` flow is only partially
protected by the `try/catch`, so `persistServerCookies` and `unwrapData` can
still throw and escape the `LoginResult` contract. Move the cookie persistence
and response unwrapping into the same guarded flow in `login`, and ensure
`unwrapData(res)` happens before `persistServerCookies(cookies)` so invalid API
envelopes do not leave cookies persisted. Keep any failure in `login` returning
`{ success: false, error: ... }` instead of rejecting.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/actions/admin-management.ts`:
- Around line 34-102: The six mutation functions in admin-management.ts all
repeat the same try/catch, ok, and fail(toApiError(err).message) pattern, so
extract a shared helper to centralize this flow. Add a reusable wrapper around
authApi calls that accepts the request promise and success message, then update
inviteAdmin, resetAdminPassword, changeAdminEmail, changeAdminRole,
deactivateAdminAccount, and reactivateAdminAccount to use it while preserving
their current messages and request payloads.

In `@src/components/admin-management/admin-action-dialogs.tsx`:
- Around line 112-117: The new email field in the admin action dialog currently
allows empty submissions because the Input only sets type="email" without
enforcing required validation. Update the Input used for newEmail in
admin-action-dialogs.tsx to include the required attribute so browser validation
blocks empty submits before the form reaches the server.
- Around line 61-63: The Reset Password action in admin-action-dialogs should
not let AlertDialogAction auto-close the dialog before the mutation completes.
Control the dialog’s open state around the relevant AlertDialog and
AlertDialogAction usage, then close it only from the password reset mutation’s
onSuccess path (or replace the action with a plain button) so failures keep the
dialog open for retries.

In `@src/hooks/api/use-admin-management.ts`:
- Around line 34-127: The five hooks in use-admin-management.ts repeat the same
mutation flow, so extract a shared factory around useMutation that centralizes
the result.ok handling, success toast, optional onSuccess callback, and
invalidation of adminManagementKeys.accounts(). Keep the per-hook differences
limited to the mutationFn and the onError message, and reuse the factory for
useResetAdminPassword, useChangeAdminEmail, useChangeAdminRole,
useDeactivateAdmin, and useReactivateAdmin. While refactoring, make sure the
shared result type (such as AdminAccountResult) matches the actual server action
return shape with ok and message.

---

Outside diff comments:
In `@src/actions/auth.ts`:
- Around line 23-52: The `login` flow is only partially protected by the
`try/catch`, so `persistServerCookies` and `unwrapData` can still throw and
escape the `LoginResult` contract. Move the cookie persistence and response
unwrapping into the same guarded flow in `login`, and ensure `unwrapData(res)`
happens before `persistServerCookies(cookies)` so invalid API envelopes do not
leave cookies persisted. Keep any failure in `login` returning `{ success:
false, error: ... }` instead of rejecting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e907136b-23c8-4943-9e21-1fcee5c29b17

📥 Commits

Reviewing files that changed from the base of the PR and between 4e8036f and e17bee0.

📒 Files selected for processing (11)
  • src/actions/admin-management.ts
  • src/actions/auth.ts
  • src/components/admin-management/admin-accounts-table.tsx
  • src/components/admin-management/admin-action-dialogs.tsx
  • src/components/admin-management/admin-toggle-activation-modal.tsx
  • src/components/admin-management/columns.tsx
  • src/components/auth/admin-login-form.tsx
  • src/hooks/api/keys.ts
  • src/hooks/api/use-admin-management.ts
  • src/mocks/admin-management.ts
  • src/types/api/admin-management.ts

Comment thread src/actions/admin-management.ts Outdated
Comment thread src/components/admin-management/admin-action-dialogs.tsx Outdated
Comment thread src/components/admin-management/admin-action-dialogs.tsx
Comment thread src/hooks/api/use-admin-management.ts Outdated
- Extract shared callAdminApi/createAdminMutation helpers to remove
  duplicated try/catch and toast/invalidation boilerplate.
- Keep the reset password dialog open until the mutation resolves
  instead of letting AlertDialogAction auto-close it.
- Require the new email field to block empty submissions client-side.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/components/admin-management/admin-action-dialogs.tsx (1)

74-138: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Stale local state when admin changes or dialog reopens.

useState(admin?.email ?? "") only initializes on first mount. Since the dialog stays mounted when open={false}, switching to a different admin or reopening the dialog retains the previously typed email. This can cause the wrong email to be submitted for a different admin.

The same issue applies to ChangeRoleDialog at line 141 where useState(admin?.role ?? "admin") won't update when admin changes.

🛡️ Proposed fix: sync local state when dialog opens
 export function ChangeEmailDialog({ admin, open, onClose }: DialogProps) {
   const [newEmail, setNewEmail] = React.useState(admin?.email ?? "");
   const mutation = useChangeAdminEmail(onClose);
+
+  React.useEffect(() => {
+    if (open) {
+      setNewEmail(admin?.email ?? "");
+    }
+  }, [open, admin]);

   const serverError = mutation.data?.ok === false ? mutation.data.message : "";

Apply the same pattern to ChangeRoleDialog:

 export function ChangeRoleDialog({ admin, open, onClose }: DialogProps) {
   const [selectedRole, setSelectedRole] = React.useState<AdminRole>(
     admin?.role ?? "admin",
   );
   const mutation = useChangeAdminRole(onClose);
+
+  React.useEffect(() => {
+    if (open) {
+      setSelectedRole(admin?.role ?? "admin");
+    }
+  }, [open, admin]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/admin-management/admin-action-dialogs.tsx` around lines 74 -
138, The dialog state is initialized once from `admin` and then becomes stale
when a different admin is selected or the dialog is reopened. Update
`ChangeEmailDialog` and `ChangeRoleDialog` to sync their local state with the
current `admin` (for example when `open` changes to true or `admin` changes) so
`newEmail` and the role value always reflect the active admin before submit. Use
the existing `ChangeEmailDialog` and `ChangeRoleDialog` components and their
`useState`/dialog open handling to reset the form state instead of relying on
the initial `useState(admin?.email ?? "")` and `useState(admin?.role ??
"admin")` values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/components/admin-management/admin-action-dialogs.tsx`:
- Around line 74-138: The dialog state is initialized once from `admin` and then
becomes stale when a different admin is selected or the dialog is reopened.
Update `ChangeEmailDialog` and `ChangeRoleDialog` to sync their local state with
the current `admin` (for example when `open` changes to true or `admin` changes)
so `newEmail` and the role value always reflect the active admin before submit.
Use the existing `ChangeEmailDialog` and `ChangeRoleDialog` components and their
`useState`/dialog open handling to reset the form state instead of relying on
the initial `useState(admin?.email ?? "")` and `useState(admin?.role ??
"admin")` values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a8489991-73a0-4e64-961b-95a56b032e32

📥 Commits

Reviewing files that changed from the base of the PR and between e17bee0 and a384e28.

📒 Files selected for processing (3)
  • src/actions/admin-management.ts
  • src/components/admin-management/admin-action-dialogs.tsx
  • src/hooks/api/use-admin-management.ts

@mrcoded mrcoded merged commit 3dcbb75 into dev Jul 9, 2026
10 checks passed
@mrcoded mrcoded deleted the feat/admin-management branch July 9, 2026 10:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants