Add initial implementation of gift context in frontend

This commit is contained in:
2025-03-16 16:05:14 +01:00
parent 861baa7b66
commit 44d6f6d837
2 changed files with 784 additions and 1 deletions

View File

@@ -0,0 +1,780 @@
"use client";
import React, { createContext, ReactNode, useContext } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
createGiftCategory,
readGiftCategories,
deleteGiftCategory,
readGiftCategory,
updateGiftCategory,
associateCategoryWithEvent,
updateCategoryEventSettings,
getEventsForCategory,
reorderGiftsInCategory,
createGiftItem,
readGiftItems,
deleteGiftItem,
readGiftItem,
updateGiftItem,
updateGiftItemStatus,
reserveGiftItem,
cancelGiftReservation,
createGiftPurchase,
readGiftPurchase,
readGiftPurchasesByGift,
readGiftPurchasesByGuest,
} from "@/client/sdk.gen";
import {
GiftCategory,
GiftCategoryCreate,
GiftCategoryUpdate,
GiftItem,
GiftItemCreate,
GiftItemUpdate,
GiftStatus,
GiftPriority,
GiftPurchase,
GiftPurchaseCreate,
} from "@/client/types.gen";
// Gift context state
interface GiftContextState {
// Gift Categories
categories: GiftCategory[] | undefined;
category: GiftCategory | undefined;
isLoadingCategories: boolean;
isLoadingCategory: boolean;
refetchCategories: (eventId: string) => Promise<any>;
fetchCategoryById: (id: string, eventId?: string) => void;
createCategory: (data: GiftCategoryCreate) => Promise<GiftCategory | undefined>;
updateCategory: (
id: string,
data: GiftCategoryUpdate,
eventId?: string
) => Promise<GiftCategory | undefined>;
deleteCategory: (id: string, eventId?: string) => Promise<GiftCategory | undefined>;
associateCategoryWithEvent: (
categoryId: string,
eventId: string,
displayOrder?: number,
isVisible?: boolean
) => Promise<GiftCategory | undefined>;
updateCategoryEventSettings: (
categoryId: string,
eventId: string,
displayOrder?: number,
isVisible?: boolean
) => Promise<GiftCategory | undefined>;
getEventsForCategory: (categoryId: string) => Promise<any>;
reorderGiftsInCategory: (
categoryId: string,
giftIds: string[]
) => Promise<GiftCategory | undefined>;
// Gift Items
items: GiftItem[] | undefined;
item: GiftItem | undefined;
isLoadingItems: boolean;
isLoadingItem: boolean;
refetchItems: (categoryId?: string, eventId?: string) => Promise<any>;
fetchItemById: (id: string) => void;
createItem: (data: GiftItemCreate) => Promise<GiftItem | undefined>;
updateItem: (
id: string,
data: GiftItemUpdate
) => Promise<GiftItem | undefined>;
deleteItem: (id: string) => Promise<GiftItem | undefined>;
updateItemStatus: (
id: string,
status: GiftStatus
) => Promise<GiftItem | undefined>;
reserveItem: (
id: string,
guestId: string,
quantity?: number
) => Promise<GiftItem | undefined>;
cancelReservation: (
id: string,
guestId: string
) => Promise<GiftItem | undefined>;
// Gift Purchases
purchases: GiftPurchase[] | undefined;
purchase: GiftPurchase | undefined;
isLoadingPurchases: boolean;
isLoadingPurchase: boolean;
refetchPurchases: (giftId?: string, guestId?: string) => Promise<any>;
fetchPurchaseById: (id: string) => void;
createPurchase: (data: GiftPurchaseCreate) => Promise<GiftPurchase | undefined>;
fetchPurchasesByGift: (giftId: string) => Promise<GiftPurchase[] | undefined>;
fetchPurchasesByGuest: (guestId: string) => Promise<GiftPurchase[] | undefined>;
// Current selections
currentCategoryId: string | null;
setCurrentCategoryId: (id: string | null) => void;
currentItemId: string | null;
setCurrentItemId: (id: string | null) => void;
currentPurchaseId: string | null;
setCurrentPurchaseId: (id: string | null) => void;
currentEventId: string | null;
setCurrentEventId: (id: string | null) => void;
error: Error | null;
}
// Default context state
const defaultGiftContextState: GiftContextState = {
// Gift Categories
categories: undefined,
category: undefined,
isLoadingCategories: false,
isLoadingCategory: false,
refetchCategories: async () => {
throw new Error("GiftContext not initialized");
},
fetchCategoryById: () => {},
createCategory: async () => {
throw new Error("GiftContext not initialized");
},
updateCategory: async () => {
throw new Error("GiftContext not initialized");
},
deleteCategory: async () => {
throw new Error("GiftContext not initialized");
},
associateCategoryWithEvent: async () => {
throw new Error("GiftContext not initialized");
},
updateCategoryEventSettings: async () => {
throw new Error("GiftContext not initialized");
},
getEventsForCategory: async () => {
throw new Error("GiftContext not initialized");
},
reorderGiftsInCategory: async () => {
throw new Error("GiftContext not initialized");
},
// Gift Items
items: undefined,
item: undefined,
isLoadingItems: false,
isLoadingItem: false,
refetchItems: async () => {
throw new Error("GiftContext not initialized");
},
fetchItemById: () => {},
createItem: async () => {
throw new Error("GiftContext not initialized");
},
updateItem: async () => {
throw new Error("GiftContext not initialized");
},
deleteItem: async () => {
throw new Error("GiftContext not initialized");
},
updateItemStatus: async () => {
throw new Error("GiftContext not initialized");
},
reserveItem: async () => {
throw new Error("GiftContext not initialized");
},
cancelReservation: async () => {
throw new Error("GiftContext not initialized");
},
// Gift Purchases
purchases: undefined,
purchase: undefined,
isLoadingPurchases: false,
isLoadingPurchase: false,
refetchPurchases: async () => {
throw new Error("GiftContext not initialized");
},
fetchPurchaseById: () => {},
createPurchase: async () => {
throw new Error("GiftContext not initialized");
},
fetchPurchasesByGift: async () => {
throw new Error("GiftContext not initialized");
},
fetchPurchasesByGuest: async () => {
throw new Error("GiftContext not initialized");
},
// Current selections
currentCategoryId: null,
setCurrentCategoryId: () => {},
currentItemId: null,
setCurrentItemId: () => {},
currentPurchaseId: null,
setCurrentPurchaseId: () => {},
currentEventId: null,
setCurrentEventId: () => {},
error: null,
};
// Create context
const GiftContext = createContext<GiftContextState>(defaultGiftContextState);
// Hook to use context
export const useGifts = () => {
const context = useContext(GiftContext);
if (!context) {
throw new Error("useGifts must be used within a GiftProvider");
}
return context;
};
// Gift Provider Props
interface GiftProviderProps {
children: ReactNode;
}
// Gift Provider Component
export const GiftProvider: React.FC<GiftProviderProps> = ({ children }) => {
const queryClient = useQueryClient();
const [currentCategoryId, setCurrentCategoryId] = React.useState<string | null>(null);
const [currentItemId, setCurrentItemId] = React.useState<string | null>(null);
const [currentPurchaseId, setCurrentPurchaseId] = React.useState<string | null>(null);
const [currentEventId, setCurrentEventId] = React.useState<string | null>(null);
// Fetch all categories for an event
const {
data: categories,
isLoading: isLoadingCategories,
error: categoriesError,
refetch: refetchCategoriesInternal,
} = useQuery({
queryKey: ["giftCategories", currentEventId],
queryFn: () =>
currentEventId
? readGiftCategories({
path: { event_id: currentEventId }
}).then((res) => res.data)
: Promise.resolve(undefined),
enabled: !!currentEventId,
});
const refetchCategories = async (eventId: string) => {
setCurrentEventId(eventId);
return refetchCategoriesInternal();
};
// Fetch specific category
const {
data: category,
isLoading: isLoadingCategory,
error: categoryError,
} = useQuery({
queryKey: ["giftCategory", currentCategoryId, currentEventId],
queryFn: () =>
currentCategoryId
? readGiftCategory({
path: {
category_id: currentCategoryId
},
query: currentEventId ? { event_id: currentEventId } : undefined
}).then((res) => res.data)
: Promise.resolve(undefined),
enabled: !!currentCategoryId,
});
const fetchCategoryById = (id: string, eventId?: string) => {
setCurrentCategoryId(id);
if (eventId) {
setCurrentEventId(eventId);
}
};
// Create Category Mutation
const createCategoryMutation = useMutation({
mutationFn: (data: GiftCategoryCreate) =>
createGiftCategory({ body: data }).then((res) => res.data),
onSuccess: () => {
if (currentEventId) {
queryClient.invalidateQueries({ queryKey: ["giftCategories", currentEventId] });
}
},
});
// Update Category Mutation
const updateCategoryMutation = useMutation({
mutationFn: ({
id,
data,
eventId
}: {
id: string;
data: GiftCategoryUpdate;
eventId?: string;
}) =>
updateGiftCategory({
path: { category_id: id },
body: data,
query: eventId ? { event_id: eventId } : undefined
}).then((res) => res.data),
onSuccess: () => {
if (currentEventId) {
queryClient.invalidateQueries({ queryKey: ["giftCategories", currentEventId] });
}
if (currentCategoryId) {
queryClient.invalidateQueries({ queryKey: ["giftCategory", currentCategoryId] });
}
},
});
// Delete Category Mutation
const deleteCategoryMutation = useMutation({
mutationFn: ({ id, eventId }: { id: string; eventId?: string }) =>
deleteGiftCategory({
path: { category_id: id },
query: eventId ? { event_id: eventId } : undefined
}).then((res) => res.data),
onSuccess: () => {
if (currentEventId) {
queryClient.invalidateQueries({ queryKey: ["giftCategories", currentEventId] });
}
},
});
// Fetch all items for a category or event
const {
data: items,
isLoading: isLoadingItems,
error: itemsError,
refetch: refetchItemsInternal,
} = useQuery({
queryKey: ["giftItems", currentCategoryId, currentEventId],
queryFn: () => {
const query: Record<string, string> = {};
if (currentCategoryId) {
query.category_id = currentCategoryId;
}
if (currentEventId) {
query.event_id = currentEventId;
}
return Object.keys(query).length > 0
? readGiftItems({ query }).then((res) => res.data)
: Promise.resolve(undefined);
},
enabled: !!(currentCategoryId || currentEventId),
});
const refetchItems = async (categoryId?: string, eventId?: string) => {
if (categoryId) {
setCurrentCategoryId(categoryId);
}
if (eventId) {
setCurrentEventId(eventId);
}
return refetchItemsInternal();
};
// Fetch specific item
const {
data: item,
isLoading: isLoadingItem,
error: itemError,
} = useQuery({
queryKey: ["giftItem", currentItemId],
queryFn: () =>
currentItemId
? readGiftItem({
path: {
gift_id: currentItemId
}
}).then((res) => res.data)
: Promise.resolve(undefined),
enabled: !!currentItemId,
});
const fetchItemById = (id: string) => {
setCurrentItemId(id);
};
// Create Item Mutation
const createItemMutation = useMutation({
mutationFn: (data: GiftItemCreate) =>
createGiftItem({ body: data }).then((res) => res.data),
onSuccess: () => {
if (currentCategoryId) {
queryClient.invalidateQueries({ queryKey: ["giftItems", currentCategoryId] });
}
if (currentEventId) {
queryClient.invalidateQueries({ queryKey: ["giftItems", null, currentEventId] });
}
},
});
// Update Item Mutation
const updateItemMutation = useMutation({
mutationFn: ({
id,
data
}: {
id: string;
data: GiftItemUpdate;
}) =>
updateGiftItem({
path: { gift_id: id },
body: data
}).then((res) => res.data),
onSuccess: () => {
if (currentCategoryId) {
queryClient.invalidateQueries({ queryKey: ["giftItems", currentCategoryId] });
}
if (currentEventId) {
queryClient.invalidateQueries({ queryKey: ["giftItems", null, currentEventId] });
}
if (currentItemId) {
queryClient.invalidateQueries({ queryKey: ["giftItem", currentItemId] });
}
},
});
// Delete Item Mutation
const deleteItemMutation = useMutation({
mutationFn: (id: string) =>
deleteGiftItem({
path: { gift_id: id }
}).then((res) => res.data),
onSuccess: () => {
if (currentCategoryId) {
queryClient.invalidateQueries({ queryKey: ["giftItems", currentCategoryId] });
}
if (currentEventId) {
queryClient.invalidateQueries({ queryKey: ["giftItems", null, currentEventId] });
}
},
});
// Update Item Status Mutation
const updateItemStatusMutation = useMutation({
mutationFn: ({
id,
status
}: {
id: string;
status: GiftStatus;
}) =>
updateGiftItemStatus({
path: { gift_id: id },
body: { status }
}).then((res) => res.data),
onSuccess: () => {
if (currentCategoryId) {
queryClient.invalidateQueries({ queryKey: ["giftItems", currentCategoryId] });
}
if (currentEventId) {
queryClient.invalidateQueries({ queryKey: ["giftItems", null, currentEventId] });
}
if (currentItemId) {
queryClient.invalidateQueries({ queryKey: ["giftItem", currentItemId] });
}
},
});
// Reserve Item Mutation
const reserveItemMutation = useMutation({
mutationFn: ({
id,
guestId,
quantity
}: {
id: string;
guestId: string;
quantity?: number;
}) =>
reserveGiftItem({
path: { gift_id: id },
body: {
guest_id: guestId,
quantity
}
}).then((res) => res.data),
onSuccess: () => {
if (currentCategoryId) {
queryClient.invalidateQueries({ queryKey: ["giftItems", currentCategoryId] });
}
if (currentEventId) {
queryClient.invalidateQueries({ queryKey: ["giftItems", null, currentEventId] });
}
if (currentItemId) {
queryClient.invalidateQueries({ queryKey: ["giftItem", currentItemId] });
}
},
});
// Cancel Reservation Mutation
const cancelReservationMutation = useMutation({
mutationFn: ({
id,
guestId
}: {
id: string;
guestId: string;
}) =>
cancelGiftReservation({
path: {
gift_id: id,
guest_id: guestId
}
}).then((res) => res.data),
onSuccess: () => {
if (currentCategoryId) {
queryClient.invalidateQueries({ queryKey: ["giftItems", currentCategoryId] });
}
if (currentEventId) {
queryClient.invalidateQueries({ queryKey: ["giftItems", null, currentEventId] });
}
if (currentItemId) {
queryClient.invalidateQueries({ queryKey: ["giftItem", currentItemId] });
}
},
});
// Associate Category With Event Mutation
const associateCategoryWithEventMutation = useMutation({
mutationFn: ({
categoryId,
eventId,
displayOrder,
isVisible
}: {
categoryId: string;
eventId: string;
displayOrder?: number;
isVisible?: boolean;
}) =>
associateCategoryWithEvent({
path: {
category_id: categoryId,
event_id: eventId
},
body: {
display_order: displayOrder,
is_visible: isVisible
}
}).then((res) => res.data),
onSuccess: () => {
if (currentEventId) {
queryClient.invalidateQueries({ queryKey: ["giftCategories", currentEventId] });
}
},
});
// Update Category Event Settings Mutation
const updateCategoryEventSettingsMutation = useMutation({
mutationFn: ({
categoryId,
eventId,
displayOrder,
isVisible
}: {
categoryId: string;
eventId: string;
displayOrder?: number;
isVisible?: boolean;
}) =>
updateCategoryEventSettings({
path: {
category_id: categoryId,
event_id: eventId
},
body: {
display_order: displayOrder,
is_visible: isVisible
}
}).then((res) => res.data),
onSuccess: () => {
if (currentEventId) {
queryClient.invalidateQueries({ queryKey: ["giftCategories", currentEventId] });
}
if (currentCategoryId) {
queryClient.invalidateQueries({ queryKey: ["giftCategory", currentCategoryId] });
}
},
});
// Get Events For Category Query
const getEventsForCategoryQuery = async (categoryId: string) => {
return getEventsForCategory({
path: { category_id: categoryId }
}).then((res) => res.data);
};
// Reorder Gifts In Category Mutation
const reorderGiftsInCategoryMutation = useMutation({
mutationFn: ({
categoryId,
giftIds
}: {
categoryId: string;
giftIds: string[];
}) =>
reorderGiftsInCategory({
path: { category_id: categoryId },
body: { gift_ids: giftIds }
}).then((res) => res.data),
onSuccess: () => {
if (currentCategoryId) {
queryClient.invalidateQueries({ queryKey: ["giftCategory", currentCategoryId] });
}
if (currentEventId) {
queryClient.invalidateQueries({ queryKey: ["giftCategories", currentEventId] });
}
},
});
// Fetch all purchases for a gift or guest
const {
data: purchases,
isLoading: isLoadingPurchases,
error: purchasesError,
refetch: refetchPurchasesInternal,
} = useQuery({
queryKey: ["giftPurchases", currentItemId, null],
queryFn: () =>
currentItemId
? readGiftPurchasesByGift({
path: { gift_id: currentItemId }
}).then((res) => res.data)
: Promise.resolve(undefined),
enabled: !!currentItemId,
});
const refetchPurchases = async (giftId?: string, guestId?: string) => {
if (giftId) {
setCurrentItemId(giftId);
}
return refetchPurchasesInternal();
};
// Fetch specific purchase
const {
data: purchase,
isLoading: isLoadingPurchase,
error: purchaseError,
} = useQuery({
queryKey: ["giftPurchase", currentPurchaseId],
queryFn: () =>
currentPurchaseId
? readGiftPurchase({
path: { purchase_id: currentPurchaseId }
}).then((res) => res.data)
: Promise.resolve(undefined),
enabled: !!currentPurchaseId,
});
const fetchPurchaseById = (id: string) => {
setCurrentPurchaseId(id);
};
// Create Purchase Mutation
const createPurchaseMutation = useMutation({
mutationFn: (data: GiftPurchaseCreate) =>
createGiftPurchase({ body: data }).then((res) => res.data),
onSuccess: () => {
if (currentItemId) {
queryClient.invalidateQueries({ queryKey: ["giftPurchases", currentItemId] });
queryClient.invalidateQueries({ queryKey: ["giftItem", currentItemId] });
}
},
});
// Fetch Purchases By Gift
const fetchPurchasesByGiftQuery = async (giftId: string) => {
return readGiftPurchasesByGift({
path: { gift_id: giftId }
}).then((res) => res.data);
};
// Fetch Purchases By Guest
const fetchPurchasesByGuestQuery = async (guestId: string) => {
return readGiftPurchasesByGuest({
path: { guest_id: guestId }
}).then((res) => res.data);
};
const contextValue: GiftContextState = {
// Gift Categories
categories,
category,
isLoadingCategories,
isLoadingCategory,
refetchCategories,
fetchCategoryById,
createCategory: createCategoryMutation.mutateAsync,
updateCategory: (id, data, eventId) =>
updateCategoryMutation.mutateAsync({ id, data, eventId }),
deleteCategory: (id, eventId) =>
deleteCategoryMutation.mutateAsync({ id, eventId }),
// Gift Items
items,
item,
isLoadingItems,
isLoadingItem,
refetchItems,
fetchItemById,
createItem: createItemMutation.mutateAsync,
updateItem: (id, data) =>
updateItemMutation.mutateAsync({ id, data }),
deleteItem: deleteItemMutation.mutateAsync,
updateItemStatus: (id, status) =>
updateItemStatusMutation.mutateAsync({ id, status }),
reserveItem: (id, guestId, quantity) =>
reserveItemMutation.mutateAsync({ id, guestId, quantity }),
cancelReservation: (id, guestId) =>
cancelReservationMutation.mutateAsync({ id, guestId }),
// Gift Categories additional methods
associateCategoryWithEvent: (categoryId, eventId, displayOrder, isVisible) =>
associateCategoryWithEventMutation.mutateAsync({ categoryId, eventId, displayOrder, isVisible }),
updateCategoryEventSettings: (categoryId, eventId, displayOrder, isVisible) =>
updateCategoryEventSettingsMutation.mutateAsync({ categoryId, eventId, displayOrder, isVisible }),
getEventsForCategory: getEventsForCategoryQuery,
reorderGiftsInCategory: (categoryId, giftIds) =>
reorderGiftsInCategoryMutation.mutateAsync({ categoryId, giftIds }),
// Gift Purchases
purchases,
purchase,
isLoadingPurchases,
isLoadingPurchase,
refetchPurchases,
fetchPurchaseById,
createPurchase: createPurchaseMutation.mutateAsync,
fetchPurchasesByGift: fetchPurchasesByGiftQuery,
fetchPurchasesByGuest: fetchPurchasesByGuestQuery,
// Current selections
currentCategoryId,
setCurrentCategoryId,
currentItemId,
setCurrentItemId,
currentPurchaseId,
setCurrentPurchaseId,
currentEventId,
setCurrentEventId,
error: (categoriesError || categoryError || itemsError || itemError || purchasesError || purchaseError) as Error | null,
};
return (
<GiftContext.Provider value={contextValue}>{children}</GiftContext.Provider>
);
};

View File

@@ -3,13 +3,16 @@ 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";
import { GiftProvider } from "@/context/gift-context";
export function DataProviders({ children }: { children: React.ReactNode }) {
return (
<EventThemesProvider>
<EventsProvider>
<GuestsProvider>
<RSVPProvider>{children}</RSVPProvider>
<GiftProvider>
<RSVPProvider>{children}</RSVPProvider>
</GiftProvider>
</GuestsProvider>
</EventsProvider>
</EventThemesProvider>