added some api
This commit is contained in:
41
lib/api.ts
41
lib/api.ts
@@ -22,7 +22,6 @@ const removeTokenFromCookie = (name: string): void => {
|
||||
}
|
||||
|
||||
const getToken = (): string | null => {
|
||||
// Check cookies first (more secure)
|
||||
const authToken = getTokenFromCookie("authToken")
|
||||
if (authToken) return authToken
|
||||
|
||||
@@ -32,6 +31,17 @@ const getToken = (): string | null => {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Map internal locale codes to API language codes
|
||||
*/
|
||||
const localeToApiLang = (locale: string): string => {
|
||||
const mapping: Record<string, string> = {
|
||||
tm: "tk",
|
||||
ru: "ru",
|
||||
}
|
||||
return mapping[locale] || locale
|
||||
}
|
||||
|
||||
/**
|
||||
* Centralized API client with interceptors
|
||||
*/
|
||||
@@ -68,13 +78,27 @@ class APIClient {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
|
||||
// Add language parameter if i18n is available
|
||||
if (typeof window !== "undefined" && (window as any).i18n) {
|
||||
const lang = (window as any).i18n.language || "tm"
|
||||
const url = config.url || ""
|
||||
config.url = `${url}${url.includes("?") ? "&" : "?"}lang=${lang}`
|
||||
// Add language parameter
|
||||
let lang = "tk" // default fallback
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
// Try to get from i18n
|
||||
if ((window as any).i18n?.language) {
|
||||
lang = localeToApiLang((window as any).i18n.language)
|
||||
}
|
||||
// Try to get from pathname as fallback
|
||||
else {
|
||||
const pathLocale = window.location.pathname.split("/")[1]
|
||||
if (pathLocale === "tm" || pathLocale === "ru") {
|
||||
lang = localeToApiLang(pathLocale)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const url = config.url || ""
|
||||
const separator = url.includes("?") ? "&" : "?"
|
||||
config.url = `${url}${separator}lang=${lang}`
|
||||
|
||||
return config
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
@@ -89,7 +113,6 @@ class APIClient {
|
||||
// Handle 401 errors
|
||||
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||
if (this.isRefreshing) {
|
||||
// Queue requests while refreshing
|
||||
return new Promise((resolve, reject) => {
|
||||
this.failedQueue.push({ resolve, reject })
|
||||
})
|
||||
@@ -101,7 +124,6 @@ class APIClient {
|
||||
this.isRefreshing = true
|
||||
|
||||
try {
|
||||
// Attempt to get guest token
|
||||
const guestTokenResponse = await axios.post(
|
||||
`${this.baseUrl}/api/v1/auth/guest-token`,
|
||||
{},
|
||||
@@ -203,10 +225,7 @@ class APIClient {
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const apiClient = new APIClient()
|
||||
|
||||
// Export helper functions
|
||||
export const setAuthToken = (token: string) => apiClient.setAuthToken(token)
|
||||
export const setGuestToken = (token: string) => apiClient.setGuestToken(token)
|
||||
export const clearAuthToken = () => apiClient.clearAuthToken()
|
||||
@@ -9,7 +9,12 @@ export * from "./useOpenStore"
|
||||
export * from "./useRegions"
|
||||
export * from "./useAddresses"
|
||||
export * from "./usePaymentTypes"
|
||||
export * from "./useCategories"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export * from "./useMedia"
|
||||
export * from "./useCollections"
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { apiClient } from "@/lib/api"
|
||||
import type { Address } from "@/lib/types/api"
|
||||
|
||||
export function useAddresses() {
|
||||
return useQuery({
|
||||
queryKey: ["addresses"],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get<Address[]>("/api/v1/addresses")
|
||||
return response.data
|
||||
},
|
||||
staleTime: 1000 * 60 * 30, // 30 minutes
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,16 +1,61 @@
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
|
||||
import { useQuery, useMutation, useQueryClient, UseQueryOptions } from "@tanstack/react-query"
|
||||
import { apiClient } from "@/lib/api"
|
||||
import type { Cart, CartItem } from "@/lib/types/api"
|
||||
|
||||
export function useCart() {
|
||||
interface CartResponse {
|
||||
message: string
|
||||
data: CartItem[]
|
||||
errorDetails?: string
|
||||
}
|
||||
|
||||
// Transform response to handle HTML/malformed responses
|
||||
function transformCartResponse(response: any): CartResponse {
|
||||
if (
|
||||
typeof response === "string" &&
|
||||
(response.trim().startsWith("<!DOCTYPE") || response.trim().startsWith("<html"))
|
||||
) {
|
||||
console.error("Received HTML response instead of JSON:", response.substring(0, 100))
|
||||
return {
|
||||
message: "error",
|
||||
data: [],
|
||||
errorDetails: "Server returned HTML instead of JSON. The server might be down or experiencing issues.",
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof response === "object") {
|
||||
if (response.data) {
|
||||
return response
|
||||
}
|
||||
return { message: "success", data: [] }
|
||||
}
|
||||
|
||||
if (typeof response === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(response)
|
||||
return parsed
|
||||
} catch (error) {
|
||||
console.error("Failed to parse response:", error)
|
||||
return { message: "error", data: [] }
|
||||
}
|
||||
}
|
||||
|
||||
return { message: "unknown", data: [] }
|
||||
}
|
||||
|
||||
export function useCart(options?: Partial<UseQueryOptions<CartResponse>>) {
|
||||
return useQuery({
|
||||
queryKey: ["cart"],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get<Cart>("/api/v1/carts")
|
||||
return response.data
|
||||
const response = await apiClient.get("/carts")
|
||||
return transformCartResponse(response.data)
|
||||
},
|
||||
staleTime: 0, // Always fetch fresh
|
||||
refetchInterval: 5000, // Poll every 5 seconds like RTK
|
||||
refetchOnMount: true,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: true,
|
||||
staleTime: 0,
|
||||
retry: 1,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,17 +64,38 @@ export function useAddToCart() {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ productId, quantity = 1 }: { productId: number; quantity?: number }) => {
|
||||
const response = await apiClient.post<Cart>("/api/v1/carts", {
|
||||
product_id: productId,
|
||||
quantity,
|
||||
const params = new URLSearchParams({
|
||||
product_id: String(productId),
|
||||
product_quantity: String(quantity),
|
||||
})
|
||||
return response.data
|
||||
|
||||
const response = await apiClient.post("/carts", params.toString(), {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
})
|
||||
|
||||
if (typeof response.data === "object" && response.data.data) {
|
||||
return response.data
|
||||
}
|
||||
|
||||
if (typeof response.data === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(response.data)
|
||||
return parsed
|
||||
} catch (error) {
|
||||
console.error("Failed to parse add to cart response:", error)
|
||||
return { message: "success", data: "Added to cart" }
|
||||
}
|
||||
}
|
||||
|
||||
return { message: "success", data: "Added to cart" }
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["cart"] })
|
||||
},
|
||||
onError: (error: any) => {
|
||||
console.error("[v0] Add to cart error:", error.response?.data?.message || error.message)
|
||||
console.error("Add to cart error:", error.response?.data?.message || error.message)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -38,8 +104,66 @@ export function useRemoveFromCart() {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (itemId: number) => {
|
||||
await apiClient.delete(`/api/v1/carts/${itemId}`)
|
||||
mutationFn: async (productId: number) => {
|
||||
const params = new URLSearchParams({ product_id: String(productId) })
|
||||
|
||||
const response = await apiClient.patch("/carts", params.toString(), {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
})
|
||||
|
||||
if (typeof response.data === "object" && response.data.data) {
|
||||
return response.data.data
|
||||
}
|
||||
|
||||
if (typeof response.data === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(response.data)
|
||||
return parsed.data || []
|
||||
} catch (error) {
|
||||
console.error("Failed to parse cart response:", error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["cart"] })
|
||||
},
|
||||
onError: (error: any) => {
|
||||
console.error("Remove from cart error:", error.response?.data?.message || error.message)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useCleanCart() {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
const response = await apiClient.delete("/carts", {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
})
|
||||
|
||||
if (typeof response.data === "object" && response.data.data) {
|
||||
return response.data.data
|
||||
}
|
||||
|
||||
if (typeof response.data === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(response.data)
|
||||
return parsed.data || []
|
||||
} catch (error) {
|
||||
console.error("Failed to parse cart response:", error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["cart"] })
|
||||
@@ -51,15 +175,40 @@ export function useUpdateCartItemQuantity() {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ itemId, quantity }: { itemId: number; quantity: number }) => {
|
||||
const response = await apiClient.patch<CartItem>(`/api/v1/carts/${itemId}`, {
|
||||
quantity,
|
||||
mutationFn: async ({ productId, quantity }: { productId: number; quantity: number }) => {
|
||||
const params = new URLSearchParams({
|
||||
product_id: String(productId),
|
||||
product_quantity: String(quantity),
|
||||
})
|
||||
return response.data
|
||||
|
||||
const response = await apiClient.post("/carts", params.toString(), {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
})
|
||||
|
||||
if (typeof response.data === "object" && response.data.data) {
|
||||
return response.data
|
||||
}
|
||||
|
||||
if (typeof response.data === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(response.data)
|
||||
return parsed
|
||||
} catch (error) {
|
||||
console.error("Failed to parse update cart response:", error)
|
||||
return { message: "success", data: "Updated cart" }
|
||||
}
|
||||
}
|
||||
|
||||
return { message: "success", data: "Updated cart" }
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["cart"] })
|
||||
},
|
||||
onError: (error: any) => {
|
||||
console.error("API update failed:", error.response?.data?.message || error.message)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -86,7 +235,36 @@ export function useCreateOrder() {
|
||||
queryClient.invalidateQueries({ queryKey: ["orders"] })
|
||||
},
|
||||
onError: (error: any) => {
|
||||
console.error("[v0] Create order error:", error.response?.data?.message || error.message)
|
||||
console.error("Create order error:", error.response?.data?.message || error.message)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
import type { Region } from "@/lib/types/api"
|
||||
|
||||
export function useRegions() {
|
||||
return useQuery({
|
||||
queryKey: ["regions"],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get<Region[]>("/api/v1/provinces")
|
||||
return response.data
|
||||
},
|
||||
staleTime: 1000 * 60 * 60, // 1 hour
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
import type { Address } from "@/lib/types/api"
|
||||
|
||||
export function useAddresses() {
|
||||
return useQuery({
|
||||
queryKey: ["addresses"],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get<Address[]>("/api/v1/addresses")
|
||||
return response.data
|
||||
},
|
||||
staleTime: 1000 * 60 * 30, // 30 minutes
|
||||
})
|
||||
}
|
||||
|
||||
@@ -92,6 +92,8 @@ export function useAllCategoryProducts(
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Get products from category and children WITH pagination (mimics RTK getAllCategoryProductsPaginated)
|
||||
export function useAllCategoryProductsPaginated(
|
||||
category: Category | undefined,
|
||||
@@ -155,4 +157,5 @@ export function useAllCategoryProductsPaginated(
|
||||
},
|
||||
enabled: options?.enabled !== false && !!category,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,42 +1,112 @@
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
|
||||
import { apiClient } from "@/lib/api"
|
||||
import type { Favorite } from "@/lib/types/api"
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { apiClient } from "@/lib/api";
|
||||
import type { Favorite } from "@/lib/types/api";
|
||||
|
||||
// Response tiplerini tanımlayalım
|
||||
interface FavoritesResponse {
|
||||
data?: Favorite[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface FavoriteActionResponse {
|
||||
data?: string | Favorite[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// Response'u transform eden yardımcı fonksiyon
|
||||
function transformFavoritesResponse(response: any): Favorite[] {
|
||||
if (typeof response === "object" && response.data) {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
if (typeof response === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(response);
|
||||
return parsed.data || [];
|
||||
} catch (error) {
|
||||
console.error("Failed to parse favorites response:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function transformActionResponse(response: any, defaultValue: string): string {
|
||||
if (typeof response === "object" && response.data) {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
if (typeof response === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(response);
|
||||
return parsed.data || defaultValue;
|
||||
} catch (error) {
|
||||
if (response.includes("<!doctype html>")) {
|
||||
return defaultValue;
|
||||
}
|
||||
console.error(`Failed to parse favorite response:`, error);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
export function useFavorites() {
|
||||
return useQuery({
|
||||
queryKey: ["favorites"],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get<Favorite[]>("/favorites")
|
||||
return response.data
|
||||
const response = await apiClient.get("/favorites");
|
||||
return transformFavoritesResponse(response.data);
|
||||
},
|
||||
staleTime: 1000 * 60 * 5,
|
||||
retry: 1,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export function useAddToFavorites() {
|
||||
const queryClient = useQueryClient()
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (productId: number) => {
|
||||
const response = await apiClient.post<Favorite[]>("/favorites", { product_id: productId })
|
||||
return response.data
|
||||
const formData = new URLSearchParams({
|
||||
product_id: productId.toString(),
|
||||
});
|
||||
|
||||
const response = await apiClient.post("/favorites", formData, {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
});
|
||||
|
||||
return transformActionResponse(response.data, "Added");
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["favorites"] })
|
||||
queryClient.invalidateQueries({ queryKey: ["favorites"] });
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export function useRemoveFromFavorites() {
|
||||
const queryClient = useQueryClient()
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (productId: number) => {
|
||||
await apiClient.delete(`/favorites/${productId}`)
|
||||
const formData = new URLSearchParams({
|
||||
product_id: productId.toString(),
|
||||
});
|
||||
|
||||
const response = await apiClient.post("/favorites", formData, {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
});
|
||||
|
||||
return transformActionResponse(response.data, "Removed");
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["favorites"] })
|
||||
queryClient.invalidateQueries({ queryKey: ["favorites"] });
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,59 +1,165 @@
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
|
||||
import { apiClient } from "@/lib/api"
|
||||
import type { Order, PaginatedResponse } from "@/lib/types/api"
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { apiClient } from "@/lib/api";
|
||||
import type { Order, PaginatedResponse } from "@/lib/types/api";
|
||||
|
||||
// Response tiplerini tanımlayalım
|
||||
interface OrderResponse {
|
||||
data?: Order | Order[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface OrderActionResponse {
|
||||
data?: string | Order;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// Response'u transform eden yardımcı fonksiyonlar
|
||||
function transformOrdersResponse(response: any): Order[] {
|
||||
if (typeof response === "object" && response.data) {
|
||||
return Array.isArray(response.data) ? response.data : [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function transformOrderResponse(response: any): Order | null {
|
||||
if (typeof response === "object" && response.data) {
|
||||
return response.data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function transformOrderActionResponse(
|
||||
response: any,
|
||||
defaultValue: string
|
||||
): string | Order {
|
||||
if (response && response.data) {
|
||||
return response.data;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
function isHtmlResponse(response: any): boolean {
|
||||
return typeof response === "string" && response.includes("<!doctype html>");
|
||||
}
|
||||
|
||||
// Orders list query
|
||||
export function useOrders(options?: { page?: number; perPage?: number }) {
|
||||
return useQuery({
|
||||
queryKey: ["orders", options?.page],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get<PaginatedResponse<Order>>("/orders", {
|
||||
const response = await apiClient.get("/orders", {
|
||||
params: {
|
||||
page: options?.page || 1,
|
||||
per_page: options?.perPage || 20,
|
||||
},
|
||||
})
|
||||
return response.data.data || response.data
|
||||
});
|
||||
return transformOrdersResponse(response.data);
|
||||
},
|
||||
staleTime: 1000 * 60 * 5,
|
||||
retry: 1,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Single order query
|
||||
export function useOrder(id: number | string) {
|
||||
return useQuery({
|
||||
queryKey: ["order", id],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get<Order>(`/orders/${id}`)
|
||||
return response.data
|
||||
const response = await apiClient.get(`/orders/${id}`);
|
||||
return transformOrderResponse(response.data);
|
||||
},
|
||||
enabled: !!id,
|
||||
})
|
||||
staleTime: 1000 * 60 * 5,
|
||||
retry: 1,
|
||||
});
|
||||
}
|
||||
|
||||
// Order times query
|
||||
export function useOrderTimes() {
|
||||
return useQuery({
|
||||
queryKey: ["order-times"],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get("/order-time");
|
||||
return transformOrdersResponse(response.data);
|
||||
},
|
||||
staleTime: 1000 * 60 * 10,
|
||||
retry: 1,
|
||||
});
|
||||
}
|
||||
|
||||
// Order payments query
|
||||
export function useOrderPayments() {
|
||||
return useQuery({
|
||||
queryKey: ["order-payments"],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get("/order-payments");
|
||||
return transformOrdersResponse(response.data);
|
||||
},
|
||||
staleTime: 1000 * 60 * 10,
|
||||
retry: 1,
|
||||
});
|
||||
}
|
||||
|
||||
// Create/Place order mutation
|
||||
export function useCreateOrder() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (orderData: Record<string, any>) => {
|
||||
try {
|
||||
const formData = new URLSearchParams();
|
||||
|
||||
// Convert orderData to URLSearchParams
|
||||
Object.entries(orderData).forEach(([key, value]) => {
|
||||
if (value !== null && value !== undefined) {
|
||||
formData.append(key, String(value));
|
||||
}
|
||||
});
|
||||
|
||||
const response = await apiClient.post("/orders", formData, {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
validateStatus: (status) => status >= 200 && status < 300,
|
||||
});
|
||||
|
||||
// Check for HTML response
|
||||
if (isHtmlResponse(response.data)) {
|
||||
throw new Error(
|
||||
"Server returned HTML instead of expected response format"
|
||||
);
|
||||
}
|
||||
|
||||
return transformOrderActionResponse(response.data, "Order placed");
|
||||
} catch (error: any) {
|
||||
// Handle HTML error response
|
||||
if (error.response && isHtmlResponse(error.response.data)) {
|
||||
throw new Error(
|
||||
"Server returned HTML instead of expected response format"
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["orders"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["cart"] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Cancel order mutation
|
||||
export function useCancelOrder() {
|
||||
const queryClient = useQueryClient()
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (orderId: number) => {
|
||||
await apiClient.post(`/orders/${orderId}/cancel`, {})
|
||||
const response = await apiClient.delete(`/orders/${orderId}`);
|
||||
return transformOrderActionResponse(response.data, "Order cancelled");
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["orders"] })
|
||||
onSuccess: (_, orderId) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["orders"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["order", orderId] });
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useCreateOrder() {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (orderData: any) => {
|
||||
const response = await apiClient.post<Order>("/orders", orderData)
|
||||
return response.data
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["orders"] })
|
||||
queryClient.invalidateQueries({ queryKey: ["cart"] })
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,50 +1,216 @@
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { apiClient } from "@/lib/api"
|
||||
import type { Product, PaginatedResponse } from "@/lib/types/api"
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { apiClient } from "@/lib/api";
|
||||
import type { Review, Product, PaginatedResponse } from "@/lib/types/api";
|
||||
|
||||
interface UseProductsOptions {
|
||||
enabled?: boolean
|
||||
staleTime?: number
|
||||
page?: number
|
||||
perPage?: number
|
||||
// Get single review by ID
|
||||
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;
|
||||
},
|
||||
enabled: options?.enabled !== false && !!reviewId,
|
||||
staleTime: 1000 * 60 * 10,
|
||||
});
|
||||
}
|
||||
|
||||
// Get all reviews with pagination
|
||||
export function useReviews(options?: {
|
||||
enabled?: boolean;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}) {
|
||||
return useQuery({
|
||||
queryKey: ["reviews", options?.page, options?.limit],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get<PaginatedResponse<Review>>(
|
||||
`/reviews`,
|
||||
{
|
||||
params: {
|
||||
page: options?.page || 1,
|
||||
limit: options?.limit,
|
||||
},
|
||||
}
|
||||
);
|
||||
return {
|
||||
data: response.data.data || [],
|
||||
pagination: response.data.pagination || {},
|
||||
};
|
||||
},
|
||||
enabled: options?.enabled !== false,
|
||||
staleTime: 1000 * 60 * 5,
|
||||
});
|
||||
}
|
||||
|
||||
// Get related reviews for a review
|
||||
export function useRelatedReviews(
|
||||
reviewId: number | string,
|
||||
options?: { enabled?: boolean }
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: ["review", reviewId, "related"],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get<PaginatedResponse<Review>>(
|
||||
`/reviews/${reviewId}/related`
|
||||
);
|
||||
return response.data.data || response.data;
|
||||
},
|
||||
enabled: options?.enabled !== false && !!reviewId,
|
||||
staleTime: 1000 * 60 * 15,
|
||||
});
|
||||
}
|
||||
|
||||
export function useProducts(options?: UseProductsOptions) {
|
||||
return useQuery({
|
||||
queryKey: ["products", options?.page, options?.perPage],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get<PaginatedResponse<Product>>("/products", {
|
||||
params: {
|
||||
page: options?.page || 1,
|
||||
per_page: options?.perPage || 20,
|
||||
},
|
||||
})
|
||||
return response.data.data || response.data
|
||||
const response = await apiClient.get<PaginatedResponse<Product>>(
|
||||
"/products",
|
||||
{
|
||||
params: {
|
||||
page: options?.page || 1,
|
||||
per_page: options?.perPage || 20,
|
||||
},
|
||||
}
|
||||
);
|
||||
return response.data.data || response.data;
|
||||
},
|
||||
staleTime: options?.staleTime ?? 1000 * 60 * 5, // 5 minutes
|
||||
staleTime: options?.staleTime ?? 1000 * 60 * 5,
|
||||
enabled: options?.enabled !== false,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export function useProduct(id: number | string, options?: { enabled?: boolean }) {
|
||||
// Get single product by ID (for review context)
|
||||
export function useProduct(
|
||||
productId: number | string,
|
||||
options?: { enabled?: boolean }
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: ["product", id],
|
||||
queryKey: ["product", productId],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get<Product>(`/products/${id}`)
|
||||
return response.data
|
||||
const response = await apiClient.get<Product>(`/products/${productId}`);
|
||||
return response.data;
|
||||
},
|
||||
staleTime: 1000 * 60 * 10, // 10 minutes
|
||||
enabled: options?.enabled !== false && !!id,
|
||||
})
|
||||
enabled: options?.enabled !== false && !!productId,
|
||||
staleTime: 1000 * 60 * 10,
|
||||
});
|
||||
}
|
||||
|
||||
export function useProductsBySlug(slug: string, options?: { enabled?: boolean }) {
|
||||
export function useProductsBySlug(
|
||||
slug: string,
|
||||
options?: { enabled?: boolean }
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: ["products", "slug", slug],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get<Product>(`/products/${slug}`)
|
||||
return response.data
|
||||
const response = await apiClient.get(`/products/${slug}`);
|
||||
// API returns { message: "success", data: {...} }
|
||||
return response.data.data || response.data;
|
||||
},
|
||||
enabled: options?.enabled !== false && !!slug,
|
||||
})
|
||||
staleTime: 1000 * 60 * 10,
|
||||
});
|
||||
}
|
||||
|
||||
// Submit review mutation
|
||||
export function useSubmitReview() {
|
||||
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",
|
||||
},
|
||||
}
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["reviews", "product", variables.productId],
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["product", variables.productId],
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["reviews"],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 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;
|
||||
}) => {
|
||||
const response = await apiClient.put<Review>(
|
||||
`/reviews/${reviewId}`,
|
||||
{ rating, title, source },
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: (data, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["review", variables.reviewId],
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["reviews"],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 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"],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { apiClient } from "@/lib/api"
|
||||
import type { Region } from "@/lib/types/api"
|
||||
|
||||
export function useRegions() {
|
||||
return useQuery({
|
||||
queryKey: ["regions"],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get<Region[]>("/api/v1/regions")
|
||||
return response.data
|
||||
},
|
||||
staleTime: 1000 * 60 * 60, // 1 hour
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,23 +4,65 @@
|
||||
*/
|
||||
|
||||
// Product Types
|
||||
export interface ProductMedia {
|
||||
thumbnail: string
|
||||
images_400x400: string
|
||||
images_720x720: string
|
||||
images_800x800: string
|
||||
images_1200x1200: string
|
||||
}
|
||||
|
||||
export interface ProductProperty {
|
||||
attribute_id: number
|
||||
name: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface ProductReviews {
|
||||
count: number
|
||||
rating: string
|
||||
}
|
||||
|
||||
export interface Product {
|
||||
id: number
|
||||
parent_id: number | null
|
||||
name: string
|
||||
slug?: string
|
||||
price: number
|
||||
slug: string
|
||||
description: string
|
||||
image: string
|
||||
images?: string[]
|
||||
category: string
|
||||
brand?: string
|
||||
stock?: number
|
||||
rating?: number
|
||||
reviews_count?: number
|
||||
is_favorite?: boolean
|
||||
is_in_cart?: boolean
|
||||
labels?: Array<{ text: string; bg_color: string }>
|
||||
struct_price_text?: string
|
||||
sku: string | null
|
||||
barcode: string
|
||||
stock: number
|
||||
price_amount: string
|
||||
old_price_amount: string | null
|
||||
backorder: string
|
||||
weight_value: number | null
|
||||
weight_unit: string | null
|
||||
height_value: number | null
|
||||
height_unit: string | null
|
||||
media: ProductMedia[]
|
||||
created_at: string
|
||||
seo_title: string | null
|
||||
seo_description: string | null
|
||||
colour: string | null
|
||||
size: string | null
|
||||
available_colors: string[]
|
||||
available_sizes: string[]
|
||||
brand: {
|
||||
id: number | null
|
||||
name: string | null
|
||||
}
|
||||
channel: Array<{
|
||||
id: number
|
||||
name: string
|
||||
}>
|
||||
properties: ProductProperty[]
|
||||
variations: any[]
|
||||
reviews: ProductReviews
|
||||
reviews_resources: any[]
|
||||
categories: Array<{
|
||||
id: number
|
||||
name: string
|
||||
}>
|
||||
}
|
||||
|
||||
// Category Types
|
||||
@@ -193,3 +235,5 @@ export interface CreateOrderPayload {
|
||||
region: string
|
||||
note?: string
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user