refactor: decompose monolithic App.tsx into per-component files
- Extract views to src/components/views/ (Overview, Logbook, Trends, Settings, Auth) - Extract standalone components (EntryModal, ImportPreviewModal, ConfirmDialog, Sidebar, TopBar, MobileNav, EntryLedger, FiltersPanel, QuickAddPanel, etc.) - Move shared helpers to src/lib (dashboard, filters, userDisplay) and static data to src/data (accents, quickAdds) - App.tsx now orchestration-only (2971 -> 777 lines); no behavior change
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import { Loader2, LogIn } from "lucide-react";
|
||||
import { useState, type CSSProperties, type FormEvent } from "react";
|
||||
import { LegalFootnote } from "../LegalFootnote";
|
||||
import { ShellBackdrop } from "../ShellBackdrop";
|
||||
import type { SetupStatus } from "../../types";
|
||||
|
||||
type AuthMode = "login" | "signup";
|
||||
|
||||
type AuthViewProps = {
|
||||
authError: string;
|
||||
busy: boolean;
|
||||
setupStatus: SetupStatus;
|
||||
shellStyle: CSSProperties;
|
||||
themeId: string;
|
||||
resolvedMode: "light" | "dark";
|
||||
onLogin: (email: string, password: string) => Promise<void>;
|
||||
onSignup: (name: string, email: string, password: string) => Promise<void>;
|
||||
};
|
||||
|
||||
export function AuthView({
|
||||
authError,
|
||||
busy,
|
||||
setupStatus,
|
||||
shellStyle,
|
||||
themeId,
|
||||
resolvedMode,
|
||||
onLogin,
|
||||
onSignup,
|
||||
}: AuthViewProps) {
|
||||
const [mode, setMode] = useState<AuthMode>("login");
|
||||
const [name, setName] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
function submit(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
if (mode === "signup") {
|
||||
void onSignup(name, email, password);
|
||||
return;
|
||||
}
|
||||
void onLogin(email, password);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="app-shell min-h-screen" data-theme={themeId} data-mode={resolvedMode} style={shellStyle}>
|
||||
<ShellBackdrop />
|
||||
<main className="flex min-h-screen items-center justify-center p-6">
|
||||
<div className="auth-panel-shell">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="hero-name text-3xl">Red Bull tracker</h1>
|
||||
<p className="hero-copy mt-2 text-sm">Track intake, sync across devices.</p>
|
||||
</div>
|
||||
|
||||
<div className="auth-panel-card">
|
||||
{setupStatus.state !== "ok" && (
|
||||
<div className="limit-alert mb-4 px-3 py-2 text-xs">
|
||||
{setupStatus.message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="auth-mode-toggle mb-5">
|
||||
<button className={mode === "login" ? "auth-mode-active" : ""} type="button" onClick={() => setMode("login")}>
|
||||
Log in
|
||||
</button>
|
||||
<button className={mode === "signup" ? "auth-mode-active" : ""} type="button" onClick={() => setMode("signup")}>
|
||||
Sign up
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form className="grid gap-3" onSubmit={submit}>
|
||||
{mode === "signup" && (
|
||||
<label className="field-label">
|
||||
Name
|
||||
<input className="field-control" type="text" value={name} onChange={(event) => setName(event.target.value)} placeholder="Ned" />
|
||||
</label>
|
||||
)}
|
||||
<label className="field-label">
|
||||
Email
|
||||
<input className="field-control" type="email" value={email} onChange={(event) => setEmail(event.target.value)} placeholder="you@example.com" required />
|
||||
</label>
|
||||
<label className="field-label">
|
||||
Password
|
||||
<input className="field-control" minLength={8} type="password" value={password} onChange={(event) => setPassword(event.target.value)} placeholder="8+ characters" required />
|
||||
</label>
|
||||
|
||||
{authError && (
|
||||
<div className="rounded-md px-3 py-2 text-sm" style={{ border: "1px solid #ffc9c2", background: "#fff3f1", color: "#9f1c16" }}>
|
||||
{authError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button className="primary-button w-full mt-1" type="submit" disabled={busy}>
|
||||
{busy ? <Loader2 className="animate-spin" size={17} aria-hidden="true" /> : <LogIn size={17} aria-hidden="true" />}
|
||||
{mode === "signup" ? "Create account" : "Log in"}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<LegalFootnote className="mt-5" />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user