All checks were successful
Deploy to Server / deploy (push) Successful in 33s
128 lines
4.3 KiB
TypeScript
128 lines
4.3 KiB
TypeScript
import { useState } from 'react';
|
|
import { Button } from './ui/button';
|
|
import { Card, CardContent } from './ui/card';
|
|
import { Input } from './ui/input';
|
|
import { Label } from './ui/label';
|
|
import { useSession } from '../lib/auth-client';
|
|
import { validatePhoneOrEmail } from '../lib/validators';
|
|
|
|
const logo = '/logo.png';
|
|
|
|
interface Props {
|
|
onComplete: () => void;
|
|
}
|
|
|
|
export function Onboarding({ onComplete }: Props) {
|
|
const { data: session } = useSession();
|
|
const [piFirstName, setPiFirstName] = useState('');
|
|
const [bldgCode, setBldgCode] = useState('');
|
|
const [lab, setLab] = useState('');
|
|
const [contact, setContact] = useState('');
|
|
const [error, setError] = useState('');
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
async function handleSubmit(e: React.FormEvent) {
|
|
e.preventDefault();
|
|
setError('');
|
|
const trimmedContact = contact.trim();
|
|
if (trimmedContact && !validatePhoneOrEmail(trimmedContact)) {
|
|
setError('Contact must be a valid phone number or email address.');
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
const res = await fetch('/api/profile', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
credentials: 'include',
|
|
body: JSON.stringify({
|
|
pi_first_name: piFirstName.trim(),
|
|
bldg_code: bldgCode.trim(),
|
|
lab: lab.trim(),
|
|
contact: trimmedContact || undefined,
|
|
}),
|
|
});
|
|
setLoading(false);
|
|
if (res.ok) {
|
|
onComplete();
|
|
} else {
|
|
const data = await res.json();
|
|
setError(data.error || 'Failed to save profile');
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex h-screen items-center justify-center bg-secondary">
|
|
<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>
|
|
<div className="space-y-1">
|
|
<h2 className="font-semibold text-lg">
|
|
Welcome{session?.user.name ? `, ${session.user.name.split(' ')[0]}` : ''}!
|
|
</h2>
|
|
<p className="text-sm text-muted-foreground">
|
|
Set up your lab details. These will be used as defaults when adding inventory.
|
|
</p>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div className="space-y-1">
|
|
<Label htmlFor="pi">
|
|
PI first name <span className="text-muted-foreground font-normal">(optional)</span>
|
|
</Label>
|
|
<Input
|
|
id="pi"
|
|
type="text"
|
|
value={piFirstName}
|
|
onChange={e => setPiFirstName(e.target.value)}
|
|
placeholder="e.g. Smith"
|
|
/>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<Label htmlFor="bldg">
|
|
Building code <span className="text-muted-foreground font-normal">(optional)</span>
|
|
</Label>
|
|
<Input
|
|
id="bldg"
|
|
type="text"
|
|
value={bldgCode}
|
|
onChange={e => setBldgCode(e.target.value)}
|
|
placeholder="e.g. EER"
|
|
/>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<Label htmlFor="lab">
|
|
Lab <span className="text-muted-foreground font-normal">(optional)</span>
|
|
</Label>
|
|
<Input
|
|
id="lab"
|
|
type="text"
|
|
value={lab}
|
|
onChange={e => setLab(e.target.value)}
|
|
placeholder="e.g. 3.822"
|
|
/>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<Label htmlFor="contact">
|
|
Contact <span className="text-muted-foreground font-normal">(optional)</span>
|
|
</Label>
|
|
<Input
|
|
id="contact"
|
|
type="text"
|
|
value={contact}
|
|
onChange={e => setContact(e.target.value)}
|
|
placeholder="Phone (e.g. 555-123-4567) or email"
|
|
/>
|
|
</div>
|
|
{error && <p className="text-sm text-red-600">{error}</p>}
|
|
<Button type="submit" className="w-full" disabled={loading}>
|
|
{loading ? 'Saving…' : 'Get started'}
|
|
</Button>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|