+ Email queue
+ {error && {error}
}
+ {JSON.stringify(stats, null, 2)}
+ {jobs.length ? (
+
+
+
+ | Recipient |
+ Template |
+ Failure |
+ |
+
+
+
+ {jobs.map((j) => (
+
+ | {j.recipient} |
+ {j.template} |
+ {j.failure} |
+
+
+ |
+
+ ))}
+
+
+ ) : (
+ No dead-letter jobs.
+ )}
+
+ );
+}
diff --git a/frontend/src/app/admin/layout.tsx b/frontend/src/app/admin/layout.tsx
index 27ce8183..48aed69e 100644
--- a/frontend/src/app/admin/layout.tsx
+++ b/frontend/src/app/admin/layout.tsx
@@ -4,7 +4,7 @@ import React, { useEffect, useState } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useI18n } from '../../lib/hooks/useI18n';
-import { useActiveNavItem } from '../../../hooks/useActiveNavItem';
+import { api, ApiError, ADMIN_KEY_STORAGE } from '../../lib/api/admin-client';
import '../../styles/admin.css';
function AdminAuthGate({ children }: { children: React.ReactNode }) {
@@ -15,52 +15,41 @@ function AdminAuthGate({ children }: { children: React.ReactNode }) {
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
- const k = sessionStorage.getItem('predictiq-admin-key');
- if (k) {
- setKey(k);
- fetch('/api/v1/admin/session', { headers: { 'X-API-Key': k } })
- .then((r) => {
- if (r.ok) {
- setOk(true);
- } else {
- sessionStorage.removeItem('predictiq-admin-key');
- setKey('');
- setError('Session expired. Please log in again.');
- }
- })
- .catch(() => {
- sessionStorage.removeItem('predictiq-admin-key');
- setKey('');
- setError('Failed to validate session.');
- });
- }
+ const k = sessionStorage.getItem(ADMIN_KEY_STORAGE);
+ if (!k) return;
+ setKey(k);
+ api
+ .listApiKeys(k)
+ .then(() => setOk(true))
+ .catch(() => {
+ sessionStorage.removeItem(ADMIN_KEY_STORAGE);
+ setKey('');
+ setError('Session expired. Please log in again.');
+ });
}, []);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setIsLoading(true);
-
try {
- const response = await fetch('/api/v1/admin/session', {
- headers: { 'X-API-Key': key },
- });
-
- if (response.ok) {
- sessionStorage.setItem('predictiq-admin-key', key);
- setOk(true);
- } else {
- setError('Invalid API key. Please try again.');
- }
- } catch {
- setError('Failed to validate API key. Please try again.');
+ await api.listApiKeys(key);
+ sessionStorage.setItem(ADMIN_KEY_STORAGE, key);
+ setOk(true);
+ } catch (err) {
+ const unauthorized = err instanceof ApiError && (err.status === 401 || err.status === 403);
+ setError(
+ unauthorized
+ ? 'Invalid API key. Please try again.'
+ : 'Failed to validate API key. Please try again.'
+ );
} finally {
setIsLoading(false);
}
};
const handleLogout = () => {
- sessionStorage.removeItem('predictiq-admin-key');
+ sessionStorage.removeItem(ADMIN_KEY_STORAGE);
setKey('');
setOk(false);
setError('');
@@ -79,7 +68,6 @@ function AdminAuthGate({ children }: { children: React.ReactNode }) {
disabled={isLoading}
/>
-