Spaces:
Running
Running
| import { useState } from "react"; | |
| import { useAuth } from "@/hooks/use-auth.tsx"; | |
| import { Redirect, Link } from "wouter"; | |
| import { z } from "zod"; | |
| import { useForm } from "react-hook-form"; | |
| import { zodResolver } from "@hookform/resolvers/zod"; | |
| import { Button } from "@/components/ui/button"; | |
| import { | |
| Form, | |
| FormControl, | |
| FormField, | |
| FormItem, | |
| FormLabel, | |
| FormMessage, | |
| } from "@/components/ui/form"; | |
| import { Input } from "@/components/ui/input"; | |
| import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; | |
| import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; | |
| import { Loader2 } from "lucide-react"; | |
| import { useToast } from "@/hooks/use-toast"; | |
| // Form validation schemas | |
| const loginSchema = z.object({ | |
| username: z.string().min(3, "Username must be at least 3 characters"), | |
| password: z.string().min(6, "Password must be at least 6 characters"), | |
| }); | |
| const registerSchema = z.object({ | |
| username: z.string().min(3, "Username must be at least 3 characters"), | |
| password: z | |
| .string() | |
| .min(6, "Password must be at least 6 characters") | |
| .max(100, "Password must be less than 100 characters"), | |
| confirmPassword: z.string(), | |
| }).refine((data) => data.password === data.confirmPassword, { | |
| message: "Passwords do not match", | |
| path: ["confirmPassword"], | |
| }); | |
| type LoginFormValues = z.infer<typeof loginSchema>; | |
| type RegisterFormValues = z.infer<typeof registerSchema>; | |
| export default function AuthPage() { | |
| const [activeTab, setActiveTab] = useState<"login" | "register">("login"); | |
| const { user, loginMutation, registerMutation, isLoading } = useAuth(); | |
| const { toast } = useToast(); | |
| // Initialize form objects up-front to avoid the hooks rules violation | |
| const loginForm = useForm<LoginFormValues>({ | |
| resolver: zodResolver(loginSchema), | |
| defaultValues: { | |
| username: "", | |
| password: "", | |
| }, | |
| }); | |
| const registerForm = useForm<RegisterFormValues>({ | |
| resolver: zodResolver(registerSchema), | |
| defaultValues: { | |
| username: "", | |
| password: "", | |
| confirmPassword: "", | |
| }, | |
| }); | |
| // If user is already logged in, redirect to home page | |
| if (user) { | |
| return <Redirect to="/" />; | |
| } | |
| // Form submission handlers | |
| const onLoginSubmit = async (data: LoginFormValues) => { | |
| try { | |
| await loginMutation.mutateAsync(data); | |
| toast({ | |
| title: "Welcome back!", | |
| description: "You've successfully logged in.", | |
| }); | |
| } catch (error) { | |
| console.error("Login error:", error); | |
| // Error handling is done in the loginMutation itself | |
| } | |
| }; | |
| const onRegisterSubmit = async (data: RegisterFormValues) => { | |
| try { | |
| const { confirmPassword, ...userData } = data; | |
| await registerMutation.mutateAsync(userData); | |
| toast({ | |
| title: "Account created!", | |
| description: "Your account has been created successfully.", | |
| }); | |
| } catch (error) { | |
| console.error("Registration error:", error); | |
| // Error handling is done in the registerMutation itself | |
| } | |
| }; | |
| const isPending = loginMutation.isPending || registerMutation.isPending || isLoading; | |
| return ( | |
| <div className="container flex min-h-screen w-full items-center justify-center px-4 py-6 overflow-y-auto"> | |
| <div className="flex w-full max-w-6xl flex-col md:flex-row gap-8 my-8"> | |
| {/* Auth Form Section */} | |
| <Card className="w-full max-w-md mx-auto"> | |
| <CardHeader className="space-y-1"> | |
| <CardTitle className="text-2xl font-bold">Welcome</CardTitle> | |
| <CardDescription> | |
| Sign in to your account or create a new one | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent> | |
| <Tabs defaultValue="login" value={activeTab} onValueChange={(v) => setActiveTab(v as "login" | "register")}> | |
| <TabsList className="grid w-full grid-cols-1"> | |
| <TabsTrigger value="login" className="w-full">Login with Replit</TabsTrigger> | |
| </TabsList> | |
| {/* Login with Replit */} | |
| <TabsContent value="login"> | |
| <div className="space-y-4 mt-4"> | |
| <Button | |
| onClick={onLoginSubmit} | |
| className="w-full" | |
| disabled={isPending} | |
| > | |
| {isPending ? ( | |
| <> | |
| <Loader2 className="mr-2 h-4 w-4 animate-spin" /> | |
| Connecting to Replit... | |
| </> | |
| ) : ( | |
| "Continue with Replit" | |
| )} | |
| </Button> | |
| </div> | |
| </TabsContent> | |
| </Tabs> | |
| </CardContent> | |
| <CardFooter className="flex justify-center"> | |
| <p className="text-sm text-muted-foreground"> | |
| {activeTab === "login" ? ( | |
| <> | |
| Don't have an account?{" "} | |
| <Button variant="link" className="p-0" onClick={() => window.location.href = '/register'}> | |
| Sign up | |
| </Button> | |
| </> | |
| ) : ( | |
| <> | |
| Already have an account?{" "} | |
| <Button variant="link" className="p-0" onClick={() => setActiveTab("login")}> | |
| Log in | |
| </Button> | |
| </> | |
| )} | |
| </p> | |
| </CardFooter> | |
| </Card> | |
| {/* Hero Section */} | |
| <div className="flex flex-col justify-center p-6 text-center md:text-left md:w-1/2"> | |
| <h1 className="text-4xl font-bold tracking-tight">AI Assistant</h1> | |
| <p className="mt-4 text-lg text-muted-foreground"> | |
| A powerful AI assistant that helps you with conversations, | |
| generates images, and creates videos based on your prompts. | |
| </p> | |
| <div className="mt-8 space-y-4"> | |
| <div className="flex items-center gap-3"> | |
| <div className="flex h-10 w-10 items-center justify-center rounded-full bg-primary/10"> | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| fill="none" | |
| viewBox="0 0 24 24" | |
| strokeWidth={1.5} | |
| stroke="currentColor" | |
| className="h-5 w-5 text-primary" | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 01-2.555-.337A5.972 5.972 0 015.41 20.97a5.969 5.969 0 01-.474-.065 4.48 4.48 0 00.978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25z" | |
| /> | |
| </svg> | |
| </div> | |
| <div className="space-y-1"> | |
| <h3 className="text-base font-medium leading-none">Smart Conversations</h3> | |
| <p className="text-sm text-muted-foreground"> | |
| Chat with an AI that understands context and provides helpful responses. | |
| </p> | |
| </div> | |
| </div> | |
| <div className="flex items-center gap-3"> | |
| <div className="flex h-10 w-10 items-center justify-center rounded-full bg-primary/10"> | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| fill="none" | |
| viewBox="0 0 24 24" | |
| strokeWidth={1.5} | |
| stroke="currentColor" | |
| className="h-5 w-5 text-primary" | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" | |
| /> | |
| </svg> | |
| </div> | |
| <div className="space-y-1"> | |
| <h3 className="text-base font-medium leading-none">Image Generation</h3> | |
| <p className="text-sm text-muted-foreground"> | |
| Create stunning images from text descriptions using FLUX.1-dev. | |
| </p> | |
| </div> | |
| </div> | |
| <div className="flex items-center gap-3"> | |
| <div className="flex h-10 w-10 items-center justify-center rounded-full bg-primary/10"> | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| fill="none" | |
| viewBox="0 0 24 24" | |
| strokeWidth={1.5} | |
| stroke="currentColor" | |
| className="h-5 w-5 text-primary" | |
| > | |
| <path | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| d="M3.375 19.5h17.25m-17.25 0a1.125 1.125 0 01-1.125-1.125M3.375 19.5h1.5C5.496 19.5 6 18.996 6 18.375m-3.75 0V5.625m0 12.75v-1.5c0-.621.504-1.125 1.125-1.125m18.375 2.625V5.625m0 12.75c0 .621-.504 1.125-1.125 1.125m1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125m0 3.75h-1.5A1.125 1.125 0 0118 18.375M20.625 4.5H3.375m17.25 0c.621 0 1.125.504 1.125 1.125M20.625 4.5h-1.5C18.504 4.5 18 5.004 18 5.625m3.75 0v1.5c0 .621-.504 1.125-1.125 1.125M3.375 4.5c-.621 0-1.125.504-1.125 1.125M3.375 4.5h1.5C5.496 4.5 6 5.004 6 5.625m-3.75 0v1.5c0 .621.504 1.125 1.125 1.125m0 0h1.5m-1.5 0c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125m1.5-3.75C5.496 8.25 6 7.746 6 7.125v-1.5M4.875 8.25C5.496 8.25 6 8.754 6 9.375v1.5m0-5.25v5.25m0-5.25C6 5.004 6.504 4.5 7.125 4.5h9.75c.621 0 1.125.504 1.125 1.125m1.125 2.625h1.5m-1.5 0A1.125 1.125 0 0118 7.125v-1.5m1.125 2.625c-.621 0-1.125.504-1.125 1.125v1.5m2.625-2.625c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125M18 5.625v5.25M7.125 12h9.75m-9.75 0A1.125 1.125 0 016 10.875M7.125 12C6.504 12 6 12.504 6 13.125m0-2.25C6 11.496 5.496 12 4.875 12M18 10.875c0 .621-.504 1.125-1.125 1.125M18 10.875c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125m-12 5.25v-5.25m0 5.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125m-12 0v-1.5c0-.621-.504-1.125-1.125-1.125M18 18.375v-5.25m0 5.25v-1.5c0-.621.504-1.125 1.125-1.125M18 13.125v1.5c0 .621.504 1.125 1.125 1.125M18 13.125c0-.621.504-1.125 1.125-1.125M6 13.125v1.5c0 .621-.504 1.125-1.125 1.125M6 13.125C6 12.504 5.496 12 4.875 12m-1.5 0h1.5m-1.5 0c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125M19.125 12h1.5m0 0c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h1.5m14.25 0h1.5" | |
| /> | |
| </svg> | |
| </div> | |
| <div className="space-y-1"> | |
| <h3 className="text-base font-medium leading-none">Video Creation</h3> | |
| <p className="text-sm text-muted-foreground"> | |
| Transform your ideas into short videos with AI-powered generation. | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } |