fixed all admin email pages. added admin user info and button on builder page to route to admin page.
CI / test (push) Successful in 5s

This commit is contained in:
2026-01-26 14:58:56 -05:00
parent fb2effca47
commit 77c31ae4f9
15 changed files with 393 additions and 214 deletions
+11 -41
View File
@@ -1,11 +1,7 @@
"use client";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useState } from "react";
import { Button, Field, Input } from "@/components/ui/form";
import { useAuth } from "@/context/AuthContext";
const API_BASE_URL =
process.env.NEXT_PUBLIC_API_BASE_URL ?? "";
type AdminBetaRequestDto = {
id: number;
@@ -40,17 +36,12 @@ type AdminInviteResponse = {
token?: string;
};
async function fetchJson(
path: string,
init: RequestInit = {},
authHeaders: HeadersInit = {}
) {
const res = await fetch(`${API_BASE_URL}${path}`, {
async function fetchJson(path: string, init: RequestInit = {}) {
const res = await fetch(path, {
...init,
headers: {
"Content-Type": "application/json",
...(init.headers ?? {}),
...authHeaders,
},
cache: "no-store",
});
@@ -101,9 +92,6 @@ function Badge({
}
export default function AdminBetaInvitesPage() {
const { getAuthHeaders, user } = useAuth();
const authHeaders = useMemo(() => getAuthHeaders(), [getAuthHeaders]);
// ------------------------------
// Batch invites
// ------------------------------
@@ -126,11 +114,9 @@ export default function AdminBetaInvitesPage() {
tokenMinutes: String(tokenMinutes || "30"),
});
const data = await fetchJson(
`/api/v1/admin/beta/invites/send?${qs.toString()}`,
{ method: "POST" },
authHeaders
);
const data = await fetchJson(`/api/admin/beta-invites?${qs.toString()}`, {
method: "POST",
});
setBatchResult(data);
await loadRequests();
@@ -166,9 +152,8 @@ export default function AdminBetaInvitesPage() {
try {
const data = (await fetchJson(
`/api/v1/admin/beta/requests?page=${page}&size=${size}`,
{ method: "GET" },
authHeaders
`/api/admin/beta/requests?page=${page}&size=${size}`,
{ method: "GET" }
)) as PageResponse<AdminBetaRequestDto>;
setRequests(data);
@@ -181,11 +166,9 @@ export default function AdminBetaInvitesPage() {
async function inviteSingle(userId: number): Promise<AdminInviteResponse> {
// calls backend: POST /api/v1/admin/beta/requests/{userId}/invite
return (await fetchJson(
`/api/v1/admin/beta/requests/${userId}/invite`,
{ method: "POST" },
authHeaders
)) as AdminInviteResponse;
return (await fetchJson(`/api/admin/beta/requests/${userId}/invite`, {
method: "POST",
})) as AdminInviteResponse;
}
async function onSendInviteEmail(userId: number) {
@@ -241,19 +224,6 @@ export default function AdminBetaInvitesPage() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [page]);
const isAdmin = (user?.role ?? "").toUpperCase() === "ADMIN";
if (!isAdmin) {
return (
<div className="space-y-2">
<h1 className="text-xl font-semibold">Admin</h1>
<p className="text-sm text-zinc-400">
You dont have access to this page.
</p>
</div>
);
}
return (
<div className="space-y-6">
<div>
+5 -20
View File
@@ -1,7 +1,6 @@
"use client";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useAuth } from "@/context/AuthContext";
import {
fetchEmailRequests,
createEmailRequest,
@@ -17,7 +16,6 @@ import { formatDate } from "@/lib/dateFormattingUtility";
type EmailStatusTab = "ALL" | "PENDING" | "SENT" | "FAILED" | "OTHER";
export default function AdminEmailPage() {
const { token } = useAuth();
const [emails, setEmails] = useState<EmailRequest[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
@@ -33,11 +31,9 @@ export default function AdminEmailPage() {
const [statusTab, setStatusTab] = useState<EmailStatusTab>("ALL");
const loadEmails = useCallback(async () => {
if (!token) return;
try {
setLoading(true);
const data = await fetchEmailRequests(token);
const data = await fetchEmailRequests();
setEmails(data);
setError(null);
} catch (e: any) {
@@ -46,7 +42,7 @@ export default function AdminEmailPage() {
} finally {
setLoading(false);
}
}, [token]);
}, []);
useEffect(() => {
loadEmails();
@@ -108,11 +104,10 @@ export default function AdminEmailPage() {
};
const handleDelete = async (id: number) => {
if (!token) return;
if (!confirm("Are you sure you want to delete this email request?")) return;
try {
await deleteEmailRequest(token, id);
await deleteEmailRequest(id);
await loadEmails();
} catch (e: any) {
alert(e?.message ?? "Failed to delete email request");
@@ -121,17 +116,15 @@ export default function AdminEmailPage() {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!token) return;
try {
setSubmitting(true);
if (modalMode === "create") {
await createEmailRequest(token, formData);
await createEmailRequest(formData);
} else if (selectedEmail) {
const updateData: UpdateEmailRequestDto = {};
if (formData.email !== selectedEmail.email) updateData.email = formData.email;
if (formData.status !== selectedEmail.status) updateData.status = formData.status;
await updateEmailRequest(token, selectedEmail.id, updateData);
await updateEmailRequest(selectedEmail.id, updateData);
}
setShowModal(false);
await loadEmails();
@@ -142,14 +135,6 @@ export default function AdminEmailPage() {
}
};
if (!token) {
return (
<div className="p-6 text-sm text-red-500">
You must be logged in to view this page.
</div>
);
}
return (
<div className="p-6 space-y-4">
{/* Header */}
+5 -20
View File
@@ -1,7 +1,6 @@
"use client";
import { useCallback, useEffect, useState } from "react";
import { useAuth } from "@/context/AuthContext";
import {
fetchEmailRequests,
createEmailRequest,
@@ -14,7 +13,6 @@ import {
import { Pencil, Trash2, Plus, X } from "lucide-react";
import { formatDate } from "@/lib/dateFormattingUtility";
export default function AdminEmailPage() {
const { token } = useAuth();
const [emails, setEmails] = useState<EmailRequest[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
@@ -28,11 +26,9 @@ export default function AdminEmailPage() {
const [submitting, setSubmitting] = useState(false);
const loadEmails = useCallback(async () => {
if (!token) return;
try {
setLoading(true);
const data = await fetchEmailRequests(token);
const data = await fetchEmailRequests();
setEmails(data);
setError(null);
} catch (e: any) {
@@ -41,7 +37,7 @@ export default function AdminEmailPage() {
} finally {
setLoading(false);
}
}, [token]);
}, []);
useEffect(() => {
loadEmails();
@@ -62,11 +58,10 @@ export default function AdminEmailPage() {
};
const handleDelete = async (id: number) => {
if (!token) return;
if (!confirm("Are you sure you want to delete this email request?")) return;
try {
await deleteEmailRequest(token, id);
await deleteEmailRequest(id);
await loadEmails();
} catch (e: any) {
alert(e?.message ?? "Failed to delete email request");
@@ -75,17 +70,15 @@ export default function AdminEmailPage() {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!token) return;
try {
setSubmitting(true);
if (modalMode === "create") {
await createEmailRequest(token, formData);
await createEmailRequest(formData);
} else if (selectedEmail) {
const updateData: UpdateEmailRequestDto = {};
if (formData.email !== selectedEmail.email) updateData.email = formData.email;
if (formData.status !== selectedEmail.status) updateData.status = formData.status;
await updateEmailRequest(token, selectedEmail.id, updateData);
await updateEmailRequest(selectedEmail.id, updateData);
}
setShowModal(false);
await loadEmails();
@@ -96,14 +89,6 @@ export default function AdminEmailPage() {
}
};
if (!token) {
return (
<div className="p-6 text-sm text-red-500">
You must be logged in to view this page.
</div>
);
}
return (
<div className="p-6 space-y-4">
{/* Header */}
+4 -7
View File
@@ -149,7 +149,7 @@ export default function AdminLayout({
return (
// ✅ prevent browser-level horizontal scroll from any wide children
<div className="min-h-screen bg-black text-zinc-50 overflow-x-hidden pt-[52px]">
<div className="h-screen bg-black text-zinc-50 overflow-hidden">
<AdminLeftNavigation
collapsed={collapsed}
onToggleCollapsed={() => setCollapsed((v) => !v)}
@@ -158,7 +158,7 @@ export default function AdminLayout({
{/* ✅ min-w-0 is the critical fix: lets the main column shrink */}
<div
className="flex min-h-screen flex-1 min-w-0 flex-col transition-all duration-300"
className="flex h-full flex-1 min-w-0 flex-col transition-all duration-300"
style={{ marginLeft: collapsed ? '68px' : '280px' }}
>
<header className="flex items-center justify-between border-b border-zinc-900 bg-zinc-950/70 px-4 py-3 sticky top-0 z-30 backdrop-blur-sm">
@@ -173,17 +173,14 @@ export default function AdminLayout({
<span className="rounded-full border border-amber-500/30 bg-amber-500/10 px-3 py-1 text-[11px] font-medium text-amber-300">
Internal v0.1
</span>
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-zinc-900 text-[11px] text-zinc-300">
ADMIN
</div>
</div>
</header>
{/* ✅ min-w-0 here prevents table min-width from widening the page */}
<main className="flex w-full flex-1 min-w-0 flex-col px-4 py-6">
<main className="flex w-full flex-1 min-w-0 flex-col overflow-y-auto px-4 py-6">
{children}
</main>
</div>
</div>
);
}
}