Files
CityCards-Website/src/components/FAQPage.tsx
priyanshuvish 97969c079b new src added
2025-10-09 19:03:24 +05:30

511 lines
20 KiB
TypeScript

import { useState, useMemo } from 'react';
import { motion, AnimatePresence } from 'motion/react';
import { Search, ChevronDown, ChevronUp } from 'lucide-react';
import { Input } from './ui/input';
import { Badge } from './ui/badge';
import { Card, CardContent } from './ui/card';
import Navbar from './Navbar';
import { CitySubmenu } from './CitySubmenu';
import { Footer } from './Footer';
import { MobileAppSection } from './MobileAppSection';
import { WhyChooseCityCards } from './WhyChooseCityCards';
import { EnhancedTestimonials } from './EnhancedTestimonials';
import { ReviewsSection } from './ReviewsSection';
interface User {
email: string;
name: string;
}
interface FAQPageProps {
onBackClick: () => void;
onHomeClick: () => void;
onMelbourneClick: () => void;
onPassesClick: () => void;
onCheckoutClick: () => void;
onSignInClick: () => void;
onSignOutClick: () => void;
onAttractionsClick: () => void;
onBlogsClick: () => void;
onHowItWorksClick: () => void;
onFAQClick: () => void;
onPrivacyPolicyClick: () => void;
onAboutUsClick: () => void;
onProfileClick: () => void;
onCityCardsClick: () => void;
onMagicItineraryClick: () => void;
onPostCardsClick: () => void;
onOffersClick: () => void;
onContactUsClick?: () => void;
onEsimsClick?: () => void;
onHotelDiscountsClick?: () => void;
currentPage: string;
user: User | null;
}
interface FAQItem {
id: string;
question: string;
answer: string;
category: string;
}
const categories = [
'General',
'Bookings',
'Passes',
'Mobile App',
'Payment',
'Cancellation',
'Support'
];
const faqData: FAQItem[] = [
{
id: '1',
question: 'How can I book my CityCards pass?',
answer: 'You can easily book your CityCards pass through our website or mobile app. Simply select your preferred city, choose between our Selective or Unlimited pass options, and complete your purchase with instant digital delivery. Your pass will be available immediately for use.',
category: 'Bookings'
},
{
id: '2',
question: 'What\'s the difference between Selective and Unlimited passes?',
answer: 'Our Selective pass allows you to choose specific attractions you want to visit, perfect for targeted exploration. The Unlimited pass gives you access to all participating attractions in the city during your pass validity period, ideal for comprehensive city discovery.',
category: 'Passes'
},
{
id: '3',
question: 'How do I use my digital pass at attractions?',
answer: 'Simply show your digital pass on your mobile device at the attraction entrance. Each pass includes a unique QR code that attraction staff will scan for quick and easy entry. No printed tickets required - it\'s all digital and hassle-free.',
category: 'Mobile App'
},
{
id: '4',
question: 'Can I cancel or refund my CityCards pass?',
answer: 'Yes, we offer flexible cancellation policies. Unused passes can be cancelled within 24 hours of purchase for a full refund. For passes used partially, refund eligibility depends on the specific terms of your pass type and usage.',
category: 'Cancellation'
},
{
id: '5',
question: 'Which cities are currently available?',
answer: 'We currently offer comprehensive CityCards experiences in Melbourne, with more exciting destinations coming soon. Each city features carefully curated attractions, local experiences, and insider recommendations to help you make the most of your visit.',
category: 'General'
},
{
id: '6',
question: 'What payment methods do you accept?',
answer: 'We accept all major credit cards (Visa, Mastercard, American Express), PayPal, Apple Pay, and Google Pay. All transactions are secured with industry-standard encryption to protect your payment information.',
category: 'Payment'
},
{
id: '7',
question: 'How long are CityCards passes valid?',
answer: 'Pass validity varies by type and city. Most passes are valid for 30 days from the date of first use, giving you plenty of flexibility to explore at your own pace. Check your specific pass details for exact validity periods.',
category: 'Passes'
},
{
id: '8',
question: 'Do I need an internet connection to use my pass?',
answer: 'While an internet connection is recommended for the best experience, our mobile app includes offline functionality. You can download your pass and attraction information for offline use, ensuring access even without internet connectivity.',
category: 'Mobile App'
},
{
id: '9',
question: 'Can I share my pass with friends or family?',
answer: 'CityCards passes are non-transferable and designed for individual use only. Each pass is linked to the purchaser\'s account and cannot be shared. For group visits, each person needs their own pass.',
category: 'General'
},
{
id: '10',
question: 'What if I have issues with my booking?',
answer: 'Our customer support team is available 24/7 to help with any booking issues or questions. Contact us through the app, website chat, email, or phone, and we\'ll resolve your concern quickly and efficiently.',
category: 'Support'
},
{
id: '11',
question: 'Are there any additional fees or hidden costs?',
answer: 'No hidden fees! The price you see is the price you pay. Your CityCards pass includes all listed attractions and experiences with no additional booking fees or surprise charges at the attractions.',
category: 'Payment'
},
{
id: '12',
question: 'How do I get customer support while traveling?',
answer: 'Access 24/7 customer support directly through our mobile app, website chat, or by calling our support hotline. Our team is always ready to assist with any questions or issues during your city exploration.',
category: 'Support'
}
];
export function FAQPage({
onHomeClick,
onMelbourneClick,
onPassesClick,
onCheckoutClick,
onSignInClick,
onSignOutClick,
onAttractionsClick,
onBlogsClick,
onHowItWorksClick,
onFAQClick,
onPrivacyPolicyClick,
onAboutUsClick,
onProfileClick,
onCityCardsClick,
onMagicItineraryClick,
onPostCardsClick,
onOffersClick,
onContactUsClick,
onEsimsClick,
onHotelDiscountsClick,
currentPage,
user
}: FAQPageProps) {
const [searchQuery, setSearchQuery] = useState('');
const [selectedCategory, setSelectedCategory] = useState('All');
const [expandedFAQs, setExpandedFAQs] = useState<Set<string>>(new Set());
const toggleFAQ = (id: string) => {
const newExpanded = new Set(expandedFAQs);
if (newExpanded.has(id)) {
newExpanded.delete(id);
} else {
newExpanded.add(id);
}
setExpandedFAQs(newExpanded);
};
const filteredFAQs = useMemo(() => {
return faqData.filter(faq => {
const matchesSearch = faq.question.toLowerCase().includes(searchQuery.toLowerCase()) ||
faq.answer.toLowerCase().includes(searchQuery.toLowerCase());
const matchesCategory = selectedCategory === 'All' || faq.category === selectedCategory;
return matchesSearch && matchesCategory;
});
}, [searchQuery, selectedCategory]);
// Group FAQs into pairs for 2-column layout
const faqPairs = [];
for (let i = 0; i < filteredFAQs.length; i += 2) {
faqPairs.push(filteredFAQs.slice(i, i + 2));
}
return (
<div className="min-h-screen bg-background">
{/* Navigation */}
<Navbar
activeCity="Melbourne"
onCityChange={() => {}}
onHomeClick={onHomeClick}
onSignInClick={onSignInClick}
onPassesClick={onPassesClick}
onCheckoutClick={onCheckoutClick}
onAttractionsClick={onAttractionsClick}
onBlogsClick={onBlogsClick}
onHowItWorksClick={onHowItWorksClick}
onFAQClick={onFAQClick}
onPrivacyPolicyClick={onPrivacyPolicyClick}
onAboutUsClick={onAboutUsClick}
onProfileClick={onProfileClick}
onCityCardsClick={onCityCardsClick}
onMagicItineraryClick={onMagicItineraryClick}
onPostCardsClick={onPostCardsClick}
onOffersClick={onOffersClick}
currentPage={currentPage}
isUserSignedIn={!!user}
user={user}
/>
<CitySubmenu
currentPage={currentPage}
onClose={() => {}}
onHomeClick={onHomeClick}
onMelbourneClick={onMelbourneClick}
onAttractionsClick={onAttractionsClick}
onPassesClick={onPassesClick}
onBlogsClick={onBlogsClick}
onHowItWorksClick={onHowItWorksClick}
/>
<div className="container mx-auto px-4 pt-52 pb-12">
{/* Page Header */}
<motion.div
className="text-center mb-12"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
>
<motion.div
className="text-sm tracking-wider text-primary mb-4"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.4, delay: 0.2 }}
>
GET STARTED NOW
</motion.div>
<h1 className="font-merchant font-light text-4xl md:text-5xl lg:text-6xl text-gray-900 mb-6">
Frequently Asked{' '}
<span className="font-bold italic bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
Questions
</span>
</h1>
<p className="font-poppins text-xl leading-relaxed font-normal text-gray-600 max-w-3xl mx-auto">
Find answers to the most common questions about CityCards, bookings, and your city exploration experience
</p>
</motion.div>
{/* Search and Filter Section */}
<motion.div
className="mb-12 max-w-4xl mx-auto"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 }}
>
{/* Search Bar */}
<div className="relative mb-8">
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<Input
type="text"
placeholder="Search for questions..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-12 pr-4 py-6 text-lg bg-white border-2 border-gray-200 rounded-2xl focus:border-primary focus:ring-4 focus:ring-primary/10 transition-all duration-200"
/>
</div>
{/* Category Tags */}
<div className="flex flex-wrap gap-3 justify-center">
<Badge
variant={selectedCategory === 'All' ? 'default' : 'secondary'}
className={`px-6 py-3 text-sm cursor-pointer transition-all duration-200 hover:scale-105 ${
selectedCategory === 'All'
? 'bg-primary text-white shadow-lg'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
onClick={() => setSelectedCategory('All')}
>
All
</Badge>
{categories.map((category) => (
<Badge
key={category}
variant={selectedCategory === category ? 'default' : 'secondary'}
className={`px-6 py-3 text-sm cursor-pointer transition-all duration-200 hover:scale-105 ${
selectedCategory === category
? 'bg-primary text-white shadow-lg'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
onClick={() => setSelectedCategory(category)}
>
{category}
</Badge>
))}
</div>
</motion.div>
{/* FAQ Content */}
<div className="w-full">
{faqPairs.length === 0 ? (
<motion.div
className="text-center py-12"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.4 }}
>
<p className="font-poppins text-xl leading-relaxed font-normal text-gray-500">No questions found matching your search.</p>
</motion.div>
) : (
<div className="space-y-8">
{faqPairs.map((pair, pairIndex) => (
<motion.div
key={pairIndex}
className="grid grid-cols-1 lg:grid-cols-2 gap-8"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: pairIndex * 0.1 }}
>
{pair.map((faq) => {
const isExpanded = expandedFAQs.has(faq.id);
return (
<div
key={faq.id}
className="faq-card bg-white rounded-2xl shadow-sm hover:shadow-md transition-all duration-300 relative overflow-hidden"
>
<button
onClick={() => toggleFAQ(faq.id)}
className="w-full p-8 text-left hover:bg-gray-100/50 transition-colors duration-200 focus:outline-none focus:ring-4 focus:ring-primary/10"
>
<div className="flex items-start gap-4 mb-4">
<span className="w-10 h-10 bg-primary text-white rounded-full flex items-center justify-center font-bold flex-shrink-0">
{faq.id}
</span>
<div className="flex-1">
<h3 className="font-merchant font-medium text-xl text-gray-900 leading-tight mb-2">
{faq.question}
</h3>
<div className="flex items-center justify-between">
<Badge variant="secondary" className="text-xs px-3 py-1 bg-gray-100 text-gray-600">
{faq.category}
</Badge>
<div className="flex-shrink-0">
{isExpanded ? (
<ChevronUp className="w-5 h-5 text-primary" />
) : (
<ChevronDown className="w-5 h-5 text-gray-400" />
)}
</div>
</div>
</div>
</div>
</button>
{/* Answer Content */}
<AnimatePresence>
{isExpanded && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.3, ease: [0.25, 0.1, 0.25, 1] }}
className="overflow-hidden"
>
<div className="px-8 pb-8">
<div className="border-t border-gray-200 pt-6">
<p className="text-gray-600 leading-relaxed">
{faq.answer}
</p>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
})}
{/* If odd number of FAQs, add empty space for the last row */}
{pair.length === 1 && (
<div className="hidden lg:block" />
)}
</motion.div>
))}
</div>
)}
</div>
{/* Pagination Section */}
<motion.div
className="mt-16 flex flex-col items-center gap-6"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.4 }}
>
<div className="text-center text-muted-foreground">
Showing 1-12 of 24 item(s)
</div>
<button className="bg-gradient-to-r from-primary to-secondary text-white px-12 py-4 rounded-full hover:shadow-lg hover:shadow-primary/25 transition-all duration-300 font-medium text-base hover:-translate-y-0.5">
Load More
</button>
</motion.div>
{/* Mobile App Section */}
<motion.div
className="mt-20"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.5 }}
>
<div className="bg-muted/30 relative overflow-hidden rounded-3xl">
{/* Subtle Background Elements */}
<div className="absolute inset-0">
<div className="absolute top-1/3 left-1/6 w-64 h-64 bg-primary/3 rounded-full blur-3xl"></div>
<div className="absolute bottom-1/2 right-1/6 w-48 h-48 bg-secondary/3 rounded-full blur-3xl"></div>
</div>
<MobileAppSection />
</div>
</motion.div>
{/* Why Choose Us Section */}
<motion.div
className="mt-20"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.6 }}
>
<WhyChooseCityCards />
</motion.div>
{/* Enhanced Testimonials Section */}
<motion.div
className="mt-20"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.65 }}
>
<EnhancedTestimonials />
</motion.div>
{/* Customer Reviews Section */}
<motion.div
className="mt-20"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.7 }}
>
<ReviewsSection />
</motion.div>
{/* Contact Support Section */}
<motion.div
className="mt-20 text-center"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.6 }}
>
<Card className="max-w-2xl mx-auto bg-gradient-to-br from-primary/5 to-secondary/5 border-2 border-primary/20 rounded-3xl p-8">
<CardContent className="p-0">
<h3 className="font-merchant font-medium text-2xl text-gray-900 mb-4">
Still have questions?
</h3>
<p className="text-gray-600 mb-6 leading-relaxed">
Our support team is available 24/7 to help you with any questions or concerns about your CityCards experience.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<a
href="mailto:support@citycards.com"
className="px-8 py-3 bg-primary text-white rounded-xl hover:bg-primary/90 transition-colors duration-200 font-medium"
>
Email Support
</a>
<a
href="tel:+1-800-CITYCARD"
className="px-8 py-3 bg-white text-primary border-2 border-primary rounded-xl hover:bg-primary/5 transition-colors duration-200 font-medium"
>
Call Us
</a>
</div>
</CardContent>
</Card>
</motion.div>
</div>
<Footer
onHomeClick={onHomeClick}
onMelbourneClick={onMelbourneClick}
onPassesClick={onPassesClick}
onSignInClick={onSignInClick}
onAttractionsClick={onAttractionsClick}
onBlogsClick={onBlogsClick}
onHowItWorksClick={onHowItWorksClick}
onFAQClick={onFAQClick}
onPrivacyPolicyClick={onPrivacyPolicyClick}
onContactUsClick={onContactUsClick}
currentPage={currentPage}
/>
</div>
);
}
export default FAQPage;