integrate the APIs for getting and editing user details

This commit is contained in:
aryabenade
2026-04-13 16:15:22 +05:30
parent 0a0cb9f6f1
commit 9008077709
3 changed files with 150 additions and 44 deletions

View File

@@ -2,12 +2,14 @@ import { configureStore } from "@reduxjs/toolkit";
import { attractionsApi } from "./services/attractions.service";
import { citiesApi } from "./services/cities.service";
import { authApi } from "./services/auth.service";
import { profileApi } from "./services/profile.service";
export const store = configureStore({
reducer: {
[attractionsApi.reducerPath]: attractionsApi.reducer,
[citiesApi.reducerPath]: citiesApi.reducer,
[authApi.reducerPath]:authApi.reducer
[authApi.reducerPath]: authApi.reducer,
[profileApi.reducerPath]: profileApi.reducer
},
@@ -15,7 +17,8 @@ export const store = configureStore({
getDefaultMiddleware().concat(
attractionsApi.middleware,
citiesApi.middleware,
authApi.middleware
authApi.middleware,
profileApi.middleware
),
});
export type RootState = ReturnType<typeof store.getState>;

View File

@@ -0,0 +1,33 @@
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { baseQuery } from "../baseQuery";
export const profileApi = createApi({
reducerPath: "profileApi",
baseQuery,
tagTypes: ["userDetails"],
endpoints: (builder) => ({
getUserProfileDetails: builder.query({
query: (id) => `/website/user/${id}`,
providesTags:["userDetails"]
}),
updateUserProfileDetails: builder.mutation({
query: ({ userDetails, userId }) => ({ // keep the name of the variables being passed here same as when calling the mutation hook
url: `/website/user/${userId}`,
method: "PUT",
body: userDetails
}),
invalidatesTags:["userDetails"]
})
})
});
export const {
useGetUserProfileDetailsQuery,
useUpdateUserProfileDetailsMutation
} = profileApi;

View File

@@ -1,11 +1,11 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { motion } from 'motion/react';
import {
ArrowLeft,
User,
CreditCard,
Calendar,
MapPin,
import {
ArrowLeft,
User,
CreditCard,
Calendar,
MapPin,
Settings,
Download,
QrCode,
@@ -26,6 +26,8 @@ import { Badge } from '../components/ui/badge';
import Navbar from '../components/Navbar';
import { Footer } from '../components/Footer';
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
import { useGetUserProfileDetailsQuery, useUpdateUserProfileDetailsMutation } from '../Redux/services/profile.service';
import { toast } from 'sonner';
interface ProfilePageProps {
onBackClick: () => void;
@@ -86,7 +88,7 @@ const mockPasses = [
usedAttractions: 8
},
{
id: '2',
id: '2',
name: 'Melbourne Selective Card',
city: 'Melbourne',
type: 'Flexi Pass',
@@ -160,26 +162,76 @@ export function ProfilePage({
currentPage
}: ProfilePageProps) {
const [activeTab, setActiveTab] = useState('profile');
const [formData, setFormData] = useState(mockUserData);
const [formData, setFormData] = useState({
firstName: '',
lastName: '',
email: '',
phone: '',
country: '',
address1: '',
address2: '',
city: '',
postalCode: ''
});
const userId = localStorage.getItem("userId")
const { data: userDetails, isLoading } = useGetUserProfileDetailsQuery(userId)
useEffect(() => {
if (userDetails) {
setFormData({
firstName: userDetails?.firstName,
lastName: userDetails?.lastName,
email: userDetails?.emailAddress,
phone: userDetails?.mobileNumber,
country: userDetails?.country,
address1: userDetails?.address1,
address2: userDetails?.address2,
city: userDetails?.cityName,
postalCode: userDetails?.zipCode
})
}
}, [userDetails])
const [updateUserProfileDetails] = useUpdateUserProfileDetailsMutation();
const handleInputChange = (field: string, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const handleSaveProfile = () => {
console.log('Saving profile...', formData);
// Handle profile save
const handleSaveProfile = async () => {
try {
console.log("Saving profile...", formData);
const response = await updateUserProfileDetails({ userDetails: formData, userId });
console.log(response)
toast.success("Profile updated successfully!");
} catch (error) {
console.error("Error saving profile:", error);
toast.error("Failed to update profile. Please try again.");
}
};
const activePasses = mockPasses.filter(pass => pass.status === 'active');
const expiredPasses = mockPasses.filter(pass => pass.status === 'expired');
if (isLoading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-[#F95F62] mx-auto"></div>
<p className="mt-4 text-gray-600">Loading...</p>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-background">
{/* Navbar */}
<Navbar
<Navbar
activeCity=""
onCityChange={() => {}}
onCityChange={() => { }}
onHomeClick={onHomeClick}
onMelbourneClick={onMelbourneClick}
onPassesClick={onPassesClick}
@@ -312,7 +364,7 @@ export function ProfilePage({
<div>
<Label htmlFor="country" className="font-poppins font-light">Country</Label>
<Select value={formData.country} onValueChange={(value) => handleInputChange('country', value)}>
{/* <Select value={formData.country} onValueChange={(value) => handleInputChange('country', value)}>
<SelectTrigger className="mt-1">
<SelectValue placeholder="Select country" />
</SelectTrigger>
@@ -326,15 +378,33 @@ export function ProfilePage({
<SelectItem value="in">India</SelectItem>
<SelectItem value="jp">Japan</SelectItem>
</SelectContent>
</Select>
</Select> */}
<Input
id="country"
value={formData.country}
onChange={(e) => handleInputChange('country', e.target.value)}
className="mt-1 font-poppins font-light"
/>
</div>
<div>
<Label htmlFor="address" className="font-poppins font-light">Street Address</Label>
<Label htmlFor="address1" className="font-poppins font-light">
Address Line 1
</Label>
<Input
id="address"
value={formData.address}
onChange={(e) => handleInputChange('address', e.target.value)}
id="address1"
value={formData.address1}
onChange={(e) => handleInputChange('address1', e.target.value)}
className="mt-1 font-poppins font-light mb-4"
/>
<Label htmlFor="address2" className="font-poppins font-light">
Address Line 2
</Label>
<Input
id="address2"
value={formData.address2}
onChange={(e) => handleInputChange('address2', e.target.value)}
className="mt-1 font-poppins font-light"
/>
</div>
@@ -362,7 +432,7 @@ export function ProfilePage({
<Button
onClick={handleSaveProfile}
className="w-full bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-normal py-3 font-poppins"
className="w-full bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-normal py-3 font-poppins cursor-pointer"
>
Save Changes
</Button>
@@ -384,7 +454,7 @@ export function ProfilePage({
// Determine which pass type to show
const hasUnlimitedPass = activePasses.some(pass => pass.type === 'Unlimited Pass');
const hasSelectivePass = activePasses.some(pass => pass.type === 'Flexi Pass');
if (hasUnlimitedPass) {
return (
<>
@@ -394,7 +464,7 @@ export function ProfilePage({
<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.
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>
@@ -423,13 +493,13 @@ export function ProfilePage({
{/* Purchase CTA */}
<div className="space-y-3">
<Button
<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
<Button
variant="outline"
onClick={onCityCardsClick}
className="w-full font-poppins font-normal"
@@ -449,7 +519,7 @@ export function ProfilePage({
{' '}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
Choose your own adventure with 12 hand-picked attractions. Perfect for visitors
who want flexibility and value.
</p>
</div>
@@ -478,13 +548,13 @@ export function ProfilePage({
{/* Purchase CTA */}
<div className="space-y-3">
<Button
<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
<Button
variant="outline"
onClick={onCityCardsClick}
className="w-full font-poppins font-normal"
@@ -504,7 +574,7 @@ export function ProfilePage({
{' '}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.
Explore Melbourne's best attractions with our flexible card options.
Choose unlimited access or select your favorites.
</p>
</div>
@@ -535,13 +605,13 @@ export function ProfilePage({
{/* Purchase CTA */}
<div className="space-y-3">
<Button
<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
<Button
variant="outline"
onClick={onCityCardsClick}
className="w-full font-poppins font-normal"
@@ -572,7 +642,7 @@ export function ProfilePage({
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{activePasses.map((pass) => (
<Card key={pass.id} className="overflow-hidden">
<div
<div
className="flex cursor-pointer hover:bg-gray-50 transition-colors duration-200 rounded-lg p-2 -m-2"
onClick={() => onDownloadAppClick?.()}
>
@@ -596,7 +666,7 @@ export function ProfilePage({
<div className="text-sm text-gray-500 line-through font-poppins font-light">${pass.originalPrice}</div>
</div>
</div>
<div className="space-y-2 text-sm font-poppins font-light">
<div className="flex justify-between">
<span>Attractions:</span>
@@ -616,10 +686,10 @@ export function ProfilePage({
</Card>
))}
</div>
{/* Offers Button */}
<div className="mt-8 text-center">
<Button
<Button
onClick={onOffersClick}
className="bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-poppins px-8 py-3 font-normal"
>
@@ -660,7 +730,7 @@ export function ProfilePage({
<div className="font-semibold text-lg font-poppins">${pass.price}</div>
</div>
</div>
<div className="space-y-2 text-sm font-poppins font-light">
<div className="flex justify-between">
<span>Attractions visited:</span>
@@ -689,7 +759,7 @@ export function ProfilePage({
>
<div className="flex items-center justify-between mb-6">
<h2 className="font-poppins text-2xl font-normal">My Itineraries</h2>
<Button
<Button
className="bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-poppins font-normal"
onClick={onCreateItineraryClick}
>
@@ -712,7 +782,7 @@ export function ProfilePage({
{itinerary.status}
</Badge>
</div>
<div className="space-y-2 text-sm font-poppins font-light">
<div className="flex items-center gap-2">
<Calendar className="w-4 h-4 text-gray-500" />
@@ -728,8 +798,8 @@ export function ProfilePage({
</div>
</div>
<Button
variant="outline"
<Button
variant="outline"
className="w-full mt-4 font-poppins font-normal"
onClick={onViewItineraryClick}
>
@@ -749,7 +819,7 @@ export function ProfilePage({
<p className="text-gray-600 mb-6 font-poppins font-light">
Create your first itinerary to plan your perfect trip
</p>
<Button
<Button
className="bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-white font-poppins font-normal"
onClick={onCreateItineraryClick}
>
@@ -766,7 +836,7 @@ export function ProfilePage({
</section>
{/* Footer */}
<Footer
<Footer
onHomeClick={onHomeClick}
onMelbourneClick={onMelbourneClick}
onPassesClick={onPassesClick}