ecom / client /src /pages /auth.tsx
shashwatIDR's picture
Upload 147 files
b89a86e verified
import { useState } from "react";
import { useLocation } from "wouter";
import { useAuth } from "@/hooks/use-auth";
import { authApi } from "@/lib/api";
import logoUrl from "@assets/assets_task_01k3qp9hccec89tp5ht8ee88x5_1756363038_img_1-removebg-preview (1)_1756364677577.png";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
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 { Checkbox } from "@/components/ui/checkbox";
import { useToast } from "@/hooks/use-toast";
import { Loader2 } from "lucide-react";
import { LocationDetector } from "@/components/location-detector";
const loginSchema = z.object({
email: z.string().email("Please enter a valid email"),
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"),
email: z.string().email("Please enter a valid email"),
password: z.string().min(6, "Password must be at least 6 characters"),
confirmPassword: z.string().min(6, "Please confirm your password"),
firstName: z.string().min(1, "First name is required"),
lastName: z.string().min(1, "Last name is required"),
phone: z.string().min(10, "Phone number is required"),
// Location fields - manual entry (fallback)
street: z.string().optional(),
city: z.string().optional(),
state: z.string().optional(),
pinCode: z.string().optional(),
country: z.string().optional(),
// Automatic location fields
latitude: z.number().optional(),
longitude: z.number().optional(),
googleMapsUrl: z.string().optional(),
locationDetectedAutomatically: z.boolean().default(false),
agreeToTerms: z.boolean().refine((val) => val === true, {
message: "You must agree to the terms and conditions"
}),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ["confirmPassword"],
});
type LoginFormData = z.infer<typeof loginSchema>;
type RegisterFormData = z.infer<typeof registerSchema>;
export default function Auth() {
const [, setLocation] = useLocation();
const { login } = useAuth();
const { toast } = useToast();
const [activeTab, setActiveTab] = useState("signin");
const [isLoading, setIsLoading] = useState(false);
const [locationProvided, setLocationProvided] = useState(false);
const [showLocationStep, setShowLocationStep] = useState(false);
const loginForm = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
defaultValues: {
email: "",
password: "",
},
});
const registerForm = useForm<RegisterFormData>({
resolver: zodResolver(registerSchema),
defaultValues: {
username: "",
email: "",
password: "",
confirmPassword: "",
firstName: "",
lastName: "",
phone: "",
street: "",
city: "",
state: "",
pinCode: "",
country: "",
latitude: undefined,
longitude: undefined,
googleMapsUrl: "",
locationDetectedAutomatically: false,
agreeToTerms: false,
},
});
const onLogin = async (data: LoginFormData) => {
setIsLoading(true);
try {
const response = await authApi.login(data);
const result = await response.json();
login(result.token, result.user);
toast({
title: "Welcome back!",
description: "You have been successfully logged in.",
});
setLocation("/");
} catch (error) {
toast({
variant: "destructive",
title: "Login failed",
description: "Invalid email or password. Please try again.",
});
} finally {
setIsLoading(false);
}
};
const handleLocationDetected = (locationData: {
latitude: number;
longitude: number;
googleMapsUrl: string;
locationDetectedAutomatically: boolean;
}) => {
registerForm.setValue('latitude', locationData.latitude);
registerForm.setValue('longitude', locationData.longitude);
registerForm.setValue('googleMapsUrl', locationData.googleMapsUrl);
registerForm.setValue('locationDetectedAutomatically', locationData.locationDetectedAutomatically);
// Clear manual location fields when automatic detection is used
if (locationData.locationDetectedAutomatically) {
registerForm.setValue('street', '');
registerForm.setValue('city', '');
registerForm.setValue('state', '');
registerForm.setValue('pinCode', '');
registerForm.setValue('country', '');
}
setLocationProvided(true);
setShowLocationStep(false);
};
const handleManualLocationSubmit = (manualData: {
street: string;
city: string;
state: string;
pinCode: string;
country: string;
locationDetectedAutomatically: false;
}) => {
registerForm.setValue('street', manualData.street);
registerForm.setValue('city', manualData.city);
registerForm.setValue('state', manualData.state);
registerForm.setValue('pinCode', manualData.pinCode);
registerForm.setValue('country', manualData.country);
registerForm.setValue('locationDetectedAutomatically', false);
// Clear automatic location fields when manual entry is used
registerForm.setValue('latitude', undefined);
registerForm.setValue('longitude', undefined);
registerForm.setValue('googleMapsUrl', '');
setLocationProvided(true);
setShowLocationStep(false);
};
const onRegister = async (data: RegisterFormData) => {
if (!locationProvided) {
setShowLocationStep(true);
toast({
title: "Location required",
description: "Please provide your location to continue registration.",
});
return;
}
setIsLoading(true);
try {
const { confirmPassword, agreeToTerms, ...registerData } = data;
const response = await authApi.register(registerData);
const result = await response.json();
login(result.token, result.user);
toast({
title: "Account created!",
description: "Welcome to Shoposphere! Your account has been created successfully.",
});
setLocation("/");
} catch (error) {
toast({
variant: "destructive",
title: "Registration failed",
description: "An error occurred while creating your account. Please try again.",
});
} finally {
setIsLoading(false);
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<div className="flex justify-center mb-4" data-testid="auth-title">
<img
src={logoUrl}
alt="Shoposphere"
className="h-16 w-auto"
/>
</div>
</CardHeader>
<CardContent>
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="grid w-full grid-cols-2" data-testid="auth-tabs">
<TabsTrigger value="signin" data-testid="tab-signin">Sign In</TabsTrigger>
<TabsTrigger value="signup" data-testid="tab-signup">Sign Up</TabsTrigger>
</TabsList>
<TabsContent value="signin" className="space-y-4">
<Form {...loginForm}>
<form onSubmit={loginForm.handleSubmit(onLogin)} className="space-y-4">
<FormField
control={loginForm.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input
{...field}
type="email"
placeholder="your@email.com"
data-testid="input-email"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={loginForm.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
{...field}
type="password"
placeholder="β€’β€’β€’β€’β€’β€’β€’β€’"
data-testid="input-password"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="submit"
className="w-full"
disabled={isLoading}
data-testid="button-signin"
>
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Sign In
</Button>
</form>
</Form>
</TabsContent>
<TabsContent value="signup" className="space-y-4">
<Form {...registerForm}>
<form onSubmit={registerForm.handleSubmit(onRegister)} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<FormField
control={registerForm.control}
name="firstName"
render={({ field }) => (
<FormItem>
<FormLabel>First Name</FormLabel>
<FormControl>
<Input {...field} placeholder="John" data-testid="input-firstName" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={registerForm.control}
name="lastName"
render={({ field }) => (
<FormItem>
<FormLabel>Last Name</FormLabel>
<FormControl>
<Input {...field} placeholder="Doe" data-testid="input-lastName" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormField
control={registerForm.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input {...field} placeholder="johndoe123" data-testid="input-username" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={registerForm.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} type="email" placeholder="your@email.com" data-testid="input-email-register" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={registerForm.control}
name="phone"
render={({ field }) => (
<FormItem>
<FormLabel>Phone</FormLabel>
<FormControl>
<Input {...field} type="tel" placeholder="+1 (555) 123-4567" data-testid="input-phone" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Location Section */}
{showLocationStep ? (
<div className="space-y-4" data-testid="location-step">
<LocationDetector
onLocationDetected={handleLocationDetected}
onManualLocationSubmit={handleManualLocationSubmit}
/>
</div>
) : locationProvided ? (
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg space-y-2" data-testid="location-confirmed">
<p className="text-sm font-semibold text-green-700 dark:text-green-300">
βœ“ Location information provided
</p>
{registerForm.getValues('locationDetectedAutomatically') ? (
<div className="text-xs text-green-600 dark:text-green-400">
<p>Coordinates: {registerForm.getValues('latitude')?.toFixed(6)}, {registerForm.getValues('longitude')?.toFixed(6)}</p>
<Button
type="button"
size="sm"
variant="outline"
onClick={() => window.open(registerForm.getValues('googleMapsUrl'), '_blank')}
data-testid="view-detected-location"
>
View on Maps
</Button>
</div>
) : (
<p className="text-xs text-green-600 dark:text-green-400">
Manual address: {registerForm.getValues('street')}, {registerForm.getValues('city')}
</p>
)}
<Button
type="button"
size="sm"
variant="outline"
onClick={() => {
setLocationProvided(false);
setShowLocationStep(true);
}}
data-testid="change-location"
>
Change Location
</Button>
</div>
) : (
<div className="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg" data-testid="location-required">
<p className="text-sm text-blue-700 dark:text-blue-300">
πŸ“ Location information will be requested after you fill out the basic details above.
</p>
</div>
)}
<FormField
control={registerForm.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input {...field} type="password" placeholder="β€’β€’β€’β€’β€’β€’β€’β€’" data-testid="input-password-register" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={registerForm.control}
name="confirmPassword"
render={({ field }) => (
<FormItem>
<FormLabel>Confirm Password</FormLabel>
<FormControl>
<Input {...field} type="password" placeholder="β€’β€’β€’β€’β€’β€’β€’β€’" data-testid="input-confirmPassword" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={registerForm.control}
name="agreeToTerms"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
data-testid="checkbox-terms"
/>
</FormControl>
<FormLabel className="text-sm">
I agree to the Terms & Conditions
</FormLabel>
<FormMessage />
</FormItem>
)}
/>
<Button
type="submit"
className="w-full"
disabled={isLoading}
data-testid="button-signup"
>
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Create Account
</Button>
</form>
</Form>
</TabsContent>
</Tabs>
</CardContent>
</Card>
</div>
);
}