added collection page

This commit is contained in:
Jelaletdin12
2025-12-15 14:33:34 +05:00
parent 633a3c9d47
commit e886359c5c
31 changed files with 2118 additions and 716 deletions

View File

@@ -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]
);
}