Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-06-30 20:41:27 +00:00
co-authored by renee-png
parent d7e7de54d3
commit a2a4e6c811
5 changed files with 359 additions and 17 deletions
+96
View File
@@ -0,0 +1,96 @@
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useState } from "react";
import { z } from "zod";
import { supabase } from "@/integrations/supabase/client";
import { lovable } from "@/integrations/lovable";
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 — School Portal" }] }),
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 () => {
const res = await lovable.auth.signInWithOAuth("google", { redirect_uri: window.location.origin });
if (res.error) toast.error(res.error.message ?? "Google sign-in failed");
else if (!res.redirected) navigate({ to: "/dashboard" });
};
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 items-center gap-2 mb-6">
<GraduationCap className="h-6 w-6 text-primary" />
<h1 className="font-semibold text-lg">School Portal</h1>
</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>
);
}