integrate cities api in the CitySelectionDialog
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import { fakeApi } from "./services/fakeApi.service";
|
||||
import { attractionsApi } from "./services/attractions.service";
|
||||
import { citiesApi } from "./services/cities.service";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
[fakeApi.reducerPath]:fakeApi.reducer,
|
||||
[attractionsApi.reducerPath]:attractionsApi.reducer
|
||||
[attractionsApi.reducerPath]:attractionsApi.reducer,
|
||||
[citiesApi.reducerPath]:citiesApi.reducer
|
||||
|
||||
},
|
||||
|
||||
@@ -14,8 +16,8 @@ export const store = configureStore({
|
||||
getDefaultMiddleware().concat(
|
||||
|
||||
fakeApi.middleware,
|
||||
attractionsApi.middleware
|
||||
|
||||
attractionsApi.middleware,
|
||||
citiesApi.middleware
|
||||
),
|
||||
});
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
|
||||
@@ -33,7 +33,6 @@ export const attractionsApi = createApi({
|
||||
query: (id: number) => `/attractions/customer/${id}`,
|
||||
}),
|
||||
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
22
src/Redux/services/cities.service.ts
Normal file
22
src/Redux/services/cities.service.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
||||
|
||||
export const citiesApi = createApi({
|
||||
reducerPath: 'citiesApi',
|
||||
baseQuery: fetchBaseQuery({
|
||||
baseUrl: 'https://testingapi.citycards.betadelivery.com',
|
||||
}),
|
||||
endpoints: (builder) => ({
|
||||
|
||||
getCityListWithBanner: builder.query({
|
||||
query: ({ search }) => {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (search) params.append('search', search);
|
||||
|
||||
return `/cities/list/customer/cities?${params.toString()}`
|
||||
}
|
||||
})
|
||||
}),
|
||||
});
|
||||
|
||||
export const { useGetCityListWithBannerQuery} = citiesApi;
|
||||
@@ -6,11 +6,12 @@ import { ArrowLeft, Search } from 'lucide-react';
|
||||
import { Input } from './ui/input';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import { ImageWithFallback } from './figma/ImageWithFallback';
|
||||
import { useGetCityListWithBannerQuery } from '../Redux/services/cities.service';
|
||||
|
||||
interface City {
|
||||
id: string;
|
||||
name: string;
|
||||
imageUrl: string;
|
||||
id: number;
|
||||
cityName: string;
|
||||
bannerImage: string;
|
||||
}
|
||||
|
||||
interface CitySelectionDialogProps {
|
||||
@@ -19,45 +20,47 @@ interface CitySelectionDialogProps {
|
||||
onCitySelect?: (cityId: string) => void; // ✅ Updated to pass cityId
|
||||
}
|
||||
|
||||
const cities: City[] = [
|
||||
{ id: 'melbourne', name: 'Melbourne', imageUrl: 'https://images.unsplash.com/photo-1624341373902-70e3a8dc9acc?...' },
|
||||
{ id: 'new-york', name: 'New York', imageUrl: 'https://images.unsplash.com/photo-1514565131-fce0801e5785?...' },
|
||||
{ id: 'abu-dhabi', name: 'Abu Dhabi', imageUrl: 'https://images.unsplash.com/photo-1584551246679-0daf3d275d0f?...' },
|
||||
{ id: 'dubai', name: 'Dubai', imageUrl: 'https://images.unsplash.com/photo-1518684079-3c830dcef090?...' },
|
||||
{ id: 'tokyo', name: 'Tokyo', imageUrl: 'https://images.unsplash.com/photo-1613487897980-50cc440ce118?...' },
|
||||
{ id: 'ontario', name: 'Ontario', imageUrl: 'https://images.unsplash.com/photo-1542704792-e30dac463c90?...' },
|
||||
{ id: 'mumbai', name: 'Mumbai', imageUrl: 'https://images.unsplash.com/photo-1600867161422-79f8f6e08c84?...' },
|
||||
{ id: 'louisiana', name: 'Louisiana', imageUrl: 'https://images.unsplash.com/photo-1646508262200-455d62c22182?...' },
|
||||
];
|
||||
|
||||
export function CitySelectionDialog({
|
||||
isOpen,
|
||||
onClose,
|
||||
onCitySelect
|
||||
}: CitySelectionDialogProps) {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [search, setSearch] = useState('');
|
||||
const navigate = useNavigate();
|
||||
|
||||
const filteredCities = useMemo(() =>
|
||||
cities.filter(city =>
|
||||
city.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
), [searchQuery]);
|
||||
const { data: cities, isLoading } = useGetCityListWithBannerQuery({ search })
|
||||
|
||||
if (isLoading) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
|
||||
const handleCityClick = (city: City) => {
|
||||
console.log('Selected city:', city.name);
|
||||
console.log('Selected city:', city.cityName);
|
||||
|
||||
// ✅ Call the onCitySelect callback if provided (passing cityId)
|
||||
if (onCitySelect) {
|
||||
onCitySelect(city.id);
|
||||
onCitySelect(String(city.id));
|
||||
} else {
|
||||
// ✅ Default behavior: navigate to passes page
|
||||
navigate(`/passes?city=${encodeURIComponent(city.name)}`);
|
||||
navigate(`/passes?city=${encodeURIComponent(city.cityName)}`);
|
||||
}
|
||||
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(e.target.value)
|
||||
}
|
||||
|
||||
const filteredCities = useMemo(() =>
|
||||
cities?.filter((city: City) =>
|
||||
city.cityName.toLowerCase().includes(search.toLowerCase())
|
||||
) ?? [],
|
||||
[cities, search]
|
||||
);
|
||||
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-md w-full p-0 gap-0 font-poppins">
|
||||
@@ -85,8 +88,8 @@ export function CitySelectionDialog({
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Cities"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
value={search}
|
||||
onChange={handleSearchChange}
|
||||
className="pl-10 bg-input border-0 rounded-lg h-11 font-poppins placeholder:text-gray-400"
|
||||
/>
|
||||
</div>
|
||||
@@ -96,7 +99,7 @@ export function CitySelectionDialog({
|
||||
<div className="px-6 pb-6 max-h-[60vh] overflow-y-auto">
|
||||
<AnimatePresence>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{filteredCities.map((city, index) => (
|
||||
{filteredCities && filteredCities.map((city: City) => (
|
||||
<motion.button
|
||||
key={city.id}
|
||||
onClick={() => handleCityClick(city)}
|
||||
@@ -108,14 +111,14 @@ export function CitySelectionDialog({
|
||||
className="relative h-28 rounded-2xl overflow-hidden group cursor-pointer"
|
||||
>
|
||||
<ImageWithFallback
|
||||
src={`${city.imageUrl}&w=400&h=300&fit=crop`}
|
||||
alt={city.name}
|
||||
src={city.bannerImage}
|
||||
alt={city.cityName}
|
||||
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent" />
|
||||
<div className="absolute bottom-3 left-3 right-3">
|
||||
<h3 className="font-poppins font-semibold text-white text-left">
|
||||
{city.name}
|
||||
{city.cityName}
|
||||
</h3>
|
||||
</div>
|
||||
</motion.button>
|
||||
@@ -123,10 +126,10 @@ export function CitySelectionDialog({
|
||||
</div>
|
||||
</AnimatePresence>
|
||||
|
||||
{filteredCities.length === 0 && (
|
||||
{filteredCities?.length === 0 && (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-gray-500 font-poppins">
|
||||
No cities found matching "{searchQuery}"
|
||||
No cities found matching "{search}"
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user