From 445d6445388c047e874f6f4d95d5625b2c2b84ec Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Sat, 15 Mar 2025 21:45:16 +0100 Subject: [PATCH] Add GuestContext for managing guest-related state Introduced a new GuestContext with accompanying provider to handle guest-related data, including fetching, creating, updating, and deleting guests. Integrated GuestsProvider into the application's data provider hierarchy to ensure guest data is accessible across components. --- frontend/package-lock.json | 31 +++++ frontend/package.json | 1 + frontend/src/context/guest-context.tsx | 185 +++++++++++++++++++++++++ frontend/src/providers/data.tsx | 5 +- 4 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 frontend/src/context/guest-context.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1bb2ffa..9a0599d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,6 +19,7 @@ "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-switch": "^1.1.3", + "@radix-ui/react-tabs": "^1.1.3", "@tanstack/react-query": "^5.67.1", "axios": "^1.8.1", "class-variance-authority": "^0.7.1", @@ -1595,6 +1596,36 @@ } } }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.3.tgz", + "integrity": "sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-roving-focus": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 6c165a9..806cf8e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,6 +24,7 @@ "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-switch": "^1.1.3", + "@radix-ui/react-tabs": "^1.1.3", "@tanstack/react-query": "^5.67.1", "axios": "^1.8.1", "class-variance-authority": "^0.7.1", diff --git a/frontend/src/context/guest-context.tsx b/frontend/src/context/guest-context.tsx new file mode 100644 index 0000000..d34b39b --- /dev/null +++ b/frontend/src/context/guest-context.tsx @@ -0,0 +1,185 @@ +"use client"; + +import React, { + createContext, + ReactNode, + useCallback, + useContext, + useState, +} from "react"; +import { + useMutation, + useQuery, + useQueryClient, + RefetchOptions, +} from "@tanstack/react-query"; +import { + GetGuestsResponse, + GuestRead, + GuestCreate, + GuestUpdate, +} from "@/client/types.gen"; +import { + getGuestsOptions, + getGuestOptions, +} from "@/client/@tanstack/react-query.gen"; +import { createGuest, updateGuest, deleteGuest } from "@/client"; + +// Guest context state +interface GuestsContextState { + guests: GetGuestsResponse | undefined; + guest: GuestRead | undefined; + isLoadingGuests: boolean; + isLoadingGuest: boolean; + error: Error | null; + + refetchGuests: (options?: RefetchOptions) => Promise; + + createGuest: (data: GuestCreate) => Promise; + updateGuest: ( + id: string, + data: GuestUpdate, + ) => Promise; + deleteGuest: (id: string) => Promise; + fetchGuestById: (id: string) => void; + + currentGuestId: string | null; + setCurrentGuestId: (id: string | null) => void; +} + +// Default context state +const defaultGuestsContextState: GuestsContextState = { + guests: undefined, + guest: undefined, + isLoadingGuests: false, + isLoadingGuest: false, + error: null, + + refetchGuests: async () => { + throw new Error("GuestsProvider is not initialized"); + }, + createGuest: async () => { + throw new Error("GuestsProvider is not initialized"); + }, + updateGuest: async () => { + throw new Error("GuestsProvider is not initialized"); + }, + deleteGuest: async () => { + throw new Error("GuestsProvider is not initialized"); + }, + fetchGuestById: () => {}, + currentGuestId: null, + setCurrentGuestId: () => {}, +}; + +// Create context +const GuestsContext = createContext( + defaultGuestsContextState, +); + +// Hook to access context +export const useGuests = () => { + const context = useContext(GuestsContext); + if (!context) { + throw new Error("useGuests must be used within GuestsProvider"); + } + return context; +}; + +// Provider Component Props +interface GuestsProviderProps { + children: ReactNode; +} + +// Provider Component +export const GuestsProvider: React.FC = ({ children }) => { + const queryClient = useQueryClient(); + const [currentGuestId, setCurrentGuestId] = useState(null); + + // Fetch all guests + const { + data: guests, + isLoading: isLoadingGuests, + error, + refetch: refetchGuests, + } = useQuery({ + ...getGuestsOptions(), + refetchOnWindowFocus: "always", + refetchInterval: 60000, + }); + + // Fetch specific guest by ID + const { data: guest, isLoading: isLoadingGuest } = useQuery({ + ...getGuestOptions({ path: { guest_id: currentGuestId || "" } }), + enabled: !!currentGuestId, + }); + + // explicitly fetch guest by id + const fetchGuestById = useCallback((id: string) => { + setCurrentGuestId(id); + }, []); + + // Create guest mutation + const createMutation = useMutation({ + mutationFn: (data: GuestCreate) => + createGuest({ body: data }).then((res) => res.data), + onSuccess: () => + queryClient.invalidateQueries({ + queryKey: getGuestsOptions().queryKey, + }), + }); + + // Update guest mutation + const updateMutation = useMutation({ + mutationFn: ({ id, data }: { id: string; data: GuestUpdate }) => + updateGuest({ path: { guest_id: id }, body: data }).then( + (res) => res.data, + ), + onSuccess: (updatedGuest) => { + queryClient.invalidateQueries({ + queryKey: getGuestsOptions().queryKey, + }); + if (updatedGuest?.id === currentGuestId) { + queryClient.invalidateQueries({ + queryKey: getGuestOptions({ path: { guest_id: currentGuestId } }) + .queryKey, + }); + } + }, + }); + + // Delete guest mutation + const deleteMutation = useMutation({ + mutationFn: (id: string) => + deleteGuest({ path: { guest_id: id } }).then((res) => res.data), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: getGuestsOptions().queryKey, + }); + setCurrentGuestId(null); + }, + }); + + // Context state value + const value: GuestsContextState = { + guests, + guest, + isLoadingGuests, + isLoadingGuest, + error, + + refetchGuests, + + createGuest: createMutation.mutateAsync, + updateGuest: (id, data) => updateMutation.mutateAsync({ id, data }), + deleteGuest: deleteMutation.mutateAsync, + + fetchGuestById, + currentGuestId, + setCurrentGuestId, + }; + + return ( + {children} + ); +}; diff --git a/frontend/src/providers/data.tsx b/frontend/src/providers/data.tsx index 07a445c..8118beb 100644 --- a/frontend/src/providers/data.tsx +++ b/frontend/src/providers/data.tsx @@ -2,12 +2,15 @@ import React from "react"; import { EventsProvider } from "@/context/event-context"; import { EventThemesProvider } from "@/context/event-theme-context"; import { RSVPProvider } from "@/context/rsvp-context"; +import { GuestsProvider } from "@/context/guest-context"; export function DataProviders({ children }: { children: React.ReactNode }) { return ( - {children} + + {children} + );