integrate the super savings offer api
This commit is contained in:
@@ -88,7 +88,7 @@ export function AppRouter({
|
||||
} />
|
||||
|
||||
{/* Home Route */}
|
||||
<Route path="/:cityName/:cityId" element={
|
||||
<Route path="/:cityName" element={
|
||||
<motion.div key="home" {...pageTransition}>
|
||||
<MelbournePage {...commonNavHandlers} />
|
||||
</motion.div>
|
||||
@@ -106,7 +106,7 @@ export function AppRouter({
|
||||
} />
|
||||
|
||||
{/* Attractions Routes */}
|
||||
<Route path="/:cityName/:cityId/attractions" element={
|
||||
<Route path="/:cityName/attractions" element={
|
||||
<motion.div key="attractions" {...pageTransition}>
|
||||
<AttractionsPage {...commonNavHandlers} />
|
||||
</motion.div>
|
||||
@@ -265,7 +265,7 @@ export function AppRouter({
|
||||
</motion.div>
|
||||
} />
|
||||
|
||||
<Route path="/super-savings" element={
|
||||
<Route path="/:cityName/super-savings" element={
|
||||
<motion.div key="super-savings" {...pageTransition}>
|
||||
<SuperSavingsPage {...commonNavHandlers} />
|
||||
</motion.div>
|
||||
|
||||
@@ -20,15 +20,28 @@ export const citiesApi = createApi({
|
||||
}),
|
||||
|
||||
getUpcomingCities: builder.query({
|
||||
|
||||
query: (listType) => `/cities/list/all?listType=${listType}`,
|
||||
|
||||
}),
|
||||
|
||||
getSelectedCityDetails:builder.query({
|
||||
getSelectedCityDetails: builder.query({
|
||||
query: (cityId) => `/website/${cityId}`,
|
||||
})
|
||||
}),
|
||||
|
||||
getSelectedCityOffers: builder.query({
|
||||
query: ({ cityId, categoryId, page, limit }) => {
|
||||
const params = new URLSearchParams()
|
||||
|
||||
params.append('cityXid', cityId);
|
||||
|
||||
if (categoryId) params.append('categoryXid', categoryId);
|
||||
if (page) params.append('page', page);
|
||||
if (limit) params.append('limit', limit);
|
||||
|
||||
return `/website/super-savings/list/offers?${params.toString()}`;
|
||||
}
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const { useGetCityListWithBannerQuery,useGetUpcomingCitiesQuery,useGetSelectedCityDetailsQuery } = citiesApi;
|
||||
export const { useGetCityListWithBannerQuery, useGetUpcomingCitiesQuery, useGetSelectedCityDetailsQuery, useGetSelectedCityOffersQuery } = citiesApi;
|
||||
@@ -42,20 +42,9 @@ export function CitySelectionDialog({
|
||||
|
||||
const handleCityClick = (city: City) => {
|
||||
console.log('Selected city:', city.cityName);
|
||||
|
||||
// ✅ Call the onCitySelect callback if provided (passing cityId)
|
||||
if (onCitySelect) {
|
||||
// onCitySelect(String(city.cityName));
|
||||
// navigate(`/${city.cityName}/${city.id}`)
|
||||
|
||||
navigate(`/${slugify(city.cityName)}`);
|
||||
localStorage.setItem("cityId", String(city.id))
|
||||
localStorage.setItem("cityName", String(city.cityName))
|
||||
} else {
|
||||
// ✅ Default behavior: navigate to passes page
|
||||
navigate(`/passes?city=${encodeURIComponent(city.cityName)}`);
|
||||
}
|
||||
|
||||
onClose();
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ import { MobileAppSection } from '../components/MobileAppSection';
|
||||
import { ReviewsSection } from '../components/ReviewsSection';
|
||||
import { TrustedCompanies } from '../components/TrustedCompanies';
|
||||
import { Layout } from '../Layout';
|
||||
import { useGetSelectedCityOffersQuery } from '../Redux/services/cities.service';
|
||||
import LoadingSpinner from '../components/LoadingSpinner';
|
||||
|
||||
interface SuperSavingsPageProps {
|
||||
onBackClick: () => void;
|
||||
@@ -44,138 +46,6 @@ interface SuperSavingsPageProps {
|
||||
user?: { email: string; name: string; } | null;
|
||||
}
|
||||
|
||||
// Mock super savings data
|
||||
const savingsData = [
|
||||
{
|
||||
id: '1',
|
||||
business: 'Grand Hotels Melbourne',
|
||||
title: 'Up to 50% Off on luxury hotel stays across Melbourne',
|
||||
discount: '50% OFF',
|
||||
savedAmount: 'Save up to $300',
|
||||
image: 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=400',
|
||||
category: 'hotels'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
business: 'Adventure Tours',
|
||||
title: '40% Off on guided adventure tours and experiences',
|
||||
discount: '40% OFF',
|
||||
savedAmount: 'Save up to $150',
|
||||
image: 'https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b?w=400',
|
||||
category: 'tours'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
business: 'Premium Spa & Wellness',
|
||||
title: '45% Off on spa packages and wellness treatments',
|
||||
discount: '45% OFF',
|
||||
savedAmount: 'Save up to $200',
|
||||
image: 'https://images.unsplash.com/photo-1544161515-4ab6ce6db874?w=400',
|
||||
category: 'wellness'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
business: 'Culinary Delights',
|
||||
title: '35% Off on fine dining at Michelin-starred restaurants',
|
||||
discount: '35% OFF',
|
||||
savedAmount: 'Save up to $120',
|
||||
image: 'https://images.unsplash.com/photo-1414235077428-338989a2e8c0?w=400',
|
||||
category: 'dining'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
business: 'Entertainment Pass',
|
||||
title: '60% Off on theater shows and concert tickets',
|
||||
discount: '60% OFF',
|
||||
savedAmount: 'Save up to $250',
|
||||
image: 'https://images.unsplash.com/photo-1514306191717-452ec28c7814?w=400',
|
||||
category: 'entertainment'
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
business: 'Museum Pass',
|
||||
title: '55% Off on museum entries and special exhibitions',
|
||||
discount: '55% OFF',
|
||||
savedAmount: 'Save up to $180',
|
||||
image: 'https://images.unsplash.com/photo-1566127992631-137a642a90f4?w=400',
|
||||
category: 'museums'
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
business: 'Luxury Shopping',
|
||||
title: '30% Off on designer boutiques and luxury shopping',
|
||||
discount: '30% OFF',
|
||||
savedAmount: 'Save up to $500',
|
||||
image: 'https://images.unsplash.com/photo-1441986300917-64674bd600d8?w=400',
|
||||
category: 'shopping'
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
business: 'Water Sports',
|
||||
title: '45% Off on water sports and beach activities',
|
||||
discount: '45% OFF',
|
||||
savedAmount: 'Save up to $175',
|
||||
image: 'https://images.unsplash.com/photo-1476514525535-07fb3b4ae5f1?w=400',
|
||||
category: 'sports'
|
||||
},
|
||||
{
|
||||
id: '9',
|
||||
business: 'Wine Tasting Tours',
|
||||
title: '40% Off on wine country tours and tastings',
|
||||
discount: '40% OFF',
|
||||
savedAmount: 'Save up to $160',
|
||||
image: 'https://images.unsplash.com/photo-1506377247377-2a5b3b417ebb?w=400',
|
||||
category: 'tours'
|
||||
},
|
||||
{
|
||||
id: '10',
|
||||
business: 'Family Fun Parks',
|
||||
title: '50% Off on family entertainment and theme parks',
|
||||
discount: '50% OFF',
|
||||
savedAmount: 'Save up to $220',
|
||||
image: 'https://images.unsplash.com/photo-1524850011238-e3d235c7d4c9?w=400',
|
||||
category: 'entertainment'
|
||||
},
|
||||
{
|
||||
id: '11',
|
||||
business: 'Boutique Stays',
|
||||
title: '55% Off on boutique hotels and bed & breakfasts',
|
||||
discount: '55% OFF',
|
||||
savedAmount: 'Save up to $280',
|
||||
image: 'https://images.unsplash.com/photo-1551882547-ff40c63fe5fa?w=400',
|
||||
category: 'hotels'
|
||||
},
|
||||
{
|
||||
id: '12',
|
||||
business: 'Art Galleries',
|
||||
title: '35% Off on contemporary art galleries and workshops',
|
||||
discount: '35% OFF',
|
||||
savedAmount: 'Save up to $140',
|
||||
image: 'https://images.unsplash.com/photo-1561214115-f2f134cc4912?w=400',
|
||||
category: 'museums'
|
||||
},
|
||||
{
|
||||
id: '13',
|
||||
business: 'Luxury Cruises',
|
||||
title: '65% Off on harbor cruises and yacht experiences',
|
||||
discount: '65% OFF',
|
||||
savedAmount: 'Save up to $400',
|
||||
image: 'https://images.unsplash.com/photo-1544551763-46a013bb70d5?w=400',
|
||||
category: 'tours'
|
||||
}
|
||||
];
|
||||
|
||||
const filterCategories = [
|
||||
{ value: 'hotels', label: 'Hotels', count: 2 },
|
||||
{ value: 'tours', label: 'Tours', count: 3 },
|
||||
{ value: 'wellness', label: 'Wellness', count: 1 },
|
||||
{ value: 'dining', label: 'Dining', count: 1 },
|
||||
{ value: 'entertainment', label: 'Entertainment', count: 2 },
|
||||
{ value: 'museums', label: 'Museums', count: 2 },
|
||||
{ value: 'shopping', label: 'Shopping', count: 1 },
|
||||
{ value: 'sports', label: 'Sports', count: 1 }
|
||||
];
|
||||
|
||||
// Categories data for the Super Savings Categories section
|
||||
const categoriesData = [
|
||||
{
|
||||
@@ -242,39 +112,28 @@ export function SuperSavingsPage({
|
||||
currentPage,
|
||||
user
|
||||
}: SuperSavingsPageProps) {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
|
||||
const [currentPage_, setCurrentPage_] = useState(1);
|
||||
const [showLoadMore, setShowLoadMore] = useState(true);
|
||||
|
||||
const toggleCategory = (category: string) => {
|
||||
setSelectedCategories(prev =>
|
||||
prev.includes(category)
|
||||
? prev.filter(c => c !== category)
|
||||
: [...prev, category]
|
||||
const [categoryId, setCategoryId] = useState(null)
|
||||
const [page, setPage] = useState(1)
|
||||
const [limit, setLimit] = useState(4)
|
||||
const cityId = localStorage.getItem("cityId")
|
||||
const { data, isLoading } = useGetSelectedCityOffersQuery({ cityId, categoryId, page, limit })
|
||||
|
||||
// optional chaining ensures no crash if data is undefined
|
||||
const offers = data?.offers ?? [];
|
||||
const categories = data?.categories ?? [];
|
||||
|
||||
const totalOffers = data?.paginationData.total ?? 0;
|
||||
const totalPages = Math.ceil(totalOffers / limit);
|
||||
|
||||
const baseUrl = import.meta.env.VITE_BASE_URL;
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<LoadingSpinner />
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const filteredSavings = savingsData.filter(saving => {
|
||||
const matchesSearch = saving.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
saving.business.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
const matchesCategory = selectedCategories.length === 0 || selectedCategories.includes(saving.category);
|
||||
|
||||
return matchesSearch && matchesCategory;
|
||||
});
|
||||
|
||||
const itemsPerPage = 12;
|
||||
const displayedSavings = filteredSavings.slice(0, currentPage_ * itemsPerPage);
|
||||
const hasMoreItems = filteredSavings.length > displayedSavings.length;
|
||||
|
||||
const handleLoadMore = () => {
|
||||
setCurrentPage_(prev => prev + 1);
|
||||
if (!hasMoreItems) setShowLoadMore(false);
|
||||
};
|
||||
|
||||
// Show different layouts based on login state
|
||||
if (!user) {
|
||||
// Not logged in - show marketing/landing page
|
||||
return (
|
||||
<Layout
|
||||
activeCity="Melbourne"
|
||||
@@ -378,19 +237,25 @@ export function SuperSavingsPage({
|
||||
|
||||
{/* Filter categories */}
|
||||
<div className="space-y-4">
|
||||
{filterCategories.map(category => (
|
||||
<div key={category.value} className="flex items-center gap-3">
|
||||
{categories.map((category: any) => (
|
||||
<div key={category.id} className="flex items-center gap-3">
|
||||
<Checkbox
|
||||
id={category.value}
|
||||
checked={selectedCategories.includes(category.value)}
|
||||
onCheckedChange={() => toggleCategory(category.value)}
|
||||
id={category.id}
|
||||
checked={categoryId === category.id}
|
||||
onCheckedChange={(checked: boolean) => {
|
||||
if (checked) {
|
||||
setCategoryId(category.id); // select this category
|
||||
} else {
|
||||
setCategoryId(null); // unselect if unchecked
|
||||
}
|
||||
}}
|
||||
className="border-gray-400"
|
||||
/>
|
||||
<label
|
||||
htmlFor={category.value}
|
||||
htmlFor={category.id}
|
||||
className="font-poppins text-sm text-gray-700 cursor-pointer flex-1"
|
||||
>
|
||||
{category.label} ({category.count})
|
||||
{category.categoryName} ({category.offerCount})
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
@@ -428,11 +293,11 @@ export function SuperSavingsPage({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Savings Grid */}
|
||||
{/* Offers Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-6 mb-16">
|
||||
{displayedSavings.map((saving, index) => (
|
||||
{offers.map((offer: any, index: number) => (
|
||||
<motion.div
|
||||
key={saving.id}
|
||||
key={offer.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
@@ -441,17 +306,17 @@ export function SuperSavingsPage({
|
||||
{/* Image */}
|
||||
<div className="relative h-52 bg-gray-300">
|
||||
<ImageWithFallback
|
||||
src={saving.image}
|
||||
alt={saving.title}
|
||||
src={`${baseUrl}/${offer.websiteBannerImage}`}
|
||||
alt={offer.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
<Button className="absolute bottom-4 right-3 bg-white rounded-full shadow-lg w-9 h-9 p-0 hover:bg-gray-100 transition-colors">
|
||||
{/* <Button className="absolute bottom-4 right-3 bg-white rounded-full shadow-lg w-9 h-9 p-0 hover:bg-gray-100 transition-colors">
|
||||
<Heart className="w-4 h-4 text-gray-800" />
|
||||
</Button>
|
||||
</Button> */}
|
||||
|
||||
{/* Discount Badge */}
|
||||
<div className="absolute top-4 left-4 bg-primary text-white px-3 py-1.5 rounded-lg">
|
||||
<span className="font-poppins font-semibold text-sm">{saving.discount}</span>
|
||||
<span className="font-poppins font-semibold text-sm">{offer.offerCode}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -459,20 +324,20 @@ export function SuperSavingsPage({
|
||||
{/* Business Name */}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 bg-gray-300 rounded"></div>
|
||||
<span className="font-poppins text-sm text-gray-500">{saving.business}</span>
|
||||
<span className="font-poppins text-sm text-gray-500">{offer.partnerName}</span>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="font-poppins font-medium text-gray-900 leading-relaxed min-h-[48px]">
|
||||
{saving.title}
|
||||
{offer.description}
|
||||
</h3>
|
||||
|
||||
{/* Saved Amount Display */}
|
||||
<div className="bg-gradient-to-r from-primary/10 to-secondary/10 h-12 flex items-center justify-center rounded-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
<Percent className="w-4 h-4 text-primary" />
|
||||
{/* <Percent className="w-4 h-4 text-primary" /> */}
|
||||
<span className="font-poppins font-semibold text-primary">
|
||||
{saving.savedAmount}
|
||||
{offer.title}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -485,34 +350,39 @@ export function SuperSavingsPage({
|
||||
{/* Minimal Pagination */}
|
||||
<div className="flex justify-center py-8">
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Previous button */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-8 h-8 p-0 font-poppins"
|
||||
disabled={currentPage_ === 1}
|
||||
disabled={page === 1}
|
||||
onClick={() => setPage(prev => Math.max(prev - 1, 1))}
|
||||
>
|
||||
<ChevronRight className="w-4 h-4 rotate-180" />
|
||||
</Button>
|
||||
|
||||
{/* Page numbers */}
|
||||
<div className="flex items-center gap-1">
|
||||
{[1, 2, 3].map((page) => (
|
||||
{Array.from({ length: totalPages }, (_, i) => i + 1).map(p => (
|
||||
<Button
|
||||
key={page}
|
||||
variant={currentPage_ === page ? "default" : "ghost"}
|
||||
key={p}
|
||||
variant={page === p ? "default" : "ghost"}
|
||||
size="sm"
|
||||
className={`w-8 h-8 p-0 font-poppins ${currentPage_ === page ? 'bg-primary hover:bg-primary/90' : ''}`}
|
||||
onClick={() => setCurrentPage_(page)}
|
||||
className={`w-8 h-8 p-0 font-poppins ${page === p ? 'bg-primary hover:bg-primary/90' : ''}`}
|
||||
onClick={() => setPage(p)}
|
||||
>
|
||||
{page}
|
||||
{p}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Next button */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-8 h-8 p-0 font-poppins"
|
||||
disabled={currentPage_ === 3}
|
||||
disabled={page === totalPages}
|
||||
onClick={() => setPage(prev => Math.min(prev + 1, totalPages))}
|
||||
>
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
</Button>
|
||||
@@ -740,6 +610,6 @@ export function SuperSavingsPage({
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user