Implement Workstream M: Admin Pages (Frontend)
Add superuser admin interface for managing organizations and users: - Admin layout with access control (redirects non-superusers) - Admin dashboard with org/user counts and quick actions - Org management: list, create, view/edit details, manage sites - User management: list, view details, toggle superuser, confirm email - SuperuserBadge component for consistent superuser indication - Sidebar shows admin link (shield icon) for superusers only - Centralized date formatting utility at $lib/utils/format-date.ts - Test plan documentation at docs/test-plans/admin.md Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
* Admin procedure helpers - shared transformation functions
|
||||
*/
|
||||
|
||||
import type { OrgSites, Orgs, Users } from "@reviq/db-schema";
|
||||
import type { Selectable } from "kysely";
|
||||
import type { Orgs, OrgSites, Users } from "@reviq/db-schema";
|
||||
|
||||
/** Transform org record to API response format */
|
||||
export const toOrgResponse = (org: Selectable<Orgs>) => ({
|
||||
|
||||
@@ -44,7 +44,7 @@ export const adminUsersCreate = os.admin.users.create
|
||||
.insertInto("users")
|
||||
.values({
|
||||
email: normalizedEmail,
|
||||
display_name: name || null,
|
||||
display_name: name ?? null,
|
||||
})
|
||||
.returning(["id"])
|
||||
.executeTakeFirstOrThrow();
|
||||
@@ -55,7 +55,7 @@ export const adminUsersCreate = os.admin.users.create
|
||||
.values({
|
||||
org_id: orgId,
|
||||
user_id: newUser.id,
|
||||
role: orgRole || "member",
|
||||
role: orgRole ?? "member",
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
@@ -27,10 +27,7 @@ export const adminUsersUpdate = os.admin.users.update
|
||||
}
|
||||
|
||||
// Prevent superuser from demoting themselves
|
||||
if (
|
||||
isSuperuser === false &&
|
||||
normalizedEmail === context.user.email.toLowerCase()
|
||||
) {
|
||||
if (!isSuperuser && normalizedEmail === context.user.email.toLowerCase()) {
|
||||
throw new ORPCError("BAD_REQUEST", {
|
||||
message: "Cannot remove your own superuser status",
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ORPCError } from "@orpc/server";
|
||||
import { adminRoutes } from "./procedures/admin/_routes.js";
|
||||
import { createLoginRequest as createLoginRequestHandler } from "./procedures/auth/create-login-request.js";
|
||||
import { forgotPassword as forgotPasswordHandler } from "./procedures/auth/forgot-password.js";
|
||||
import { loginIfRequestIsCompleted as loginIfRequestIsCompletedHandler } from "./procedures/auth/login-if-completed.js";
|
||||
@@ -9,8 +10,11 @@ import { resendVerificationEmail as resendVerificationHandler } from "./procedur
|
||||
import { resetPassword as resetPasswordHandler } from "./procedures/auth/reset-password.js";
|
||||
import { signup as signupHandler } from "./procedures/auth/signup.js";
|
||||
import { verifyEmail as verifyEmailHandler } from "./procedures/auth/verify-email.js";
|
||||
import { authMiddleware, loginRequestMiddleware, os } from "./procedures/base.js";
|
||||
import { adminRoutes } from "./procedures/admin/_routes.js";
|
||||
import {
|
||||
authMiddleware,
|
||||
loginRequestMiddleware,
|
||||
os,
|
||||
} from "./procedures/base.js";
|
||||
import { meDelete } from "./procedures/me/delete.js";
|
||||
import {
|
||||
getDeviceInfo,
|
||||
|
||||
Reference in New Issue
Block a user