2026-04-02 14:29:56 -05:00
import { useRef , useState , useEffect } from 'react' ;
2026-03-19 19:59:01 -05:00
import { Button } from '../ui/button' ;
import { Card , CardContent } from '../ui/card' ;
import { Input } from '../ui/input' ;
import { Label } from '../ui/label' ;
2026-04-02 14:29:56 -05:00
import { signIn , sendVerificationEmail } from '../../lib/auth-client' ;
2026-03-19 19:59:01 -05:00
import {
Package ,
FileCheck ,
MessageSquare ,
Camera ,
AlertTriangle ,
ChevronDown ,
} from 'lucide-react' ;
const logo = '/logo.png' ;
interface Props {
onSignUp : ( ) = > void ;
onForgotPassword : ( ) = > void ;
}
const features = [
{
icon : Package ,
title : 'Smart Chemical Inventory' ,
description :
'Track every chemical in your lab with detailed records — storage locations, CAS numbers, container counts, and expiration dates. Get instant alerts before stock runs low or chemicals expire.' ,
} ,
{
icon : FileCheck ,
title : 'AI Protocol Checker' ,
description :
'Paste or upload any lab protocol and receive instant AI-powered safety feedback with citations to OSHA standards. Catch PPE gaps, ventilation requirements, and hazard oversights before they become incidents.' ,
} ,
{
icon : MessageSquare ,
title : 'Lab Safety Assistant' ,
description :
'Ask anything about chemical handling, disposal, compatibility, or regulatory compliance. Get sourced answers drawn from OSHA, EPA, and lab safety literature — available the moment you need them.' ,
} ,
{
icon : Camera ,
title : 'Photo Label Scanning' ,
description :
'Point your camera at any chemical label and LabWise auto-fills the inventory entry for you. Reduce manual entry errors and get new chemicals logged in seconds.' ,
} ,
] ;
export function LoginForm ( { onSignUp , onForgotPassword } : Props ) {
const [ email , setEmail ] = useState ( '' ) ;
const [ password , setPassword ] = useState ( '' ) ;
const [ error , setError ] = useState ( '' ) ;
const [ loading , setLoading ] = useState ( false ) ;
2026-04-02 14:29:56 -05:00
const [ resendCooldown , setResendCooldown ] = useState ( 0 ) ;
const [ resendLoading , setResendLoading ] = useState ( false ) ;
2026-03-19 19:59:01 -05:00
const aboutRef = useRef < HTMLElement > ( null ) ;
const heroRef = useRef < HTMLElement > ( null ) ;
2026-04-02 14:29:56 -05:00
useEffect ( ( ) = > {
if ( resendCooldown <= 0 ) return ;
const t = setTimeout ( ( ) = > setResendCooldown ( c = > c - 1 ) , 1000 ) ;
return ( ) = > clearTimeout ( t ) ;
} , [ resendCooldown ] ) ;
async function handleResend() {
setResendLoading ( true ) ;
await sendVerificationEmail ( { email , callbackURL : window.location.origin } ) ;
setResendLoading ( false ) ;
setResendCooldown ( 60 ) ;
}
2026-03-19 19:59:01 -05:00
async function handleSubmit ( e : React.FormEvent ) {
e . preventDefault ( ) ;
setError ( '' ) ;
setLoading ( true ) ;
const res = await signIn . email ( {
email ,
password ,
callbackURL : window.location.origin ,
} ) ;
setLoading ( false ) ;
if ( res . error ) {
setError ( res . error . message || 'Invalid email or password' ) ;
}
}
async function handleGoogle() {
2026-04-01 16:43:19 -05:00
await signIn . social ( { provider : 'google' , callbackURL : window.location.origin , errorCallbackURL : window.location.origin } ) ;
2026-03-19 19:59:01 -05:00
}
function scrollToAbout() {
aboutRef . current ? . scrollIntoView ( { behavior : 'smooth' } ) ;
}
function scrollToLogin() {
heroRef . current ? . scrollIntoView ( { behavior : 'smooth' } ) ;
}
return (
< div className = "min-h-screen bg-secondary" >
{ /* Sticky Navbar */ }
< nav className = "sticky top-0 z-50 bg-card border-b border-border" >
< div className = "max-w-6xl mx-auto px-6 h-14 flex items-center justify-between" >
2026-04-09 14:20:18 -05:00
< a href = "/" > < img src = { logo } alt = "LabWise" className = "h-7" / > < / a >
2026-03-19 19:59:01 -05:00
< div className = "flex items-center gap-2" >
< Button variant = "ghost" size = "sm" onClick = { scrollToAbout } >
About
< / Button >
< Button variant = "outline" size = "sm" onClick = { scrollToLogin } >
Login
< / Button >
< / div >
< / div >
< / nav >
{ /* Hero — login card */ }
< section
ref = { heroRef }
className = "min-h-[calc(100vh-3.5rem)] flex flex-col items-center justify-center px-4 py-16"
>
< div className = "text-center mb-8" >
< p className = "text-muted-foreground text-sm tracking-wide uppercase font-medium" >
AI - powered lab management
< / p >
< h1 className = "text-3xl font-bold text-foreground mt-1" >
Your lab , under control .
< / h1 >
< / div >
< Card className = "w-full max-w-sm" >
< CardContent className = "p-8 space-y-6" >
< div className = "flex justify-center" >
< img src = { logo } alt = "LabWise" className = "h-12" / >
< / div >
< p className = "text-center text-muted-foreground text-sm" >
Sign in to access your lab inventory and protocols .
< / p >
< form onSubmit = { handleSubmit } className = "space-y-4" >
< div className = "space-y-1" >
< Label htmlFor = "email" > Email < / Label >
< Input
id = "email"
type = "email"
value = { email }
onChange = { e = > setEmail ( e . target . value ) }
required
autoComplete = "email"
/ >
< / div >
< div className = "space-y-1" >
< Label htmlFor = "password" > Password < / Label >
< Input
id = "password"
type = "password"
value = { password }
onChange = { e = > setPassword ( e . target . value ) }
required
autoComplete = "current-password"
/ >
< / div >
2026-04-02 14:29:56 -05:00
{ error && (
< div className = "space-y-1" >
< p className = "text-sm text-red-600" > { error } < / p >
{ error === 'Email not verified' && (
< button
type = "button"
onClick = { handleResend }
disabled = { resendLoading || resendCooldown > 0 }
className = "text-sm text-primary hover:underline disabled:opacity-50 disabled:no-underline"
>
{ resendLoading
? 'Sending…'
: resendCooldown > 0
? ` Resend verification email ( ${ resendCooldown } s) `
: 'Resend verification email' }
< / button >
) }
< / div >
) }
2026-03-19 19:59:01 -05:00
< Button type = "submit" className = "w-full" disabled = { loading } >
{ loading ? 'Signing in…' : 'Sign in' }
< / Button >
< / form >
< div className = "flex items-center gap-3" >
< div className = "flex-1 border-t border-border" / >
< span className = "text-xs text-muted-foreground" > or < / span >
< div className = "flex-1 border-t border-border" / >
< / div >
< Button
variant = "outline"
className = "w-full flex items-center gap-3"
onClick = { handleGoogle }
>
< svg viewBox = "0 0 24 24" className = "h-4 w-4" xmlns = "http://www.w3.org/2000/svg" >
< path d = "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill = "#4285F4" / >
< path d = "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill = "#34A853" / >
< path d = "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z" fill = "#FBBC05" / >
< path d = "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill = "#EA4335" / >
< / svg >
Sign in with Google
< / Button >
< div className = "flex justify-between text-sm" >
< button
onClick = { onForgotPassword }
className = "text-muted-foreground hover:text-foreground transition-colors"
>
Forgot password ?
< / button >
< button
onClick = { onSignUp }
className = "text-primary hover:underline font-medium"
>
Create account
< / button >
< / div >
2026-04-09 14:20:18 -05:00
< div className = "text-center" >
< a href = "/privacy" className = "text-xs text-muted-foreground hover:text-foreground transition-colors" >
Privacy Policy
< / a >
< / div >
2026-03-19 19:59:01 -05:00
< / CardContent >
< / Card >
{ /* Scroll hint */ }
< button
onClick = { scrollToAbout }
className = "mt-12 flex flex-col items-center gap-1 text-muted-foreground hover:text-foreground transition-colors group"
>
< span className = "text-xs tracking-wide" > Learn more < / span >
< ChevronDown className = "h-4 w-4 animate-bounce group-hover:text-primary" / >
< / button >
< / section >
{ /* About Section */ }
< section ref = { aboutRef } id = "about" className = "bg-card border-t border-border" >
< div className = "max-w-5xl mx-auto px-6 py-20" >
{ /* Intro */ }
< div className = "text-center mb-16" >
< div className = "inline-flex items-center gap-2 bg-accent text-primary text-xs font-medium px-3 py-1 rounded-full mb-4" >
< AlertTriangle className = "h-3 w-3" / >
Built for research labs
< / div >
< h2 className = "text-3xl font-bold text-foreground mb-4" >
Lab management , made easy .
< / h2 >
< p className = "text-muted-foreground max-w-2xl mx-auto leading-relaxed" >
LabWise brings together chemical inventory tracking , AI - powered protocol review , and real - time
safety alerts in one place — so your team spends less time on paperwork and more time on research .
Whether you ' re managing a single lab or multiple rooms across a department , LabWise keeps
everything organized , compliant , and accessible .
< / p >
< / div >
{ /* Feature grid */ }
< div className = "grid grid-cols-1 md:grid-cols-2 gap-6 mb-16" >
{ features . map ( ( { icon : Icon , title , description } ) = > (
< div
key = { title }
className = "flex gap-4 p-6 rounded-xl border border-border bg-secondary hover:shadow-md transition-shadow"
>
< div className = "shrink-0 p-3 bg-accent rounded-lg h-fit" >
< Icon className = "h-5 w-5 text-primary" / >
< / div >
< div >
< h3 className = "font-semibold text-foreground mb-1" > { title } < / h3 >
< p className = "text-sm text-muted-foreground leading-relaxed" > { description } < / p >
< / div >
< / div >
) ) }
< / div >
{ /* CTA */ }
< div className = "text-center space-y-4" >
< h3 className = "text-xl font-semibold text-foreground" > Ready to get started ? < / h3 >
< div className = "flex items-center justify-center gap-3" >
< Button onClick = { scrollToLogin } > Sign in < / Button >
< Button variant = "outline" onClick = { onSignUp } >
Create account
< / Button >
< / div >
< / div >
< / div >
{ /* Footer */ }
< div className = "border-t border-border" >
< div className = "max-w-5xl mx-auto px-6 py-6 flex items-center justify-between text-xs text-muted-foreground" >
< span > © { new Date ( ) . getFullYear ( ) } LabWise < / span >
2026-04-09 14:20:18 -05:00
< a href = "/privacy" className = "hover:text-foreground transition-colors" > Privacy Policy < / a >
2026-03-19 19:59:01 -05:00
< / div >
< / div >
< / section >
< / div >
) ;
}