197 lines
5.0 KiB
TypeScript
197 lines
5.0 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import type { AdminSession, CreateUserResponse, MailUser } from "@internal-mail/shared";
|
|
import { api } from "./api/client";
|
|
import DashboardPage from "./pages/DashboardPage";
|
|
import LoginPage from "./pages/LoginPage";
|
|
import ResetPasswordPage from "./pages/ResetPasswordPage";
|
|
|
|
const anonymousSession: AdminSession = {
|
|
authenticated: false,
|
|
adminEmail: null
|
|
};
|
|
|
|
const getPath = (): string => window.location.pathname;
|
|
|
|
export default function App() {
|
|
const [path, setPath] = useState(getPath());
|
|
const [session, setSession] = useState<AdminSession | null>(null);
|
|
const [users, setUsers] = useState<MailUser[]>([]);
|
|
const [busy, setBusy] = useState(false);
|
|
const [loginError, setLoginError] = useState<string | null>(null);
|
|
const [feedback, setFeedback] = useState<string | null>(null);
|
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
const handlePopstate = () => setPath(getPath());
|
|
window.addEventListener("popstate", handlePopstate);
|
|
return () => window.removeEventListener("popstate", handlePopstate);
|
|
}, []);
|
|
|
|
const resetToken = new URLSearchParams(window.location.search).get("token") ?? "";
|
|
|
|
const loadUsers = async () => {
|
|
const response = await api.listUsers();
|
|
setUsers(response.items);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (path.startsWith("/reset-password")) {
|
|
return;
|
|
}
|
|
|
|
api
|
|
.getSession()
|
|
.then((response) => {
|
|
setSession(response.session);
|
|
if (!response.session.authenticated) {
|
|
return;
|
|
}
|
|
|
|
loadUsers().catch((error) => {
|
|
setErrorMessage(error instanceof Error ? error.message : "Could not load users.");
|
|
});
|
|
})
|
|
.catch(() => {
|
|
setSession(anonymousSession);
|
|
});
|
|
}, [path]);
|
|
|
|
const handleLogin = async (payload: { email: string; password: string }) => {
|
|
setBusy(true);
|
|
setLoginError(null);
|
|
|
|
try {
|
|
const response = await api.login(payload);
|
|
setSession(response.session);
|
|
setErrorMessage(null);
|
|
await loadUsers().catch((error) => {
|
|
setErrorMessage(error instanceof Error ? error.message : "Signed in, but could not load users.");
|
|
});
|
|
} catch (error) {
|
|
setLoginError(error instanceof Error ? error.message : "Could not sign in.");
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
|
|
const handleLogout = async () => {
|
|
setBusy(true);
|
|
try {
|
|
const response = await api.logout();
|
|
setSession(response.session);
|
|
setUsers([]);
|
|
setFeedback(null);
|
|
setErrorMessage(null);
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
|
|
const handleCreateUser = async (email: string): Promise<CreateUserResponse> => {
|
|
setBusy(true);
|
|
setFeedback(null);
|
|
setErrorMessage(null);
|
|
|
|
try {
|
|
const result = await api.createUser({ email });
|
|
await loadUsers();
|
|
return result;
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
|
|
const handleUpdatePassword = async (email: string, password: string) => {
|
|
setBusy(true);
|
|
setFeedback(null);
|
|
setErrorMessage(null);
|
|
|
|
try {
|
|
await api.updatePassword(email, { password });
|
|
await loadUsers();
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
|
|
const handleDeleteUser = async (email: string) => {
|
|
setBusy(true);
|
|
setFeedback(null);
|
|
setErrorMessage(null);
|
|
|
|
try {
|
|
await api.deleteUser(email);
|
|
await loadUsers();
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
|
|
const handleTriggerReset = async (email: string) => {
|
|
setBusy(true);
|
|
setFeedback(null);
|
|
setErrorMessage(null);
|
|
|
|
try {
|
|
const result = await api.triggerPasswordReset(email);
|
|
return result.expiresAt;
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
|
|
const handleRefresh = async () => {
|
|
setBusy(true);
|
|
try {
|
|
await loadUsers();
|
|
setErrorMessage(null);
|
|
} catch (error) {
|
|
setErrorMessage(error instanceof Error ? error.message : "Could not refresh users.");
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
|
|
if (path.startsWith("/reset-password")) {
|
|
return <ResetPasswordPage token={resetToken} />;
|
|
}
|
|
|
|
if (session === null) {
|
|
return (
|
|
<main className="auth-shell">
|
|
<section className="auth-card">
|
|
<div className="eyebrow">Mail Admin</div>
|
|
<h1>Loading</h1>
|
|
<p className="auth-copy">Checking your admin session.</p>
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
if (!session || !session.authenticated || !session.adminEmail) {
|
|
return (
|
|
<LoginPage
|
|
busy={busy}
|
|
error={loginError}
|
|
onSubmit={handleLogin}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<DashboardPage
|
|
adminEmail={session.adminEmail}
|
|
users={users}
|
|
busy={busy}
|
|
feedback={feedback}
|
|
errorMessage={errorMessage}
|
|
onCreateUser={handleCreateUser}
|
|
onUpdatePassword={handleUpdatePassword}
|
|
onDeleteUser={handleDeleteUser}
|
|
onTriggerReset={handleTriggerReset}
|
|
onRefresh={handleRefresh}
|
|
onLogout={handleLogout}
|
|
/>
|
|
);
|
|
}
|