From e1121da7b95aafcafb4ed58ff850127d91e0eeca Mon Sep 17 00:00:00 2001 From: Mitchell Magro Date: Thu, 11 Dec 2025 15:06:04 +0100 Subject: [PATCH] Added next SSR and caching for admin/users --- app/api/auth/register/route.ts | 10 +++- app/api/dashboard/admin/users/[id]/route.ts | 15 +++++- app/dashboard/admin/users/page.tsx | 50 +++---------------- app/features/Pages/Admin/Users/users.tsx | 2 - .../UserRoles/DeleteUser/DeleteUser.tsx | 49 ++++++++++-------- app/features/UserRoles/userRoleCard.tsx | 2 +- app/services/constants.ts | 3 +- app/services/users.ts | 45 +++++++++++++++++ 8 files changed, 106 insertions(+), 70 deletions(-) create mode 100644 app/services/users.ts diff --git a/app/api/auth/register/route.ts b/app/api/auth/register/route.ts index 6a0c0c8..349e96f 100644 --- a/app/api/auth/register/route.ts +++ b/app/api/auth/register/route.ts @@ -1,4 +1,10 @@ -import { AUTH_COOKIE_NAME, BE_BASE_URL } from "@/app/services/constants"; +import { revalidateTag } from "next/cache"; + +import { + AUTH_COOKIE_NAME, + BE_BASE_URL, + USERS_CACHE_TAG, +} from "@/app/services/constants"; import { NextResponse } from "next/server"; import { cookies } from "next/headers"; @@ -59,6 +65,8 @@ export async function POST(request: Request) { const data = await resp.json(); + revalidateTag(USERS_CACHE_TAG); + return NextResponse.json( { success: true, diff --git a/app/api/dashboard/admin/users/[id]/route.ts b/app/api/dashboard/admin/users/[id]/route.ts index 6950f2e..4ed8d5b 100644 --- a/app/api/dashboard/admin/users/[id]/route.ts +++ b/app/api/dashboard/admin/users/[id]/route.ts @@ -1,5 +1,11 @@ // app/api/users/[id]/route.ts -import { AUTH_COOKIE_NAME, BE_BASE_URL } from "@/app/services/constants"; +import { revalidateTag } from "next/cache"; + +import { + AUTH_COOKIE_NAME, + BE_BASE_URL, + USERS_CACHE_TAG, +} from "@/app/services/constants"; import { NextResponse } from "next/server"; // Field mapping: snake_case input -> { snake_case for data, PascalCase for fields } @@ -93,6 +99,9 @@ export async function PUT( }); const data = await response.json(); + if (response.ok) { + revalidateTag(USERS_CACHE_TAG); + } return NextResponse.json(data, { status: response.status }); } catch (err: unknown) { const errorMessage = @@ -137,6 +146,10 @@ export async function DELETE( data = { success: response.ok }; } + if (response.ok) { + revalidateTag(USERS_CACHE_TAG); + } + return NextResponse.json(data ?? { success: response.ok }, { status: response.status, }); diff --git a/app/dashboard/admin/users/page.tsx b/app/dashboard/admin/users/page.tsx index 4cb8b83..ec4834f 100644 --- a/app/dashboard/admin/users/page.tsx +++ b/app/dashboard/admin/users/page.tsx @@ -1,50 +1,16 @@ import Users from "@/app/features/Pages/Admin/Users/users"; -import { cookies } from "next/headers"; -import { getBaseUrl } from "@/app/services/constants"; +import { IUser } from "@/app/features/Pages/Admin/Users/interfaces"; +import { fetchUsers } from "@/app/services/users"; -export default async function BackOfficeUsersPage({ - searchParams, -}: { - searchParams: Promise>; -}) { - // Await searchParams and extract pagination - const params = await searchParams; - const limit = params.limit || "10"; - const page = params.page || "1"; +export default async function BackOfficeUsersPage() { + let users: IUser[] = []; - const baseUrl = getBaseUrl(); - - const url = new URL(`${baseUrl}/api/dashboard/admin/users`); - url.searchParams.set("limit", typeof limit === "string" ? limit : limit[0]); - url.searchParams.set("page", typeof page === "string" ? page : page[0]); - - // Forward cookies for auth when calling the internal API route - const cookieStore = await cookies(); - const cookieHeader = cookieStore - .getAll() - .map((c: { name: string; value: string }) => `${c.name}=${c.value}`) - .join("; "); - - const res = await fetch(url.toString(), { - headers: { - Cookie: cookieHeader, - }, - cache: "no-store", - }); - - if (!res.ok) { - console.error("Failed to fetch users:", res.status, res.statusText); - return ( -
- -
- ); + try { + users = await fetchUsers(); + } catch (error) { + console.error("Failed to fetch users:", error); } - const data = await res.json(); - // Handle different response structures: could be array directly, or wrapped in data/users property - const users = Array.isArray(data) ? data : data.users || data.data || []; - return (
diff --git a/app/features/Pages/Admin/Users/users.tsx b/app/features/Pages/Admin/Users/users.tsx index 567bb6f..396977f 100644 --- a/app/features/Pages/Admin/Users/users.tsx +++ b/app/features/Pages/Admin/Users/users.tsx @@ -17,8 +17,6 @@ const Users: React.FC = ({ users }) => { const [showAddUser, setShowAddUser] = useState(false); const dispatch = useDispatch(); - console.log("[Users] - users", users); - return (
= ({ open, onClose, user }) => { const dispatch = useDispatch(); const router = useRouter(); const { status, error } = useSelector((state: RootState) => state.user); + const currentUser = useSelector(selectUser); const loading = status === "loading"; + const isSelfDeletion = currentUser?.id === user?.id; const handleDelete = async () => { if (!user?.id) { @@ -32,6 +34,11 @@ const DeleteUser: React.FC = ({ open, onClose, user }) => { return; } + if (isSelfDeletion) { + toast.error("You cannot delete your own account"); + return; + } + try { const resultAction = await dispatch(deleteUser(user.id)); @@ -60,10 +67,6 @@ const DeleteUser: React.FC = ({ open, onClose, user }) => { } }; - if (!user) { - return null; - } - return (
@@ -74,22 +77,24 @@ const DeleteUser: React.FC = ({ open, onClose, user }) => {

-
-
- -
{user.username}
-
-
- -
- {user.first_name} {user.last_name} + {user && ( +
+
+ +
{user.username}
+
+
+ +
+ {user.first_name} {user.last_name} +
+
+
+ +
{user.email}
-
- -
{user.email}
-
-
+ )} {error && (
@@ -109,7 +114,7 @@ const DeleteUser: React.FC = ({ open, onClose, user }) => {
- {openDeleteUser && ( + {openDeleteUser && user && ( { + const cookieStore = await cookies(); + const token = cookieStore.get(AUTH_COOKIE_NAME)?.value; + + if (!token) { + throw new Error("Missing auth token"); + } + + const backendUrl = `${BE_BASE_URL}/api/v1/users`; + + const response = await fetch(backendUrl, { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + next: { + revalidate: REVALIDATE_SECONDS, + tags: [USERS_CACHE_TAG], + }, + }); + + if (!response.ok) { + const errorData = await response + .json() + .catch(() => ({ message: "Failed to fetch users" })); + throw new Error(errorData?.message || "Failed to fetch users"); + } + + const data = await response.json(); + + const users = Array.isArray(data) ? data : data.users || data.data || []; + + return users; +}