integrate cities api in the CitySelectionDialog

This commit is contained in:
aryabenade
2026-03-20 11:45:14 +05:30
parent b3e1c0faf4
commit a09d53db7d
4 changed files with 60 additions and 34 deletions

View File

@@ -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>;

View File

@@ -33,7 +33,6 @@ export const attractionsApi = createApi({
query: (id: number) => `/attractions/customer/${id}`,
}),
}),
});

View 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;

View File

@@ -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>
)}