Vitron-Front/app/requestHandler/use-home-hooks.ts
fazli 272362cfdf fix(product): detail page price now matches list tiles + fresher feeds
Single-product page showed basePrice with no discount while list tiles
showed the cheapest variant's discounted price. Cause: the detail view
defaulted selectedColor/selectedSize to the first in-stock color and the
first in-stock size independently — a combo that often isn't a real
variant, so no variant matched and it fell back to basePrice / no sale.

- ProductDetailView: derive a `defaultVariant` (cheapest in-stock variant
  by final price, == the tile's min/discount price) and use it for the
  initial selection, the initial displayed price, and the fallback when no
  exact color/size combo is matched. A shared `pricingOf` helper applies
  the same discount rule everywhere. Detail price now matches the tile.
- use-home-hooks: drop staleTime 5m -> 60s on the For You / discounts /
  top-sellers feeds so price/discount edits show up promptly in lists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 19:55:11 +00:00

229 lines
7.0 KiB
TypeScript

import { useQuery, useInfiniteQuery } from "@tanstack/react-query";
import { useAuthToken } from "../hooks/use-root-data";
import { createHomepageApi } from "~/utils/api-client-factory";
// Home query keys
export const homeKeys = {
all: ["home"] as const,
banners: () => [...homeKeys.all, "banners"] as const,
categories: () => [...homeKeys.all, "categories"] as const,
discounts: () => [...homeKeys.all, "discounts"] as const,
forYou: () => [...homeKeys.all, "forYou"] as const,
topSellers: () => [...homeKeys.all, "topSellers"] as const,
sellerTiles: () => [...homeKeys.all, "sellerTiles"] as const,
sellerTilesAll: () => [...homeKeys.all, "sellerTilesAll"] as const,
};
/**
* Hook for fetching home page banners
* @returns Query result with home page banners
*/
export function useGetHomeBanners() {
const token = useAuthToken();
return useQuery({
queryKey: homeKeys.banners(),
queryFn: async () => {
try {
console.log("[Home Hooks] Fetching banners - Token:", !!token);
const homeApi = createHomepageApi(token);
console.log("[Home Hooks] HomepageApi created, making API call...");
const result = await homeApi.apiHomeV1BannersList();
console.log("[Home Hooks] Banners fetched successfully:", result);
return result;
} catch (error) {
console.error("Error fetching home banners:", error);
throw error;
}
},
staleTime: 5 * 60 * 1000, // 5 minutes cache
refetchOnWindowFocus: false,
});
}
/**
* Hook for fetching home page discounts with cursor pagination
* @returns Infinite query result with home page discounts
*/
export function useGetHomeDiscounts() {
const token = useAuthToken();
return useInfiniteQuery({
queryKey: homeKeys.discounts(),
queryFn: async ({ pageParam }) => {
try {
const homeApi = createHomepageApi(token);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result = await homeApi.apiHomeV1DiscountsList({
cursor: pageParam,
pageSize: 20,
});
// API returns { next, previous, results } but type says Array
// Return the whole response to access 'next' in getNextPageParam
return result;
} catch (error) {
console.error("Error fetching home discounts:", error);
throw error;
}
},
initialPageParam: undefined as string | undefined,
getNextPageParam: (lastPage) => {
// Extract cursor from next URL
if (lastPage?.next) {
const url = new URL(lastPage.next);
return url.searchParams.get("cursor");
}
return undefined;
},
staleTime: 60 * 1000, // 60s — prices/discounts must stay reasonably fresh
refetchOnWindowFocus: false,
});
}
/**
* Hook for fetching home page "For You" products with infinite scroll
* @returns Infinite query result with personalized products
*/
export function useGetHomeForYou() {
const token = useAuthToken();
return useInfiniteQuery({
queryKey: homeKeys.forYou(),
queryFn: async ({ pageParam }) => {
try {
const homeApi = createHomepageApi(token);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result = await homeApi.apiHomeV1ForYouList({
cursor: pageParam,
pageSize: 20,
});
// API returns { next, previous, results } but type says Array
// Return the whole response to access 'next' in getNextPageParam
return result;
} catch (error) {
console.error("Error fetching home 'For You' products:", error);
throw error;
}
},
initialPageParam: undefined as string | undefined,
getNextPageParam: (lastPage) => {
// Extract cursor from next URL
if (lastPage?.next) {
const url = new URL(lastPage.next);
return url.searchParams.get("cursor");
}
return undefined;
},
staleTime: 60 * 1000, // 60s — prices/discounts must stay reasonably fresh
refetchOnWindowFocus: false,
});
}
/**
* Hook for fetching home page top sellers with cursor pagination
* @returns Infinite query result with top selling products
*/
export function useGetHomeTopSellers() {
const token = useAuthToken();
return useInfiniteQuery({
queryKey: homeKeys.topSellers(),
queryFn: async ({ pageParam }) => {
try {
const homeApi = createHomepageApi(token);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result = await homeApi.apiHomeV1TopSellersList({
cursor: pageParam,
pageSize: 20,
});
// API returns { next, previous, results } but type says Array
// Return the whole response to access 'next' in getNextPageParam
return result;
} catch (error) {
console.error("Error fetching home top sellers:", error);
throw error;
}
},
initialPageParam: undefined as string | undefined,
getNextPageParam: (lastPage) => {
// Extract cursor from next URL
if (lastPage?.next) {
const url = new URL(lastPage.next);
return url.searchParams.get("cursor");
}
return undefined;
},
staleTime: 60 * 1000, // 60s — prices/discounts must stay reasonably fresh
refetchOnWindowFocus: false,
});
}
/**
* Hook for fetching home page categories
* @returns Query result with home page categories
*/
export function useGetHomeCategories() {
const token = useAuthToken();
return useQuery({
queryKey: homeKeys.categories(),
queryFn: async () => {
try {
const homeApi = createHomepageApi(token);
return await homeApi.apiHomeV1CategoriesList();
} catch (error) {
console.error("Error fetching home categories:", error);
throw error;
}
},
staleTime: 20 * 60 * 1000, // 5 minutes cache
refetchOnWindowFocus: false,
});
}
/**
* Hook for fetching top 4 seller tiles for homepage
* @returns Query result with seller tiles
*/
export function useGetHomeSellerTiles() {
const token = useAuthToken();
return useQuery({
queryKey: homeKeys.sellerTiles(),
queryFn: async () => {
try {
const homeApi = createHomepageApi(token);
return await homeApi.apiHomeV1SellerTilesList();
} catch (error) {
console.error("Error fetching home seller tiles:", error);
throw error;
}
},
staleTime: 5 * 60 * 1000,
refetchOnWindowFocus: false,
});
}
/**
* Hook for fetching all seller tiles for 'See more' page
* @returns Query result with all seller tiles
*/
export function useGetAllSellerTiles() {
const token = useAuthToken();
return useQuery({
queryKey: homeKeys.sellerTilesAll(),
queryFn: async () => {
try {
const homeApi = createHomepageApi(token);
return await homeApi.apiHomeV1SellerTilesAllList();
} catch (error) {
console.error("Error fetching all seller tiles:", error);
throw error;
}
},
staleTime: 5 * 60 * 1000,
refetchOnWindowFocus: false,
});
}