510 lines
No EOL
15 KiB
TypeScript
510 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;
|
|
shortDescription?: string | null;
|
|
longDescription?: string | null;
|
|
price: string;
|
|
marketPrice?: string | null;
|
|
unitNotation: string;
|
|
images: string[];
|
|
isOutOfStock: boolean;
|
|
store?: { id: number; name: string; description?: string | null } | null;
|
|
incrementStep: number;
|
|
productQuantity: number;
|
|
isFlashAvailable: boolean;
|
|
flashPrice?: string | null;
|
|
deliverySlots: Array<{ id: number; deliveryTime: Date; freezeTime: Date }>;
|
|
specialDeals: Array<{ quantity: string; price: string; validTill: Date }>;
|
|
}
|
|
|
|
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 }; |