diff --git a/package-lock.json b/package-lock.json
index a63086e..af711a4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"name": "shop",
"version": "0.1.0",
"dependencies": {
+ "@tanstack/react-query": "^5.84.1",
"next": "15.4.5",
"react": "19.1.0",
"react-dom": "19.1.0"
@@ -1265,6 +1266,32 @@
"tailwindcss": "4.1.11"
}
},
+ "node_modules/@tanstack/query-core": {
+ "version": "5.83.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.83.1.tgz",
+ "integrity": "sha512-OG69LQgT7jSp+5pPuCfzltq/+7l2xoweggjme9vlbCPa/d7D7zaqv5vN/S82SzSYZ4EDLTxNO1PWrv49RAS64Q==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.84.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.84.1.tgz",
+ "integrity": "sha512-zo7EUygcWJMQfFNWDSG7CBhy8irje/XY0RDVKKV4IQJAysb+ZJkkJPcnQi+KboyGUgT+SQebRFoTqLuTtfoDLw==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.83.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
+ }
+ },
"node_modules/@tybys/wasm-util": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz",
diff --git a/package.json b/package.json
index 2c3e235..759dc51 100644
--- a/package.json
+++ b/package.json
@@ -9,19 +9,20 @@
"lint": "next lint"
},
"dependencies": {
+ "@tanstack/react-query": "^5.84.1",
+ "next": "15.4.5",
"react": "19.1.0",
- "react-dom": "19.1.0",
- "next": "15.4.5"
+ "react-dom": "19.1.0"
},
"devDependencies": {
- "typescript": "^5",
+ "@eslint/eslintrc": "^3",
+ "@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
- "@tailwindcss/postcss": "^4",
- "tailwindcss": "^4",
"eslint": "^9",
"eslint-config-next": "15.4.5",
- "@eslint/eslintrc": "^3"
+ "tailwindcss": "^4",
+ "typescript": "^5"
}
}
diff --git a/src/config/QueryProvider.tsx b/src/config/QueryProvider.tsx
new file mode 100644
index 0000000..cd72152
--- /dev/null
+++ b/src/config/QueryProvider.tsx
@@ -0,0 +1,35 @@
+'use client'
+
+import { IApiErrorRepsonse } from '@/types/error.types'
+import { QueryClient, QueryClientProvider, HydrationBoundary, QueryCache } from '@tanstack/react-query'
+import { ReactNode, useState } from 'react'
+import { removeToken } from './func'
+import { useSharedStore } from '@/share/store/sharedStore'
+
+export default function QueryProvider({ children }: { children: ReactNode }) {
+ const { setIsLogin } = useSharedStore()
+ const [client] = useState(() => new QueryClient({
+ queryCache: new QueryCache({
+ onError: async (error: IApiErrorRepsonse) => {
+ if (error.response?.status === 401) {
+ removeToken()
+ setIsLogin(false)
+ }
+ }
+ }),
+ defaultOptions: {
+ queries: {
+ staleTime: 1000 * 60 * 5, // دادهها برای 5 دقیقه معتبر هستند
+
+ }
+ }
+ }))
+
+ return (
+
+
+ {children}
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/config/const.ts b/src/config/const.ts
new file mode 100644
index 0000000..c7dbf9b
--- /dev/null
+++ b/src/config/const.ts
@@ -0,0 +1,2 @@
+export const TOKEN_NAME = "sh_token";
+export const REFRESH_TOKEN_NAME = "sh_refresh_token";
diff --git a/src/config/func.ts b/src/config/func.ts
new file mode 100644
index 0000000..e2332a4
--- /dev/null
+++ b/src/config/func.ts
@@ -0,0 +1,96 @@
+import { TOKEN_NAME, REFRESH_TOKEN_NAME } from "./const";
+
+export function isEmail(input: string): boolean {
+ const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
+ return emailRegex.test(input);
+}
+
+export function NumberFormat(number: number): string {
+ return Intl.NumberFormat("fa-IR").format(number);
+ // return Intl.NumberFormat("fa-IR").format(number);
+}
+
+export const timeAgo = (date: string | Date): string => {
+ const now = new Date();
+ const past = new Date(date);
+ const diff = Math.floor((now.getTime() - past.getTime()) / 1000); // اختلاف بر حسب ثانیه
+
+ const units = [
+ { name: "سال", value: 60 * 60 * 24 * 365 },
+ { name: "ماه", value: 60 * 60 * 24 * 30 },
+ { name: "روز", value: 60 * 60 * 24 },
+ { name: "ساعت", value: 60 * 60 },
+ { name: "دقیقه", value: 60 },
+ { name: "ثانیه", value: 1 },
+ ];
+
+ for (const unit of units) {
+ const amount = Math.floor(diff / unit.value);
+ if (amount >= 1) return `${amount} ${unit.name} پیش`;
+ }
+
+ return "همین الان";
+};
+export const getToken = async () => {
+ return localStorage.getItem(TOKEN_NAME);
+};
+
+export const getRefreshToken = () => {
+ return localStorage.getItem(REFRESH_TOKEN_NAME);
+};
+
+export const setToken = (token: string) => {
+ localStorage.setItem(TOKEN_NAME, token);
+};
+
+export const setRefreshToken = (refreshToken: string) => {
+ localStorage.setItem(REFRESH_TOKEN_NAME, refreshToken);
+};
+
+export const removeToken = () => {
+ localStorage.removeItem(TOKEN_NAME);
+};
+
+export const removeRefreshToken = () => {
+ localStorage.removeItem(REFRESH_TOKEN_NAME);
+};
+
+export function toJalaliDate(
+ date: Date | string | number,
+ options?: Intl.DateTimeFormatOptions,
+ locale: string = "fa-IR-u-ca-persian"
+): string {
+ const parsedDate = new Date(date);
+
+ if (isNaN(parsedDate.getTime())) {
+ return "";
+ throw new Error("Invalid date provided to toJalaliDate");
+ }
+
+ const defaultOptions: Intl.DateTimeFormatOptions = {
+ year: "numeric",
+ month: "numeric",
+ day: "numeric",
+ // weekday: "long",
+ // hour: "numeric",
+ // minute: "numeric",
+ };
+
+ const formatter = new Intl.DateTimeFormat(locale, {
+ ...defaultOptions,
+ ...options,
+ });
+
+ return formatter.format(parsedDate);
+}
+
+export function limitHtmlWords(htmlString: string, wordLimit: number): string {
+ // پاک کردن تگها
+ const plainText = htmlString.replace(/<[^>]*>/g, "");
+
+ // تقسیم و محدود کردن کلمات
+ const words = plainText.split(/\s+/).slice(0, wordLimit);
+ const result = words.join(" ") + "...";
+
+ return result;
+}
diff --git a/src/share/store/sharedStore.ts b/src/share/store/sharedStore.ts
new file mode 100644
index 0000000..f60ed88
--- /dev/null
+++ b/src/share/store/sharedStore.ts
@@ -0,0 +1,9 @@
+import { create } from "zustand";
+import { SharedStoreType } from "../types/SharedTypes";
+
+export const useSharedStore = create((set) => ({
+ openSidebar: false,
+ setOpenSidebar: (value) => set({ openSidebar: value }),
+ isLogin: false,
+ setIsLogin: (value) => set({ isLogin: value }),
+}));
diff --git a/src/share/types/SharedTypes.ts b/src/share/types/SharedTypes.ts
new file mode 100644
index 0000000..7d81147
--- /dev/null
+++ b/src/share/types/SharedTypes.ts
@@ -0,0 +1,37 @@
+export type SharedStoreType = {
+ openSidebar: boolean;
+ setOpenSidebar: (value: boolean) => void;
+
+ isLogin: boolean;
+ setIsLogin: (value: boolean) => void;
+};
+
+export interface ApiResponse {
+ statusCode: number;
+ success: boolean;
+ data: T;
+}
+
+export type UserMeResponseType = ApiResponse<{
+ user: {
+ id: string;
+ createdAt: string;
+ updatedAt: string;
+ firstName: string;
+ lastName: string;
+ email: string;
+ emailVerified: boolean;
+ phone: string;
+ birthDate: string;
+ nationalCode: string;
+ financialType: string;
+ profilePic: string | null;
+ userName: string | null;
+ roles: Array<{
+ id: string;
+ createdAt: string;
+ name: string;
+ // Add other role properties as needed
+ }>;
+ };
+}>;
diff --git a/src/types/error.types.ts b/src/types/error.types.ts
new file mode 100644
index 0000000..facf569
--- /dev/null
+++ b/src/types/error.types.ts
@@ -0,0 +1,14 @@
+import { DefaultError } from "@tanstack/react-query";
+import { IBaseResponse } from "./response.types";
+
+interface IError {
+ message: string[];
+}
+
+export interface IErrorResponse extends IBaseResponse {
+ error: IError;
+}
+
+export interface IApiErrorRepsonse extends DefaultError {
+ response?: IErrorResponse;
+}
diff --git a/src/types/response.types.ts b/src/types/response.types.ts
new file mode 100644
index 0000000..be1b704
--- /dev/null
+++ b/src/types/response.types.ts
@@ -0,0 +1,7 @@
+export interface IBaseResponse {
+ status: number;
+ success: boolean;
+}
+export interface IResponse extends IBaseResponse {
+ data: T;
+}