diff --git a/web/src/components/login-modal.tsx b/web/src/components/login-modal.tsx new file mode 100644 index 00000000..caee4fd5 --- /dev/null +++ b/web/src/components/login-modal.tsx @@ -0,0 +1,266 @@ +"use client" + +import { Github } from "lucide-react" +import type React from "react" +import { useRef, useState } from "react" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Separator } from "@/components/ui/separator" +import { pb } from "@/lib/pb" + +interface LoginModalProps { + open: boolean + onOpenChange: (open: boolean) => void +} + +export function LoginModal({ open, onOpenChange }: LoginModalProps) { + const [isRegister, setIsRegister] = useState(false) + const [email, setEmail] = useState("") + const [username, setUsername] = useState("") + const [password, setPassword] = useState("") + const [confirmPassword, setConfirmPassword] = useState("") + const [error, setError] = useState("") + const [isLoading, setIsLoading] = useState(false) + const emailRef = useRef(null) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setError("") + setIsLoading(true) + + try { + if (isRegister) { + if (password !== confirmPassword) { + setError("Passwords do not match") + setIsLoading(false) + return + } + + if (!username.trim()) { + setError("Username is required") + setIsLoading(false) + return + } + + if (!email.trim()) { + setError("Email is required") + setIsLoading(false) + return + } + + await pb.collection("users").create({ + username: username.trim(), + email: email.trim(), + password, + passwordConfirm: confirmPassword, + }) + + await pb.collection("users").authWithPassword(email, password) + } else { + await pb.collection("users").authWithPassword(email, password) + } + + onOpenChange(false) + setEmail("") + setUsername("") + setPassword("") + setConfirmPassword("") + } catch (err: any) { + console.error("Auth error:", err) + setError(err?.message || "Authentication failed. Please try again.") + } finally { + setIsLoading(false) + } + } + + const toggleMode = () => { + setIsRegister(!isRegister) + emailRef.current?.focus() + setEmail("") + setUsername("") + setPassword("") + setConfirmPassword("") + setError("") + } + + return ( + + + + + {isRegister ? "Create account" : "Sign in"} + + + {isRegister + ? "Enter your details to create an account" + : "Enter your credentials to continue"} + + + +
+ {error && ( +
+ + + + {error} +
+ )} + + + +
+ + + or + +
+ +
+
+ + setEmail(e.target.value)} + aria-invalid={error ? "true" : "false"} + className="h-11 text-base" + required + /> + {isRegister && ( +

+ Used only to send you updates about your submissions +

+ )} +
+ + {isRegister && ( +
+ + setUsername(e.target.value)} + aria-invalid={error && !username.trim() ? "true" : "false"} + className="h-11 text-base" + required + /> +

+ This will be displayed publicly with your submissions +

+
+ )} + +
+ + setPassword(e.target.value)} + aria-invalid={error ? "true" : "false"} + className="h-11 text-base" + required + /> +
+ + {isRegister && ( +
+ + setConfirmPassword(e.target.value)} + aria-invalid={error && password !== confirmPassword ? "true" : "false"} + className="h-11 text-base" + required + /> +
+ )} +
+ + +
+
+
+ ) +} +