376 lines
11 KiB
TypeScript
376 lines
11 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import { useAuthToken } from "../hooks/use-root-data";
|
|
import { useToast } from "../hooks/use-toast";
|
|
import { useNavigate } from "@remix-run/react";
|
|
import {
|
|
CashOut,
|
|
CashOutGatewayResponse,
|
|
SellerOrderUpdate,
|
|
} from "../../src/api/types";
|
|
import { useState, useEffect } from "react";
|
|
import {
|
|
createShoppingCartApi,
|
|
createOrderManagementApi,
|
|
createApiApi,
|
|
} from "~/utils/api-client-factory";
|
|
|
|
// Order query keys
|
|
export const orderKeys = {
|
|
all: ["orders"] as const,
|
|
lists: () => [...orderKeys.all, "list"] as const,
|
|
list: (filters: string) => [...orderKeys.lists(), { filters }] as const,
|
|
details: () => [...orderKeys.all, "detail"] as const,
|
|
detail: (id: string) => [...orderKeys.details(), id] as const,
|
|
};
|
|
|
|
/**
|
|
* Hook for fetching the authenticated user's orders
|
|
* @returns Query result with the user's orders
|
|
*/
|
|
export function useGetUserOrders() {
|
|
const token = useAuthToken();
|
|
|
|
return useQuery({
|
|
queryKey: orderKeys.lists(),
|
|
queryFn: async () => {
|
|
if (!token) {
|
|
throw new Error("Authentication token is required");
|
|
}
|
|
|
|
try {
|
|
const orderApi = createApiApi(token);
|
|
return await orderApi.apiOrderV1OrdersList();
|
|
} catch (error) {
|
|
console.error("Error fetching user orders:", error);
|
|
throw error;
|
|
}
|
|
},
|
|
enabled: !!token,
|
|
staleTime: 60 * 1000, // 1 minute cache
|
|
refetchOnWindowFocus: false,
|
|
});
|
|
}
|
|
|
|
export function useCashOutCart(cartId: string) {
|
|
const token = useAuthToken();
|
|
const queryClient = useQueryClient();
|
|
const { toast } = useToast();
|
|
const navigate = useNavigate();
|
|
|
|
return useMutation<CashOutGatewayResponse, Error, CashOut>({
|
|
mutationFn: async (payload: CashOut) => {
|
|
try {
|
|
if (!token) {
|
|
throw new Error("Authentication required");
|
|
}
|
|
const shoppingCartApi = createShoppingCartApi(token);
|
|
const response = await shoppingCartApi.apiShoppingcartV1CashOutCreate({
|
|
data: {
|
|
shippingInfoId: payload.shippingInfoId,
|
|
},
|
|
});
|
|
return response;
|
|
} catch (error) {
|
|
console.error(`Error during cash out for cart ${cartId}:`, error);
|
|
throw error; // Re-throw the error to be caught by the hook
|
|
}
|
|
},
|
|
onSuccess: (data) => {
|
|
queryClient.invalidateQueries({ queryKey: ["carts", "detail", cartId] });
|
|
queryClient.invalidateQueries({ queryKey: ["carts", "list"] });
|
|
queryClient.invalidateQueries({ queryKey: ["carts", "count"] });
|
|
queryClient.invalidateQueries({ queryKey: ["getWalletService"] });
|
|
|
|
if (data.redirect) {
|
|
if (data.redirect.startsWith("http")) {
|
|
// External URL - use window.location
|
|
window.location.href = data.redirect;
|
|
} else {
|
|
// Internal route - use navigate
|
|
navigate(data.redirect);
|
|
}
|
|
} else {
|
|
toast({
|
|
title: "Success",
|
|
description: "Order created successfully!",
|
|
});
|
|
// Navigate to orders page if no redirect URL is provided
|
|
navigate("/orders");
|
|
}
|
|
},
|
|
onError: (error) => {
|
|
toast({
|
|
title: "Error",
|
|
description: `Failed to create order: ${error.message}`,
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useGetInvoiceAndShippingInfo(
|
|
invoiceNumber?: string,
|
|
shippingInfoId?: string
|
|
) {
|
|
const token = useAuthToken();
|
|
|
|
return useQuery({
|
|
queryKey: ["invoice", invoiceNumber, shippingInfoId],
|
|
queryFn: async () => {
|
|
if (!token) {
|
|
throw new Error("Authentication token is required");
|
|
}
|
|
|
|
if (!invoiceNumber || !shippingInfoId) {
|
|
throw new Error("Invoice number and shipping info ID are required");
|
|
}
|
|
|
|
try {
|
|
const orderApi = createOrderManagementApi(token);
|
|
return await orderApi.apiOrderV1InfoList({
|
|
invoiceNumber,
|
|
shippingInfoId,
|
|
});
|
|
} catch (error) {
|
|
console.error("Error fetching invoice and shipping info:", error);
|
|
throw error;
|
|
}
|
|
},
|
|
enabled: !!invoiceNumber && !!shippingInfoId && !!token,
|
|
staleTime: 60 * 1000, // 1 minute cache
|
|
refetchOnWindowFocus: false,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Hook for fetching a specific order's details
|
|
* @param orderId - The order ID to fetch
|
|
* @returns Query result with the order details
|
|
*/
|
|
export function useGetOrderDetail(orderId: string | null) {
|
|
const token = useAuthToken();
|
|
|
|
return useQuery({
|
|
queryKey: [...orderKeys.details(), orderId || "null"],
|
|
queryFn: async () => {
|
|
if (!orderId) return null;
|
|
if (!token) {
|
|
throw new Error("Authentication token is required");
|
|
}
|
|
|
|
try {
|
|
const orderApi = createOrderManagementApi(token);
|
|
return await orderApi.apiOrderV1OrderDetailList({ orderId });
|
|
} catch (error) {
|
|
console.error("Error fetching order details:", error);
|
|
throw error;
|
|
}
|
|
},
|
|
enabled: !!orderId && !!token,
|
|
staleTime: 5 * 60 * 1000, // 1 minute cache
|
|
refetchOnWindowFocus: false,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Hook for fetching the seller's orders
|
|
* @returns Query result with the seller's orders
|
|
*/
|
|
export function useGetSellerOrders() {
|
|
const token = useAuthToken();
|
|
|
|
return useQuery({
|
|
queryKey: [...orderKeys.all, "seller"],
|
|
queryFn: async () => {
|
|
if (!token) {
|
|
throw new Error("Authentication token is required");
|
|
}
|
|
|
|
try {
|
|
const orderApi = createOrderManagementApi(token);
|
|
return await orderApi.apiOrderV1SellerordersList();
|
|
} catch (error) {
|
|
console.error("Error fetching seller orders:", error);
|
|
throw error;
|
|
}
|
|
},
|
|
enabled: !!token,
|
|
staleTime: 30 * 60 * 1000, // 30 seconds - balance between freshness and performance
|
|
refetchOnMount: false, // Always refetch when component mounts
|
|
refetchOnWindowFocus: false,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Hook for fetching a specific order's details for seller
|
|
* @param orderId - The order ID to fetch
|
|
* @returns Query result with the order details for seller
|
|
*/
|
|
export function useGetSellerOrderDetail(orderId: string | null) {
|
|
const token = useAuthToken();
|
|
|
|
return useQuery({
|
|
queryKey: [...orderKeys.all, "seller", "detail", orderId || "null"],
|
|
queryFn: async () => {
|
|
if (!orderId) return null;
|
|
if (!token) {
|
|
throw new Error("Authentication token is required");
|
|
}
|
|
|
|
try {
|
|
const orderApi = createOrderManagementApi(token);
|
|
return await orderApi.apiOrderV1SellerordersDetailRead({ id: orderId });
|
|
} catch (error) {
|
|
console.error("Error fetching seller order details:", error);
|
|
throw error;
|
|
}
|
|
},
|
|
enabled: !!orderId && !!token,
|
|
staleTime: 5 * 60 * 1000, // 5 minute cache
|
|
refetchOnWindowFocus: false,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Hook for updating a seller order (accept/reject order, add tracking code, etc.)
|
|
* @returns Mutation for updating seller order
|
|
*/
|
|
export function useUpdateSellerOrder() {
|
|
const token = useAuthToken();
|
|
const queryClient = useQueryClient();
|
|
const { toast } = useToast();
|
|
|
|
return useMutation<
|
|
void,
|
|
Error,
|
|
{ orderId: string; data: SellerOrderUpdate },
|
|
{ previousSellerOrders?: unknown; previousOrderDetail?: unknown }
|
|
>({
|
|
mutationFn: async ({ orderId, data }) => {
|
|
if (!token) {
|
|
throw new Error("Authentication required");
|
|
}
|
|
|
|
try {
|
|
const orderApi = createOrderManagementApi(token);
|
|
await orderApi.apiOrderV1SellerordersPartialUpdate({
|
|
id: orderId,
|
|
data,
|
|
});
|
|
} catch (error) {
|
|
console.error(`Error updating seller order ${orderId}:`, error);
|
|
throw error;
|
|
}
|
|
},
|
|
// Optimistically update the cache before the mutation
|
|
onMutate: async ({ orderId }) => {
|
|
// Cancel any outgoing refetches to prevent race conditions
|
|
await queryClient.cancelQueries({
|
|
queryKey: [...orderKeys.all, "seller"],
|
|
});
|
|
await queryClient.cancelQueries({
|
|
queryKey: [...orderKeys.all, "seller", "detail", orderId],
|
|
});
|
|
|
|
// Snapshot the previous values
|
|
const previousSellerOrders = queryClient.getQueryData([
|
|
...orderKeys.all,
|
|
"seller",
|
|
]);
|
|
const previousOrderDetail = queryClient.getQueryData([
|
|
...orderKeys.all,
|
|
"seller",
|
|
"detail",
|
|
orderId,
|
|
]);
|
|
|
|
return { previousSellerOrders, previousOrderDetail };
|
|
},
|
|
onSuccess: async (_, { orderId }) => {
|
|
// Invalidate and refetch relevant queries to refresh the data
|
|
await queryClient.invalidateQueries({
|
|
queryKey: [...orderKeys.all, "seller"],
|
|
});
|
|
await queryClient.invalidateQueries({
|
|
queryKey: [...orderKeys.all, "seller", "detail", orderId],
|
|
});
|
|
|
|
toast({
|
|
title: "موفق",
|
|
description: "سفارش با موفقیت بروزرسانی شد",
|
|
});
|
|
},
|
|
onError: (error, _, context) => {
|
|
// Rollback to previous values on error
|
|
if (context?.previousSellerOrders) {
|
|
queryClient.setQueryData(
|
|
[...orderKeys.all, "seller"],
|
|
context.previousSellerOrders
|
|
);
|
|
}
|
|
if (context?.previousOrderDetail) {
|
|
queryClient.setQueryData(
|
|
[...orderKeys.all, "seller", "detail"],
|
|
context.previousOrderDetail
|
|
);
|
|
}
|
|
|
|
console.error("Update seller order error:", error);
|
|
toast({
|
|
title: "خطا",
|
|
description: `خطا در بروزرسانی سفارش: ${error.message}`,
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Hook for getting seller order count
|
|
* @returns Query result with the seller order count
|
|
*/
|
|
export function useGetSellerOrderCount(enabled: boolean) {
|
|
const token = useAuthToken();
|
|
|
|
return useQuery({
|
|
queryKey: [...orderKeys.all, "seller", "count"],
|
|
queryFn: async () => {
|
|
if (!token) {
|
|
return 0;
|
|
}
|
|
|
|
try {
|
|
const orderApi = createOrderManagementApi(token);
|
|
const orders = await orderApi.apiOrderV1SellerordersList();
|
|
return orders.filter((order) => order.status === "pending").length || 0;
|
|
} catch (error) {
|
|
console.error("Error fetching seller order count:", error);
|
|
return 0;
|
|
}
|
|
},
|
|
enabled: !!token && enabled,
|
|
staleTime: 2 * 60 * 1000, // 2 minutes - order count doesn't change frequently
|
|
gcTime: 5 * 60 * 1000, // 5 minutes
|
|
refetchOnWindowFocus: false,
|
|
refetchOnMount: false,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Convenient hook for getting seller order count with local state management
|
|
* Similar to useCartCount hook
|
|
*/
|
|
export const useSellerOrderCount = (enabled: boolean) => {
|
|
const [count, setCount] = useState(0);
|
|
const { data: orderCount, isLoading } = useGetSellerOrderCount(enabled);
|
|
|
|
// Update local state whenever the query data changes
|
|
useEffect(() => {
|
|
if (!isLoading && orderCount !== undefined) {
|
|
setCount(orderCount);
|
|
}
|
|
}, [orderCount, isLoading]);
|
|
|
|
return count;
|
|
};
|