added collection page
This commit is contained in:
@@ -2,101 +2,147 @@ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { apiClient } from "@/lib/api";
|
||||
import type { Review, Product, PaginatedResponse } from "@/lib/types/api";
|
||||
|
||||
// Get single review by ID
|
||||
// Types
|
||||
interface PaginationOptions {
|
||||
enabled?: boolean;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
interface ReviewSubmission {
|
||||
productId: number | string;
|
||||
rating: number;
|
||||
title: string;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
interface ReviewUpdate {
|
||||
reviewId: number | string;
|
||||
rating?: number;
|
||||
title?: string;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
// Constants
|
||||
const DEFAULT_STALE_TIME = 1000 * 60 * 5; // 5 minutes
|
||||
const EXTENDED_STALE_TIME = 1000 * 60 * 15; // 15 minutes
|
||||
|
||||
// Query Keys Factory
|
||||
const reviewKeys = {
|
||||
all: ["reviews"],
|
||||
lists: () => [...reviewKeys.all, "list"],
|
||||
list: (page?: number, limit?: number) => [...reviewKeys.lists(), page, limit],
|
||||
details: () => [...reviewKeys.all, "detail"],
|
||||
detail: (id: number | string) => [...reviewKeys.details(), id],
|
||||
related: (id: number | string) => [...reviewKeys.detail(id), "related"],
|
||||
byProduct: (productId: number | string, page?: number, limit?: number) => [
|
||||
...reviewKeys.all,
|
||||
"product",
|
||||
productId,
|
||||
page,
|
||||
limit,
|
||||
],
|
||||
};
|
||||
|
||||
const productKeys = {
|
||||
all: ["products"],
|
||||
details: () => [...productKeys.all, "detail"],
|
||||
detail: (id: number | string) => [...productKeys.details(), id],
|
||||
bySlug: (slug: string) => [...productKeys.all, "slug", slug],
|
||||
related: (id: number | string) => [...productKeys.detail(id), "related"],
|
||||
};
|
||||
|
||||
// Generic fetch function
|
||||
async function fetchData<T>(
|
||||
url: string,
|
||||
params?: Record<string, any>
|
||||
): Promise<T> {
|
||||
const response = await apiClient.get<T>(url, { params });
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// Review Queries
|
||||
export function useReview(
|
||||
reviewId: number | string,
|
||||
options?: { enabled?: boolean }
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: ["review", reviewId],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get<Review>(`/reviews/${reviewId}`);
|
||||
return response.data;
|
||||
},
|
||||
queryKey: reviewKeys.detail(reviewId),
|
||||
queryFn: () => fetchData<Review>(`/reviews/${reviewId}`),
|
||||
enabled: options?.enabled !== false && !!reviewId,
|
||||
staleTime: 1000 * 60 * 10,
|
||||
staleTime: DEFAULT_STALE_TIME * 2,
|
||||
});
|
||||
}
|
||||
|
||||
// Get all reviews with pagination
|
||||
export function useReviews(options?: {
|
||||
enabled?: boolean;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}) {
|
||||
export function useReviews(options?: PaginationOptions) {
|
||||
return useQuery({
|
||||
queryKey: ["reviews", options?.page, options?.limit],
|
||||
queryKey: reviewKeys.list(options?.page, options?.limit),
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get<PaginatedResponse<Review>>(
|
||||
`/reviews`,
|
||||
{
|
||||
params: {
|
||||
page: options?.page || 1,
|
||||
limit: options?.limit,
|
||||
},
|
||||
}
|
||||
);
|
||||
const response = await fetchData<PaginatedResponse<Review>>("/reviews", {
|
||||
page: options?.page || 1,
|
||||
limit: options?.limit,
|
||||
});
|
||||
return {
|
||||
data: response.data.data || [],
|
||||
pagination: response.data.pagination || {},
|
||||
data: response.data || [],
|
||||
pagination: response.pagination || {},
|
||||
};
|
||||
},
|
||||
enabled: options?.enabled !== false,
|
||||
staleTime: 1000 * 60 * 5,
|
||||
staleTime: DEFAULT_STALE_TIME,
|
||||
});
|
||||
}
|
||||
|
||||
// Get related reviews for a review
|
||||
export function useRelatedReviews(
|
||||
reviewId: number | string,
|
||||
options?: { enabled?: boolean }
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: ["review", reviewId, "related"],
|
||||
queryKey: reviewKeys.related(reviewId),
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get<PaginatedResponse<Review>>(
|
||||
const response = await fetchData<PaginatedResponse<Review>>(
|
||||
`/reviews/${reviewId}/related`
|
||||
);
|
||||
return response.data.data || response.data;
|
||||
return response.data || response;
|
||||
},
|
||||
enabled: options?.enabled !== false && !!reviewId,
|
||||
staleTime: 1000 * 60 * 15,
|
||||
staleTime: EXTENDED_STALE_TIME,
|
||||
});
|
||||
}
|
||||
|
||||
export function useProducts(options?: UseProductsOptions) {
|
||||
export function useProductReviews(
|
||||
productId: number | string,
|
||||
options?: PaginationOptions
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: ["products", options?.page, options?.perPage],
|
||||
queryKey: reviewKeys.byProduct(productId, options?.page, options?.limit),
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get<PaginatedResponse<Product>>(
|
||||
"/products",
|
||||
const response = await fetchData<PaginatedResponse<Review>>(
|
||||
`/products/${productId}/reviews`,
|
||||
{
|
||||
params: {
|
||||
page: options?.page || 1,
|
||||
per_page: options?.perPage || 20,
|
||||
},
|
||||
page: options?.page || 1,
|
||||
limit: options?.limit || 10,
|
||||
}
|
||||
);
|
||||
return response.data.data || response.data;
|
||||
return {
|
||||
data: response.data || [],
|
||||
pagination: response.pagination || {},
|
||||
};
|
||||
},
|
||||
staleTime: options?.staleTime ?? 1000 * 60 * 5,
|
||||
enabled: options?.enabled !== false,
|
||||
enabled: options?.enabled !== false && !!productId,
|
||||
staleTime: DEFAULT_STALE_TIME,
|
||||
});
|
||||
}
|
||||
|
||||
// Get single product by ID (for review context)
|
||||
// Product Queries
|
||||
export function useProduct(
|
||||
productId: number | string,
|
||||
options?: { enabled?: boolean }
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: ["product", productId],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get<Product>(`/products/${productId}`);
|
||||
return response.data;
|
||||
},
|
||||
queryKey: productKeys.detail(productId),
|
||||
queryFn: () => fetchData<Product>(`/products/${productId}`),
|
||||
enabled: options?.enabled !== false && !!productId,
|
||||
staleTime: 1000 * 60 * 10,
|
||||
staleTime: DEFAULT_STALE_TIME * 2,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -105,112 +151,86 @@ export function useProductsBySlug(
|
||||
options?: { enabled?: boolean }
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: ["products", "slug", slug],
|
||||
queryKey: productKeys.bySlug(slug),
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get(`/products/${slug}`);
|
||||
// API returns { message: "success", data: {...} }
|
||||
return response.data.data || response.data;
|
||||
const response = await fetchData<{ data: Product }>(`/products/${slug}`);
|
||||
return response.data || response;
|
||||
},
|
||||
enabled: options?.enabled !== false && !!slug,
|
||||
staleTime: 1000 * 60 * 10,
|
||||
staleTime: DEFAULT_STALE_TIME * 2,
|
||||
});
|
||||
}
|
||||
|
||||
// Submit review mutation
|
||||
export function useSubmitReview() {
|
||||
export function useRelatedProducts(
|
||||
productId: number | string,
|
||||
options?: { enabled?: boolean }
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: productKeys.related(productId),
|
||||
queryFn: async () => {
|
||||
const response = await fetchData<PaginatedResponse<Product>>(
|
||||
`/products/${productId}/related`
|
||||
);
|
||||
return response.data || [];
|
||||
},
|
||||
enabled: options?.enabled !== false && !!productId,
|
||||
staleTime: EXTENDED_STALE_TIME,
|
||||
});
|
||||
}
|
||||
|
||||
// Review Mutations
|
||||
function useReviewMutation<TVariables, TData = any>(
|
||||
mutationFn: (variables: TVariables) => Promise<TData>,
|
||||
invalidateKeys: (variables: TVariables, data?: TData) => any[]
|
||||
) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
productId,
|
||||
rating,
|
||||
title,
|
||||
source,
|
||||
}: {
|
||||
productId: number | string;
|
||||
rating: number;
|
||||
title: string;
|
||||
source: string;
|
||||
}) => {
|
||||
const response = await apiClient.post<Review>(
|
||||
`/products/${productId}/reviews`,
|
||||
{ rating, title, source },
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
mutationFn,
|
||||
onSuccess: (data, variables) => {
|
||||
const keys = invalidateKeys(variables, data);
|
||||
keys.forEach((key) => {
|
||||
queryClient.invalidateQueries({ queryKey: key });
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useSubmitReview() {
|
||||
return useReviewMutation<ReviewSubmission>(
|
||||
async ({ productId, rating, title, source = "site" }) => {
|
||||
const response = await apiClient.post<{
|
||||
message: string;
|
||||
data: Review[];
|
||||
}>(`/products/${productId}/reviews`, { rating, title, source });
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["reviews", "product", variables.productId],
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["product", variables.productId],
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["reviews"],
|
||||
});
|
||||
},
|
||||
});
|
||||
(variables) => [
|
||||
reviewKeys.byProduct(variables.productId),
|
||||
productKeys.bySlug(""), // Invalidates all slug queries
|
||||
reviewKeys.all,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Update review mutation
|
||||
export function useUpdateReview() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
reviewId,
|
||||
rating,
|
||||
title,
|
||||
source,
|
||||
}: {
|
||||
reviewId: number | string;
|
||||
rating?: number;
|
||||
title?: string;
|
||||
source?: string;
|
||||
}) => {
|
||||
return useReviewMutation<ReviewUpdate>(
|
||||
async ({ reviewId, rating, title, source }) => {
|
||||
const response = await apiClient.put<Review>(
|
||||
`/reviews/${reviewId}`,
|
||||
{ rating, title, source },
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: (data, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["review", variables.reviewId],
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["reviews"],
|
||||
});
|
||||
},
|
||||
});
|
||||
(variables) => [reviewKeys.detail(variables.reviewId), reviewKeys.all]
|
||||
);
|
||||
}
|
||||
|
||||
// Delete review mutation
|
||||
export function useDeleteReview() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (reviewId: number | string) => {
|
||||
const response = await apiClient.delete(`/reviews/${reviewId}`);
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: (_, reviewId) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["review", reviewId],
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["reviews"],
|
||||
});
|
||||
},
|
||||
});
|
||||
return useReviewMutation<number | string>(
|
||||
(reviewId) =>
|
||||
apiClient.delete(`/reviews/${reviewId}`).then((res) => res.data),
|
||||
(reviewId) => [reviewKeys.detail(reviewId), reviewKeys.all]
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user