From d31ba9f1df31476ecfb4e6f5dd67b62c17a630ba Mon Sep 17 00:00:00 2001 From: BradyMitch Date: Thu, 12 Mar 2026 13:42:49 -0700 Subject: [PATCH 1/4] updates --- .vscode/extensions.json | 1 - .../migration.sql | 2 + prisma/models/app/enums/role.prisma | 1 + prisma/seed.ts | 86 ++++++++++++++++++ src/app/api/auth/login/route.ts | 9 +- src/app/api/protected/route.ts | 2 +- src/app/protected/settings/users/page.tsx | 19 ++-- .../ConfirmArchiveLocationModal.tsx | 29 ++++-- .../CreateLocationModal.tsx | 1 + .../EditLocationModal/EditLocationModal.tsx | 1 + .../ConfirmArchiveServiceCategoryModal.tsx | 70 +++++++++------ .../CreateServiceCategoryModal.tsx | 1 + .../EditServiceCategoryModal.tsx | 1 + .../ConfirmArchiveServiceModal.tsx | 27 ++++-- .../CreateServiceModal/CreateServiceModal.tsx | 1 + .../EditServiceModal/EditServiceModal.tsx | 1 + .../ConfirmArchiveUserModal.tsx | 32 ++++--- .../EditStaffUserModal/EditStaffUserModal.tsx | 38 ++++---- .../users/StaffUserTable/StaffUserTable.tsx | 55 +++++++++++- .../settings/users/StaffUserTable/columns.tsx | 14 ++- src/hooks/index.ts | 1 - src/hooks/useEditableRoles/index.ts | 1 - .../useEditableRoles/useEditableRoles.test.ts | 88 ------------------- .../useEditableRoles/useEditableRoles.ts | 34 ------- src/lib/prisma/staff_user/getAllStaffUsers.ts | 8 +- .../staff_user/getStaffUserBySub.test.ts | 17 ++-- .../prisma/staff_user/getStaffUserBySub.ts | 11 ++- .../prisma/staff_user/insertStaffUser.test.ts | 50 +++++++++-- src/lib/prisma/staff_user/insertStaffUser.ts | 12 ++- src/lib/prisma/staff_user/types.ts | 5 ++ .../prisma/staff_user/updateStaffUser.test.ts | 29 +++++- src/lib/prisma/staff_user/updateStaffUser.ts | 25 ++++-- src/middleware/README.md | 28 ++++-- src/middleware/auth.ts | 7 +- src/utils/auth/README.md | 2 +- src/utils/auth/getAuthContext.test.ts | 51 +++++------ src/utils/auth/getAuthContext.ts | 13 ++- src/utils/policies/policies.ts | 5 +- src/utils/policies/resources/staff_user.ts | 39 ++++++++ src/utils/policies/types.ts | 11 ++- src/utils/user/assignNewRoleFromCSR.test.ts | 15 +++- src/utils/user/assignNewRoleFromCSR.ts | 3 +- src/utils/user/updateUserOnLogin.ts | 2 +- 43 files changed, 557 insertions(+), 291 deletions(-) create mode 100644 prisma/migrations/20260311163820_new_authenticated_role/migration.sql delete mode 100644 src/hooks/useEditableRoles/index.ts delete mode 100644 src/hooks/useEditableRoles/useEditableRoles.test.ts delete mode 100644 src/hooks/useEditableRoles/useEditableRoles.ts create mode 100644 src/lib/prisma/staff_user/types.ts create mode 100644 src/utils/policies/resources/staff_user.ts diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 78d5c9ce..72331b8f 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,7 +2,6 @@ "recommendations": [ "biomejs.biome", "prisma.prisma", - "GitHub.copilot", "GitHub.copilot-chat", "github.vscode-github-actions", "github.vscode-pull-request-github", diff --git a/prisma/migrations/20260311163820_new_authenticated_role/migration.sql b/prisma/migrations/20260311163820_new_authenticated_role/migration.sql new file mode 100644 index 00000000..c94ecc8b --- /dev/null +++ b/prisma/migrations/20260311163820_new_authenticated_role/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "app"."Role" ADD VALUE 'Authenticated'; diff --git a/prisma/models/app/enums/role.prisma b/prisma/models/app/enums/role.prisma index 165c94b6..4e761077 100644 --- a/prisma/models/app/enums/role.prisma +++ b/prisma/models/app/enums/role.prisma @@ -1,4 +1,5 @@ enum Role { + Authenticated CSR SCSR SDM diff --git a/prisma/seed.ts b/prisma/seed.ts index 45b03b06..c08405a7 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -162,6 +162,92 @@ async function main() { data: { categories: { connect: { id: categoryIdentity.id } } }, }) } + + // --- Mock test staff users for each role --- + const testLocation = await prisma.location.findUnique({ + where: { legacyOfficeNumber: 999 }, + }) + + if (testLocation) { + // Authenticated role + await prisma.staffUser.upsert({ + where: { sub: "test-authenticated@idir" }, + update: {}, + create: { + guid: "11111111-1111-1111-1111-111111111111", + sub: "test-authenticated@idir", + username: "sarah.chen", + displayName: "Sarah Chen", + role: "Authenticated", + isActive: false, + locationCode: testLocation.code, + counterId: counter.id, + }, + }) + + // CSR role + await prisma.staffUser.upsert({ + where: { sub: "test-csr@idir" }, + update: {}, + create: { + guid: "22222222-2222-2222-2222-222222222222", + sub: "test-csr@idir", + username: "michael.rodriguez", + displayName: "Michael Rodriguez", + role: "CSR", + isActive: false, + locationCode: testLocation.code, + counterId: counter.id, + }, + }) + + // SCSR role + await prisma.staffUser.upsert({ + where: { sub: "test-scsr@idir" }, + update: {}, + create: { + guid: "33333333-3333-3333-3333-333333333333", + sub: "test-scsr@idir", + username: "jennifer.smith", + displayName: "Jennifer Smith", + role: "SCSR", + isActive: false, + locationCode: testLocation.code, + counterId: reception.id, + }, + }) + + // SDM role + await prisma.staffUser.upsert({ + where: { sub: "test-sdm@idir" }, + update: {}, + create: { + guid: "44444444-4444-4444-4444-444444444444", + sub: "test-sdm@idir", + username: "robert.taylor", + displayName: "Robert Taylor", + role: "SDM", + isActive: false, + locationCode: testLocation.code, + isOfficeManager: true, + }, + }) + + // Administrator role + await prisma.staffUser.upsert({ + where: { sub: "test-admin@idir" }, + update: {}, + create: { + guid: "55555555-5555-5555-5555-555555555555", + sub: "test-admin@idir", + username: "patricia.williams", + displayName: "Patricia Williams", + role: "Administrator", + isActive: false, + locationCode: testLocation.code, + }, + }) + } } catch (error) { console.error("❌ Seeding database failed:", error) diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts index 0513f5de..aa08b42e 100644 --- a/src/app/api/auth/login/route.ts +++ b/src/app/api/auth/login/route.ts @@ -30,7 +30,14 @@ export async function GET(_request: NextRequest) { }) // Redirect the user to the SSO login page - return NextResponse.redirect(redirectURL) + const response = NextResponse.redirect(redirectURL) + + // Prevent caching of redirect responses to avoid stale maintenance page redirects + response.headers.set("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate") + response.headers.set("Pragma", "no-cache") + response.headers.set("Expires", "0") + + return response } catch (error) { console.error("Error in login route:", error) return NextResponse.json({ error: "Failed to generate login URL" }, { status: 500 }) diff --git a/src/app/api/protected/route.ts b/src/app/api/protected/route.ts index 9568890a..509c3933 100644 --- a/src/app/api/protected/route.ts +++ b/src/app/api/protected/route.ts @@ -4,7 +4,7 @@ import { getAuthContext } from "@/utils/auth/getAuthContext" export async function GET(request: NextRequest) { try { - const authContext = getAuthContext(request) + const authContext = getAuthContext(request.headers) if (!authContext) { return NextResponse.json( diff --git a/src/app/protected/settings/users/page.tsx b/src/app/protected/settings/users/page.tsx index 695e0520..71a2faf0 100644 --- a/src/app/protected/settings/users/page.tsx +++ b/src/app/protected/settings/users/page.tsx @@ -1,25 +1,34 @@ import { revalidatePath } from "next/cache" +import { headers } from "next/headers" import { StaffUserTable } from "@/components/settings/users/StaffUserTable" import { getAllLocations } from "@/lib/prisma/location/getAllLocations" import { getAllStaffUsers } from "@/lib/prisma/staff_user/getAllStaffUsers" +import { getStaffUserBySub } from "@/lib/prisma/staff_user/getStaffUserBySub" import { updateStaffUser } from "@/lib/prisma/staff_user/updateStaffUser" +import { getAuthContext } from "@/utils/auth/getAuthContext" // This page should always be rendered dynamically to ensure fresh data export const dynamic = "force-dynamic" +const revalidateTable = async () => { + "use server" + revalidatePath("/protected/settings/users") +} + export default async function Page() { + const headersList = await headers() + const authContext = getAuthContext(headersList) + const user = authContext?.user + + const currentUser = await getStaffUserBySub(user?.sub ?? "") const users = await getAllStaffUsers() const locations = await getAllLocations() - const revalidateTable = async () => { - "use server" - revalidatePath("/protected/settings/users") - } - return (

Users

{ + const [error, setError] = useState(null) const [formData, setFormData] = useState(null) const [previousLocation, setPreviousLocation] = useState(null) const [archiveConfirmation, setArchiveConfirmation] = useState("") @@ -45,14 +46,20 @@ export const ConfirmArchiveLocationModal = ({ if (!location || !formData || !previousLocation) return null const handleSave = async () => { - if (formData) { - await updateLocation( - { ...formData, deletedAt: isArchived ? null : new Date() }, - previousLocation - ) - await revalidateTable() - setArchiveConfirmation("") - onClose() + try { + if (formData) { + await updateLocation({ deletedAt: isArchived ? null : new Date() }, previousLocation) + await revalidateTable() + setArchiveConfirmation("") + onClose() + window.location.href = "/protected/settings/locations" + } + } catch (e: unknown) { + if (e instanceof Error) { + setError(e.message) + } else { + setError("An unknown error occurred") + } } } @@ -66,6 +73,12 @@ export const ConfirmArchiveLocationModal = ({
+ {error && ( +
+

{error}

+
+ )} +