The seller store page (useGetSellerProducts) inherited the global 30s staleTime with refetchOnWindowFocus disabled, so after a seller edited a price the store tiles could keep showing the pre-edit price while the (SSR) single-product page already showed the new one — a list-vs-detail mismatch. The backend serves both from live DB and is consistent. Set staleTime: 0 + refetchOnMount/refetchOnWindowFocus on the storefront query so it revalidates on every visit and tab refocus, keeping the list prices current. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
296 lines
8.6 KiB
TypeScript
296 lines
8.6 KiB
TypeScript
import {
|
|
useQuery,
|
|
useMutation,
|
|
useQueryClient,
|
|
useInfiniteQuery,
|
|
} from "@tanstack/react-query";
|
|
import { useAuthToken } from "../hooks/use-root-data";
|
|
import {
|
|
SellerApplication,
|
|
InstagramVerification,
|
|
} from "../../src/api/types/models";
|
|
import { toast } from "~/hooks/use-toast";
|
|
import { useNavigate } from "react-router-dom";
|
|
import {
|
|
createFollowsManagementApi,
|
|
createSellerManagementApi,
|
|
} from "~/utils/api-client-factory";
|
|
|
|
/**
|
|
* Custom hook to fetch seller data by sellerId using React Query
|
|
*/
|
|
export const useSellerData = (sellerId: string | undefined) => {
|
|
const token = useAuthToken();
|
|
|
|
return useQuery({
|
|
queryKey: ["seller", sellerId],
|
|
queryFn: async () => {
|
|
if (!sellerId) throw new Error("Seller ID is required");
|
|
|
|
const sellerApi = createSellerManagementApi(token);
|
|
return await sellerApi.sellerRead({ username: sellerId });
|
|
},
|
|
enabled: !!sellerId, // Only run the query if sellerId exists
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Custom hook to check if the current user is following a seller
|
|
*/
|
|
export const useSellerFollowStatus = (sellerId: string | undefined) => {
|
|
const token = useAuthToken();
|
|
|
|
return useQuery({
|
|
queryKey: ["sellerFollowStatus", sellerId],
|
|
queryFn: async () => {
|
|
if (!sellerId) throw new Error("Seller ID is required");
|
|
if (!token) throw new Error("Authentication required");
|
|
|
|
try {
|
|
const followsApi = createFollowsManagementApi(token);
|
|
const response = await followsApi.apiEngagementV1FollowsCheckRead({
|
|
sellerId,
|
|
});
|
|
return response?.following;
|
|
} catch (error) {
|
|
return false; // If error is thrown, user is not following the seller
|
|
}
|
|
},
|
|
enabled: !!sellerId && !!token, // Only run the query if sellerId and token exist
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Custom hook to follow a seller
|
|
*/
|
|
export const useSellerFollow = () => {
|
|
const token = useAuthToken();
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async ({ sellerId }: { sellerId: string }) => {
|
|
if (!token) throw new Error("Authentication required");
|
|
|
|
const followsApi = createFollowsManagementApi(token);
|
|
await followsApi.apiEngagementV1FollowsCreate({
|
|
data: { followingId: sellerId },
|
|
});
|
|
return { success: true };
|
|
},
|
|
onSuccess: (_, variables) => {
|
|
// Invalidate and refetch follow status after mutation
|
|
queryClient.invalidateQueries({
|
|
queryKey: ["sellerFollowStatus", variables.sellerId],
|
|
});
|
|
queryClient.invalidateQueries({
|
|
queryKey: ["getFollowedSellersService"],
|
|
});
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Custom hook to unfollow a seller
|
|
*/
|
|
export const useSellerUnfollow = () => {
|
|
const token = useAuthToken();
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async ({ sellerId }: { sellerId: string }) => {
|
|
if (!token) throw new Error("Authentication required");
|
|
|
|
const followsApi = createFollowsManagementApi(token);
|
|
await followsApi.apiEngagementV1FollowsDelete({ sellerId });
|
|
return { success: true };
|
|
},
|
|
onSuccess: (_, variables) => {
|
|
// Invalidate and refetch follow status after mutation
|
|
queryClient.invalidateQueries({
|
|
queryKey: ["sellerFollowStatus", variables.sellerId],
|
|
});
|
|
queryClient.invalidateQueries({
|
|
queryKey: ["getFollowedSellersService"],
|
|
});
|
|
},
|
|
});
|
|
};
|
|
|
|
export const useGetSellerProducts = (
|
|
sellerId: string,
|
|
options?: {
|
|
q?: string;
|
|
priceMin?: number;
|
|
priceMax?: number;
|
|
ordering?: string;
|
|
}
|
|
) => {
|
|
const token = useAuthToken();
|
|
return useInfiniteQuery({
|
|
queryKey: ["sellerProducts", sellerId, options],
|
|
queryFn: async ({ pageParam = 1 }) => {
|
|
const sellerApi = createSellerManagementApi(token);
|
|
return await sellerApi.sellerProductList({
|
|
username: sellerId,
|
|
page: pageParam,
|
|
q: options?.q,
|
|
priceMin: options?.priceMin,
|
|
priceMax: options?.priceMax,
|
|
ordering: options?.ordering,
|
|
});
|
|
},
|
|
getNextPageParam: (lastPage) => {
|
|
if (!lastPage.next) return undefined;
|
|
// Extract page number from the next URL
|
|
const url = new URL(lastPage.next);
|
|
const page = url.searchParams.get("page");
|
|
return page ? parseInt(page, 10) : undefined;
|
|
},
|
|
initialPageParam: 1,
|
|
enabled: !!sellerId,
|
|
// Public storefront: prices/discounts must reflect the seller's latest
|
|
// edits. Always treat as stale and revalidate on mount/refocus so the
|
|
// store list can't keep showing a pre-edit price (the detail page is SSR
|
|
// and already fresh, which caused the list-vs-detail mismatch).
|
|
staleTime: 0,
|
|
refetchOnMount: true,
|
|
refetchOnWindowFocus: true,
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Custom hook to fetch seller categories
|
|
*/
|
|
export const useSellerCategories = () => {
|
|
return useQuery({
|
|
queryKey: ["sellerCategories"],
|
|
queryFn: async () => {
|
|
const sellerApi = createSellerManagementApi();
|
|
return await sellerApi.apiSellerV1CategoriesList();
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Custom hook to fetch state and city data
|
|
*/
|
|
export const useStateCityData = () => {
|
|
return useQuery({
|
|
queryKey: ["stateCityData"],
|
|
queryFn: async () => {
|
|
const sellerApi = createSellerManagementApi();
|
|
return await sellerApi.apiSellerV1StateCityList();
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Custom hook to create seller application
|
|
*/
|
|
export const useCreateSellerApplication = () => {
|
|
const token = useAuthToken();
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async (data: SellerApplication) => {
|
|
if (!token) throw new Error("Authentication required");
|
|
const sellerApi = createSellerManagementApi(token);
|
|
return await sellerApi.apiSellerV1ApplyCreate({ data });
|
|
},
|
|
onSuccess: () => {
|
|
// Invalidate profile query to refresh sellerUsername
|
|
queryClient.invalidateQueries({ queryKey: ["getProfile"] });
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Custom hook to apply Instagram verification
|
|
*/
|
|
export const useInstagramVerification = () => {
|
|
const token = useAuthToken();
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async (
|
|
data: InstagramVerification
|
|
): Promise<InstagramVerification> => {
|
|
if (!token) throw new Error("Authentication required");
|
|
|
|
const sellerApi = createSellerManagementApi(token);
|
|
return await sellerApi.apiSellerV1InstapplyCreate({ data });
|
|
},
|
|
onSuccess: (data: InstagramVerification) => {
|
|
if (data?.isVerified) {
|
|
toast({
|
|
title: "احراز اکانت اینستاگرام با موفقیت انجام شد",
|
|
description: "اکانت اینستاگرام شما با موفقیت احراز شد",
|
|
});
|
|
// Invalidate seller data after successful verification
|
|
queryClient.invalidateQueries({
|
|
queryKey: ["seller"],
|
|
});
|
|
} else {
|
|
toast({
|
|
title: "احراز اکانت اینستاگرام با موفقیت انجام نشد",
|
|
description: "لطفا مجددا تلاش کنید",
|
|
variant: "destructive",
|
|
});
|
|
}
|
|
},
|
|
onError: () => {
|
|
toast({
|
|
title: "احراز اکانت اینستاگرام با موفقیت انجام نشد",
|
|
description: "لطفا مجددا تلاش کنید",
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Custom hook to update seller information
|
|
*/
|
|
export const useSellerUpdate = () => {
|
|
const token = useAuthToken();
|
|
const queryClient = useQueryClient();
|
|
const navigate = useNavigate();
|
|
return useMutation({
|
|
mutationFn: async ({
|
|
username,
|
|
data,
|
|
}: {
|
|
username: string;
|
|
data: {
|
|
storeName: string;
|
|
storeDescription?: string | null;
|
|
storeLogo?: string | null;
|
|
instagramId?: string | null;
|
|
};
|
|
}) => {
|
|
if (!token) throw new Error("Authentication required");
|
|
|
|
const sellerApi = createSellerManagementApi(token);
|
|
return await sellerApi.sellerPartialUpdate({ username, data });
|
|
},
|
|
onSuccess: (_, variables) => {
|
|
toast({
|
|
title: "اطلاعات فروشگاه با موفقیت بهروزرسانی شد",
|
|
description: "تغییرات شما ذخیره شد",
|
|
});
|
|
// Invalidate seller data after successful update
|
|
queryClient.invalidateQueries({
|
|
queryKey: ["seller", variables.username],
|
|
});
|
|
navigate(`/store/${variables.username}`);
|
|
},
|
|
onError: () => {
|
|
toast({
|
|
title: "خطا در بهروزرسانی اطلاعات",
|
|
description: "لطفا مجددا تلاش کنید",
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
});
|
|
};
|