integrate the register user api
This commit is contained in:
@@ -7,6 +7,7 @@ import { Label } from './ui/label';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useLoginMutation, useVerifyOtpMutation } from '../Redux/services/auth.service';
|
||||
import { toast } from 'sonner';
|
||||
import { RegisterModal } from './RegisterModal';
|
||||
|
||||
interface LoginModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -20,6 +21,7 @@ export function LoginModal({ isOpen, onClose }: LoginModalProps) {
|
||||
const [countdown, setCountdown] = useState(0);
|
||||
const [helperText, setHelperText] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [showRegisterModal, setShowRegisterModal] = useState(false);
|
||||
|
||||
const { login } = useAuth();
|
||||
|
||||
@@ -158,136 +160,156 @@ export function LoginModal({ isOpen, onClose }: LoginModalProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50"
|
||||
onClick={onClose}
|
||||
/>
|
||||
<>
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50"
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
className="fixed inset-0 flex items-center justify-center z-50 p-4"
|
||||
>
|
||||
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-md mx-auto overflow-hidden">
|
||||
<div className="relative px-8 pt-8 pb-4">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-6 right-6 w-8 h-8 flex items-center justify-center rounded-full bg-gray-100 hover:bg-gray-200 transition-colors"
|
||||
>
|
||||
<X className="w-4 h-4 text-gray-600" />
|
||||
</button>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
className="fixed inset-0 flex items-center justify-center z-50 p-4"
|
||||
>
|
||||
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-md mx-auto overflow-hidden">
|
||||
<div className="relative px-8 pt-8 pb-4">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-6 right-6 w-8 h-8 flex items-center justify-center rounded-full bg-gray-100 hover:bg-gray-200 transition-colors cursor-pointer"
|
||||
>
|
||||
<X className="w-4 h-4 text-gray-600" />
|
||||
</button>
|
||||
|
||||
<h2 className="font-merchant text-2xl font-semibold text-gray-900 mb-2">
|
||||
Login
|
||||
</h2>
|
||||
<p className="font-poppins text-sm text-gray-600">
|
||||
Enter your email and verify with OTP
|
||||
</p>
|
||||
</div>
|
||||
<h2 className="font-merchant text-2xl font-semibold text-gray-900 mb-2">
|
||||
Login
|
||||
</h2>
|
||||
<p className="font-poppins text-sm text-gray-600">
|
||||
Enter your email and verify with OTP
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="px-8 pb-8">
|
||||
{step === 'email' ? (
|
||||
// ... Email step (unchanged)
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
Email Address
|
||||
</Label>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="name@example.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleSendOTP()}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
{error && <p className="text-red-500 text-xs">{error}</p>}
|
||||
{helperText && <p className="text-green-600 text-xs">{helperText}</p>}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={handleSendOTP}
|
||||
disabled={isSendingOtp}
|
||||
className="w-full h-12 bg-gray-800 hover:bg-gray-900 text-white font-poppins font-semibold rounded-xl"
|
||||
>
|
||||
{isSendingOtp ? 'Sending OTP...' : 'Send OTP'}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{/* Email Display */}
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">Email</Label>
|
||||
<div className="h-12 bg-gray-50 rounded-xl flex items-center px-4 font-poppins text-base text-gray-600">
|
||||
{email}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* OTP Inputs with Paste Support */}
|
||||
<div className="space-y-3">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
Enter OTP
|
||||
</Label>
|
||||
<div className="flex gap-3 justify-between">
|
||||
{otp.map((digit, index) => (
|
||||
<input
|
||||
key={index}
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
maxLength={1}
|
||||
value={digit}
|
||||
onChange={(e) => handleOTPChange(index, e.target.value.replace(/\D/g, ''))}
|
||||
onKeyDown={(e) => handleOTPKeyDown(index, e)}
|
||||
onPaste={handlePaste} // ← Paste support added here
|
||||
data-otp-index={index}
|
||||
className="w-12 h-12 text-center font-poppins font-semibold text-lg bg-gray-100 border-0 rounded-xl focus:bg-white focus:ring-2 focus:ring-gray-800 focus:outline-none transition-all"
|
||||
/>
|
||||
))}
|
||||
<div className="px-8 pb-8">
|
||||
{step === 'email' ? (
|
||||
// ... Email step (unchanged)
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
Email Address
|
||||
</Label>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="name@example.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleSendOTP()}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
{error && <p className="text-red-500 text-xs">{error}</p>}
|
||||
{helperText && <p className="text-green-600 text-xs">{helperText}</p>}
|
||||
</div>
|
||||
|
||||
{countdown > 0 && (
|
||||
<p className="text-center text-xs text-gray-500">
|
||||
Resend OTP in {formatCountdown(countdown)}
|
||||
</p>
|
||||
<Button
|
||||
onClick={handleSendOTP}
|
||||
disabled={isSendingOtp}
|
||||
className="w-full h-12 bg-gray-800 hover:bg-gray-900 text-white font-poppins font-semibold rounded-xl cursor-pointer"
|
||||
>
|
||||
{isSendingOtp ? 'Sending OTP...' : 'Send OTP'}
|
||||
</Button>
|
||||
<div className="text-center">
|
||||
<button
|
||||
onClick={() => setShowRegisterModal(true)}
|
||||
className="font-poppins text-sm text-gray-600 hover:text-gray-800 transition-colors cursor-pointer"
|
||||
>
|
||||
Don't have an account? <span className="text-primary font-semibold">Register</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{/* Email Display */}
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">Email</Label>
|
||||
<div className="h-12 bg-gray-50 rounded-xl flex items-center px-4 font-poppins text-base text-gray-600">
|
||||
{email}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* OTP Inputs with Paste Support */}
|
||||
<div className="space-y-3">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
Enter OTP
|
||||
</Label>
|
||||
<div className="flex gap-3 justify-between">
|
||||
{otp.map((digit, index) => (
|
||||
<input
|
||||
key={index}
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
maxLength={1}
|
||||
value={digit}
|
||||
onChange={(e) => handleOTPChange(index, e.target.value.replace(/\D/g, ''))}
|
||||
onKeyDown={(e) => handleOTPKeyDown(index, e)}
|
||||
onPaste={handlePaste} // ← Paste support added here
|
||||
data-otp-index={index}
|
||||
className="w-12 h-12 text-center font-poppins font-semibold text-lg bg-gray-300 border-0 rounded-xl focus:bg-white focus:ring-2 focus:ring-gray-800 focus:outline-none transition-all"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{countdown > 0 && (
|
||||
<p className="text-center text-xs text-gray-500">
|
||||
Resend OTP in {formatCountdown(countdown)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error && <p className="text-red-500 text-xs text-center">{error}</p>}
|
||||
|
||||
<Button
|
||||
onClick={handleVerifyLogin}
|
||||
disabled={isVerifying || otp.join('').length !== 6}
|
||||
className="w-full h-12 bg-gray-800 hover:bg-gray-900 text-white font-poppins font-semibold rounded-xl disabled:opacity-50 cursor-pointer"
|
||||
>
|
||||
{isVerifying ? 'Verifying...' : 'Verify & Login'}
|
||||
</Button>
|
||||
|
||||
{countdown === 0 && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setStep('email');
|
||||
setOtp(['', '', '', '', '', '']);
|
||||
setError('');
|
||||
}}
|
||||
className="w-full text-sm text-gray-600 hover:text-gray-800 font-poppins"
|
||||
>
|
||||
Didn't receive OTP? Send again
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error && <p className="text-red-500 text-xs text-center">{error}</p>}
|
||||
|
||||
<Button
|
||||
onClick={handleVerifyLogin}
|
||||
disabled={isVerifying || otp.join('').length !== 6}
|
||||
className="w-full h-12 bg-gray-800 hover:bg-gray-900 text-white font-poppins font-semibold rounded-xl disabled:opacity-50"
|
||||
>
|
||||
{isVerifying ? 'Verifying...' : 'Verify & Login'}
|
||||
</Button>
|
||||
|
||||
{countdown === 0 && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setStep('email');
|
||||
setOtp(['', '', '', '', '', '']);
|
||||
setError('');
|
||||
}}
|
||||
className="w-full text-sm text-gray-600 hover:text-gray-800 font-poppins"
|
||||
>
|
||||
Didn't receive OTP? Send again
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</AnimatePresence >
|
||||
<RegisterModal
|
||||
isOpen={showRegisterModal}
|
||||
onClose={() => setShowRegisterModal(false)}
|
||||
onLoginClick={() => {
|
||||
setShowRegisterModal(false);
|
||||
setStep('email');
|
||||
setEmail('');
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
391
src/components/RegisterModal.tsx
Normal file
391
src/components/RegisterModal.tsx
Normal file
@@ -0,0 +1,391 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import { X } from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
import { Input } from './ui/input';
|
||||
import { Label } from './ui/label';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
|
||||
import { useRegisterMutation } from '../Redux/services/auth.service';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface RegisterModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onLoginClick: () => void;
|
||||
}
|
||||
|
||||
export function RegisterModal({ isOpen, onClose, onLoginClick }: RegisterModalProps) {
|
||||
const [formData, setFormData] = useState({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
emailAddress: '',
|
||||
isdCode: '+91',
|
||||
mobileNumber: '',
|
||||
address1: '',
|
||||
address2: '',
|
||||
city: '',
|
||||
state: '',
|
||||
country: 'Australia',
|
||||
postalCode: ''
|
||||
});
|
||||
const [helperText, setHelperText] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const [register, { isLoading: isRegistering }] = useRegisterMutation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setFormData({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
emailAddress: '',
|
||||
isdCode: '+91',
|
||||
mobileNumber: '',
|
||||
address1: '',
|
||||
address2: '',
|
||||
city: '',
|
||||
state: '',
|
||||
country: 'Australia',
|
||||
postalCode: ''
|
||||
});
|
||||
setHelperText('');
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const handleInputChange = (field: string, value: string) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const validateForm = () => {
|
||||
if (!formData.firstName.trim()) {
|
||||
toast.error('First name is required');
|
||||
return false;
|
||||
}
|
||||
if (!formData.lastName.trim()) {
|
||||
toast.error('Last name is required');
|
||||
return false;
|
||||
}
|
||||
if (!formData.emailAddress.trim() || !formData.emailAddress.includes('@')) {
|
||||
toast.error('Please enter a valid email address');
|
||||
return false;
|
||||
}
|
||||
if (!formData.mobileNumber.trim()) {
|
||||
toast.error('Mobile number is required');
|
||||
return false;
|
||||
}
|
||||
if (!/^\d+$/.test(formData.mobileNumber.trim())) {
|
||||
toast.error('Mobile number must contain only digits');
|
||||
return false;
|
||||
}
|
||||
if (!formData.address1.trim()) {
|
||||
toast.error('Address is required');
|
||||
return false;
|
||||
}
|
||||
if (!formData.city.trim()) {
|
||||
toast.error('City is required');
|
||||
return false;
|
||||
}
|
||||
if (!formData.state.trim()) {
|
||||
toast.error('State is required');
|
||||
return false;
|
||||
}
|
||||
if (!formData.postalCode.trim()) {
|
||||
toast.error('Postal code is required');
|
||||
return false;
|
||||
}
|
||||
if (!/^\d+$/.test(formData.postalCode.trim())) {
|
||||
toast.error('Postal code must contain only digits');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
const handleRegister = async () => {
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setHelperText('');
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const response = await register(formData).unwrap();
|
||||
console.log('Registration response:', response);
|
||||
|
||||
toast.success('Registration successful! Please login.');
|
||||
setTimeout(() => {
|
||||
onLoginClick();
|
||||
onClose();
|
||||
}, 2000);
|
||||
} catch (error: any) {
|
||||
console.error('Registration error:', error);
|
||||
const errorMessage = error?.data?.message || 'Registration failed. Please try again.';
|
||||
toast.error(errorMessage);
|
||||
setHelperText(errorMessage);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
handleRegister();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50"
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
className="fixed inset-0 flex items-center justify-center z-50 p-4 overflow-y-auto"
|
||||
>
|
||||
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-2xl mx-auto overflow-hidden max-h-[90vh] overflow-y-auto">
|
||||
<div className="relative px-8 pt-8 pb-4 top-0 bg-white z-10">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-6 right-6 w-8 h-8 flex items-center justify-center rounded-full bg-gray-100 hover:bg-gray-200 transition-colors cursor-pointer"
|
||||
>
|
||||
<X className="w-4 h-4 text-gray-600" />
|
||||
</button>
|
||||
|
||||
<h2 className="font-merchant text-2xl font-semibold text-gray-900 mb-2">
|
||||
Create Account
|
||||
</h2>
|
||||
<p className="font-poppins text-sm text-gray-600">
|
||||
Register to get started with City Cards
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="px-8 pb-8">
|
||||
<div className="space-y-6">
|
||||
{/* Personal Information */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-poppins text-base font-semibold text-gray-800">Personal Information</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
First Name <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Enter your first name"
|
||||
value={formData.firstName}
|
||||
onChange={(e) => handleInputChange('firstName', e.target.value)}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
Last Name <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Enter your last name"
|
||||
value={formData.lastName}
|
||||
onChange={(e) => handleInputChange('lastName', e.target.value)}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
Email Address <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Enter your email address"
|
||||
value={formData.emailAddress}
|
||||
onChange={(e) => handleInputChange('emailAddress', e.target.value)}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
ISD Code
|
||||
</Label>
|
||||
<Select value={formData.isdCode} onValueChange={(value: any) => handleInputChange('isdCode', value)}>
|
||||
<SelectTrigger className="h-12 bg-gray-50 border-0 rounded-xl cursor-pointer">
|
||||
<SelectValue placeholder="Select code" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="+1">+1 (USA)</SelectItem>
|
||||
<SelectItem value="+44">+44 (UK)</SelectItem>
|
||||
<SelectItem value="+61">+61 (Australia)</SelectItem>
|
||||
<SelectItem value="+91">+91 (India)</SelectItem>
|
||||
<SelectItem value="+86">+86 (China)</SelectItem>
|
||||
<SelectItem value="+81">+81 (Japan)</SelectItem>
|
||||
<SelectItem value="+49">+49 (Germany)</SelectItem>
|
||||
<SelectItem value="+33">+33 (France)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2 space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
Mobile Number <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
type="tel"
|
||||
placeholder="Enter your mobile number"
|
||||
value={formData.mobileNumber}
|
||||
onChange={(e) => handleInputChange('mobileNumber', e.target.value)}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Address Information */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-poppins text-base font-semibold text-gray-800">Address Information</h3>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
Address Line 1 <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Enter street address"
|
||||
value={formData.address1}
|
||||
onChange={(e) => handleInputChange('address1', e.target.value)}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
Address Line 2
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Enter apartment, suite, unit (optional)"
|
||||
value={formData.address2}
|
||||
onChange={(e) => handleInputChange('address2', e.target.value)}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
City <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Enter city name"
|
||||
value={formData.city}
|
||||
onChange={(e) => handleInputChange('city', e.target.value)}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
State <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Enter state name"
|
||||
value={formData.state}
|
||||
onChange={(e) => handleInputChange('state', e.target.value)}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
Country <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Select value={formData.country} onValueChange={(value: any) => handleInputChange('country', value)}>
|
||||
<SelectTrigger className="h-12 bg-gray-50 border-0 rounded-xl cursor-pointer">
|
||||
<SelectValue placeholder="Select country" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="Australia">Australia</SelectItem>
|
||||
<SelectItem value="United States">United States</SelectItem>
|
||||
<SelectItem value="United Kingdom">United Kingdom</SelectItem>
|
||||
<SelectItem value="Canada">Canada</SelectItem>
|
||||
<SelectItem value="India">India</SelectItem>
|
||||
<SelectItem value="Germany">Germany</SelectItem>
|
||||
<SelectItem value="France">France</SelectItem>
|
||||
<SelectItem value="Japan">Japan</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="font-poppins text-sm font-medium text-gray-700">
|
||||
Postal Code <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
placeholder="Enter postal code"
|
||||
value={formData.postalCode}
|
||||
onChange={(e) => handleInputChange('postalCode', e.target.value)}
|
||||
className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{helperText && (
|
||||
<p className={`font-poppins text-xs ${helperText.includes('successful') ? 'text-green-600' : 'text-red-500'}`}>
|
||||
{helperText}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<Button
|
||||
onClick={handleRegister}
|
||||
disabled={isLoading || isRegistering}
|
||||
className="w-full h-12 bg-gray-800 hover:bg-gray-900 text-white font-poppins font-semibold rounded-xl transition-colors cursor-pointer"
|
||||
>
|
||||
{isLoading || isRegistering ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 border-2 border-white/20 border-t-white rounded-full animate-spin" />
|
||||
Creating Account...
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
Register
|
||||
<svg className="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<div className="text-center">
|
||||
<button
|
||||
onClick={() => {
|
||||
onLoginClick();
|
||||
onClose();
|
||||
}}
|
||||
className="font-poppins text-sm text-gray-600 hover:text-gray-800 transition-colors cursor-pointer"
|
||||
>
|
||||
Already have an account? <span className="text-primary font-semibold">Login</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
2
src/global.d.ts
vendored
2
src/global.d.ts
vendored
@@ -32,3 +32,5 @@ declare module '*.mp4' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module "*.css";
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import App from "./App";
|
||||
import "./index.css";
|
||||
import { Provider } from "react-redux";
|
||||
import { store } from "./Redux/Store";
|
||||
import { Toaster } from "sonner";
|
||||
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<Provider store={store}>
|
||||
<BrowserRouter>
|
||||
|
||||
Reference in New Issue
Block a user