integrate the APIs for getting and editing user details
This commit is contained in:
@@ -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>;
|
||||
|
||||
33
src/Redux/services/profile.service.ts
Normal file
33
src/Redux/services/profile.service.ts
Normal 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;
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user