This commit is contained in:
Anaïs Saraiva
2026-03-29 18:45:25 +02:00
parent 0f7d692d12
commit afc839d43d
42 changed files with 3809 additions and 58 deletions
+77
View File
@@ -0,0 +1,77 @@
import { useState } from "react";
import type { ProfileSettings } from "@internal-mail/shared";
import { LoginPage } from "./pages/LoginPage";
import { MailboxPage } from "./pages/MailboxPage";
import { SettingsPage } from "./pages/SettingsPage";
import { useSession } from "./hooks/useSession";
type View = "inbox" | "compose" | "settings";
const AppShell = () => {
const { session, logout } = useSession();
const [currentView, setCurrentView] = useState<View>("inbox");
const [profileOverride, setProfileOverride] = useState<ProfileSettings | null>(null);
const user = profileOverride ?? session.user;
return (
<main className="app-shell">
<aside className="sidebar">
<div className="brand-block">
<div className="brand-block__overline">Zero-trust posture</div>
<h1>Secure Webmail</h1>
<p>Docker-managed mailboxes with a web layer designed to fail closed.</p>
</div>
<nav className="nav-stack">
<button type="button" className={currentView === "inbox" ? "nav-button nav-button--active" : "nav-button"} onClick={() => setCurrentView("inbox")}>
Inbox
</button>
<button type="button" className={currentView === "compose" ? "nav-button nav-button--active" : "nav-button"} onClick={() => setCurrentView("compose")}>
Compose
</button>
<button type="button" className={currentView === "settings" ? "nav-button nav-button--active" : "nav-button"} onClick={() => setCurrentView("settings")}>
Settings
</button>
</nav>
<div className="sidebar__footer">
<div className="identity-card">
{user?.avatarUrl ? <img src={user.avatarUrl} alt="" className="avatar-mini" /> : <div className="avatar-fallback">{user?.displayName.slice(0, 1) ?? "?"}</div>}
<div>
<strong>{user?.displayName}</strong>
<p>{user?.email}</p>
<small>{user?.totpEnabled ? "2FA enabled" : "2FA optional"}</small>
</div>
</div>
<button type="button" className="ghost-button" onClick={() => void logout()}>
Sign out
</button>
</div>
</aside>
<section className="content-shell">
{currentView === "settings" ? (
<SettingsPage onProfileUpdate={(profile) => setProfileOverride(profile)} />
) : (
<MailboxPage currentView={currentView === "compose" ? "compose" : "inbox"} />
)}
</section>
</main>
);
};
export const App = () => {
const { loading, authBusy, error, login, session } = useSession();
if (loading) {
return <main className="loading-screen">Loading secure session context...</main>;
}
if (!session.authenticated) {
return <LoginPage busy={authBusy} error={error} onLogin={login} />;
}
return <AppShell />;
};