freshyo/apps/user-ui/hooks/cart-query-hooks.tsx
2026-01-24 00:13:15 +05:30

501 lines
No EOL
15 KiB
TypeScript

import { trpc } from '@/src/trpc-client';
import { Alert } from 'react-native';
import { useState, useEffect } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { StorageServiceCasual } from 'common-ui/src/services/StorageServiceCasual';
// Cart type definition
export type CartType = "regular" | "flash";
// const CART_MODE: 'remote' | 'local' = 'remote';
const CART_MODE: 'remote' | 'local' = 'local';
const getCartStorageKey = (cartType: CartType = "regular"): string => {
return cartType === "flash" ? "flash_cart_items" : "cart_items";
};
interface LocalCartItem {
id: number;
productId: number;
quantity: number;
slotId: number;
addedAt: string;
}
interface ProductSummary {
id: number;
name: string;
price: number;
unit: string;
isOutOfStock: boolean;
images: string[];
incrementStep?: number;
}
interface CartItem {
id: number;
productId: number;
quantity: number;
addedAt: string;
product: ProductSummary;
subtotal: number;
}
const getLocalCart = async (cartType: CartType = "regular"): Promise<LocalCartItem[]> => {
const key = getCartStorageKey(cartType);
const data = await StorageServiceCasual.getItem(key);
return data ? JSON.parse(data) : [];
};
const saveLocalCart = async (items: LocalCartItem[], cartType: CartType = "regular"): Promise<void> => {
const key = getCartStorageKey(cartType);
await StorageServiceCasual.setItem(key, JSON.stringify(items));
const fetchedItems = await getLocalCart(cartType);
};
const getNextCartItemId = (items: LocalCartItem[]): number => {
const maxId = items.length > 0 ? Math.max(...items.map(item => item.id)) : 0;
return maxId + 1;
};
const addToLocalCart = async (productId: number, quantity: number, slotId?: number, cartType: CartType = "regular"): Promise<LocalCartItem[]> => {
const items = await getLocalCart(cartType);
const existingIndex = items.findIndex(item => item.productId === productId);
if (existingIndex >= 0) {
items[existingIndex].quantity += quantity;
if (slotId !== undefined) {
items[existingIndex].slotId = slotId;
}
} else {
const newId = getNextCartItemId(items);
const cartItem = {
id: newId,
productId,
quantity,
slotId: slotId ?? 0, // Default to 0 if not provided
addedAt: new Date().toISOString(),
}
items.push(cartItem);
}
await saveLocalCart(items, cartType);
return items;
};
const updateLocalCartItem = async (itemId: number, quantity: number, cartType: CartType = "regular"): Promise<LocalCartItem[]> => {
const items = await getLocalCart(cartType);
const item = items.find(i => i.id === itemId);
if (item) {
item.quantity = quantity;
await saveLocalCart(items, cartType);
}
return items;
};
const removeFromLocalCart = async (itemId: number, cartType: CartType = "regular"): Promise<LocalCartItem[]> => {
const items = await getLocalCart(cartType);
const filtered = items.filter(i => i.id !== itemId);
await saveLocalCart(filtered, cartType);
return filtered;
};
const clearLocalCart = async (cartType: CartType = "regular"): Promise<void> => {
const key = getCartStorageKey(cartType);
await StorageServiceCasual.setItem(key, JSON.stringify([]));
};
export function useGetCart(options?: {
refetchOnWindowFocus?: boolean;
enabled?: boolean;
}, cartType: CartType = "regular") {
if (CART_MODE === 'remote') {
const query = trpc.user.cart.getCart.useQuery(undefined, {
refetchOnWindowFocus: options?.refetchOnWindowFocus ?? true,
enabled: options?.enabled ?? true,
...options
});
return {
// Original tRPC returns
data: query.data,
isLoading: query.isLoading,
error: query.error,
refetch: query.refetch,
// Computed properties
cartItems: query.data?.items || [],
totalItems: query.data?.totalItems || 0,
totalPrice: query.data?.totalAmount || 0,
// Helper methods
isEmpty: !query.data?.items?.length,
hasItems: Boolean(query.data?.items?.length),
};
} else {
const { data: products } = trpc.user.product.getAllProductsSummary.useQuery();
const query = useQuery({
queryKey: [`local-cart-${cartType}`],
queryFn: async () => {
const cartItems = await getLocalCart(cartType);
// const productMap = Object.fromEntries(products?.map((p: ProductSummary) => [p.id, p]) || []);
const productMap = Object.fromEntries(products?.map((p) => [p.id, p]) || []);
const items: CartItem[] = cartItems.map(cartItem => {
const product = productMap[cartItem.productId];
if (!product) return null as any;
return {
id: cartItem.id,
productId: cartItem.productId,
quantity: cartItem.quantity,
addedAt: cartItem.addedAt,
product,
incrementStep: product.incrementStep,
subtotal: Number(product.price) * cartItem.quantity,
slotId: cartItem.slotId,
};
}).filter(Boolean) as CartItem[];
const totalAmount = items.reduce((sum, item) => sum + item.subtotal, 0);
return {
items,
totalItems: items.length,
totalAmount,
};
},
refetchOnWindowFocus: options?.refetchOnWindowFocus ?? true,
enabled: (options?.enabled ?? true) && !!products,
});
return {
data: query.data,
isLoading: query.isLoading,
error: query.error,
refetch: query.refetch,
// Computed properties
cartItems: query.data?.items || [],
totalItems: query.data?.totalItems || 0,
totalPrice: query.data?.totalAmount || 0,
// Helper methods
isEmpty: !query.data?.items?.length,
hasItems: Boolean(query.data?.items?.length),
};
}
}
interface UseAddToCartReturn {
mutate: any;
mutateAsync: any;
isLoading: boolean;
error: any;
data: any;
addToCart: (productId: number, quantity?: number, slotId?: number, onSettled?: (data: any, error: any) => void) => void;
addToCartAsync: (productId: number, quantity?: number, slotId?: number) => Promise<any>;
}
export function useAddToCart(options?: {
onSuccess?: (data: any, variables: any) => void;
onError?: (error: any) => void;
showSuccessAlert?: boolean;
showErrorAlert?: boolean;
refetchCart?: boolean;
}, cartType: CartType = "regular"): UseAddToCartReturn {
if (CART_MODE === 'remote') {
const utils = trpc.useUtils();
const mutation = trpc.user.cart.addToCart.useMutation({
onSuccess: (data, variables) => {
// Default success handling
if (options?.showSuccessAlert !== false) {
Alert.alert("Success", "Item added to cart!");
}
// Auto-refetch cart if requested
if (options?.refetchCart) {
utils.user.cart.getCart.invalidate();
}
// Custom success callback
options?.onSuccess?.(data, variables);
},
onError: (error) => {
// Default error handling
if (options?.showErrorAlert !== false) {
Alert.alert("Error", error.message || "Failed to add item to cart");
}
// Custom error callback
options?.onError?.(error);
},
}) as any;
const addToCart = (productId: number, quantity = 1, slotId?: number, onSettled?: (data: any, error: any) => void) => {
if (slotId == null) {
throw new Error('slotId is required for adding to cart');
}
return mutation.mutate({ productId, quantity, slotId }, {
onSettled: (data: any, error: any) => {
onSettled?.(data, error);
}
});
};
return {
// Original mutation returns
mutate: mutation.mutate,
mutateAsync: mutation.mutateAsync,
isLoading: mutation.isPending,
error: mutation.error,
data: mutation.data,
addToCart,
addToCartAsync: (productId: number, quantity = 1, slotId?: number) => {
if (slotId == null) {
throw new Error('slotId is required for adding to cart');
}
return mutation.mutateAsync({ productId, quantity, slotId });
},
};
} else {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: async ({ productId, quantity, slotId }: { productId: number, quantity: number, slotId: number }) => {
return await addToLocalCart(productId, quantity, slotId, cartType);
},
onSuccess: (data, variables) => {
queryClient.invalidateQueries({ queryKey: [`local-cart-${cartType}`] });
if (options?.showSuccessAlert !== false) {
Alert.alert("Success", "Item added to cart!");
}
options?.onSuccess?.(data, variables);
},
onError: (error) => {
if (options?.showErrorAlert !== false) {
Alert.alert("Error", error.message || "Failed to add item to cart");
}
options?.onError?.(error);
},
});
const addToCart = (productId: number, quantity = 1, slotId?: number, onSettled?: (data: any, error: any) => void) => {
if (slotId == null) {
throw new Error('slotId is required for adding to cart');
}
return mutation.mutate({ productId, quantity, slotId }, {
onSettled: (data: any, error: any) => {
onSettled?.(data, error);
}
});
};
return {
mutate: mutation.mutate,
mutateAsync: mutation.mutateAsync,
isLoading: mutation.isPending,
error: mutation.error,
data: mutation.data,
addToCart,
addToCartAsync: (productId: number, quantity = 1, slotId?: number) => {
if (slotId == null) {
throw new Error('slotId is required for adding to cart');
}
return mutation.mutateAsync({ productId, quantity, slotId });
},
};
}
}
export function useUpdateCartItem(options?: {
onSuccess?: (data: any, variables: any) => void;
onError?: (error: any) => void;
showSuccessAlert?: boolean;
showErrorAlert?: boolean;
refetchCart?: boolean;
}, cartType: CartType = "regular") {
if (CART_MODE === 'remote') {
const utils = trpc.useUtils();
const mutation = trpc.user.cart.updateCartItem.useMutation({
onSuccess: (data, variables) => {
// Default success handling
if (options?.showSuccessAlert !== false) {
Alert.alert("Success", "Cart item updated!");
}
// Auto-refetch cart if requested
if (options?.refetchCart) {
utils.user.cart.getCart.invalidate();
}
// Custom success callback
options?.onSuccess?.(data, variables);
},
onError: (error) => {
// Default error handling
if (options?.showErrorAlert !== false) {
Alert.alert("Error", error.message || "Failed to update cart item");
}
// Custom error callback
options?.onError?.(error);
},
});
return {
// Original mutation returns
mutate: mutation.mutate,
mutateAsync: mutation.mutateAsync,
isLoading: mutation.isPending,
error: mutation.error,
data: mutation.data,
// Helper methods
updateCartItem: (itemId: number, quantity: number) =>
mutation.mutate({ itemId, quantity }),
updateCartItemAsync: (itemId: number, quantity: number) =>
mutation.mutateAsync({ itemId, quantity }),
};
} else {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: async ({ itemId, quantity }: { itemId: number, quantity: number }) => {
return await updateLocalCartItem(itemId, quantity, cartType);
},
onSuccess: (data, variables) => {
queryClient.invalidateQueries({ queryKey: [`local-cart-${cartType}`] });
if (options?.showSuccessAlert !== false) {
Alert.alert("Success", "Cart item updated!");
}
options?.onSuccess?.(data, variables);
},
onError: (error) => {
if (options?.showErrorAlert !== false) {
Alert.alert("Error", error.message || "Failed to update cart item");
}
options?.onError?.(error);
},
});
return {
mutate: mutation.mutate,
mutateAsync: mutation.mutateAsync,
isLoading: mutation.isPending,
error: mutation.error,
data: mutation.data,
updateCartItem: (itemId: number, quantity: number) =>
mutation.mutate({ itemId, quantity }),
updateCartItemAsync: (itemId: number, quantity: number) =>
mutation.mutateAsync({ itemId, quantity }),
};
}
}
export function useRemoveFromCart(options?: {
onSuccess?: (data: any, variables: any) => void;
onError?: (error: any) => void;
showSuccessAlert?: boolean;
showErrorAlert?: boolean;
refetchCart?: boolean;
}, cartType: CartType = "regular") {
if (CART_MODE === 'remote') {
const utils = trpc.useUtils();
const mutation = trpc.user.cart.removeFromCart.useMutation({
onSuccess: (data, variables) => {
// Default success handling
if (options?.showSuccessAlert !== false) {
Alert.alert("Success", "Item removed from cart!");
}
// Auto-refetch cart if requested
if (options?.refetchCart) {
utils.user.cart.getCart.invalidate();
}
// Custom success callback
options?.onSuccess?.(data, variables);
},
onError: (error) => {
// Default error handling
if (options?.showErrorAlert !== false) {
Alert.alert("Error", error.message || "Failed to remove item from cart");
}
// Custom error callback
options?.onError?.(error);
},
});
return {
// Original mutation returns
mutate: mutation.mutate,
mutateAsync: mutation.mutateAsync,
isLoading: mutation.isPending,
error: mutation.error,
data: mutation.data,
// Helper methods
removeFromCart: (itemId: number) =>
mutation.mutate({ itemId }),
removeFromCartAsync: (itemId: number) =>
mutation.mutateAsync({ itemId }),
};
} else {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: async ({ itemId }: { itemId: number }) => {
return await removeFromLocalCart(itemId, cartType);
},
onSuccess: (data, variables) => {
queryClient.invalidateQueries({ queryKey: [`local-cart-${cartType}`] });
if (options?.showSuccessAlert !== false) {
Alert.alert("Success", "Item removed from cart!");
}
options?.onSuccess?.(data, variables);
},
onError: (error) => {
if (options?.showErrorAlert !== false) {
Alert.alert("Error", error.message || "Failed to remove item from cart");
}
options?.onError?.(error);
},
});
return {
mutate: mutation.mutate,
mutateAsync: mutation.mutateAsync,
isLoading: mutation.isPending,
error: mutation.error,
data: mutation.data,
removeFromCart: (itemId: number) =>
mutation.mutate({ itemId }),
removeFromCartAsync: (itemId: number) =>
mutation.mutateAsync({ itemId }),
};
}
}
// Export clear cart function for direct use
export { clearLocalCart };