This commit is contained in:
@@ -68,35 +68,38 @@ export function Onboarding({ onComplete }: Props) {
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="pi">PI first name</Label>
|
||||
<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)}
|
||||
required
|
||||
placeholder="e.g. Smith"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="bldg">Building code</Label>
|
||||
<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)}
|
||||
required
|
||||
placeholder="e.g. EER"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="lab">Lab</Label>
|
||||
<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)}
|
||||
required
|
||||
placeholder="e.g. 3.822"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -112,10 +112,16 @@ CREATE INDEX IF NOT EXISTS protocols_user_id_idx ON protocols(user_id);
|
||||
-- User profile — lab defaults pre-filled on chemical entries
|
||||
CREATE TABLE IF NOT EXISTS user_profile (
|
||||
user_id TEXT NOT NULL PRIMARY KEY REFERENCES "user"("id") ON DELETE CASCADE,
|
||||
pi_first_name TEXT NOT NULL,
|
||||
bldg_code TEXT NOT NULL,
|
||||
lab TEXT NOT NULL,
|
||||
pi_first_name TEXT,
|
||||
bldg_code TEXT,
|
||||
lab TEXT,
|
||||
contact TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Allow existing deployments to have nullable profile fields.
|
||||
-- DROP NOT NULL on an already-nullable column is a no-op in Postgres.
|
||||
ALTER TABLE user_profile ALTER COLUMN pi_first_name DROP NOT NULL;
|
||||
ALTER TABLE user_profile ALTER COLUMN bldg_code DROP NOT NULL;
|
||||
ALTER TABLE user_profile ALTER COLUMN lab DROP NOT NULL;
|
||||
|
||||
@@ -21,9 +21,8 @@ router.get('/', requireAuth, async (req, res) => {
|
||||
|
||||
router.post('/', requireAuth, async (req, res) => {
|
||||
const { pi_first_name, bldg_code, lab, contact } = req.body;
|
||||
if (!pi_first_name || !bldg_code || !lab) {
|
||||
return res.status(400).json({ error: 'pi_first_name, bldg_code, and lab are required' });
|
||||
}
|
||||
// Coerce empty strings to null so the DB stores a clean null rather than "".
|
||||
const toNull = (v: unknown) => (typeof v === 'string' && v.trim() !== '' ? v.trim() : null);
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`INSERT INTO user_profile (user_id, pi_first_name, bldg_code, lab, contact)
|
||||
@@ -35,7 +34,7 @@ router.post('/', requireAuth, async (req, res) => {
|
||||
contact = EXCLUDED.contact,
|
||||
updated_at = NOW()
|
||||
RETURNING *`,
|
||||
[req.user!.id, pi_first_name, bldg_code, lab, contact || null]
|
||||
[req.user!.id, toNull(pi_first_name), toNull(bldg_code), toNull(lab), toNull(contact)]
|
||||
);
|
||||
res.json(result.rows[0]);
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user