Vitron-Front/app/requestHandler/use-order-hooks.ts
2026-04-29 01:44:16 +03:30

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;
};