replace the registration modal with the registration page

This commit is contained in:
aryabenade
2026-04-23 12:38:39 +05:30
parent 0c7667bf26
commit 0cb3d6c326
6 changed files with 253 additions and 485 deletions

15
package-lock.json generated
View File

@@ -2257,6 +2257,7 @@
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-9.2.0.tgz",
"integrity": "sha512-YSzLC0t6VS9MDdPTynSMqU8IxrItFUjkDORALFT6sSMR/XZ5Vgm3RDp/Gk7z727MC4A9s4MFVel0gF0c7+kdrg==",
"peer": true,
"engines": {
"node": ">=12.16"
}
@@ -3097,6 +3098,7 @@
"integrity": "sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
@@ -3107,6 +3109,7 @@
"integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -3117,6 +3120,7 @@
"integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==",
"devOptional": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^19.0.0"
}
@@ -3386,7 +3390,8 @@
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/embla-carousel-react": {
"version": "8.6.0",
@@ -3981,6 +3986,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -4038,6 +4044,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -4064,6 +4071,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@@ -4099,6 +4107,7 @@
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
@@ -4301,7 +4310,8 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/redux-thunk": {
"version": "3.1.0",
@@ -4564,6 +4574,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",

View File

@@ -36,6 +36,7 @@ import { PaymentCancelPage } from './pages/PaymentCancelPage';
import { ItineraryViewPage } from './pages/ItineraryViewPage';
import { CheckoutPage } from './pages/CheckoutPage';
import { CreateMagicItineraryPage } from './pages/CreateMagicIternaryPage';
import RegisterPage from './components/RegisterPage';
// User type definition
interface User {
@@ -283,6 +284,11 @@ export function AppRouter({
<CheckoutPage {...commonNavHandlers} />
</motion.div>
} />
<Route path="/register" element={
<motion.div key="register" {...pageTransition}>
<RegisterPage {...commonNavHandlers} />
</motion.div>
} />
<Route path="/payment/:bookingId" element={
<motion.div key="super-savings" {...pageTransition}>

View File

@@ -7,7 +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';
import { useNavigate } from 'react-router-dom';
interface LoginModalProps {
isOpen: boolean;
@@ -24,6 +24,7 @@ export function LoginModal({ isOpen, onClose }: LoginModalProps) {
const [showRegisterModal, setShowRegisterModal] = useState(false);
const { login } = useAuth();
const navigate = useNavigate()
const [sendOtp, { isLoading: isSendingOtp }] = useLoginMutation();
const [verifyOtp, { isLoading: isVerifying }] = useVerifyOtpMutation();
@@ -155,7 +156,11 @@ export function LoginModal({ isOpen, onClose }: LoginModalProps) {
};
login(userData);
if (!response?.data?.userExists) {
navigate("/register")
} else {
toast.success("User Logged in successfully")
}
onClose();
} catch (err: any) {
setError(err?.data?.message || 'Invalid OTP. Please try again.');
@@ -234,7 +239,7 @@ export function LoginModal({ isOpen, onClose }: LoginModalProps) {
</Button>
<div className="text-center">
<button
onClick={() => setShowRegisterModal(true)}
onClick={() => navigate("/register")}
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>
@@ -314,15 +319,6 @@ export function LoginModal({ isOpen, onClose }: LoginModalProps) {
)
}
</AnimatePresence >
<RegisterModal
isOpen={showRegisterModal}
onClose={() => setShowRegisterModal(false)}
onLoginClick={() => {
setShowRegisterModal(false);
setStep('email');
setEmail('');
}}
/>
</>
);
}

View File

@@ -1,391 +0,0 @@
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>
);
}

View File

@@ -0,0 +1,226 @@
import { useState } from 'react';
import { Button } from './ui/button';
import { Input } from './ui/input';
import { useRegisterMutation } from '../Redux/services/auth.service';
import { toast } from 'sonner';
import { useAuth } from '../context/AuthContext';
import Navbar from './Navbar';
import { Footer } from './Footer';
import { useNavigate } from 'react-router-dom';
export default function RegisterPage() {
const { login, user } = useAuth();
const [formData, setFormData] = useState({
firstName: '',
lastName: '',
emailAddress: user?.email ?? "",
isdCode: '',
mobileNumber: '',
address1: '',
address2: '',
city: '',
state: '',
country: '',
postalCode: ''
});
const [helperText, setHelperText] = useState('');
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate()
const [register, { isLoading: isRegistering }] = useRegisterMutation();
const emailAddress = user?.email
const handleInputChange = (field: string, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const validateForm = () => {
if (!formData.firstName.trim()) return toast.error('First name is required'), false;
if (!formData.lastName.trim()) return toast.error('Last name is required'), false;
if (!formData.emailAddress.includes('@')) return toast.error('Invalid email address'), false;
if (!formData.isdCode.startsWith("+")) toast.error("ISD code must start with '+'"), false;
if (!/^\+\d+$/.test(formData.isdCode)) toast.error("ISD code must contain only numbers after '+'"), false;
if (!/^\d+$/.test(formData.mobileNumber)) return toast.error('Invalid mobile number'), false;
if (!formData.address1.trim()) return toast.error('Address required'), false;
if (!formData.city.trim()) return toast.error('City required'), false;
if (!formData.state.trim()) return toast.error('State required'), false;
if (!/^\d+$/.test(formData.postalCode)) return toast.error('Postal code should only contain numbers'), false;
return true;
};
const handleRegister = async () => {
if (!validateForm()) return;
setIsLoading(true);
setHelperText('');
try {
const response = await register(formData).unwrap();
toast.success('Registration successful!');
const userData = {
userId: response?.user?.id,
email: response?.email || formData.emailAddress,
name: response?.name || formData.emailAddress.split('@')[0].charAt(0).toUpperCase() + formData.emailAddress.split('@')[0].slice(1),
accessToken: response?.accessToken,
};
login(userData);
navigate("/")
} catch (err: any) {
const msg = err?.data?.message || 'Registration failed';
toast.error(msg);
setHelperText(msg);
} finally {
setIsLoading(false);
}
};
return (
<div className="min-h-screen flex flex-col bg-gray-50 w-full">
{/* Navbar */}
<Navbar activeCity="" />
{/* Main Content */}
<div className="flex-grow w-full px-6 md:px-10 py-10 mt-20">
<div className="w-full max-w-5xl mx-auto">
{/* Header */}
<div className="mb-8">
<h2 className="font-merchant text-3xl font-semibold text-gray-900 mb-2">
Create Account
</h2>
<p className="font-poppins text-gray-600">
Register to get started with City Cards
</p>
</div>
{/* Form Container */}
<div className="bg-white rounded-2xl border border-gray-200 p-8 space-y-8">
{/* Personal Info */}
<div className="space-y-4">
<h3 className="font-poppins font-semibold text-gray-800 text-lg">
Personal Information
</h3>
<div className="grid md:grid-cols-2 gap-6">
<Input
placeholder="First Name"
value={formData.firstName}
onChange={(e) => handleInputChange('firstName', e.target.value)}
className="h-12 bg-gray-50 border-0 rounded-xl"
/>
<Input
placeholder="Last Name"
value={formData.lastName}
onChange={(e) => handleInputChange('lastName', e.target.value)}
className="h-12 bg-gray-50 border-0 rounded-xl"
/>
</div>
<Input
type="email"
placeholder="Email Address"
value={emailAddress}
onChange={(e) => handleInputChange('emailAddress', e.target.value)}
className="h-12 bg-gray-50 border-0 rounded-xl"
/>
<div className="grid md:grid-cols-3 gap-6">
<Input
placeholder="ISD Code"
value={formData.isdCode}
onChange={(e) => handleInputChange('isdCode', e.target.value)}
className="h-12 bg-gray-50 border-0 rounded-xl"
/>
<div className="md:col-span-2">
<Input
placeholder="Mobile Number"
value={formData.mobileNumber}
onChange={(e) => handleInputChange('mobileNumber', e.target.value)}
className="h-12 bg-gray-50 border-0 rounded-xl"
/>
</div>
</div>
</div>
{/* Address */}
<div className="space-y-4">
<h3 className="font-poppins font-semibold text-gray-800 text-lg">
Address Information
</h3>
<Input
placeholder="Address Line 1"
value={formData.address1}
onChange={(e) => handleInputChange('address1', e.target.value)}
className="h-12 bg-gray-50 border-0 rounded-xl"
/>
<Input
placeholder="Address Line 2"
value={formData.address2}
onChange={(e) => handleInputChange('address2', e.target.value)}
className="h-12 bg-gray-50 border-0 rounded-xl"
/>
<div className="grid md:grid-cols-2 gap-6">
<Input
placeholder="City"
value={formData.city}
onChange={(e) => handleInputChange('city', e.target.value)}
className="h-12 bg-gray-50 border-0 rounded-xl"
/>
<Input
placeholder="State"
value={formData.state}
onChange={(e) => handleInputChange('state', e.target.value)}
className="h-12 bg-gray-50 border-0 rounded-xl"
/>
</div>
<div className="grid md:grid-cols-2 gap-6">
<Input
placeholder="Country"
value={formData.country}
onChange={(e) => handleInputChange('country', e.target.value)}
className="h-12 bg-gray-50 border-0 rounded-xl"
/>
<Input
placeholder="Postal Code"
value={formData.postalCode}
onChange={(e) => handleInputChange('postalCode', e.target.value)}
className="h-12 bg-gray-50 border-0 rounded-xl"
/>
</div>
</div>
{helperText && (
<p className="text-sm text-red-500">{helperText}</p>
)}
<Button
onClick={handleRegister}
disabled={isLoading || isRegistering}
className="w-full cursor-pointer bg-gray-800 hover:bg-gray-900 md:px-10 h-12 text-white rounded-xl"
>
{isLoading || isRegistering ? 'Registering...' : 'Register'}
</Button>
</div>
</div>
</div>
{/* Footer */}
<div className="mt-auto">
<Footer />
</div>
</div>
);
}

View File

@@ -60,71 +60,6 @@ interface ProfilePageProps {
currentPage: string;
}
// Mock passes data
const mockPasses = [
{
id: '1',
name: 'Melbourne Unlimited Card',
city: 'Melbourne',
type: 'Unlimited Pass',
status: 'active',
price: 149.00,
originalPrice: 249.00,
discount: 40,
attractions: 25,
validFrom: '2024-01-15',
validUntil: '2024-01-22',
daysRemaining: 3,
image: 'https://images.unsplash.com/photo-1514395462725-fb4566210144?w=400',
usedAttractions: 8
},
{
id: '2',
name: 'Melbourne Selective Card',
city: 'Melbourne',
type: 'Flexi Pass',
status: 'active',
price: 89.00,
originalPrice: 149.00,
discount: 40,
attractions: 12,
validFrom: '2024-02-01',
validUntil: '2024-02-08',
daysRemaining: 12,
image: 'https://images.unsplash.com/photo-1502602898536-47ad22581b52?w=400',
usedAttractions: 3
},
{
id: '3',
name: 'Sydney Explorer Pass',
city: 'Sydney',
type: 'Standard Pass',
status: 'expired',
price: 89.00,
originalPrice: 149.00,
discount: 40,
attractions: 15,
validFrom: '2023-12-01',
validUntil: '2023-12-08',
daysRemaining: 0,
image: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400',
usedAttractions: 12
}
];
// Mock itineraries data
const mockItineraries = [
{
id: '1',
name: 'Melbourne Unlimited Card',
city: 'Melbourne',
duration: '7 days',
attractions: 25,
createdDate: '2024-01-15',
status: 'active'
}
];
export function ProfilePage({
onBackClick,
onHomeClick,
@@ -342,21 +277,6 @@ export function ProfilePage({
<div>
<Label htmlFor="country" className="font-poppins font-light">Country</Label>
{/* <Select value={formData.country} onValueChange={(value) => handleInputChange('country', value)}>
<SelectTrigger className="mt-1">
<SelectValue placeholder="Select country" />
</SelectTrigger>
<SelectContent>
<SelectItem value="us">United States</SelectItem>
<SelectItem value="au">Australia</SelectItem>
<SelectItem value="uk">United Kingdom</SelectItem>
<SelectItem value="ca">Canada</SelectItem>
<SelectItem value="de">Germany</SelectItem>
<SelectItem value="fr">France</SelectItem>
<SelectItem value="in">India</SelectItem>
<SelectItem value="jp">Japan</SelectItem>
</SelectContent>
</Select> */}
<Input
id="country"
value={formData.country}