base structure
This commit is contained in:
Generated
+27
@@ -8,6 +8,7 @@
|
|||||||
"name": "shop",
|
"name": "shop",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@tanstack/react-query": "^5.84.1",
|
||||||
"next": "15.4.5",
|
"next": "15.4.5",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0"
|
"react-dom": "19.1.0"
|
||||||
@@ -1265,6 +1266,32 @@
|
|||||||
"tailwindcss": "4.1.11"
|
"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": {
|
"node_modules/@tybys/wasm-util": {
|
||||||
"version": "0.10.0",
|
"version": "0.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz",
|
||||||
|
|||||||
+7
-6
@@ -9,19 +9,20 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@tanstack/react-query": "^5.84.1",
|
||||||
|
"next": "15.4.5",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0"
|
||||||
"next": "15.4.5"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5",
|
"@eslint/eslintrc": "^3",
|
||||||
|
"@tailwindcss/postcss": "^4",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"@tailwindcss/postcss": "^4",
|
|
||||||
"tailwindcss": "^4",
|
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.4.5",
|
"eslint-config-next": "15.4.5",
|
||||||
"@eslint/eslintrc": "^3"
|
"tailwindcss": "^4",
|
||||||
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<QueryClientProvider client={client}>
|
||||||
|
<HydrationBoundary state={null}>
|
||||||
|
{children}
|
||||||
|
</HydrationBoundary>
|
||||||
|
</QueryClientProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export const TOKEN_NAME = "sh_token";
|
||||||
|
export const REFRESH_TOKEN_NAME = "sh_refresh_token";
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import { SharedStoreType } from "../types/SharedTypes";
|
||||||
|
|
||||||
|
export const useSharedStore = create<SharedStoreType>((set) => ({
|
||||||
|
openSidebar: false,
|
||||||
|
setOpenSidebar: (value) => set({ openSidebar: value }),
|
||||||
|
isLogin: false,
|
||||||
|
setIsLogin: (value) => set({ isLogin: value }),
|
||||||
|
}));
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
export type SharedStoreType = {
|
||||||
|
openSidebar: boolean;
|
||||||
|
setOpenSidebar: (value: boolean) => void;
|
||||||
|
|
||||||
|
isLogin: boolean;
|
||||||
|
setIsLogin: (value: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ApiResponse<T> {
|
||||||
|
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
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export interface IBaseResponse {
|
||||||
|
status: number;
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
|
export interface IResponse<T> extends IBaseResponse {
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user