Files
LabWise/App.tsx

171 lines
5.4 KiB
TypeScript

import { useState, useEffect } from 'react';
import { Dashboard } from './components/Dashboard';
import { Inventory } from './components/Inventory';
import { ProtocolChecker } from './components/ProtocolChecker';
import { Onboarding } from './components/Onboarding';
import { ProfileSettings } from './components/ProfileSettings';
import { LoginForm } from './components/auth/LoginForm';
import { SignUpForm } from './components/auth/SignUpForm';
import { EmailVerification } from './components/auth/EmailVerification';
import { ForgotPassword } from './components/auth/ForgotPassword';
import { ResetPassword } from './components/auth/ResetPassword';
import { useSession, signOut } from './lib/auth-client';
import { LayoutDashboard, Package, FileCheck, LogOut, UserCircle } from 'lucide-react';
const logo = '/logo.png';
type AppView =
| 'loading'
| 'login'
| 'signup'
| 'verify-email'
| 'forgot-password'
| 'reset-password'
| 'onboarding'
| 'app';
type Tab = 'dashboard' | 'inventory' | 'protocol' | 'profile';
function isResetPasswordRoute() {
return (
window.location.pathname === '/reset-password' &&
Boolean(new URLSearchParams(window.location.search).get('token'))
);
}
export default function App() {
const { data: session, isPending } = useSession();
const [view, setView] = useState<AppView>(
isResetPasswordRoute() ? 'reset-password' : 'loading'
);
const [activeTab, setActiveTab] = useState<Tab>('dashboard');
useEffect(() => {
if (view === 'reset-password') return;
if (isPending) return;
if (!session) {
if (view !== 'signup' && view !== 'forgot-password') setView('login');
return;
}
if (!session.user.emailVerified) {
setView('verify-email');
return;
}
// Check if lab profile exists
fetch('/api/profile', { credentials: 'include' }).then(r => {
setView(r.ok ? 'app' : 'onboarding');
});
}, [session, isPending, view]);
if (view === 'reset-password') {
return (
<ResetPassword
onSuccess={() => {
window.history.replaceState({}, '', '/');
setView('login');
}}
/>
);
}
if (view === 'loading') {
return (
<div className="flex h-screen items-center justify-center bg-secondary">
<img src={logo} alt="LabWise" className="h-12 opacity-50 animate-pulse" />
</div>
);
}
if (!session) {
if (view === 'signup')
return (
<SignUpForm
onLogin={() => setView('login')}
onVerify={() => setView('verify-email')}
/>
);
if (view === 'forgot-password')
return <ForgotPassword onBack={() => setView('login')} />;
return (
<LoginForm
onSignUp={() => setView('signup')}
onForgotPassword={() => setView('forgot-password')}
/>
);
}
if (view === 'verify-email')
return <EmailVerification email={session.user.email} />;
if (view === 'onboarding')
return <Onboarding onComplete={() => setView('app')} />;
if (view !== 'app') return null;
const navItems = [
{ id: 'dashboard' as Tab, label: 'Dashboard', icon: LayoutDashboard },
{ id: 'inventory' as Tab, label: 'Inventory', icon: Package },
{ id: 'protocol' as Tab, label: 'Protocol Checker', icon: FileCheck },
];
return (
<div className="flex h-screen bg-secondary">
<aside className="w-64 bg-card border-r border-border flex flex-col">
<div className="p-6 border-b border-border flex items-center justify-center">
<img src={logo} alt="labwise" className="h-15" />
</div>
<nav className="flex-1 p-4">
{navItems.map(item => {
const Icon = item.icon;
return (
<button
key={item.id}
onClick={() => setActiveTab(item.id)}
className={`w-full flex items-center gap-3 px-4 py-3 rounded-lg mb-2 transition-colors ${
activeTab === item.id
? 'bg-accent text-primary'
: 'text-muted-foreground hover:bg-muted'
}`}
>
<Icon className="w-5 h-5" />
<span>{item.label}</span>
</button>
);
})}
</nav>
<div className="p-4 border-t border-border space-y-1">
<button
onClick={() => setActiveTab('profile')}
className={`w-full flex items-center gap-3 px-4 py-2 rounded-lg transition-colors text-sm ${
activeTab === 'profile'
? 'bg-accent text-primary'
: 'text-muted-foreground hover:bg-muted'
}`}
>
<UserCircle className="w-4 h-4 shrink-0" />
<span className="truncate text-left">
{session.user.name || session.user.email}
</span>
</button>
<button
onClick={() => signOut()}
className="w-full flex items-center gap-3 px-4 py-2 rounded-lg text-muted-foreground hover:bg-muted transition-colors text-sm"
>
<LogOut className="w-4 h-4" />
<span>Sign out</span>
</button>
</div>
</aside>
<main className="flex-1 overflow-auto">
{activeTab === 'dashboard' && <Dashboard setActiveTab={setActiveTab} />}
{activeTab === 'inventory' && <Inventory />}
{activeTab === 'protocol' && <ProtocolChecker />}
{activeTab === 'profile' && <ProfileSettings />}
</main>
</div>
);
}