Merge pull request 'arya-branch' (#32) from arya-branch into main
Reviewed-on: #32
This commit is contained in:
@@ -94,10 +94,8 @@ export default function Navbar({
|
|||||||
|
|
||||||
const baseUrl = import.meta.env.VITE_BASE_URL;
|
const baseUrl = import.meta.env.VITE_BASE_URL;
|
||||||
|
|
||||||
const protectedPaths = ["/passes", "/whats-included", "/", "/melbourne"];
|
|
||||||
|
|
||||||
const handleOpenLoginModal = () => {
|
const handleOpenLoginModal = () => {
|
||||||
if (!user && protectedPaths.includes(location.pathname)) {
|
if (!user) {
|
||||||
setLoginOpen(true);
|
setLoginOpen(true);
|
||||||
}
|
}
|
||||||
else if (!user) {
|
else if (!user) {
|
||||||
@@ -613,27 +611,7 @@ export default function Navbar({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Shopping Cart */}
|
<ShoppingBag className="w-6 h-6 cursor-pointer" onClick={() => navigate("/cart")} />
|
||||||
{/* <Dropdown
|
|
||||||
ref={cartRef}
|
|
||||||
isOpen={activeCartDropdown}
|
|
||||||
onToggle={() => setActiveCartDropdown(prev => !prev)}
|
|
||||||
items={cartDropdownItems}
|
|
||||||
title="Shopping Cart"
|
|
||||||
trigger={
|
|
||||||
<div className="relative text-gray-700 hover:text-gray-900 transition-colors duration-200 rounded-lg hover:bg-gray-50/50 cursor-pointer p-2">
|
|
||||||
<ShoppingBag className="w-6 h-6" />
|
|
||||||
<motion.div
|
|
||||||
className="absolute -top-1 -right-1 w-5 h-5 bg-primary rounded-full flex items-center justify-center"
|
|
||||||
animate={{ scale: [1, 1.1, 1] }}
|
|
||||||
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
|
|
||||||
>
|
|
||||||
<span className="text-xs text-primary-foreground font-bold">{cartItems.length}</span>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/> */}
|
|
||||||
<ShoppingBag className="w-6 h-6" onClick={() => navigate("/cart")} />
|
|
||||||
|
|
||||||
{/* Enhanced City Card Button with Source Tracking */}
|
{/* Enhanced City Card Button with Source Tracking */}
|
||||||
<div className="flex items-center gap-3 pl-2">
|
<div className="flex items-center gap-3 pl-2">
|
||||||
|
|||||||
@@ -46,54 +46,62 @@ 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 (!/^[A-Za-z]+$/.test(formData.firstName)) return toast.error('First name must contain only alphabets'), false;
|
if (/\s/.test(formData.firstName)) return toast.error('First name must not contain spaces'), false;
|
||||||
if (formData.firstName.length < 2 || formData.firstName.length > 50) return toast.error('First name must be between 2 and 50 characters'), 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;
|
||||||
|
|
||||||
// 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 (!/^[A-Za-z]+$/.test(formData.lastName)) return toast.error('Last name must contain only alphabets'), false;
|
if (/\s/.test(formData.lastName)) return toast.error('Last name must not contain spaces'), false;
|
||||||
if (formData.lastName.length < 2 || formData.lastName.length > 50) return toast.error('Last name must be between 2 and 50 characters'), 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;
|
||||||
|
|
||||||
// Email
|
// Email
|
||||||
if (!formData.emailAddress.includes('@')) return toast.error('Invalid email address'), 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;
|
||||||
|
|
||||||
// ISD Code
|
// ISD
|
||||||
if (!formData.isdCode.startsWith("+")) return toast.error("ISD code must start with '+'"), false;
|
if (!formData.isdCode.trim()) return toast.error('ISD code is required'), false;
|
||||||
if (!/^\+\d+$/.test(formData.isdCode)) return toast.error("ISD code must contain only numbers after '+'"), 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 (!/^\+\d+$/.test(formData.isdCode)) return toast.error("ISD code must contain only digits after '+'"), false;
|
||||||
|
|
||||||
// Mobile Number
|
// Phone
|
||||||
if (!/^\d+$/.test(formData.mobileNumber)) return toast.error('Invalid mobile number'), false;
|
if (!formData.mobileNumber.trim()) return toast.error('Mobile number is required'), false;
|
||||||
if (formData.mobileNumber.length < 7 || formData.mobileNumber.length > 15) return toast.error('Mobile number must be between 7 and 15 digits'), 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 (formData.mobileNumber.length < 7 || formData.mobileNumber.length > 15) return toast.error('Mobile number must be between 7 and 15 digits'), false;
|
||||||
|
|
||||||
// Address Line 1
|
// Address
|
||||||
if (!formData.address1.trim()) return toast.error('Address 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 must be alphanumeric'), 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 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 must contain only alphabets'), false;
|
if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.city)) return toast.error('City can only contain letters and spaces'), false;
|
||||||
if (formData.city.length < 2 || formData.city.length > 50) return toast.error('City must be between 2 and 50 characters'), 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 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 must contain only alphabets'), false;
|
if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.state)) return toast.error('State can only contain letters and spaces'), false;
|
||||||
if (formData.state.length < 2 || formData.state.length > 50) return toast.error('State must be between 2 and 50 characters'), 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 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 must contain only alphabets'), false;
|
if (!/^[A-Za-z\s\-'À-ÿ]+$/.test(formData.country)) return toast.error('Country can only contain letters and spaces'), false;
|
||||||
if (formData.country.length < 2 || formData.country.length > 50) return toast.error('Country must be between 2 and 50 characters'), false;
|
|
||||||
|
|
||||||
// Postal Code
|
// Postal Code
|
||||||
if (!/^\d+$/.test(formData.postalCode)) return toast.error('Postal code should only contain numbers'), false;
|
if (!formData.postalCode.trim()) return toast.error('Postal code is required'), false;
|
||||||
if (formData.postalCode.length < 4 || formData.postalCode.length > 10) return toast.error('Postal code must be between 4 and 10 digits'), 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 (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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ export function TrustSection() {
|
|||||||
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%),
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ export function MagicItineraryPage({
|
|||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="font-poppins text-sm font-normal text-gray-600 leading-relaxed">
|
<p className="font-poppins text-sm font-normal text-gray-600 leading-relaxed">
|
||||||
A perfectly planned Melbourne adventure
|
A perfectly planned {cityName} adventure
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ export function PassesPage({
|
|||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const cityId = localStorage.getItem("cityId")
|
const cityId = localStorage.getItem("cityId")
|
||||||
|
const cityName = localStorage.getItem("cityName")
|
||||||
|
|
||||||
const { data: cityDetails, isLoading: loadingCityDetails } = useGetSelectedCityDetailsQuery(cityId)
|
const { data: cityDetails, isLoading: loadingCityDetails } = useGetSelectedCityDetailsQuery(cityId)
|
||||||
const cards = cityDetails?.city?.cards ?? []
|
const cards = cityDetails?.city?.cards ?? []
|
||||||
@@ -597,7 +598,7 @@ export function PassesPage({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-white font-semibold text-lg">CityCards</h3>
|
<h3 className="text-white font-semibold text-lg">CityCards</h3>
|
||||||
<p className="text-white/70 text-sm">Melbourne Explorer</p>
|
<p className="text-white/70 text-sm">{cityName} Explorer</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -770,7 +771,7 @@ export function PassesPage({
|
|||||||
<h3 className="heading-dynamic text-4xl mb-4">
|
<h3 className="heading-dynamic text-4xl mb-4">
|
||||||
<span className="font-light">Ready to</span>{' '}
|
<span className="font-light">Ready to</span>{' '}
|
||||||
<span className="font-bold italic text-emphasis">explore</span>{' '}
|
<span className="font-bold italic text-emphasis">explore</span>{' '}
|
||||||
<span className="font-semibold">Melbourne?</span>
|
<span className="font-semibold">{cityName}?</span>
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xl mb-8 max-w-2xl mx-auto opacity-90 font-light">
|
<p className="text-xl mb-8 max-w-2xl mx-auto opacity-90 font-light">
|
||||||
Choose your pass and start discovering amazing attractions with skip-the-line access.
|
Choose your pass and start discovering amazing attractions with skip-the-line access.
|
||||||
|
|||||||
@@ -230,25 +230,57 @@ 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 (!giftFirstName.trim()) e.giftFirstName = 'Required';
|
|
||||||
if (!giftLastName.trim()) e.giftLastName = 'Required';
|
|
||||||
if (!giftIsd.trim()) e.giftIsd = 'Required';
|
|
||||||
if (!giftMessage.trim()) e.giftMessage = 'Required';
|
|
||||||
if (!giftEmail.trim() || !/\S+@\S+\.\S+/.test(giftEmail)) {
|
|
||||||
e.giftEmail = 'Valid email required';
|
|
||||||
}
|
|
||||||
if (!giftPhone.trim() || !/^\+?[0-9]{7,15}$/.test(giftPhone)) {
|
|
||||||
e.giftPhone = 'Valid phone required';
|
|
||||||
}
|
|
||||||
if (!giftCity.trim()) e.giftCity = 'Required';
|
|
||||||
if (!giftCountry.trim()) e.giftCountry = 'Required';
|
|
||||||
}
|
|
||||||
return e;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
if (selectedTab === 'gift') {
|
||||||
|
// First Name
|
||||||
|
if (!giftFirstName.trim()) e.giftFirstName = 'First name is required';
|
||||||
|
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 (giftFirstName.length < 2 || giftFirstName.length > 50) e.giftFirstName = 'First name must be between 2 and 50 characters';
|
||||||
|
|
||||||
|
// Last Name
|
||||||
|
if (!giftLastName.trim()) e.giftLastName = 'Last name is required';
|
||||||
|
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 (giftLastName.length < 2 || giftLastName.length > 50) e.giftLastName = 'Last name must be between 2 and 50 characters';
|
||||||
|
|
||||||
|
// ISD Code
|
||||||
|
if (!giftIsd.trim()) e.giftIsd = 'ISD code is required';
|
||||||
|
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 (!/^\+\d+$/.test(giftIsd)) e.giftIsd = "ISD code must contain only digits after '+'";
|
||||||
|
|
||||||
|
// Email
|
||||||
|
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)';
|
||||||
|
|
||||||
|
// Phone
|
||||||
|
if (!giftPhone.trim()) e.giftPhone = 'Phone number is required';
|
||||||
|
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 (giftPhone.length < 7 || giftPhone.length > 15) e.giftPhone = 'Phone number must be between 7 and 15 digits';
|
||||||
|
|
||||||
|
// Message
|
||||||
|
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 > 500) e.giftMessage = 'Message must not exceed 500 characters';
|
||||||
|
|
||||||
|
// City
|
||||||
|
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 (/\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';
|
||||||
|
|
||||||
|
// Country
|
||||||
|
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 (giftCountry.length < 2 || giftCountry.length > 50) e.giftCountry = 'Country must be between 2 and 50 characters';
|
||||||
|
}
|
||||||
|
|
||||||
|
return e;
|
||||||
|
};
|
||||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||||
|
|
||||||
const handlePayment = async () => {
|
const handlePayment = async () => {
|
||||||
|
|||||||
@@ -129,22 +129,50 @@ 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 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 () => {
|
||||||
try {
|
if (!validateForm()) return;
|
||||||
console.log("Saving profile...", formData);
|
try {
|
||||||
const response = await updateUserProfileDetails({ userDetails: formData, userId });
|
const response = await updateUserProfileDetails({ userDetails: formData, userId });
|
||||||
console.log(response)
|
toast.success("Profile updated successfully!");
|
||||||
toast.success("Profile updated successfully!");
|
} catch (error) {
|
||||||
} catch (error) {
|
toast.error("Failed to update profile. Please try again.");
|
||||||
console.error("Error saving profile:", error);
|
}
|
||||||
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);
|
||||||
@@ -256,6 +284,7 @@ export function ProfilePage({
|
|||||||
id="email"
|
id="email"
|
||||||
type="email"
|
type="email"
|
||||||
value={formData.email}
|
value={formData.email}
|
||||||
|
disabled
|
||||||
onChange={(e) => handleInputChange('email', e.target.value)}
|
onChange={(e) => handleInputChange('email', e.target.value)}
|
||||||
className="mt-1 font-poppins font-light"
|
className="mt-1 font-poppins font-light"
|
||||||
/>
|
/>
|
||||||
@@ -340,192 +369,6 @@ export function ProfilePage({
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* App Download Section */}
|
|
||||||
<div className="lg:col-span-1">
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.5, delay: 0.4 }}
|
|
||||||
>
|
|
||||||
<Card className="border border-gray-200 shadow-sm">
|
|
||||||
<CardContent className="p-8 space-y-6">
|
|
||||||
{(() => {
|
|
||||||
// Determine which pass type to show
|
|
||||||
const hasUnlimitedPass = activeCards.some((card: any) => card.cardType.cardTypeName === 'selective_pass');
|
|
||||||
const hasSelectivePass = activeCards.some((card: any) => card.cardType.cardTypeName === 'unlimited_card');
|
|
||||||
|
|
||||||
if (hasUnlimitedPass) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<h3 className="font-poppins text-xl font-normal">
|
|
||||||
Get{' '}
|
|
||||||
<span className="text-primary">Melbourne Unlimited Card</span>
|
|
||||||
</h3>
|
|
||||||
<p className="text-sm text-gray-600 font-poppins leading-relaxed font-light">
|
|
||||||
Unlimited access to 25+ attractions. Visit as many places as you want with one simple card.
|
|
||||||
Save up to 40% compared to individual tickets.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Card Benefits */}
|
|
||||||
<div className="bg-gradient-to-br from-primary/5 to-secondary/5 rounded-lg p-4 space-y-2">
|
|
||||||
<div className="flex items-center gap-2 text-sm font-poppins font-normal">
|
|
||||||
<div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
|
|
||||||
<CreditCard className="w-3 h-3 text-white" />
|
|
||||||
</div>
|
|
||||||
<span>Unlimited entries to all attractions</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 text-sm font-poppins font-normal">
|
|
||||||
<div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
|
|
||||||
<Calendar className="w-3 h-3 text-white" />
|
|
||||||
</div>
|
|
||||||
<span>Valid for 7 consecutive days</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 text-sm font-poppins font-normal">
|
|
||||||
<div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
|
|
||||||
<MapPin className="w-3 h-3 text-white" />
|
|
||||||
</div>
|
|
||||||
<span>Skip the queue at major venues</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Purchase CTA */}
|
|
||||||
<div className="space-y-3">
|
|
||||||
<Button
|
|
||||||
onClick={onPassesClick}
|
|
||||||
className="w-full bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-poppins font-medium h-12"
|
|
||||||
>
|
|
||||||
Purchase Unlimited Card
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={onCityCardsClick}
|
|
||||||
className="w-full font-poppins font-normal"
|
|
||||||
>
|
|
||||||
Learn More
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else if (hasSelectivePass) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<h3 className="font-poppins text-xl font-normal">
|
|
||||||
Get{' '}
|
|
||||||
<span className="text-primary">Selective Card</span>
|
|
||||||
{' '}now
|
|
||||||
</h3>
|
|
||||||
<p className="text-sm text-gray-600 font-poppins leading-relaxed font-light">
|
|
||||||
Choose your own adventure with 12 hand-picked attractions. Perfect for visitors
|
|
||||||
who want flexibility and value.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Card Benefits */}
|
|
||||||
<div className="bg-gradient-to-br from-primary/5 to-secondary/5 rounded-lg p-4 space-y-2">
|
|
||||||
<div className="flex items-center gap-2 text-sm font-poppins font-normal">
|
|
||||||
<div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
|
|
||||||
<CreditCard className="w-3 h-3 text-white" />
|
|
||||||
</div>
|
|
||||||
<span>Choose from 12 curated attractions</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 text-sm font-poppins font-normal">
|
|
||||||
<div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
|
|
||||||
<Calendar className="w-3 h-3 text-white" />
|
|
||||||
</div>
|
|
||||||
<span>Flexible 7-day validity period</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 text-sm font-poppins font-normal">
|
|
||||||
<div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center flex-shrink-0">
|
|
||||||
<Star className="w-3 h-3 text-white" />
|
|
||||||
</div>
|
|
||||||
<span>Save 40% on combined ticket price</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Purchase CTA */}
|
|
||||||
<div className="space-y-3">
|
|
||||||
<Button
|
|
||||||
onClick={onPassesClick}
|
|
||||||
className="w-full bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-poppins font-medium h-12"
|
|
||||||
>
|
|
||||||
Purchase Selective Card
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={onCityCardsClick}
|
|
||||||
className="w-full font-poppins font-normal"
|
|
||||||
>
|
|
||||||
View All Attractions
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<h3 className="font-poppins text-xl font-normal">
|
|
||||||
Get{' '}
|
|
||||||
<span className="text-primary">CityCards</span>
|
|
||||||
{' '}now
|
|
||||||
</h3>
|
|
||||||
<p className="text-sm text-gray-600 font-poppins leading-relaxed font-light">
|
|
||||||
Explore Melbourne's best attractions with our flexible card options.
|
|
||||||
Choose unlimited access or select your favorites.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Card Options */}
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="bg-gradient-to-br from-primary/5 to-secondary/5 rounded-lg p-4">
|
|
||||||
<div className="flex items-start justify-between mb-2">
|
|
||||||
<div>
|
|
||||||
<h4 className="font-poppins text-base font-medium mb-1">Unlimited Card</h4>
|
|
||||||
<p className="text-xs text-gray-600 font-poppins font-light">25+ attractions, unlimited visits</p>
|
|
||||||
</div>
|
|
||||||
<Badge className="bg-primary text-white">Popular</Badge>
|
|
||||||
</div>
|
|
||||||
<div className="text-2xl font-poppins font-semibold text-primary">$149</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-gray-50 rounded-lg p-4">
|
|
||||||
<div className="flex items-start justify-between mb-2">
|
|
||||||
<div>
|
|
||||||
<h4 className="font-poppins text-base font-medium mb-1">Selective Card</h4>
|
|
||||||
<p className="text-xs text-gray-600 font-poppins font-light">12 attractions of your choice</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-2xl font-poppins font-semibold text-primary">$89</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Purchase CTA */}
|
|
||||||
<div className="space-y-3">
|
|
||||||
<Button
|
|
||||||
onClick={onPassesClick}
|
|
||||||
className="w-full bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-poppins font-medium h-12"
|
|
||||||
>
|
|
||||||
Explore All Cards
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={onCityCardsClick}
|
|
||||||
className="w-full font-poppins font-normal"
|
|
||||||
>
|
|
||||||
Learn More
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ export function SuperSavingsDetailsPage({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Sidebar - Calendar and Booking (preserved, but you can add a real calendar if needed) */}
|
{/* Right Sidebar - Calendar and Booking (preserved, but you can add a real calendar if needed) */}
|
||||||
<div className="lg:col-span-1">
|
{/* <div className="lg:col-span-1">
|
||||||
<Card className="sticky top-32 p-6 bg-white border border-primary/20 shadow-xl rounded-2xl">
|
<Card className="sticky top-32 p-6 bg-white border border-primary/20 shadow-xl rounded-2xl">
|
||||||
<h3 className="text-2xl font-bold text-[#2d3134] mb-4">Book This Offer</h3>
|
<h3 className="text-2xl font-bold text-[#2d3134] mb-4">Book This Offer</h3>
|
||||||
<div className="space-y-4 mb-6">
|
<div className="space-y-4 mb-6">
|
||||||
@@ -379,7 +379,7 @@ export function SuperSavingsDetailsPage({
|
|||||||
Proceed to Checkout
|
Proceed to Checkout
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
Reference in New Issue
Block a user