Upload signed contract (admin only)
setTitle(e.target.value)} />
diff --git a/src/routes/_authenticated/students.new.tsx b/src/routes/_authenticated/students.new.tsx
index 5638f5f..9b90e68 100644
--- a/src/routes/_authenticated/students.new.tsx
+++ b/src/routes/_authenticated/students.new.tsx
@@ -1,209 +1,40 @@
import { createFileRoute, useNavigate, Link } from "@tanstack/react-router";
-import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
import { supabase } from "@/integrations/supabase/client";
import { useAuth } from "@/hooks/use-auth";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
-import { Textarea } from "@/components/ui/textarea";
-import { Checkbox } from "@/components/ui/checkbox";
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
-import { ArrowLeft, Plus, Trash2, Lock, Loader2 } from "lucide-react";
+import { ArrowLeft, Lock, Loader2 } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
export const Route = createFileRoute("/_authenticated/students/new")({
head: () => ({ meta: [{ title: "New student — School Portal" }] }),
component: NewStudentPage,
- errorComponent: ({ error }) => (
-
-
This page hit an error
-
{error?.message}{"\n\n"}{error?.stack}
-
- ),
});
-type Guardian = {
- full_name: string; relationship: string; phone: string; address: string;
- city: string; state: string; zip: string; email: string; employer: string;
-};
-type Contact = { name: string; relationship: string; phone: string; alt_phone: string };
-type Pickup = { name: string; relationship: string; phone: string; notes: string };
-type Login = { website: string; student_login: string; student_password: string; parent_account: string; parent_password: string };
-
-const emptyGuardian: Guardian = { full_name: "", relationship: "", phone: "", address: "", city: "", state: "", zip: "", email: "", employer: "" };
-const emptyLogin: Login = { website: "", student_login: "", student_password: "", parent_account: "", parent_password: "" };
-
-const DISMISSAL_OPTIONS = [
- { value: "not_allowed", label: "NOT ALLOWED" },
- { value: "bicycle", label: "Bicycle" },
- { value: "scooter", label: "Scooter" },
- { value: "walking", label: "Walking" },
- { value: "rideshare", label: "Rideshare (Uber/Lyft)" },
- { value: "other", label: "Other" },
-];
-
-// Small labeled-field helpers to keep the long form readable.
-function Field({ label, children, className = "" }: { label: string; children: React.ReactNode; className?: string }) {
- return
{children}
;
-}
-function Section({ title, description, children }: { title: string; description?: string; children: React.ReactNode }) {
- return (
-
-
-
{title}
- {description &&
{description}
}
-
- {children}
-
- );
-}
-
-// Top-level (not defined inside the page) so the inputs don't remount / lose focus on each keystroke.
-function GuardianFields({ g, setG }: { g: Guardian; setG: (v: Guardian) => void }) {
- const f = (k: keyof Guardian) => (e: React.ChangeEvent
) => setG({ ...g, [k]: e.target.value });
- return (
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
function NewStudentPage() {
const { roles } = useAuth();
const navigate = useNavigate();
const qc = useQueryClient();
+ const [firstName, setFirstName] = useState("");
+ const [lastName, setLastName] = useState("");
+ const [dob, setDob] = useState("");
- const { data: classes } = useQuery({
- queryKey: ["classes"],
- queryFn: async () => (await supabase.from("classes").select("id, name").order("name")).data ?? [],
- });
-
- // ── form state ─────────────────────────────────────────────────────────────
- const [s, setS] = useState({
- first_name: "", last_name: "", dob: "", gender: "", grade_level: "", preferred_start_date: "", class_id: "",
- allergies: "", chronic_conditions: "", primary_physician: "", physician_phone: "",
- home_ed_eval_due: "", previous_schools: "", special_needs: "", portfolio_keeper: "", disciplinary_history: "", custody_agreement: "",
- dismissal_other: "", backup_transport_plan: "", interests: "", other_info: "",
- agreement_signed_by: "", agreement_signed_date: "",
- });
- const set = (k: keyof typeof s) => (e: React.ChangeEvent) => setS({ ...s, [k]: e.target.value });
-
- const [primary, setPrimary] = useState({ ...emptyGuardian });
- const [secondary, setSecondary] = useState({ ...emptyGuardian });
- const [contacts, setContacts] = useState([
- { name: "", relationship: "", phone: "", alt_phone: "" },
- { name: "", relationship: "", phone: "", alt_phone: "" },
- ]);
- const [pickups, setPickups] = useState([
- { name: "", relationship: "", phone: "", notes: "" },
- { name: "", relationship: "", phone: "", notes: "" },
- { name: "", relationship: "", phone: "", notes: "" },
- ]);
- const [logins, setLogins] = useState([{ ...emptyLogin }]);
- const [dismissal, setDismissal] = useState([]);
- const [agree, setAgree] = useState(false);
-
- const toggleDismissal = (v: string) =>
- setDismissal((d) => (d.includes(v) ? d.filter((x) => x !== v) : [...d, v]));
-
- const save = useMutation({
+ const create = useMutation({
mutationFn: async () => {
- // 1. student
- const { data: student, error } = await supabase
+ const { data, error } = await supabase
.from("students")
- .insert({
- first_name: s.first_name.trim(),
- last_name: s.last_name.trim(),
- dob: s.dob || null,
- gender: s.gender || null,
- grade_level: s.grade_level || null,
- preferred_start_date: s.preferred_start_date || null,
- class_id: s.class_id || null,
- allergies: s.allergies || null,
- chronic_conditions: s.chronic_conditions || null,
- primary_physician: s.primary_physician || null,
- physician_phone: s.physician_phone || null,
- home_ed_eval_due: s.home_ed_eval_due || null,
- previous_schools: s.previous_schools || null,
- special_needs: s.special_needs || null,
- portfolio_keeper: s.portfolio_keeper || null,
- disciplinary_history: s.disciplinary_history || null,
- custody_agreement: s.custody_agreement || null,
- dismissal_methods: dismissal,
- dismissal_other: s.dismissal_other || null,
- backup_transport_plan: s.backup_transport_plan || null,
- interests: s.interests || null,
- other_info: s.other_info || null,
- agreement_signed_by: s.agreement_signed_by || null,
- agreement_signed_date: s.agreement_signed_date || null,
- })
+ .insert({ first_name: firstName.trim(), last_name: lastName.trim(), dob: dob || null })
.select("id")
.single();
if (error) throw error;
- const studentId = student.id;
-
- // 2. guardians
- const guardianRows = [
- { g: primary, is_primary: true },
- { g: secondary, is_primary: false },
- ]
- .filter(({ g }) => g.full_name.trim())
- .map(({ g, is_primary }) => ({
- student_id: studentId, is_primary,
- full_name: g.full_name.trim(), relationship: g.relationship || null, phone: g.phone || null,
- address: g.address || null, city: g.city || null, state: g.state || null, zip: g.zip || null,
- email: g.email || null, employer: g.employer || null,
- }));
- if (guardianRows.length) {
- const { error: gErr } = await supabase.from("student_guardians").insert(guardianRows);
- if (gErr) throw gErr;
- }
-
- // 3. authorized pickups + emergency contacts (one table, distinguished by kind)
- const pickupRows = [
- ...pickups.filter((p) => p.name.trim()).map((p, i) => ({
- student_id: studentId, kind: "pickup", sort_order: i,
- name: p.name.trim(), relationship: p.relationship || null, phone: p.phone || null,
- alt_phone: null as string | null, notes: p.notes || null,
- })),
- ...contacts.filter((c) => c.name.trim()).map((c, i) => ({
- student_id: studentId, kind: "emergency", sort_order: i,
- name: c.name.trim(), relationship: c.relationship || null, phone: c.phone || null,
- alt_phone: c.alt_phone || null, notes: null as string | null,
- })),
- ];
- if (pickupRows.length) {
- const { error: pErr } = await supabase.from("authorized_pickups").insert(pickupRows);
- if (pErr) throw pErr;
- }
-
- // 4. curriculum logins
- const loginRows = logins
- .filter((l) => Object.values(l).some((v) => v.trim()))
- .map((l) => ({
- student_id: studentId,
- website: l.website || null, student_login: l.student_login || null, student_password: l.student_password || null,
- parent_account: l.parent_account || null, parent_password: l.parent_password || null,
- }));
- if (loginRows.length) {
- const { error: lErr } = await supabase.from("student_curriculum_logins").insert(loginRows);
- if (lErr) throw lErr;
- }
-
- return studentId as string;
+ return data.id as string;
},
onSuccess: (id) => {
- toast.success("Student added");
qc.invalidateQueries({ queryKey: ["students"] });
+ toast.success("Student created — fill out the rest of the profile");
navigate({ to: "/students/$id", params: { id } });
},
onError: (e: Error) => toast.error(e.message),
@@ -213,147 +44,23 @@ function NewStudentPage() {
return Admins only
Only admins can add students.
;
}
- const canSave = s.first_name.trim() && s.last_name.trim() && !save.isPending;
+ const canSave = firstName.trim() && lastName.trim() && !create.isPending;
return (
-
+
Back to students
New student
-
Student Information & Authorized Pick-Up — Bayside Academy
+
Start with a name — you'll fill out the full profile (or send it to a parent) next.
-