From 61a934074c74deeb16ecfce5238a5e3958a19305 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Sun, 3 Aug 2025 09:12:48 +0330 Subject: [PATCH] base structure --- package-lock.json | 27 ++++++++++ package.json | 13 ++--- src/config/QueryProvider.tsx | 35 +++++++++++++ src/config/const.ts | 2 + src/config/func.ts | 96 ++++++++++++++++++++++++++++++++++ src/share/store/sharedStore.ts | 9 ++++ src/share/types/SharedTypes.ts | 37 +++++++++++++ src/types/error.types.ts | 14 +++++ src/types/response.types.ts | 7 +++ 9 files changed, 234 insertions(+), 6 deletions(-) create mode 100644 src/config/QueryProvider.tsx create mode 100644 src/config/const.ts create mode 100644 src/config/func.ts create mode 100644 src/share/store/sharedStore.ts create mode 100644 src/share/types/SharedTypes.ts create mode 100644 src/types/error.types.ts create mode 100644 src/types/response.types.ts 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; +}