Compare commits
7 Commits
7fc7f1b433
...
5abda5b6cb
| Author | SHA1 | Date | |
|---|---|---|---|
| 5abda5b6cb | |||
|
|
a03d1999bf | ||
|
|
201e8b86d4 | ||
|
|
39e63deca2 | ||
|
|
c7af20fac7 | ||
|
|
e0c314d3af | ||
|
|
4c985e5177 |
@@ -259,7 +259,7 @@ export function LandingBookAttractionSection() {
|
|||||||
whileHover={{ scale: 1.05 }}
|
whileHover={{ scale: 1.05 }}
|
||||||
whileTap={{ scale: 0.95 }}
|
whileTap={{ scale: 0.95 }}
|
||||||
className={`px-6 py-4 h-14 rounded-full font-medium transition-all duration-300 ${activeCategory === category
|
className={`px-6 py-4 h-14 rounded-full font-medium transition-all duration-300 ${activeCategory === category
|
||||||
? 'bg-warm-coral text-white shadow-xl shadow-warm-coral/25 ring-2 ring-warm-coral/20'
|
? 'bg-red-400 text-white shadow-xl shadow-warm-coral/25 ring-2 ring-warm-coral/20'
|
||||||
: 'bg-white/80 backdrop-blur-sm text-gray-700 hover:text-gray-900 hover:shadow-lg border border-gray-200/50 hover:border-warm-coral/20 hover:bg-white'
|
: 'bg-white/80 backdrop-blur-sm text-gray-700 hover:text-gray-900 hover:shadow-lg border border-gray-200/50 hover:border-warm-coral/20 hover:bg-white'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Button } from './ui/button';
|
|||||||
|
|
||||||
// Import your video from assets
|
// Import your video from assets
|
||||||
import cityTourVideo from '../assets/itinenary-animation-vid.mp4';
|
import cityTourVideo from '../assets/itinenary-animation-vid.mp4';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
interface ItineraryCard {
|
interface ItineraryCard {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -22,6 +23,8 @@ export function LandingMagicItinerary() {
|
|||||||
const [isPlaying, setIsPlaying] = useState(true);
|
const [isPlaying, setIsPlaying] = useState(true);
|
||||||
const [videoLoaded, setVideoLoaded] = useState(false);
|
const [videoLoaded, setVideoLoaded] = useState(false);
|
||||||
|
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const handleVideoLoad = () => {
|
const handleVideoLoad = () => {
|
||||||
setVideoLoaded(true);
|
setVideoLoaded(true);
|
||||||
};
|
};
|
||||||
@@ -31,7 +34,7 @@ export function LandingMagicItinerary() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="relative py-20 lg:py-32 overflow-hidden -mt-20 pt-32 z-[49]">
|
<section className="relative py-20 lg:py-15 overflow-hidden -mt-20 z-[49]">
|
||||||
{/* Dynamic Background */}
|
{/* Dynamic Background */}
|
||||||
<div className="absolute inset-0 overflow-hidden pointer-events-none z-[5]">
|
<div className="absolute inset-0 overflow-hidden pointer-events-none z-[5]">
|
||||||
{/* Background Image as fallback */}
|
{/* Background Image as fallback */}
|
||||||
@@ -97,7 +100,7 @@ export function LandingMagicItinerary() {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="text-center mb-16 max-w-5xl w-full">
|
<div className="text-center mb-16 max-w-5xl w-full">
|
||||||
<motion.div
|
<motion.div
|
||||||
className="inline-flex items-center gap-3 bg-gradient-to-r from-warm-coral/10 to-orange-100/50 backdrop-blur-sm px-6 py-3 rounded-full border-2 border-warm-coral/30 shadow-xl mb-8"
|
className="inline-flex items-center gap-3 bg-gradient-to-r from-warm-coral/10 to-orange-100/50 backdrop-blur-sm pl-6 py-3 rounded-full border-2 border-warm-coral/30 shadow-xl mb-8"
|
||||||
initial={{ opacity: 0, scale: 0.8, y: 20 }}
|
initial={{ opacity: 0, scale: 0.8, y: 20 }}
|
||||||
whileInView={{ opacity: 1, scale: 1, y: 0 }}
|
whileInView={{ opacity: 1, scale: 1, y: 0 }}
|
||||||
transition={{ duration: 0.7, ease: [0.34, 1.56, 0.64, 1] }}
|
transition={{ duration: 0.7, ease: [0.34, 1.56, 0.64, 1] }}
|
||||||
@@ -131,7 +134,7 @@ export function LandingMagicItinerary() {
|
|||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
>
|
>
|
||||||
<span className="font-light">Plan Your</span>{' '}
|
<span className="font-light">Plan Your</span>{' '}
|
||||||
<span className="font-bold italic bg-gradient-to-r via-orange-500 to-rose-500 bg-clip-text pr-2 text-transparent drop-shadow-lg">
|
<span className="font-bold italic bg-gradient-to-r from-red-500 via-orange-500 to-rose-500 bg-clip-text pr-2 text-transparent drop-shadow-lg">
|
||||||
Dream Journey
|
Dream Journey
|
||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
@@ -250,6 +253,7 @@ export function LandingMagicItinerary() {
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
withShine={true}
|
withShine={true}
|
||||||
|
onClick={() => navigate('/landing-magic-itinerary')}
|
||||||
className="py-6 px-14 rounded-full text-lg font-bold bg-gradient-to-r via-orange-500 to-rose-500 hover:from-warm-coral/90 hover:via-orange-500/90 hover:to-rose-500/90 shadow-2xl hover:shadow-warm-coral/50 transition-all hover:scale-105 hover:-translate-y-1"
|
className="py-6 px-14 rounded-full text-lg font-bold bg-gradient-to-r via-orange-500 to-rose-500 hover:from-warm-coral/90 hover:via-orange-500/90 hover:to-rose-500/90 shadow-2xl hover:shadow-warm-coral/50 transition-all hover:scale-105 hover:-translate-y-1"
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-3">
|
<span className="flex items-center gap-3">
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ export function LandingTrustSection() {
|
|||||||
style={{
|
style={{
|
||||||
transform: `rotate(${cardRotation}deg) translateY(${cardOffset}px)`,
|
transform: `rotate(${cardRotation}deg) translateY(${cardOffset}px)`,
|
||||||
transformOrigin: 'center center',
|
transformOrigin: 'center center',
|
||||||
minHeight: '480px',
|
minHeight: '360px',
|
||||||
background: `
|
background: `
|
||||||
radial-gradient(circle at 20% 80%, rgba(255, 248, 235, 0.8) 0%, transparent 50%),
|
radial-gradient(circle at 20% 80%, rgba(255, 248, 235, 0.8) 0%, transparent 50%),
|
||||||
radial-gradient(circle at 80% 20%, rgba(250, 245, 230, 0.6) 0%, transparent 50%),
|
radial-gradient(circle at 80% 20%, rgba(250, 245, 230, 0.6) 0%, transparent 50%),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { Footer } from './Footer';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Label } from './ui/label';
|
import { Label } from './ui/label';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import { AlertCircle } from 'lucide-react';
|
||||||
|
|
||||||
export default function RegisterPage() {
|
export default function RegisterPage() {
|
||||||
const { login, user } = useAuth();
|
const { login, user } = useAuth();
|
||||||
@@ -30,6 +31,7 @@ export default function RegisterPage() {
|
|||||||
|
|
||||||
const [helperText, setHelperText] = useState('');
|
const [helperText, setHelperText] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
@@ -46,63 +48,120 @@ export default function RegisterPage() {
|
|||||||
setFormData(prev => ({ ...prev, [field]: value }));
|
setFormData(prev => ({ ...prev, [field]: value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateForm = () => {
|
// const validateForm = () => {
|
||||||
// First Name
|
// // First Name
|
||||||
if (!formData.firstName.trim()) return toast.error('First name is required'), false;
|
// if (!formData.firstName.trim()) return toast.error('First name is required'), false;
|
||||||
if (/\s/.test(formData.firstName)) return toast.error('First name must not contain spaces'), false;
|
// if (/\s/.test(formData.firstName)) return toast.error('First name must not contain spaces'), false;
|
||||||
if (!/^[A-Za-z]+$/.test(formData.firstName)) return toast.error('First name must contain only letters (A–Z)'), false;
|
// if (!/^[A-Za-z]+$/.test(formData.firstName)) return toast.error('First name must contain only letters (A–Z)'), false;
|
||||||
if (formData.firstName.length < 2 || formData.firstName.length > 50) return toast.error('First name must be between 2 and 50 characters'), false;
|
// if (formData.firstName.length < 2 || formData.firstName.length > 50) return toast.error('First name must be between 2 and 50 characters'), false;
|
||||||
|
|
||||||
// Last Name
|
// // Last Name
|
||||||
if (!formData.lastName.trim()) return toast.error('Last name is required'), false;
|
// if (!formData.lastName.trim()) return toast.error('Last name is required'), false;
|
||||||
if (/\s/.test(formData.lastName)) return toast.error('Last name must not contain spaces'), false;
|
// if (/\s/.test(formData.lastName)) return toast.error('Last name must not contain spaces'), false;
|
||||||
if (!/^[A-Za-z]+$/.test(formData.lastName)) return toast.error('Last name must contain only letters (A–Z)'), false;
|
// if (!/^[A-Za-z]+$/.test(formData.lastName)) return toast.error('Last name must contain only letters (A–Z)'), false;
|
||||||
if (formData.lastName.length < 2 || formData.lastName.length > 50) return toast.error('Last name must be between 2 and 50 characters'), false;
|
// if (formData.lastName.length < 2 || formData.lastName.length > 50) return toast.error('Last name must be between 2 and 50 characters'), false;
|
||||||
|
|
||||||
// Email
|
// // Email
|
||||||
if (!formData.emailAddress.trim()) return toast.error('Email address is required'), false;
|
// if (!formData.emailAddress.trim()) return toast.error('Email address is required'), false;
|
||||||
if (!/\S+@\S+\.\S+/.test(formData.emailAddress)) return toast.error('Enter a valid email (e.g. name@example.com)'), false;
|
// if (!/\S+@\S+\.\S+/.test(formData.emailAddress)) return toast.error('Enter a valid email (e.g. name@example.com)'), false;
|
||||||
|
|
||||||
// ISD
|
// // ISD
|
||||||
if (!formData.isdCode.trim()) return toast.error('ISD code is required'), false;
|
// if (!formData.isdCode.trim()) return toast.error('ISD code is required'), false;
|
||||||
if (/\s/.test(formData.isdCode)) return toast.error('ISD code must not contain spaces'), false;
|
// if (/\s/.test(formData.isdCode)) return toast.error('ISD code must not contain spaces'), false;
|
||||||
if (!formData.isdCode.startsWith('+')) return toast.error("ISD code must start with '+' (e.g. +91)"), false;
|
// if (!formData.isdCode.startsWith('+')) return toast.error("ISD code must start with '+' (e.g. +91)"), false;
|
||||||
if (!/^\+\d+$/.test(formData.isdCode)) return toast.error("ISD code must contain only digits after '+'"), false;
|
// if (!/^\+\d+$/.test(formData.isdCode)) return toast.error("ISD code must contain only digits after '+'"), false;
|
||||||
|
|
||||||
// Phone
|
// // Phone
|
||||||
if (!formData.mobileNumber.trim()) return toast.error('Mobile number is required'), false;
|
// if (!formData.mobileNumber.trim()) return toast.error('Mobile number is required'), false;
|
||||||
if (/\s/.test(formData.mobileNumber)) return toast.error('Mobile number must not contain spaces'), false;
|
// if (/\s/.test(formData.mobileNumber)) return toast.error('Mobile number must not contain spaces'), false;
|
||||||
if (!/^\d+$/.test(formData.mobileNumber)) return toast.error('Mobile number must contain only digits (0–9)'), false;
|
// if (!/^\d+$/.test(formData.mobileNumber)) return toast.error('Mobile number must contain only digits (0–9)'), false;
|
||||||
if (formData.mobileNumber.length < 7 || formData.mobileNumber.length > 15) return toast.error('Mobile number must be between 7 and 15 digits'), false;
|
// if (formData.mobileNumber.length < 7 || formData.mobileNumber.length > 15) return toast.error('Mobile number must be between 7 and 15 digits'), false;
|
||||||
|
|
||||||
// Address
|
// // Address
|
||||||
if (!formData.address1.trim()) return toast.error('Address is required'), false;
|
// if (!formData.address1.trim()) return toast.error('Address is required'), false;
|
||||||
if (!/^[A-Za-z0-9\s,\-.]+$/.test(formData.address1)) return toast.error('Address can only contain letters, numbers, spaces, commas, dots, and hyphens'), false;
|
// if (!/^[A-Za-z0-9\s,\-.]+$/.test(formData.address1)) return toast.error('Address can only contain letters, numbers, spaces, commas, dots, and hyphens'), false;
|
||||||
if (formData.address1.length < 5 || formData.address1.length > 100) return toast.error('Address must be between 5 and 100 characters'), false;
|
// if (formData.address1.length < 5 || formData.address1.length > 100) return toast.error('Address must be between 5 and 100 characters'), false;
|
||||||
|
|
||||||
// City
|
// // City
|
||||||
if (!formData.city.trim()) return toast.error('City is required'), false;
|
// if (!formData.city.trim()) return toast.error('City is required'), false;
|
||||||
if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.city)) return toast.error('City can only contain letters and spaces'), false;
|
// if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.city)) return toast.error('City can only contain letters and spaces'), false;
|
||||||
if (/\s{2,}/.test(formData.city)) return toast.error('City must not contain multiple consecutive spaces'), false;
|
// if (/\s{2,}/.test(formData.city)) return toast.error('City must not contain multiple consecutive spaces'), false;
|
||||||
|
|
||||||
// State
|
// // State
|
||||||
if (!formData.state.trim()) return toast.error('State is required'), false;
|
// if (!formData.state.trim()) return toast.error('State is required'), false;
|
||||||
if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.state)) return toast.error('State can only contain letters and spaces'), false;
|
// if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.state)) return toast.error('State can only contain letters and spaces'), false;
|
||||||
if (/\s{2,}/.test(formData.state)) return toast.error('State must not contain multiple consecutive spaces'), false;
|
// if (/\s{2,}/.test(formData.state)) return toast.error('State must not contain multiple consecutive spaces'), false;
|
||||||
|
|
||||||
// Country
|
// // Country
|
||||||
if (!formData.country.trim()) return toast.error('Country is required'), false;
|
// if (!formData.country.trim()) return toast.error('Country is required'), false;
|
||||||
if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.country)) return toast.error('Country can only contain letters and spaces'), false;
|
// if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.country)) return toast.error('Country can only contain letters and spaces'), false;
|
||||||
|
|
||||||
// Postal Code
|
// // Postal Code
|
||||||
if (!formData.postalCode.trim()) return toast.error('Postal code is required'), false;
|
// if (!formData.postalCode.trim()) return toast.error('Postal code is required'), false;
|
||||||
if (/\s/.test(formData.postalCode)) return toast.error('Postal code must not contain spaces'), false;
|
// if (/\s/.test(formData.postalCode)) return toast.error('Postal code must not contain spaces'), false;
|
||||||
if (!/^[A-Za-z0-9]+$/.test(formData.postalCode)) return toast.error('Postal code must contain only letters and numbers'), false;
|
// if (!/^[A-Za-z0-9]+$/.test(formData.postalCode)) return toast.error('Postal code must contain only letters and numbers'), false;
|
||||||
if (formData.postalCode.length < 4 || formData.postalCode.length > 10) return toast.error('Postal code must be between 4 and 10 characters'), false;
|
// if (formData.postalCode.length < 4 || formData.postalCode.length > 10) return toast.error('Postal code must be between 4 and 10 characters'), false;
|
||||||
|
|
||||||
return true;
|
// return true;
|
||||||
};
|
// };
|
||||||
|
const validateForm = () => {
|
||||||
|
const e: Record<string, string> = {};
|
||||||
|
|
||||||
|
if (!formData.firstName.trim()) e.firstName = 'First name is required';
|
||||||
|
else if (/\s/.test(formData.firstName)) e.firstName = 'First name must not contain spaces';
|
||||||
|
else if (!/^[A-Za-z]+$/.test(formData.firstName)) e.firstName = 'First name must contain only letters (A–Z)';
|
||||||
|
else if (formData.firstName.length < 2 || formData.firstName.length > 50) e.firstName = 'First name must be between 2 and 50 characters';
|
||||||
|
|
||||||
|
if (!formData.lastName.trim()) e.lastName = 'Last name is required';
|
||||||
|
else if (/\s/.test(formData.lastName)) e.lastName = 'Last name must not contain spaces';
|
||||||
|
else if (!/^[A-Za-z]+$/.test(formData.lastName)) e.lastName = 'Last name must contain only letters (A–Z)';
|
||||||
|
else if (formData.lastName.length < 2 || formData.lastName.length > 50) e.lastName = 'Last name must be between 2 and 50 characters';
|
||||||
|
|
||||||
|
if (!formData.emailAddress.trim()) e.emailAddress = 'Email address is required';
|
||||||
|
else if (!/\S+@\S+\.\S+/.test(formData.emailAddress)) e.emailAddress = 'Enter a valid email (e.g. name@example.com)';
|
||||||
|
|
||||||
|
if (!formData.isdCode.trim()) e.isdCode = 'ISD code is required';
|
||||||
|
else if (/\s/.test(formData.isdCode)) e.isdCode = 'ISD code must not contain spaces';
|
||||||
|
else if (!formData.isdCode.startsWith('+')) e.isdCode = "ISD code must start with '+' (e.g. +91)";
|
||||||
|
else if (!/^\+\d+$/.test(formData.isdCode)) e.isdCode = "ISD code must contain only digits after '+'";
|
||||||
|
|
||||||
|
if (!formData.mobileNumber.trim()) e.mobileNumber = 'Mobile number is required';
|
||||||
|
else if (/\s/.test(formData.mobileNumber)) e.mobileNumber = 'Mobile number must not contain spaces';
|
||||||
|
else if (!/^\d+$/.test(formData.mobileNumber)) e.mobileNumber = 'Mobile number must contain only digits (0–9)';
|
||||||
|
else if (formData.mobileNumber.length < 7 || formData.mobileNumber.length > 15) e.mobileNumber = 'Mobile number must be between 7 and 15 digits';
|
||||||
|
|
||||||
|
if (!formData.address1.trim()) e.address1 = 'Address is required';
|
||||||
|
else if (!/^[A-Za-z0-9\s,\-.]+$/.test(formData.address1)) e.address1 = 'Address can only contain letters, numbers, spaces, commas, dots, and hyphens';
|
||||||
|
else if (formData.address1.length < 5 || formData.address1.length > 100) e.address1 = 'Address must be between 5 and 100 characters';
|
||||||
|
|
||||||
|
if (!formData.city.trim()) e.city = 'City is required';
|
||||||
|
else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.city)) e.city = 'City can only contain letters and spaces';
|
||||||
|
else if (/\s{2,}/.test(formData.city)) e.city = 'City must not contain multiple consecutive spaces';
|
||||||
|
|
||||||
|
if (!formData.state.trim()) e.state = 'State is required';
|
||||||
|
else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.state)) e.state = 'State can only contain letters and spaces';
|
||||||
|
else if (/\s{2,}/.test(formData.state)) e.state = 'State must not contain multiple consecutive spaces';
|
||||||
|
|
||||||
|
if (!formData.country.trim()) e.country = 'Country is required';
|
||||||
|
else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.country)) e.country = 'Country can only contain letters and spaces';
|
||||||
|
else if (formData.country.length < 2 || formData.country.length > 50) e.country = 'Country must be between 2 and 50 characters';
|
||||||
|
|
||||||
|
if (!formData.postalCode.trim()) e.postalCode = 'Postal code is required';
|
||||||
|
else if (/\s/.test(formData.postalCode)) e.postalCode = 'Postal code must not contain spaces';
|
||||||
|
else if (!/^[A-Za-z0-9]+$/.test(formData.postalCode)) e.postalCode = 'Postal code must contain only letters and numbers';
|
||||||
|
else if (formData.postalCode.length < 4 || formData.postalCode.length > 10) e.postalCode = 'Postal code must be between 4 and 10 characters';
|
||||||
|
|
||||||
|
setFieldErrors(e);
|
||||||
|
return Object.keys(e).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper to render inline error (add once near top of return or as a small component)
|
||||||
|
const FieldError = ({ name }: { name: string }) =>
|
||||||
|
fieldErrors[name] ? (
|
||||||
|
<p className="text-xs text-red-500 mt-1 flex items-center gap-1">
|
||||||
|
<AlertCircle className="w-3 h-3" />{fieldErrors[name]}
|
||||||
|
</p>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
|
||||||
const handleRegister = async () => {
|
const handleRegister = async () => {
|
||||||
@@ -162,28 +221,36 @@ export default function RegisterPage() {
|
|||||||
|
|
||||||
<div className="grid md:grid-cols-2 gap-6">
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="firstName" className="font-poppins font-light">First Name</Label>
|
<Label htmlFor="firstName" className="font-poppins font-light">
|
||||||
|
First Name <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="firstName"
|
id="firstName"
|
||||||
value={formData.firstName}
|
value={formData.firstName}
|
||||||
onChange={(e) => handleInputChange('firstName', e.target.value)}
|
onChange={(e) => handleInputChange('firstName', e.target.value)}
|
||||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
className={`h-12 bg-gray-50 border-0 rounded-xl mt-1 ${fieldErrors.firstName ? 'border border-red-400' : ''}`}
|
||||||
/>
|
/>
|
||||||
|
<FieldError name="firstName" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="lastName" className="font-poppins font-light">Last Name</Label>
|
<Label htmlFor="lastName" className="font-poppins font-light">
|
||||||
|
Last Name <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="lastName"
|
id="lastName"
|
||||||
value={formData.lastName}
|
value={formData.lastName}
|
||||||
onChange={(e) => handleInputChange('lastName', e.target.value)}
|
onChange={(e) => handleInputChange('lastName', e.target.value)}
|
||||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
className={`h-12 bg-gray-50 border-0 rounded-xl mt-1 ${fieldErrors.lastName ? 'border border-red-400' : ''}`}
|
||||||
/>
|
/>
|
||||||
|
<FieldError name="lastName" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="emailAddress" className="font-poppins font-light">Email Address</Label>
|
<Label htmlFor="emailAddress" className="font-poppins font-light">
|
||||||
|
Email Address <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="emailAddress"
|
id="emailAddress"
|
||||||
type="email"
|
type="email"
|
||||||
@@ -192,28 +259,35 @@ export default function RegisterPage() {
|
|||||||
onChange={(e) => handleInputChange('emailAddress', e.target.value)}
|
onChange={(e) => handleInputChange('emailAddress', e.target.value)}
|
||||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
||||||
/>
|
/>
|
||||||
|
<FieldError name="emailAddress" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 gap-6">
|
<div className="grid md:grid-cols-3 gap-6">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="isdCode" className="font-poppins font-light">ISD Code</Label>
|
<Label htmlFor="isdCode" className="font-poppins font-light">
|
||||||
|
ISD Code <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="isdCode"
|
id="isdCode"
|
||||||
placeholder="example: +91"
|
placeholder="example: +91"
|
||||||
value={formData.isdCode}
|
value={formData.isdCode}
|
||||||
onChange={(e) => handleInputChange('isdCode', e.target.value)}
|
onChange={(e) => handleInputChange('isdCode', e.target.value)}
|
||||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
className={`h-12 bg-gray-50 border-0 rounded-xl mt-1 ${fieldErrors.isdCode ? 'border border-red-400' : ''}`}
|
||||||
/>
|
/>
|
||||||
|
<FieldError name="isdCode" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<Label htmlFor="mobileNumber" className="font-poppins font-light">Mobile Number</Label>
|
<Label htmlFor="mobileNumber" className="font-poppins font-light">
|
||||||
|
Mobile Number <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="mobileNumber"
|
id="mobileNumber"
|
||||||
value={formData.mobileNumber}
|
value={formData.mobileNumber}
|
||||||
onChange={(e) => handleInputChange('mobileNumber', e.target.value)}
|
onChange={(e) => handleInputChange('mobileNumber', e.target.value)}
|
||||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
className={`h-12 bg-gray-50 border-0 rounded-xl mt-1 ${fieldErrors.mobileNumber ? 'border border-red-400' : ''}`}
|
||||||
/>
|
/>
|
||||||
|
<FieldError name="mobileNumber" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -225,13 +299,16 @@ export default function RegisterPage() {
|
|||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="address1" className="font-poppins font-light">Address Line 1</Label>
|
<Label htmlFor="address1" className="font-poppins font-light">
|
||||||
|
Address Line 1 <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="address1"
|
id="address1"
|
||||||
value={formData.address1}
|
value={formData.address1}
|
||||||
onChange={(e) => handleInputChange('address1', e.target.value)}
|
onChange={(e) => handleInputChange('address1', e.target.value)}
|
||||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
className={`h-12 bg-gray-50 border-0 rounded-xl mt-1 ${fieldErrors.address1 ? 'border border-red-400' : ''}`}
|
||||||
/>
|
/>
|
||||||
|
<FieldError name="address1" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -246,45 +323,57 @@ export default function RegisterPage() {
|
|||||||
|
|
||||||
<div className="grid md:grid-cols-2 gap-6">
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="city" className="font-poppins font-light">City</Label>
|
<Label htmlFor="city" className="font-poppins font-light">
|
||||||
|
City <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="city"
|
id="city"
|
||||||
value={formData.city}
|
value={formData.city}
|
||||||
onChange={(e) => handleInputChange('city', e.target.value)}
|
onChange={(e) => handleInputChange('city', e.target.value)}
|
||||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
className={`h-12 bg-gray-50 border-0 rounded-xl mt-1 ${fieldErrors.city ? 'border border-red-400' : ''}`}
|
||||||
/>
|
/>
|
||||||
|
<FieldError name="city" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="state" className="font-poppins font-light">State</Label>
|
<Label htmlFor="state" className="font-poppins font-light">
|
||||||
|
State <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="state"
|
id="state"
|
||||||
value={formData.state}
|
value={formData.state}
|
||||||
onChange={(e) => handleInputChange('state', e.target.value)}
|
onChange={(e) => handleInputChange('state', e.target.value)}
|
||||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
className={`h-12 bg-gray-50 border-0 rounded-xl mt-1 ${fieldErrors.state ? 'border border-red-400' : ''}`}
|
||||||
/>
|
/>
|
||||||
|
<FieldError name="state" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-2 gap-6">
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="country" className="font-poppins font-light">Country</Label>
|
<Label htmlFor="country" className="font-poppins font-light">
|
||||||
|
Country <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="country"
|
id="country"
|
||||||
value={formData.country}
|
value={formData.country}
|
||||||
onChange={(e) => handleInputChange('country', e.target.value)}
|
onChange={(e) => handleInputChange('country', e.target.value)}
|
||||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
className={`h-12 bg-gray-50 border-0 rounded-xl mt-1 ${fieldErrors.country ? 'border border-red-400' : ''}`}
|
||||||
/>
|
/>
|
||||||
|
<FieldError name="country" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="postalCode" className="font-poppins font-light">Postal Code</Label>
|
<Label htmlFor="postalCode" className="font-poppins font-light">
|
||||||
|
Postal Code <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="postalCode"
|
id="postalCode"
|
||||||
value={formData.postalCode}
|
value={formData.postalCode}
|
||||||
onChange={(e) => handleInputChange('postalCode', e.target.value)}
|
onChange={(e) => handleInputChange('postalCode', e.target.value)}
|
||||||
className="h-12 bg-gray-50 border-0 rounded-xl mt-1"
|
className={`h-12 bg-gray-50 border-0 rounded-xl mt-1 ${fieldErrors.postalCode ? 'border border-red-400' : ''}`}
|
||||||
/>
|
/>
|
||||||
|
<FieldError name="postalCode" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ export function MelbournePage({
|
|||||||
</div> */}
|
</div> */}
|
||||||
|
|
||||||
|
|
||||||
<div className="container mx-auto px-4 py-12 space-y-24">
|
<div className="container mx-auto px-4 py-12">
|
||||||
{/* Features Grid */}
|
{/* Features Grid */}
|
||||||
<motion.section
|
<motion.section
|
||||||
id="overview"
|
id="overview"
|
||||||
@@ -392,7 +392,7 @@ export function MelbournePage({
|
|||||||
>
|
>
|
||||||
<Wand2 className="w-6 h-6 text-primary drop-shadow-lg" />
|
<Wand2 className="w-6 h-6 text-primary drop-shadow-lg" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<span className="font-poppins font-semibold text-gray-800">AI-Powered Magic Itinerary</span>
|
<span className="font-poppins font-semibold text-gray-800">Magic Itinerary</span>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="w-2 h-2 bg-primary rounded-full"
|
className="w-2 h-2 bg-primary rounded-full"
|
||||||
animate={{
|
animate={{
|
||||||
@@ -791,18 +791,18 @@ export function MelbournePage({
|
|||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<p className="font-poppins text-gray-600 text-sm flex items-center gap-2">
|
{/* <p className="font-poppins text-gray-600 text-sm flex items-center gap-2">
|
||||||
<Sparkles className="w-4 h-4 text-primary" />
|
<Sparkles className="w-4 h-4 text-primary" />
|
||||||
<span>Free to use • No credit card required</span>
|
<span>Free to use • No credit card required</span>
|
||||||
<Sparkles className="w-4 h-4 text-primary" />
|
<Sparkles className="w-4 h-4 text-primary" />
|
||||||
</p>
|
</p> */}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="container mx-auto px-4 py-12 space-y-24">
|
<div className="container mx-auto px-4 py-12">
|
||||||
{/* Testimonials */}
|
{/* Testimonials */}
|
||||||
<EnhancedTestimonials />
|
<EnhancedTestimonials />
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ function Field({
|
|||||||
prefilled,
|
prefilled,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}: {
|
}: {
|
||||||
label: string;
|
label: React.ReactNode;
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (v: string) => void;
|
onChange: (v: string) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
@@ -230,64 +230,64 @@ export function PaymentDetailsPage({
|
|||||||
|
|
||||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||||
|
|
||||||
const validate = () => {
|
const validate = () => {
|
||||||
const e: Record<string, string> = {};
|
const e: Record<string, string> = {};
|
||||||
|
|
||||||
if (selectedTab === 'gift') {
|
if (selectedTab === 'gift') {
|
||||||
// First Name
|
// First Name
|
||||||
if (!giftFirstName.trim()) e.giftFirstName = 'First name is required';
|
if (!giftFirstName.trim()) e.giftFirstName = 'First name is required';
|
||||||
else if (/\s/.test(giftFirstName)) e.giftFirstName = 'First name must not contain spaces';
|
else if (/\s/.test(giftFirstName)) e.giftFirstName = 'First name must not contain spaces';
|
||||||
else if (!/^[A-Za-z]+$/.test(giftFirstName)) e.giftFirstName = 'First name must contain only letters (A–Z)';
|
else if (!/^[A-Za-z]+$/.test(giftFirstName)) e.giftFirstName = 'First name must contain only letters (A–Z)';
|
||||||
else if (giftFirstName.length < 2 || giftFirstName.length > 50) e.giftFirstName = 'First name must be between 2 and 50 characters';
|
else if (giftFirstName.length < 2 || giftFirstName.length > 50) e.giftFirstName = 'First name must be between 2 and 50 characters';
|
||||||
|
|
||||||
// Last Name
|
// Last Name
|
||||||
if (!giftLastName.trim()) e.giftLastName = 'Last name is required';
|
if (!giftLastName.trim()) e.giftLastName = 'Last name is required';
|
||||||
else if (/\s/.test(giftLastName)) e.giftLastName = 'Last name must not contain spaces';
|
else if (/\s/.test(giftLastName)) e.giftLastName = 'Last name must not contain spaces';
|
||||||
else if (!/^[A-Za-z]+$/.test(giftLastName)) e.giftLastName = 'Last name must contain only letters (A–Z)';
|
else if (!/^[A-Za-z]+$/.test(giftLastName)) e.giftLastName = 'Last name must contain only letters (A–Z)';
|
||||||
else if (giftLastName.length < 2 || giftLastName.length > 50) e.giftLastName = 'Last name must be between 2 and 50 characters';
|
else if (giftLastName.length < 2 || giftLastName.length > 50) e.giftLastName = 'Last name must be between 2 and 50 characters';
|
||||||
|
|
||||||
// ISD Code
|
// ISD Code
|
||||||
if (!giftIsd.trim()) e.giftIsd = 'ISD code is required';
|
if (!giftIsd.trim()) e.giftIsd = 'ISD code is required';
|
||||||
else if (/\s/.test(giftIsd)) e.giftIsd = 'ISD code must not contain spaces';
|
else if (/\s/.test(giftIsd)) e.giftIsd = 'ISD code must not contain spaces';
|
||||||
else if (!giftIsd.startsWith('+')) e.giftIsd = "ISD code must start with '+' (e.g. +91)";
|
else if (!giftIsd.startsWith('+')) e.giftIsd = "ISD code must start with '+' (e.g. +91)";
|
||||||
else if (!/^\+\d+$/.test(giftIsd)) e.giftIsd = "ISD code must contain only digits after '+'";
|
else if (!/^\+\d+$/.test(giftIsd)) e.giftIsd = "ISD code must contain only digits after '+'";
|
||||||
|
|
||||||
// Email
|
// Email
|
||||||
if (!giftEmail.trim()) e.giftEmail = 'Email address is required';
|
if (!giftEmail.trim()) e.giftEmail = 'Email address is required';
|
||||||
else if (!/\S+@\S+\.\S+/.test(giftEmail)) e.giftEmail = 'Enter a valid email (e.g. name@example.com)';
|
else if (!/\S+@\S+\.\S+/.test(giftEmail)) e.giftEmail = 'Enter a valid email (e.g. name@example.com)';
|
||||||
|
|
||||||
// Phone
|
// Phone
|
||||||
if (!giftPhone.trim()) e.giftPhone = 'Phone number is required';
|
if (!giftPhone.trim()) e.giftPhone = 'Phone number is required';
|
||||||
else if (/\s/.test(giftPhone)) e.giftPhone = 'Phone number must not contain spaces';
|
else if (/\s/.test(giftPhone)) e.giftPhone = 'Phone number must not contain spaces';
|
||||||
else if (!/^\d+$/.test(giftPhone)) e.giftPhone = 'Phone number must contain only digits (0–9)';
|
else if (!/^\d+$/.test(giftPhone)) e.giftPhone = 'Phone number must contain only digits (0–9)';
|
||||||
else if (giftPhone.length < 7 || giftPhone.length > 15) e.giftPhone = 'Phone number must be between 7 and 15 digits';
|
else if (giftPhone.length < 7 || giftPhone.length > 15) e.giftPhone = 'Phone number must be between 7 and 15 digits';
|
||||||
|
|
||||||
// Message
|
// Message
|
||||||
if (!giftMessage.trim()) e.giftMessage = 'Message is required';
|
if (!giftMessage.trim()) e.giftMessage = 'Message is required';
|
||||||
else if (giftMessage.length < 5) e.giftMessage = 'Message must be at least 5 characters long';
|
else if (giftMessage.length < 5) e.giftMessage = 'Message must be at least 5 characters long';
|
||||||
else if (giftMessage.length > 500) e.giftMessage = 'Message must not exceed 500 characters';
|
else if (giftMessage.length > 500) e.giftMessage = 'Message must not exceed 500 characters';
|
||||||
|
|
||||||
// City
|
// City
|
||||||
if (!giftCity.trim()) e.giftCity = 'City is required';
|
if (!giftCity.trim()) e.giftCity = 'City is required';
|
||||||
else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(giftCity)) e.giftCity = 'City can only contain letters and spaces';
|
else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(giftCity)) e.giftCity = 'City can only contain letters and spaces';
|
||||||
else if (/\s{2,}/.test(giftCity)) e.giftCity = 'City must not contain multiple consecutive spaces';
|
else if (/\s{2,}/.test(giftCity)) e.giftCity = 'City must not contain multiple consecutive spaces';
|
||||||
else if (giftCity.length < 2 || giftCity.length > 50) e.giftCity = 'City must be between 2 and 50 characters';
|
else if (giftCity.length < 2 || giftCity.length > 50) e.giftCity = 'City must be between 2 and 50 characters';
|
||||||
|
|
||||||
// Country
|
// Country
|
||||||
if (!giftCountry.trim()) e.giftCountry = 'Country is required';
|
if (!giftCountry.trim()) e.giftCountry = 'Country is required';
|
||||||
else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(giftCountry)) e.giftCountry = 'Country can only contain letters and spaces';
|
else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(giftCountry)) e.giftCountry = 'Country can only contain letters and spaces';
|
||||||
else if (giftCountry.length < 2 || giftCountry.length > 50) e.giftCountry = 'Country must be between 2 and 50 characters';
|
else if (giftCountry.length < 2 || giftCountry.length > 50) e.giftCountry = 'Country must be between 2 and 50 characters';
|
||||||
}
|
}
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
};
|
};
|
||||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||||
|
|
||||||
const handlePayment = async () => {
|
const handlePayment = async () => {
|
||||||
const validationErrors = validate();
|
const validationErrors = validate();
|
||||||
setErrors(validationErrors);
|
setErrors(validationErrors);
|
||||||
if (Object.keys(validationErrors).length > 0) {
|
if (Object.keys(validationErrors).length > 0) {
|
||||||
toast.error('Please fill all required fields');
|
// toast.error('Please fill all required fields');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,8 +413,8 @@ const validate = () => {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setSelectedTab('myself')}
|
onClick={() => setSelectedTab('myself')}
|
||||||
className={`flex-1 flex items-center justify-center gap-2 py-3 rounded-xl font-poppins text-sm font-medium transition-all duration-200 ${selectedTab === 'myself'
|
className={`flex-1 flex items-center justify-center gap-2 py-3 rounded-xl font-poppins text-sm font-medium transition-all duration-200 ${selectedTab === 'myself'
|
||||||
? 'bg-[#F95F62] text-white shadow-md shadow-[#F95F62]/20'
|
? 'bg-[#F95F62] text-white shadow-md shadow-[#F95F62]/20'
|
||||||
: 'bg-gray-100 text-[#555] hover:bg-gray-200'
|
: 'bg-gray-100 text-[#555] hover:bg-gray-200'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<User className="w-4 h-4" />
|
<User className="w-4 h-4" />
|
||||||
@@ -423,8 +423,8 @@ const validate = () => {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setSelectedTab('gift')}
|
onClick={() => setSelectedTab('gift')}
|
||||||
className={`flex-1 flex items-center justify-center gap-2 py-3 rounded-xl font-poppins text-sm font-medium transition-all duration-200 ${selectedTab === 'gift'
|
className={`flex-1 flex items-center justify-center gap-2 py-3 rounded-xl font-poppins text-sm font-medium transition-all duration-200 ${selectedTab === 'gift'
|
||||||
? 'bg-[#F95F62] text-white shadow-md shadow-[#F95F62]/20'
|
? 'bg-[#F95F62] text-white shadow-md shadow-[#F95F62]/20'
|
||||||
: 'bg-gray-100 text-[#555] hover:bg-gray-200'
|
: 'bg-gray-100 text-[#555] hover:bg-gray-200'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Gift className="w-4 h-4" />
|
<Gift className="w-4 h-4" />
|
||||||
@@ -486,64 +486,14 @@ const validate = () => {
|
|||||||
<h3 className="font-poppins text-base font-semibold text-[#2a2a2a]">Gift Recipient Details</h3>
|
<h3 className="font-poppins text-base font-semibold text-[#2a2a2a]">Gift Recipient Details</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<Field
|
<Field label={<>Recipient First Name <span className="text-red-500">*</span></>} value={giftFirstName} onChange={setGiftFirstName} placeholder="Enter recipient's first name" error={errors.giftFirstName} />
|
||||||
label="Recipient First Name"
|
<Field label={<>Recipient Last Name <span className="text-red-500">*</span></>} value={giftLastName} onChange={setGiftLastName} placeholder="Enter recipient's last name" error={errors.giftLastName} />
|
||||||
value={giftFirstName}
|
<Field label={<>Recipient ISD Code <span className="text-red-500">*</span></>} value={giftIsd} onChange={setGiftIsd} placeholder="e.g., +61" error={errors.giftIsd} />
|
||||||
onChange={setGiftFirstName}
|
<Field label={<>Recipient Phone <span className="text-red-500">*</span></>} value={giftPhone} onChange={setGiftPhone} type="tel" placeholder="Enter recipient's phone number" error={errors.giftPhone} />
|
||||||
placeholder="Enter recipient's first name"
|
<Field label={<>Recipient Email <span className="text-red-500">*</span></>} value={giftEmail} onChange={setGiftEmail} type="email" placeholder="Enter recipient's email" error={errors.giftEmail} />
|
||||||
error={errors.giftFirstName}
|
<Field label={<>Recipient City <span className="text-red-500">*</span></>} value={giftCity} onChange={setGiftCity} placeholder="Enter recipient's city" error={errors.giftCity} />
|
||||||
/>
|
<Field label={<>Recipient Country <span className="text-red-500">*</span></>} value={giftCountry} onChange={setGiftCountry} placeholder="Enter recipient's country" error={errors.giftCountry} />
|
||||||
<Field
|
<Field label={<>Gift Message <span className="text-red-500">*</span></>} value={giftMessage} onChange={setGiftMessage} placeholder="Write a heartfelt message" error={errors.giftMessage} />
|
||||||
label="Recipient Last Name"
|
|
||||||
value={giftLastName}
|
|
||||||
onChange={setGiftLastName}
|
|
||||||
placeholder="Enter recipient's last name"
|
|
||||||
error={errors.giftLastName}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
label="Recipient ISD Code"
|
|
||||||
value={giftIsd}
|
|
||||||
onChange={setGiftIsd}
|
|
||||||
placeholder="e.g., 61"
|
|
||||||
error={errors.giftIsd}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
label="Recipient Phone"
|
|
||||||
value={giftPhone}
|
|
||||||
onChange={setGiftPhone}
|
|
||||||
type="tel"
|
|
||||||
placeholder="Enter recipient's phone number"
|
|
||||||
error={errors.giftPhone}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
label="Recipient Email"
|
|
||||||
value={giftEmail}
|
|
||||||
onChange={setGiftEmail}
|
|
||||||
type="email"
|
|
||||||
placeholder="Enter recipient's email"
|
|
||||||
error={errors.giftEmail}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
label="Recipient City"
|
|
||||||
value={giftCity}
|
|
||||||
onChange={setGiftCity}
|
|
||||||
placeholder="Enter recipient's city"
|
|
||||||
error={errors.giftCity}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
label="Recipient Country"
|
|
||||||
value={giftCountry}
|
|
||||||
onChange={setGiftCountry}
|
|
||||||
placeholder="Enter recipient's country"
|
|
||||||
error={errors.giftCountry}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
label="Gift Message"
|
|
||||||
value={giftMessage}
|
|
||||||
onChange={setGiftMessage}
|
|
||||||
placeholder="Write a heartfelt message"
|
|
||||||
error={errors.giftMessage}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -589,8 +539,8 @@ const validate = () => {
|
|||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div
|
<div
|
||||||
className={`w-16 h-10 rounded-lg flex-shrink-0 flex items-center justify-center ${bookingDetails?.cardMode?.toLowerCase() === 'flexi'
|
className={`w-16 h-10 rounded-lg flex-shrink-0 flex items-center justify-center ${bookingDetails?.cardMode?.toLowerCase() === 'flexi'
|
||||||
? 'bg-gradient-to-br from-[#f95faf] to-[#F95F62]'
|
? 'bg-gradient-to-br from-[#f95faf] to-[#F95F62]'
|
||||||
: 'bg-gradient-to-br from-[#F95F62] to-[#c94245]'
|
: 'bg-gradient-to-br from-[#F95F62] to-[#c94245]'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span className="font-poppins text-[10px] font-semibold text-white">{bookingDetails?.cardMode}</span>
|
<span className="font-poppins text-[10px] font-semibold text-white">{bookingDetails?.cardMode}</span>
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import {
|
|||||||
Clock,
|
Clock,
|
||||||
Star,
|
Star,
|
||||||
Badge as BadgeIcon,
|
Badge as BadgeIcon,
|
||||||
Camera
|
Camera,
|
||||||
|
AlertCircle
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '../components/ui/button';
|
import { Button } from '../components/ui/button';
|
||||||
import { Input } from '../components/ui/input';
|
import { Input } from '../components/ui/input';
|
||||||
@@ -21,7 +22,6 @@ import { Label } from '../components/ui/label';
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/card';
|
||||||
import { Separator } from '../components/ui/separator';
|
import { Separator } from '../components/ui/separator';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../components/ui/tabs';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../components/ui/select';
|
|
||||||
import { Badge } from '../components/ui/badge';
|
import { Badge } from '../components/ui/badge';
|
||||||
import Navbar from '../components/Navbar';
|
import Navbar from '../components/Navbar';
|
||||||
import { Footer } from '../components/Footer';
|
import { Footer } from '../components/Footer';
|
||||||
@@ -88,6 +88,7 @@ export function ProfilePage({
|
|||||||
currentPage
|
currentPage
|
||||||
}: ProfilePageProps) {
|
}: ProfilePageProps) {
|
||||||
const [activeTab, setActiveTab] = useState('profile');
|
const [activeTab, setActiveTab] = useState('profile');
|
||||||
|
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
firstName: '',
|
firstName: '',
|
||||||
lastName: '',
|
lastName: '',
|
||||||
@@ -106,7 +107,7 @@ export function ProfilePage({
|
|||||||
const cityId = localStorage.getItem("cityId")
|
const cityId = localStorage.getItem("cityId")
|
||||||
const { data: userDetails, isLoading } = useGetUserProfileDetailsQuery(userId)
|
const { data: userDetails, isLoading } = useGetUserProfileDetailsQuery(userId)
|
||||||
const [updateUserProfileDetails, { isLoading: savingChanges }] = useUpdateUserProfileDetailsMutation();
|
const [updateUserProfileDetails, { isLoading: savingChanges }] = useUpdateUserProfileDetailsMutation();
|
||||||
const { data, isLoading: loadingCards } = useGetUserCardsQuery({sort,cityId})
|
const { data, isLoading: loadingCards } = useGetUserCardsQuery({ sort, cityId })
|
||||||
const { data: userItineraries, isLoading: loadingItineraries } = useGetUserItinerariesQuery(cityId)
|
const { data: userItineraries, isLoading: loadingItineraries } = useGetUserItinerariesQuery(cityId)
|
||||||
|
|
||||||
const cards = data ?? []
|
const cards = data ?? []
|
||||||
@@ -129,50 +130,90 @@ export function ProfilePage({
|
|||||||
|
|
||||||
}, [userDetails])
|
}, [userDetails])
|
||||||
|
|
||||||
|
// const validateForm = () => {
|
||||||
|
// if (!formData.firstName.trim()) return toast.error('First name is required'), false;
|
||||||
|
// if (/\s/.test(formData.firstName)) return toast.error('First name must not contain spaces'), false;
|
||||||
|
// if (!/^[A-Za-z]+$/.test(formData.firstName)) return toast.error('First name must contain only letters'), false;
|
||||||
|
|
||||||
|
// if (!formData.lastName.trim()) return toast.error('Last name is required'), false;
|
||||||
|
// if (/\s/.test(formData.lastName)) return toast.error('Last name must not contain spaces'), false;
|
||||||
|
// if (!/^[A-Za-z]+$/.test(formData.lastName)) return toast.error('Last name must contain only letters'), false;
|
||||||
|
|
||||||
|
// if (!formData.phone.trim()) return toast.error('Mobile number is required'), false;
|
||||||
|
// if (/\s/.test(formData.phone)) return toast.error('Mobile number must not contain spaces'), false;
|
||||||
|
// if (!/^\d+$/.test(formData.phone)) return toast.error('Mobile number must contain only digits'), false;
|
||||||
|
|
||||||
|
// if (!formData.address1.trim()) return toast.error('Address is required'), false;
|
||||||
|
// if (!/^[A-Za-z0-9\s,\-.]+$/.test(formData.address1)) return toast.error('Address contains invalid characters'), false;
|
||||||
|
|
||||||
|
// if (!formData.city.trim()) return toast.error('City is required'), false;
|
||||||
|
// if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.city)) return toast.error('City can only contain letters and spaces'), false;
|
||||||
|
// if (/\s{2,}/.test(formData.city)) return toast.error('City must not contain multiple consecutive spaces'), false;
|
||||||
|
|
||||||
|
// if (!formData.country.trim()) return toast.error('Country is required'), false;
|
||||||
|
// if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.country)) return toast.error('Country can only contain letters and spaces'), false;
|
||||||
|
|
||||||
|
// if (!formData.postalCode.trim()) return toast.error('Postal code is required'), false;
|
||||||
|
// if (/\s/.test(formData.postalCode)) return toast.error('Postal code must not contain spaces'), false;
|
||||||
|
// if (!/^[A-Za-z0-9]+$/.test(formData.postalCode)) return toast.error('Postal code must contain only letters and numbers'), false;
|
||||||
|
|
||||||
|
// return true;
|
||||||
|
// };
|
||||||
|
|
||||||
const validateForm = () => {
|
const validateForm = () => {
|
||||||
if (!formData.firstName.trim()) return toast.error('First name is required'), false;
|
const e: Record<string, string> = {};
|
||||||
if (/\s/.test(formData.firstName)) return toast.error('First name must not contain spaces'), false;
|
|
||||||
if (!/^[A-Za-z]+$/.test(formData.firstName)) return toast.error('First name must contain only letters'), false;
|
|
||||||
|
|
||||||
if (!formData.lastName.trim()) return toast.error('Last name is required'), false;
|
if (!formData.firstName.trim()) e.firstName = 'First name is required';
|
||||||
if (/\s/.test(formData.lastName)) return toast.error('Last name must not contain spaces'), false;
|
else if (/\s/.test(formData.firstName)) e.firstName = 'First name must not contain spaces';
|
||||||
if (!/^[A-Za-z]+$/.test(formData.lastName)) return toast.error('Last name must contain only letters'), false;
|
else if (!/^[A-Za-z]+$/.test(formData.firstName)) e.firstName = 'First name must contain only letters';
|
||||||
|
|
||||||
if (!formData.phone.trim()) return toast.error('Mobile number is required'), false;
|
if (!formData.lastName.trim()) e.lastName = 'Last name is required';
|
||||||
if (/\s/.test(formData.phone)) return toast.error('Mobile number must not contain spaces'), false;
|
else if (/\s/.test(formData.lastName)) e.lastName = 'Last name must not contain spaces';
|
||||||
if (!/^\d+$/.test(formData.phone)) return toast.error('Mobile number must contain only digits'), false;
|
else if (!/^[A-Za-z]+$/.test(formData.lastName)) e.lastName = 'Last name must contain only letters';
|
||||||
|
|
||||||
if (!formData.address1.trim()) return toast.error('Address is required'), false;
|
if (!formData.phone.trim()) e.phone = 'Mobile number is required';
|
||||||
if (!/^[A-Za-z0-9\s,\-.]+$/.test(formData.address1)) return toast.error('Address contains invalid characters'), false;
|
else if (/\s/.test(formData.phone)) e.phone = 'Mobile number must not contain spaces';
|
||||||
|
else if (!/^\d+$/.test(formData.phone)) e.phone = 'Mobile number must contain only digits';
|
||||||
|
|
||||||
if (!formData.city.trim()) return toast.error('City is required'), false;
|
if (!formData.address1.trim()) e.address1 = 'Address is required';
|
||||||
if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.city)) return toast.error('City can only contain letters and spaces'), false;
|
else if (!/^[A-Za-z0-9\s,\-.]+$/.test(formData.address1)) e.address1 = 'Address contains invalid characters';
|
||||||
if (/\s{2,}/.test(formData.city)) return toast.error('City must not contain multiple consecutive spaces'), false;
|
|
||||||
|
|
||||||
if (!formData.country.trim()) return toast.error('Country is required'), false;
|
if (!formData.city.trim()) e.city = 'City is required';
|
||||||
if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.country)) return toast.error('Country can only contain letters and spaces'), false;
|
else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.city)) e.city = 'City can only contain letters and spaces';
|
||||||
|
else if (/\s{2,}/.test(formData.city)) e.city = 'City must not contain multiple consecutive spaces';
|
||||||
|
|
||||||
if (!formData.postalCode.trim()) return toast.error('Postal code is required'), false;
|
if (!formData.country.trim()) e.country = 'Country is required';
|
||||||
if (/\s/.test(formData.postalCode)) return toast.error('Postal code must not contain spaces'), false;
|
else if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.country)) e.country = 'Country can only contain letters and spaces';
|
||||||
if (!/^[A-Za-z0-9]+$/.test(formData.postalCode)) return toast.error('Postal code must contain only letters and numbers'), false;
|
|
||||||
|
|
||||||
return true;
|
if (!formData.postalCode.trim()) e.postalCode = 'Postal code is required';
|
||||||
};
|
else if (/\s/.test(formData.postalCode)) e.postalCode = 'Postal code must not contain spaces';
|
||||||
|
else if (!/^[A-Za-z0-9]+$/.test(formData.postalCode)) e.postalCode = 'Postal code must contain only letters and numbers';
|
||||||
|
|
||||||
|
setFieldErrors(e);
|
||||||
|
return Object.keys(e).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// inside ProfilePage function body:
|
||||||
|
const FieldError = ({ name }: { name: string }) =>
|
||||||
|
fieldErrors[name] ? (
|
||||||
|
<p className="text-xs text-red-500 mt-1 flex items-center gap-1">
|
||||||
|
<AlertCircle className="w-3 h-3" />{fieldErrors[name]}
|
||||||
|
</p>
|
||||||
|
) : null;
|
||||||
|
|
||||||
const handleInputChange = (field: string, value: string) => {
|
const handleInputChange = (field: string, value: string) => {
|
||||||
setFormData(prev => ({ ...prev, [field]: value }));
|
setFormData(prev => ({ ...prev, [field]: value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveProfile = async () => {
|
const handleSaveProfile = async () => {
|
||||||
if (!validateForm()) return;
|
if (!validateForm()) return;
|
||||||
try {
|
try {
|
||||||
const response = await updateUserProfileDetails({ userDetails: formData, userId });
|
const response = await updateUserProfileDetails({ userDetails: formData, userId });
|
||||||
toast.success("Profile updated successfully!");
|
toast.success("Profile updated successfully!");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error("Failed to update profile. Please try again.");
|
toast.error("Failed to update profile. Please try again.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const activeCards = cards.filter((card: any) => card.isActive === true);
|
const activeCards = cards.filter((card: any) => card.isActive === true);
|
||||||
const expiredCards = cards.filter((card: any) => card.isActive === false);
|
const expiredCards = cards.filter((card: any) => card.isActive === false);
|
||||||
@@ -259,27 +300,35 @@ export function ProfilePage({
|
|||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="firstName" className="font-poppins font-light">First Name</Label>
|
<Label htmlFor="firstName" className="font-poppins font-light">
|
||||||
|
First Name <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="firstName"
|
id="firstName"
|
||||||
value={formData.firstName}
|
value={formData.firstName}
|
||||||
onChange={(e) => handleInputChange('firstName', e.target.value)}
|
onChange={(e) => handleInputChange('firstName', e.target.value)}
|
||||||
className="mt-1 font-poppins font-light"
|
className={`mt-1 font-poppins font-light ${fieldErrors.firstName ? 'border-red-400' : ''}`}
|
||||||
/>
|
/>
|
||||||
|
<FieldError name="firstName" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="lastName" className="font-poppins font-light">Last Name</Label>
|
<Label htmlFor="lastName" className="font-poppins font-light">
|
||||||
|
Last Name <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="lastName"
|
id="lastName"
|
||||||
value={formData.lastName}
|
value={formData.lastName}
|
||||||
onChange={(e) => handleInputChange('lastName', e.target.value)}
|
onChange={(e) => handleInputChange('lastName', e.target.value)}
|
||||||
className="mt-1 font-poppins font-light"
|
className={`mt-1 font-poppins font-light ${fieldErrors.lastName ? 'border-red-400' : ''}`}
|
||||||
/>
|
/>
|
||||||
|
<FieldError name="lastName" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="email" className="font-poppins font-light">Email Address</Label>
|
<Label htmlFor="email" className="font-poppins font-light">
|
||||||
|
Email Address
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="email"
|
id="email"
|
||||||
type="email"
|
type="email"
|
||||||
@@ -291,14 +340,17 @@ export function ProfilePage({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="phone" className="font-poppins font-light">Phone Number</Label>
|
<Label htmlFor="phone" className="font-poppins font-light">
|
||||||
|
Phone Number <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="phone"
|
id="phone"
|
||||||
type="tel"
|
type="tel"
|
||||||
value={formData.phone}
|
value={formData.phone}
|
||||||
onChange={(e) => handleInputChange('phone', e.target.value)}
|
onChange={(e) => handleInputChange('phone', e.target.value)}
|
||||||
className="mt-1 font-poppins font-light"
|
className={`mt-1 font-poppins font-light ${fieldErrors.phone ? 'border-red-400' : ''}`}
|
||||||
/>
|
/>
|
||||||
|
<FieldError name="phone" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
@@ -306,29 +358,31 @@ export function ProfilePage({
|
|||||||
<h3 className="font-poppins font-normal">Billing Address</h3>
|
<h3 className="font-poppins font-normal">Billing Address</h3>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="country" className="font-poppins font-light">Country</Label>
|
<Label htmlFor="country" className="font-poppins font-light">
|
||||||
|
Country <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="country"
|
id="country"
|
||||||
value={formData.country}
|
value={formData.country}
|
||||||
onChange={(e) => handleInputChange('country', e.target.value)}
|
onChange={(e) => handleInputChange('country', e.target.value)}
|
||||||
className="mt-1 font-poppins font-light"
|
className={`mt-1 font-poppins font-light ${fieldErrors.country ? 'border-red-400' : ''}`}
|
||||||
/>
|
/>
|
||||||
|
<FieldError name="country" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="address1" className="font-poppins font-light">
|
<Label htmlFor="address1" className="font-poppins font-light">
|
||||||
Address Line 1
|
Address Line 1 <span className="text-red-500">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="address1"
|
id="address1"
|
||||||
value={formData.address1}
|
value={formData.address1}
|
||||||
onChange={(e) => handleInputChange('address1', e.target.value)}
|
onChange={(e) => handleInputChange('address1', e.target.value)}
|
||||||
className="mt-1 font-poppins font-light mb-4"
|
className={`mt-1 font-poppins font-light mb-4 ${fieldErrors.address1 ? 'border-red-400' : ''}`}
|
||||||
/>
|
/>
|
||||||
|
<FieldError name="address1" />
|
||||||
|
|
||||||
<Label htmlFor="address2" className="font-poppins font-light">
|
<Label htmlFor="address2" className="font-poppins font-light">Address Line 2</Label>
|
||||||
Address Line 2
|
|
||||||
</Label>
|
|
||||||
<Input
|
<Input
|
||||||
id="address2"
|
id="address2"
|
||||||
value={formData.address2}
|
value={formData.address2}
|
||||||
@@ -339,22 +393,28 @@ export function ProfilePage({
|
|||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="city" className="font-poppins font-light">City</Label>
|
<Label htmlFor="city" className="font-poppins font-light">
|
||||||
|
City <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="city"
|
id="city"
|
||||||
value={formData.city}
|
value={formData.city}
|
||||||
onChange={(e) => handleInputChange('city', e.target.value)}
|
onChange={(e) => handleInputChange('city', e.target.value)}
|
||||||
className="mt-1 font-poppins font-light"
|
className={`mt-1 font-poppins font-light ${fieldErrors.city ? 'border-red-400' : ''}`}
|
||||||
/>
|
/>
|
||||||
|
<FieldError name="city" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="postalCode" className="font-poppins font-light">Postal Code</Label>
|
<Label htmlFor="postalCode" className="font-poppins font-light">
|
||||||
|
Postal Code <span className="text-red-500">*</span>
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="postalCode"
|
id="postalCode"
|
||||||
value={formData.postalCode}
|
value={formData.postalCode}
|
||||||
onChange={(e) => handleInputChange('postalCode', e.target.value)}
|
onChange={(e) => handleInputChange('postalCode', e.target.value)}
|
||||||
className="mt-1 font-poppins font-light"
|
className={`mt-1 font-poppins font-light ${fieldErrors.postalCode ? 'border-red-400' : ''}`}
|
||||||
/>
|
/>
|
||||||
|
<FieldError name="postalCode" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -569,7 +629,7 @@ export function ProfilePage({
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="w-full mt-4 font-poppins font-normal"
|
className="w-full mt-4 font-poppins font-normal"
|
||||||
onClick={()=>navigate(`/view-itinerary/${itinerary.id}`)}
|
onClick={() => navigate(`/view-itinerary/${itinerary.id}`)}
|
||||||
>
|
>
|
||||||
View Itinerary
|
View Itinerary
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user