diff --git a/src/pages/catalogue/List.tsx b/src/pages/catalogue/List.tsx index df9adc4..4a97b1d 100644 --- a/src/pages/catalogue/List.tsx +++ b/src/pages/catalogue/List.tsx @@ -1,7 +1,7 @@ import GridWrapper from "@/components/GridWrapper"; import { usePageTitle } from "@/hooks/usePageTitle"; import { type FC } from "react"; -import { useGetCatalog } from "../home/hooks/useHomeData"; +import { useGetCatalog } from "./hooks/useCatalogueData"; import ButtonAddCatalogue from "./components/ButtonAddCatalogue"; import CatalogueGridSkeleton from "./components/CatalogueGridSkeleton"; import CatalogueItem from "./components/CatalogueItem"; diff --git a/src/pages/catalogue/components/CatalogueItem.tsx b/src/pages/catalogue/components/CatalogueItem.tsx index 61b0f58..beac1b7 100644 --- a/src/pages/catalogue/components/CatalogueItem.tsx +++ b/src/pages/catalogue/components/CatalogueItem.tsx @@ -2,7 +2,7 @@ import { toast } from "@/components/Toast"; import TrashWithConfrim from "@/components/TrashWithConfrim"; import { Paths } from "@/config/Paths"; import { extractErrorMessage } from "@/helpers/utils"; -import { useDleteCatalog } from "@/pages/home/hooks/useHomeData"; +import { useDleteCatalog } from "@/pages/catalogue/hooks/useCatalogueData"; import { requestViewerFullscreen } from "@/pages/viewer/utils/viewerFullscreen"; import { Edit, Eye, Share } from "iconsax-react"; import moment from "moment-jalaali"; diff --git a/src/pages/catalogue/hooks/useCatalogueData.ts b/src/pages/catalogue/hooks/useCatalogueData.ts index 9f6f59d..7614058 100644 --- a/src/pages/catalogue/hooks/useCatalogueData.ts +++ b/src/pages/catalogue/hooks/useCatalogueData.ts @@ -1,6 +1,127 @@ import { useMutation, useQuery } from "@tanstack/react-query"; +import { useCallback, useEffect, useRef, useState } from "react"; import * as api from "../service/CatalogueService"; +export const useCreateCatalog = () => { + return useMutation({ + mutationFn: api.createCatalog, + }); +}; + +export const useDleteCatalog = () => { + return useMutation({ + mutationFn: (id: string) => api.deleteCatalogById(id), + }); +}; + +export const useGetCatalog = () => { + return useQuery({ + queryKey: ["catalogs"], + queryFn: api.getCatalogs, + }); +}; + +const catalogMongoIdPattern = /^[a-f\d]{24}$/i; + +export const isCatalogMongoId = (value: string) => + catalogMongoIdPattern.test(value); + +export const useGetCatalogById = (id: string) => { + return useQuery({ + queryKey: ["catalog", id], + queryFn: () => api.getCatalogById(id), + enabled: !!id, + // جلوگیری از بازنویسی state محلی با refetch هنگام فوکوس پنجره (مثلاً دو کاربر همزمان) + refetchOnWindowFocus: false, + staleTime: 5 * 60 * 1000, + }); +}; + +/** Route param may be Mongo id (editor/admin) or slug (public viewer-style URLs). */ +export const useGetCatalogByIdOrSlug = (param: string) => { + const isId = isCatalogMongoId(param); + const byId = useGetCatalogById(isId ? param : ""); + const bySlug = useGetCatalogBySlug(isId ? "" : param); + return isId ? byId : bySlug; +}; + +export const useGetCatalogBySlug = (slug: string) => { + return useQuery({ + queryKey: ["catalog", slug], + queryFn: () => api.getCatalogBySlug(slug), + enabled: !!slug, + // جلوگیری از بازنویسی state محلی با refetch هنگام فوکوس پنجره (مثلاً دو کاربر همزمان) + refetchOnWindowFocus: false, + staleTime: 5 * 60 * 1000, + }); +}; + +export const useUpdateCatalog = () => { + const mutation = useMutation({ + mutationFn: api.updateCatalog, + }); + + const timeoutRef = useRef | null>(null); + const pendingParamsRef = useRef< + Parameters[0] | null + >(null); + const [isScheduledSaving, setIsScheduledSaving] = useState(false); + + const cancelPendingUpdate = useCallback(() => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + pendingParamsRef.current = null; + setIsScheduledSaving(false); + }, []); + + const scheduleUpdate = useCallback( + (params: Parameters[0], delayMs = 3000) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + pendingParamsRef.current = params; + setIsScheduledSaving(true); + + timeoutRef.current = setTimeout(() => { + const pendingParams = pendingParamsRef.current; + timeoutRef.current = null; + pendingParamsRef.current = null; + setIsScheduledSaving(false); + if (pendingParams) { + mutation.mutate(pendingParams); + } + }, delayMs); + }, + [mutation], + ); + + useEffect( + () => () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + // اگر کاربر قبل از پایان debounce از ادیتور خارج شد، + // آخرین تغییرات معلق را همان لحظه ذخیره کن. + if (pendingParamsRef.current) { + mutation.mutate(pendingParamsRef.current); + pendingParamsRef.current = null; + } + setIsScheduledSaving(false); + }, + [mutation], + ); + + return { + ...mutation, + scheduleUpdate, + cancelPendingUpdate, + isSaving: isScheduledSaving || mutation.isPending, + }; +}; + export const usePurchaseInitate = () => { return useMutation({ mutationFn: api.purchaseInitate, diff --git a/src/pages/catalogue/service/CatalogueService.ts b/src/pages/catalogue/service/CatalogueService.ts index e56bb33..aeafbec 100644 --- a/src/pages/catalogue/service/CatalogueService.ts +++ b/src/pages/catalogue/service/CatalogueService.ts @@ -1,5 +1,47 @@ import axios from "@/config/axios"; -import type { CatalogResponseType, PurchaseInitiate } from "../types/Types"; +import type { + CatalogByIdResponseType, + CatalogResponseType, + CreateCatalogParamsType, + PurchaseInitiate, + UpdateCatalogParamsType, +} from "../types/Types"; + +export const createCatalog = async (params: CreateCatalogParamsType) => { + const { data } = await axios.post("/admin/catalogue", params); + return data; +}; + +export const getCatalogs = async () => { + const { data } = await axios.get("/admin/catalogue"); + return data; +}; + +export const getCatalogById = async (id: string) => { + const { data } = await axios.get( + `/admin/catalogue/${id}`, + ); + return data; +}; + +export const getCatalogBySlug = async (slug: string) => { + const { data } = await axios.get( + `/public/catalogue/slug/${slug}`, + ); + return data; +}; + +export const updateCatalog = async (params: UpdateCatalogParamsType) => { + const { data } = await axios.patch(`/admin/catalogue/${params.id}`, { + content: params.content, + }); + return data; +}; + +export const deleteCatalogById = async (id: string) => { + const { data } = await axios.delete(`/admin/catalogue/${id}`); + return data; +}; export const purchaseInitate = async (params: PurchaseInitiate) => { const { data } = await axios.post( diff --git a/src/pages/catalogue/types/Types.ts b/src/pages/catalogue/types/Types.ts index 6687c6b..ef21929 100644 --- a/src/pages/catalogue/types/Types.ts +++ b/src/pages/catalogue/types/Types.ts @@ -16,3 +16,16 @@ export type CatalogByIdResponseType = BaseResponse; export type PurchaseInitiate = { count: number; }; + +export type CreateCatalogParamsType = { + name: string; + slug: string; + size: string; + content?: string; + id?: string; +}; + +export type UpdateCatalogParamsType = { + id: string; + content: string; +}; diff --git a/src/pages/editor/Editor.tsx b/src/pages/editor/Editor.tsx index 9b7a89d..9c88d18 100644 --- a/src/pages/editor/Editor.tsx +++ b/src/pages/editor/Editor.tsx @@ -4,7 +4,10 @@ import Konva from "konva"; import { type FC, useEffect, useRef, useState } from "react"; import { useParams } from "react-router-dom"; import { useSharedStore } from "@/shared/store/sharedStore"; -import { useGetCatalogById, useUpdateCatalog } from "../home/hooks/useHomeData"; +import { + useGetCatalogById, + useUpdateCatalog, +} from "@/pages/catalogue/hooks/useCatalogueData"; import EditorCanvas from "./components/EditorCanvas"; import EditorSidebar from "./components/EditorSidebar"; import LayersPanel from "./components/LayersPanel"; diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx index 5bae5fc..89e53ae 100644 --- a/src/pages/home/Home.tsx +++ b/src/pages/home/Home.tsx @@ -6,11 +6,11 @@ import { toast } from "@/components/Toast"; import { Paths } from "@/config/Paths"; import { clx, extractErrorMessage } from "@/helpers/utils"; import { usePageTitle } from "@/hooks/usePageTitle"; +import { useCreateCatalog } from "@/pages/catalogue/hooks/useCatalogueData"; import { ArrowLeft, DocumentText } from "iconsax-react"; import { useState, type FC } from "react"; import { useNavigate } from "react-router-dom"; import DirectLogin from "../auth/components/DirectLogin"; -import { useCreateCatalog } from "./hooks/useHomeData"; const CATALOG_SIZES = [ { id: "a4", label: "A4" }, @@ -71,7 +71,7 @@ const Home: FC = () => { type="button" onClick={() => setActive(id)} className={clx( - "flex w-full h-[126px] sm:w-[120px] cursor-pointer items-center justify-center rounded-[20px] border border-border sm:h-[146px] sm:w-[140px] lg:h-[166px] lg:w-[160px]", + "flex w-full h-[126px] cursor-pointer items-center justify-center rounded-[20px] border border-border sm:h-[146px] sm:w-[140px] lg:h-[166px] lg:w-[160px]", active === id && "border-black!", )} > diff --git a/src/pages/home/hooks/useHomeData.ts b/src/pages/home/hooks/useHomeData.ts deleted file mode 100644 index ac9c2d0..0000000 --- a/src/pages/home/hooks/useHomeData.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { useMutation, useQuery } from "@tanstack/react-query"; -import { useCallback, useEffect, useRef, useState } from "react"; -import * as api from "../service/HomeService"; - -export const useCreateCatalog = () => { - return useMutation({ - mutationFn: api.createCatalog, - }); -}; - -export const useDleteCatalog = () => { - return useMutation({ - mutationFn: (id: string) => api.deleteCatalogById(id), - }); -}; - -export const useGetCatalog = () => { - return useQuery({ - queryKey: ["catalogs"], - queryFn: api.getCatalogs, - }); -}; - -const catalogMongoIdPattern = /^[a-f\d]{24}$/i; - -export const isCatalogMongoId = (value: string) => - catalogMongoIdPattern.test(value); - -export const useGetCatalogById = (id: string) => { - return useQuery({ - queryKey: ["catalog", id], - queryFn: () => api.getCatalogById(id), - enabled: !!id, - // جلوگیری از بازنویسی state محلی با refetch هنگام فوکوس پنجره (مثلاً دو کاربر همزمان) - refetchOnWindowFocus: false, - staleTime: 5 * 60 * 1000, - }); -}; - -/** Route param may be Mongo id (editor/admin) or slug (public viewer-style URLs). */ -export const useGetCatalogByIdOrSlug = (param: string) => { - const isId = isCatalogMongoId(param); - const byId = useGetCatalogById(isId ? param : ""); - const bySlug = useGetCatalogBySlug(isId ? "" : param); - return isId ? byId : bySlug; -}; - -export const useGetCatalogBySlug = (slug: string) => { - return useQuery({ - queryKey: ["catalog", slug], - queryFn: () => api.getCatalogBySlug(slug), - enabled: !!slug, - // جلوگیری از بازنویسی state محلی با refetch هنگام فوکوس پنجره (مثلاً دو کاربر همزمان) - refetchOnWindowFocus: false, - staleTime: 5 * 60 * 1000, - }); -}; - -export const useUpdateCatalog = () => { - const mutation = useMutation({ - mutationFn: api.updateCatalog, - }); - - const timeoutRef = useRef | null>(null); - const pendingParamsRef = useRef< - Parameters[0] | null - >(null); - const [isScheduledSaving, setIsScheduledSaving] = useState(false); - - const cancelPendingUpdate = useCallback(() => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - timeoutRef.current = null; - } - pendingParamsRef.current = null; - setIsScheduledSaving(false); - }, []); - - const scheduleUpdate = useCallback( - (params: Parameters[0], delayMs = 3000) => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - pendingParamsRef.current = params; - setIsScheduledSaving(true); - - timeoutRef.current = setTimeout(() => { - const pendingParams = pendingParamsRef.current; - timeoutRef.current = null; - pendingParamsRef.current = null; - setIsScheduledSaving(false); - if (pendingParams) { - mutation.mutate(pendingParams); - } - }, delayMs); - }, - [mutation], - ); - - useEffect( - () => () => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - timeoutRef.current = null; - } - // اگر کاربر قبل از پایان debounce از ادیتور خارج شد، - // آخرین تغییرات معلق را همان لحظه ذخیره کن. - if (pendingParamsRef.current) { - mutation.mutate(pendingParamsRef.current); - pendingParamsRef.current = null; - } - setIsScheduledSaving(false); - }, - [mutation], - ); - - return { - ...mutation, - scheduleUpdate, - cancelPendingUpdate, - isSaving: isScheduledSaving || mutation.isPending, - }; -}; diff --git a/src/pages/home/service/HomeService.ts b/src/pages/home/service/HomeService.ts deleted file mode 100644 index ddbf633..0000000 --- a/src/pages/home/service/HomeService.ts +++ /dev/null @@ -1,45 +0,0 @@ -import axios from "@/config/axios"; -import type { - CatalogByIdResponseType, - CatalogResponseType, -} from "@/pages/catalogue/types/Types"; -import type { - CreateCatalogParamsType, - UpdateCatalogParamsType, -} from "../types/Types"; - -export const createCatalog = async (params: CreateCatalogParamsType) => { - const { data } = await axios.post("/admin/catalogue", params); - return data; -}; - -export const getCatalogs = async () => { - const { data } = await axios.get("/admin/catalogue"); - return data; -}; - -export const getCatalogById = async (id: string) => { - const { data } = await axios.get( - `/admin/catalogue/${id}`, - ); - return data; -}; - -export const getCatalogBySlug = async (slug: string) => { - const { data } = await axios.get( - `/public/catalogue/slug/${slug}`, - ); - return data; -}; - -export const updateCatalog = async (params: UpdateCatalogParamsType) => { - const { data } = await axios.patch(`/admin/catalogue/${params.id}`, { - content: params.content, - }); - return data; -}; - -export const deleteCatalogById = async (id: string) => { - const { data } = await axios.delete(`/admin/catalogue/${id}`); - return data; -}; diff --git a/src/pages/home/types/Types.ts b/src/pages/home/types/Types.ts deleted file mode 100644 index 10eb7a4..0000000 --- a/src/pages/home/types/Types.ts +++ /dev/null @@ -1,12 +0,0 @@ -export type CreateCatalogParamsType = { - name: string; - slug: string; - size: string; - content?: string; - id?: string; -}; - -export type UpdateCatalogParamsType = { - id: string; - content: string; -}; diff --git a/src/pages/viewer/Viewer.tsx b/src/pages/viewer/Viewer.tsx index e5cec42..bf91e7e 100644 --- a/src/pages/viewer/Viewer.tsx +++ b/src/pages/viewer/Viewer.tsx @@ -3,7 +3,7 @@ import type { DocumentSettings } from "@/pages/editor/store/editorStore"; import { useGetCatalogById, useGetCatalogBySlug, -} from "@/pages/home/hooks/useHomeData"; +} from "@/pages/catalogue/hooks/useCatalogueData"; import { type FC, useEffect, useState } from "react"; import { useParams } from "react-router-dom"; import BookViewer from "./components/BookViewer";