diff --git a/package-lock.json b/package-lock.json index abfa862..af5b3cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx index da7f45d..ed1dd57 100644 --- a/src/AppRouter.tsx +++ b/src/AppRouter.tsx @@ -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({ } /> + + + + } /> diff --git a/src/components/LoginModal.tsx b/src/components/LoginModal.tsx index b15cdc6..0fa4523 100644 --- a/src/components/LoginModal.tsx +++ b/src/components/LoginModal.tsx @@ -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); - toast.success("User Logged in successfully") + 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) { 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? Register @@ -314,15 +319,6 @@ export function LoginModal({ isOpen, onClose }: LoginModalProps) { ) } - setShowRegisterModal(false)} - onLoginClick={() => { - setShowRegisterModal(false); - setStep('email'); - setEmail(''); - }} - /> > ); } \ No newline at end of file diff --git a/src/components/RegisterModal.tsx b/src/components/RegisterModal.tsx deleted file mode 100644 index e690a89..0000000 --- a/src/components/RegisterModal.tsx +++ /dev/null @@ -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 ( - - {isOpen && ( - <> - - - - - - - - - - - Create Account - - - Register to get started with City Cards - - - - - - {/* Personal Information */} - - Personal Information - - - - First Name * - - handleInputChange('firstName', e.target.value)} - className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" - /> - - - - - Last Name * - - handleInputChange('lastName', e.target.value)} - className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" - /> - - - - - - Email Address * - - handleInputChange('emailAddress', e.target.value)} - className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" - /> - - - - - - ISD Code - - handleInputChange('isdCode', value)}> - - - - - +1 (USA) - +44 (UK) - +61 (Australia) - +91 (India) - +86 (China) - +81 (Japan) - +49 (Germany) - +33 (France) - - - - - - - Mobile Number * - - handleInputChange('mobileNumber', e.target.value)} - className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" - /> - - - - - {/* Address Information */} - - Address Information - - - - Address Line 1 * - - handleInputChange('address1', e.target.value)} - className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" - /> - - - - - Address Line 2 - - handleInputChange('address2', e.target.value)} - className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" - /> - - - - - - City * - - handleInputChange('city', e.target.value)} - className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" - /> - - - - - State * - - handleInputChange('state', e.target.value)} - className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" - /> - - - - - - - Country * - - handleInputChange('country', value)}> - - - - - Australia - United States - United Kingdom - Canada - India - Germany - France - Japan - - - - - - - Postal Code * - - handleInputChange('postalCode', e.target.value)} - className="font-poppins text-base h-12 bg-gray-50 border-0 rounded-xl placeholder:text-gray-400" - /> - - - - - {helperText && ( - - {helperText} - - )} - - - {isLoading || isRegistering ? ( - - - Creating Account... - - ) : ( - <> - Register - - - - > - )} - - - - { - onLoginClick(); - onClose(); - }} - className="font-poppins text-sm text-gray-600 hover:text-gray-800 transition-colors cursor-pointer" - > - Already have an account? Login - - - - - - - > - )} - - ); -} diff --git a/src/components/RegisterPage.tsx b/src/components/RegisterPage.tsx new file mode 100644 index 0000000..9908650 --- /dev/null +++ b/src/components/RegisterPage.tsx @@ -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 ( + + {/* Navbar */} + + {/* Main Content */} + + + + + {/* Header */} + + + Create Account + + + Register to get started with City Cards + + + + {/* Form Container */} + + + {/* Personal Info */} + + + Personal Information + + + + handleInputChange('firstName', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl" + /> + handleInputChange('lastName', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl" + /> + + + handleInputChange('emailAddress', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl" + /> + + + handleInputChange('isdCode', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl" + /> + + handleInputChange('mobileNumber', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl" + /> + + + + + {/* Address */} + + + Address Information + + + handleInputChange('address1', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl" + /> + + handleInputChange('address2', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl" + /> + + + handleInputChange('city', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl" + /> + handleInputChange('state', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl" + /> + + + + handleInputChange('country', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl" + /> + handleInputChange('postalCode', e.target.value)} + className="h-12 bg-gray-50 border-0 rounded-xl" + /> + + + + {helperText && ( + {helperText} + )} + + + {isLoading || isRegistering ? 'Registering...' : 'Register'} + + + + + + + {/* Footer */} + + + + + + ); +} \ No newline at end of file diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index 7b1550f..9cf0d70 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -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({ Country - {/* handleInputChange('country', value)}> - - - - - United States - Australia - United Kingdom - Canada - Germany - France - India - Japan - - */}
- Register to get started with City Cards -
- {helperText} -
+ Register to get started with City Cards +
{helperText}