feat(auth): add form login with session

This commit is contained in:
u1
2026-01-06 16:05:23 +01:00
parent 77122e0428
commit e20a1f5198
6 changed files with 472 additions and 66 deletions

View File

@@ -1,4 +1,4 @@
import { useMemo } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useLocalStorageState } from './app/hooks/useLocalStorageState';
import AppShell from './layout/AppShell';
import ChartPanel from './features/chart/ChartPanel';
@@ -10,6 +10,7 @@ import MarketHeader from './features/market/MarketHeader';
import Button from './ui/Button';
import TopNav from './layout/TopNav';
import AuthStatus from './layout/AuthStatus';
import LoginScreen from './layout/LoginScreen';
function envNumber(name: string, fallback: number): number {
const v = (import.meta as any).env?.[name];
@@ -35,7 +36,69 @@ function formatQty(v: number | null | undefined, decimals: number): string {
return v.toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
}
type WhoamiResponse = {
ok?: boolean;
user?: string | null;
mode?: string;
};
export default function App() {
const [user, setUser] = useState<string | null>(null);
const [authLoading, setAuthLoading] = useState(true);
useEffect(() => {
let cancelled = false;
setAuthLoading(true);
fetch('/whoami', { cache: 'no-store' })
.then(async (res) => {
const json = (await res.json().catch(() => null)) as WhoamiResponse | null;
const u = typeof json?.user === 'string' ? json.user.trim() : '';
return u || null;
})
.then((u) => {
if (cancelled) return;
setUser(u);
})
.catch(() => {
if (cancelled) return;
setUser(null);
})
.finally(() => {
if (cancelled) return;
setAuthLoading(false);
});
return () => {
cancelled = true;
};
}, []);
const logout = async () => {
try {
await fetch('/auth/logout', { method: 'POST' });
} finally {
setUser(null);
}
};
if (authLoading) {
return (
<div className="loginScreen">
<div className="loginCard" role="status" aria-label="Ładowanie">
Ładowanie
</div>
</div>
);
}
if (!user) {
return <LoginScreen onLoggedIn={setUser} />;
}
return <TradeApp user={user} onLogout={() => void logout()} />;
}
function TradeApp({ user, onLogout }: { user: string; onLogout: () => void }) {
const markets = useMemo(() => ['PUMP-PERP', 'SOL-PERP', 'BTC-PERP', 'ETH-PERP'], []);
const [symbol, setSymbol] = useLocalStorageState('trade.symbol', envString('VITE_SYMBOL', 'PUMP-PERP'));
@@ -156,7 +219,7 @@ export default function App() {
return (
<AppShell
header={<TopNav active="trade" rightEndSlot={<AuthStatus />} />}
header={<TopNav active="trade" rightEndSlot={<AuthStatus user={user} onLogout={onLogout} />} />}
top={<TickerBar items={topItems} />}
main={
<div className="tradeMain">