import { createServerFn } from "@tanstack/react-start"; import { requireSupabaseAuth } from "@/integrations/supabase/auth-middleware"; export type IntakePayload = { student: { dob: string; gender: string; grade_level: string; preferred_start_date: string; allergies: string; chronic_conditions: string; primary_physician: string; physician_phone: string; home_ed_eval_due: string; previous_schools: string; special_needs: string; portfolio_keeper: string; disciplinary_history: string; custody_agreement: string; dismissal_methods: string[]; dismissal_other: string; backup_transport_plan: string; interests: string; other_info: string; agreement_signed_by: string; agreement_signed_date: string; }; guardians: { is_primary: boolean; full_name: string; relationship: string; phone: string; address: string; city: string; state: string; zip: string; email: string; employer: string }[]; contacts: { name: string; relationship: string; phone: string; alt_phone: string }[]; pickups: { name: string; relationship: string; phone: string; notes: string }[]; logins: { website: string; student_login: string; student_password: string; parent_account: string; parent_password: string }[]; }; const orNull = (v: string) => (v && v.trim() ? v.trim() : null); // ── Admin: create a one-time intake link for a student ──────────────────────── export const createIntakeToken = createServerFn({ method: "POST" }) .middleware([requireSupabaseAuth]) .validator((d: { studentId: string }) => d) .handler(async ({ data, context }) => { const { supabaseAdmin } = await import("@/integrations/supabase/client.server"); const callerId = (context as { userId: string }).userId; const { data: adminRow } = await supabaseAdmin.from("user_roles").select("role").eq("user_id", callerId).eq("role", "admin").maybeSingle(); if (!adminRow) throw new Error("Only admins can create intake links."); const token = crypto.randomUUID().replace(/-/g, "") + crypto.randomUUID().replace(/-/g, ""); const expires_at = new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString(); const { error } = await supabaseAdmin.from("intake_tokens").insert({ token, student_id: data.studentId, created_by: callerId, expires_at }); if (error) throw new Error(error.message); return { token }; }); // ── Public: validate a token, return the student's name for the form ────────── export const getIntakeToken = createServerFn({ method: "POST" }) .validator((d: { token: string }) => d) .handler(async ({ data }) => { const { supabaseAdmin } = await import("@/integrations/supabase/client.server"); const { data: row } = await supabaseAdmin.from("intake_tokens").select("student_id, expires_at, used_at").eq("token", data.token).maybeSingle(); if (!row) return { status: "invalid" as const }; if (row.used_at) return { status: "used" as const }; if (new Date(row.expires_at) < new Date()) return { status: "expired" as const }; const { data: student } = await supabaseAdmin.from("students").select("first_name, last_name").eq("id", row.student_id).maybeSingle(); return { status: "ok" as const, firstName: student?.first_name ?? "", lastName: student?.last_name ?? "" }; }); // ── Public: submit the intake, write to the student's record, mark used ─────── export const submitIntake = createServerFn({ method: "POST" }) .validator((d: { token: string; payload: IntakePayload }) => d) .handler(async ({ data }) => { const { supabaseAdmin } = await import("@/integrations/supabase/client.server"); const { data: row } = await supabaseAdmin.from("intake_tokens").select("id, student_id, expires_at, used_at").eq("token", data.token).maybeSingle(); if (!row) throw new Error("This link is not valid."); if (row.used_at) throw new Error("This link has already been used."); if (new Date(row.expires_at) < new Date()) throw new Error("This link has expired."); const sid = row.student_id; const s = data.payload.student; const { error: e1 } = await supabaseAdmin.from("students").update({ dob: orNull(s.dob), gender: orNull(s.gender), grade_level: orNull(s.grade_level), preferred_start_date: orNull(s.preferred_start_date), allergies: orNull(s.allergies), chronic_conditions: orNull(s.chronic_conditions), primary_physician: orNull(s.primary_physician), physician_phone: orNull(s.physician_phone), home_ed_eval_due: orNull(s.home_ed_eval_due), previous_schools: orNull(s.previous_schools), special_needs: orNull(s.special_needs), portfolio_keeper: orNull(s.portfolio_keeper), disciplinary_history: orNull(s.disciplinary_history), custody_agreement: orNull(s.custody_agreement), dismissal_methods: s.dismissal_methods ?? [], dismissal_other: orNull(s.dismissal_other), backup_transport_plan: orNull(s.backup_transport_plan), interests: orNull(s.interests), other_info: orNull(s.other_info), agreement_signed_by: orNull(s.agreement_signed_by), agreement_signed_date: orNull(s.agreement_signed_date), }).eq("id", sid); if (e1) throw new Error(e1.message); // Replace collections with what was submitted (initial intake is the source of truth here). await supabaseAdmin.from("student_guardians").delete().eq("student_id", sid); await supabaseAdmin.from("authorized_pickups").delete().eq("student_id", sid); await supabaseAdmin.from("student_curriculum_logins").delete().eq("student_id", sid); const guardianRows = data.payload.guardians.filter((g) => g.full_name.trim()).map((g) => ({ student_id: sid, is_primary: g.is_primary, full_name: g.full_name.trim(), relationship: orNull(g.relationship), phone: orNull(g.phone), address: orNull(g.address), city: orNull(g.city), state: orNull(g.state), zip: orNull(g.zip), email: orNull(g.email), employer: orNull(g.employer), })); if (guardianRows.length) { const { error } = await supabaseAdmin.from("student_guardians").insert(guardianRows); if (error) throw new Error(error.message); } const pickupRows = [ ...data.payload.pickups.filter((p) => p.name.trim()).map((p, i) => ({ student_id: sid, kind: "pickup", sort_order: i, name: p.name.trim(), relationship: orNull(p.relationship), phone: orNull(p.phone), alt_phone: null as string | null, notes: orNull(p.notes) })), ...data.payload.contacts.filter((c) => c.name.trim()).map((c, i) => ({ student_id: sid, kind: "emergency", sort_order: i, name: c.name.trim(), relationship: orNull(c.relationship), phone: orNull(c.phone), alt_phone: orNull(c.alt_phone), notes: null as string | null })), ]; if (pickupRows.length) { const { error } = await supabaseAdmin.from("authorized_pickups").insert(pickupRows); if (error) throw new Error(error.message); } const loginRows = data.payload.logins.filter((l) => Object.values(l).some((v) => v.trim())).map((l) => ({ student_id: sid, website: orNull(l.website), student_login: orNull(l.student_login), student_password: orNull(l.student_password), parent_account: orNull(l.parent_account), parent_password: orNull(l.parent_password), })); if (loginRows.length) { const { error } = await supabaseAdmin.from("student_curriculum_logins").insert(loginRows); if (error) throw new Error(error.message); } await supabaseAdmin.from("intake_tokens").update({ used_at: new Date().toISOString() }).eq("id", row.id); return { ok: true }; });