Files
info-share-spot/src/routes/auth.tsx
T
adminandClaude Opus 4.8 d3c9c71564 Brand with Bayside Academy navy + logo
- Theme primary -> Bayside navy (#06315a) in light & dark, sidebar + focus ring
- Bayside logo in the sidebar, login page, and public intake header
- Login title -> Bayside Academy

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 19:42:19 -04:00

100 lines
4.5 KiB
TypeScript

import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useState } from "react";
import { z } from "zod";
import { supabase } from "@/integrations/supabase/client";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import { toast } from "sonner";
import { GraduationCap } from "lucide-react";
const searchSchema = z.object({ mode: z.enum(["signin", "signup"]).optional() });
export const Route = createFileRoute("/auth")({
validateSearch: searchSchema,
head: () => ({ meta: [{ title: "Sign in — Bayside Academy" }] }),
component: AuthPage,
});
function AuthPage() {
const navigate = useNavigate();
const { mode } = Route.useSearch();
const [tab, setTab] = useState<"signin" | "signup">(mode ?? "signin");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [fullName, setFullName] = useState("");
const [loading, setLoading] = useState(false);
const onSignIn = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
const { error } = await supabase.auth.signInWithPassword({ email, password });
setLoading(false);
if (error) return toast.error(error.message);
navigate({ to: "/dashboard" });
};
const onSignUp = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
const { error } = await supabase.auth.signUp({
email,
password,
options: { emailRedirectTo: window.location.origin, data: { full_name: fullName } },
});
setLoading(false);
if (error) return toast.error(error.message);
toast.success("Account created. You can sign in now.");
setTab("signin");
};
const onGoogle = async () => {
// Native Supabase OAuth: redirects the browser to Google, then back to
// redirectTo. supabase-js (detectSessionInUrl) restores the session on
// return, and useAuth's onAuthStateChange picks it up.
const { error } = await supabase.auth.signInWithOAuth({
provider: "google",
options: { redirectTo: `${window.location.origin}/dashboard` },
});
if (error) toast.error(error.message ?? "Google sign-in failed");
};
return (
<div className="min-h-screen flex items-center justify-center bg-muted/30 p-4">
<div className="w-full max-w-md bg-card border rounded-lg p-8 shadow-sm">
<div className="flex justify-center mb-6">
<img src="/bayside-logo.png" alt="Bayside Academy" className="h-12 w-auto" />
</div>
<Tabs value={tab} onValueChange={(v) => setTab(v as "signin" | "signup")}>
<TabsList className="grid grid-cols-2 w-full">
<TabsTrigger value="signin">Sign in</TabsTrigger>
<TabsTrigger value="signup">Create account</TabsTrigger>
</TabsList>
<TabsContent value="signin">
<form onSubmit={onSignIn} className="space-y-4 mt-4">
<div><Label>Email</Label><Input type="email" required value={email} onChange={(e) => setEmail(e.target.value)} /></div>
<div><Label>Password</Label><Input type="password" required value={password} onChange={(e) => setPassword(e.target.value)} /></div>
<Button type="submit" className="w-full" disabled={loading}>Sign in</Button>
</form>
</TabsContent>
<TabsContent value="signup">
<form onSubmit={onSignUp} className="space-y-4 mt-4">
<div><Label>Full name</Label><Input required value={fullName} onChange={(e) => setFullName(e.target.value)} /></div>
<div><Label>Email</Label><Input type="email" required value={email} onChange={(e) => setEmail(e.target.value)} /></div>
<div><Label>Password</Label><Input type="password" required minLength={6} value={password} onChange={(e) => setPassword(e.target.value)} /></div>
<Button type="submit" className="w-full" disabled={loading}>Create account</Button>
<p className="text-xs text-muted-foreground">The first account becomes admin. Other accounts default to parent — an admin can change your role.</p>
</form>
</TabsContent>
</Tabs>
<div className="my-4 flex items-center gap-3 text-xs text-muted-foreground">
<div className="flex-1 border-t" /> OR <div className="flex-1 border-t" />
</div>
<Button variant="outline" className="w-full" onClick={onGoogle}>Continue with Google</Button>
</div>
</div>
);
}