copy base dmenu to dkala
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import { api } from "../axiosInstance";
|
||||
|
||||
export type LoginRequestModel = {
|
||||
phone: string,
|
||||
password: string,
|
||||
}
|
||||
|
||||
export const login = async (model: LoginRequestModel) => {
|
||||
const res = await api.post('/login', model)
|
||||
return res.data
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { api } from "../axiosInstance";
|
||||
|
||||
export const fetchMe = async () => {
|
||||
const res = await api.get('/user')
|
||||
return res.data
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import axios from "axios";
|
||||
import { API_BASE_URL } from "@/config/const";
|
||||
|
||||
export type RefreshTokenRequestModel = {
|
||||
token: string;
|
||||
};
|
||||
|
||||
export const refreshToken = async (model: RefreshTokenRequestModel) => {
|
||||
const res = await axios.post(
|
||||
`${API_BASE_URL}/public/auth/refresh`,
|
||||
{ refreshToken: model.token },
|
||||
{
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
return res.data;
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { api } from "../axiosInstance";
|
||||
|
||||
export type OtpRequestModel = {
|
||||
phone: string,
|
||||
}
|
||||
|
||||
export const requestOtp = async ({ phone }: OtpRequestModel) => {
|
||||
const res = await api.get(`/otp/${encodeURIComponent(phone)}`)
|
||||
return res.data
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { api } from "../axiosInstance";
|
||||
|
||||
export type ResetPasswordRequestModel = {
|
||||
phone: string,
|
||||
otp: string,
|
||||
newPassword: string,
|
||||
}
|
||||
|
||||
export const resetPassword = async (model: ResetPasswordRequestModel) => {
|
||||
const res = await api.post('/reset-password', model)
|
||||
return res.data
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { api } from "../axiosInstance";
|
||||
|
||||
export type SignupRequestModel = {
|
||||
phone: string,
|
||||
password: string,
|
||||
}
|
||||
|
||||
export const signup = async (model: SignupRequestModel) => {
|
||||
const res = await api.post('/signup', model)
|
||||
return res.data
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { api } from "../axiosInstance";
|
||||
|
||||
export type CheckUserExistsModel = {
|
||||
phone: string,
|
||||
}
|
||||
|
||||
export const checkUserExists = async ({ phone }: CheckUserExistsModel) => {
|
||||
const res = await api.get(`/exists/${encodeURIComponent(phone)}`)
|
||||
return res.data.exists as boolean
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { api } from "../axiosInstance";
|
||||
|
||||
export type OtpValidationRequestModel = {
|
||||
phone: string,
|
||||
otp: string,
|
||||
}
|
||||
|
||||
export const validateOtp = async (model: OtpValidationRequestModel) => {
|
||||
const res = await api.post(`/otp/check`, model)
|
||||
return res.data
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import axios from "axios";
|
||||
import {
|
||||
getToken,
|
||||
getRefreshToken,
|
||||
setToken,
|
||||
setRefreshToken,
|
||||
removeToken,
|
||||
removeRefreshToken,
|
||||
} from "./func";
|
||||
import { refreshToken } from "./auth/refresh-token";
|
||||
import { API_BASE_URL } from "@/config/const";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
isRefreshingToken?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
const axiosInstance = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
|
||||
axiosInstance.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
const originalRequest = error.config;
|
||||
|
||||
if (error?.response?.status === 401 && !originalRequest._retry) {
|
||||
if (typeof window !== "undefined" && window.isRefreshingToken) {
|
||||
// Wait for the refresh to complete
|
||||
await new Promise((resolve) => {
|
||||
const checkComplete = setInterval(() => {
|
||||
if (typeof window !== "undefined" && !window.isRefreshingToken) {
|
||||
clearInterval(checkComplete);
|
||||
resolve(true);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
return axiosInstance(originalRequest);
|
||||
}
|
||||
|
||||
originalRequest._retry = true;
|
||||
if (typeof window !== "undefined") {
|
||||
window.isRefreshingToken = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const refreshTokenValue = await getRefreshToken();
|
||||
|
||||
if (!refreshTokenValue) {
|
||||
await removeRefreshToken();
|
||||
await removeToken();
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await refreshToken({
|
||||
token: refreshTokenValue,
|
||||
});
|
||||
|
||||
const accessToken = data?.data?.accessToken?.token;
|
||||
const refreshTokenValueNew = data?.data?.refreshToken?.token;
|
||||
|
||||
if (accessToken) {
|
||||
await setToken(accessToken);
|
||||
|
||||
if (refreshTokenValueNew) {
|
||||
await setRefreshToken(refreshTokenValueNew);
|
||||
}
|
||||
|
||||
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
|
||||
return axiosInstance(originalRequest);
|
||||
} else {
|
||||
await removeRefreshToken();
|
||||
await removeToken();
|
||||
return;
|
||||
}
|
||||
} catch (refreshError) {
|
||||
console.error("Token refresh failed:", refreshError);
|
||||
await removeToken();
|
||||
await removeRefreshToken();
|
||||
if (typeof window !== "undefined") {
|
||||
// const pathname = window.location.pathname;
|
||||
// const slug = pathname.split("/")[1];
|
||||
// window.location.href = slug ? `/${slug}` : `/`;
|
||||
}
|
||||
return Promise.reject(refreshError);
|
||||
} finally {
|
||||
if (typeof window !== "undefined") {
|
||||
window.isRefreshingToken = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
axiosInstance.interceptors.request.use(async function (config) {
|
||||
const tokenValue = await getToken();
|
||||
const token = tokenValue ? tokenValue : "";
|
||||
|
||||
let slug = "dmenu";
|
||||
if (typeof window !== "undefined") {
|
||||
const pathname = window.location.pathname;
|
||||
const pathParts = pathname.split("/").filter(Boolean);
|
||||
if (pathParts.length > 0) {
|
||||
slug = pathParts[0];
|
||||
}
|
||||
}
|
||||
|
||||
config.headers.Authorization = "Bearer " + token;
|
||||
config.headers["X-SLUG"] = slug;
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
export { axiosInstance as api };
|
||||
@@ -0,0 +1,31 @@
|
||||
import { REFRESH_TOKEN_NAME, TOKEN_NAME } from "@/config/const";
|
||||
|
||||
export const getToken = async (): Promise<string | null> => {
|
||||
if (typeof window === "undefined") return null;
|
||||
return localStorage.getItem(TOKEN_NAME);
|
||||
};
|
||||
|
||||
export const getRefreshToken = async (): Promise<string | null> => {
|
||||
if (typeof window === "undefined") return null;
|
||||
return localStorage.getItem(REFRESH_TOKEN_NAME);
|
||||
};
|
||||
|
||||
export const setToken = async (token: string): Promise<void> => {
|
||||
if (typeof window === "undefined") return;
|
||||
localStorage.setItem(TOKEN_NAME, token);
|
||||
};
|
||||
|
||||
export const setRefreshToken = async (token: string): Promise<void> => {
|
||||
if (typeof window === "undefined") return;
|
||||
localStorage.setItem(REFRESH_TOKEN_NAME, token);
|
||||
};
|
||||
|
||||
export const removeToken = async (): Promise<void> => {
|
||||
if (typeof window === "undefined") return;
|
||||
localStorage.removeItem(TOKEN_NAME);
|
||||
};
|
||||
|
||||
export const removeRefreshToken = async (): Promise<void> => {
|
||||
if (typeof window === "undefined") return;
|
||||
localStorage.removeItem(REFRESH_TOKEN_NAME);
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
// const baseURL = process.env.NEXT_PUBLIC_API_URL || 'https://api.example.com'
|
||||
// const headers = { 'Content-Type': 'application/json' }
|
||||
export type AboutDataModel = {
|
||||
name: string,
|
||||
title_fa: string,
|
||||
description: string,
|
||||
foundationDate: string,
|
||||
offering: string,
|
||||
address: string,
|
||||
metadata: {
|
||||
title: string,
|
||||
description: string,
|
||||
},
|
||||
contacts: {
|
||||
instagram: string,
|
||||
whatsapp: string,
|
||||
telegram: string,
|
||||
phone: string,
|
||||
},
|
||||
open: Array<{ day: string, open: string, close: string }>
|
||||
}
|
||||
|
||||
export async function getAboutData(name: string) {
|
||||
// const res = await fetch(`${baseURL}/info/${name}`, {
|
||||
// next: { revalidate: 60 }, // or 'force-cache' for build-time caching
|
||||
// headers
|
||||
// });
|
||||
// return res.json();
|
||||
|
||||
let title_fa = '';
|
||||
if (name.toLocaleLowerCase() == 'zhivan') title_fa = 'کافه رستوران ژیوان'
|
||||
if (name.toLocaleLowerCase() == 'boote') title_fa = 'کافه رستوران بوته'
|
||||
|
||||
return {
|
||||
name,
|
||||
title_fa,
|
||||
description: 'لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است، و برای شرایط فعلی تکنولوژی مورد نیاز، و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد',
|
||||
foundationDate: String(Math.round(Math.random() * 10000)),
|
||||
offering: 'ایرانی، خارجی',
|
||||
address: 'خیابان دانشگاه روبروی بیمارستان خوانساری ساختمان مهر',
|
||||
metadata: {
|
||||
title: 'درباره ما | ' + name,
|
||||
description: 'همه چیز درباره مجموعه ' + name
|
||||
},
|
||||
contacts: {
|
||||
instagram: 'instagram',
|
||||
whatsapp: 'whatsapp',
|
||||
telegram: 'telegram',
|
||||
phone: 'phone',
|
||||
},
|
||||
open: [
|
||||
{ day: 'شنبه', open: '00:00', close: '12:00' },
|
||||
{ day: 'یک شنبه', open: '05:00', close: '12:00' },
|
||||
{ day: 'دو شنبه', open: '10:00', close: '14:00' },
|
||||
{ day: 'سه شنبه', open: '08:00', close: '10:00' },
|
||||
{ day: 'چهار شنبه', open: '15:00', close: '18:00' },
|
||||
{ day: 'پنج شنبه', open: '20:00', close: '24:00' },
|
||||
{ day: 'جمعه', open: '07:00', close: '13:00' },
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
interface IDanakError {
|
||||
status?: number;
|
||||
statusCode?: number;
|
||||
success?: boolean;
|
||||
error?: string | { message?: string[] | string };
|
||||
message?: string[] | string;
|
||||
}
|
||||
|
||||
const resolveMessage = (
|
||||
messages: string[] | string | undefined
|
||||
): string | null => {
|
||||
if (Array.isArray(messages) && messages.length > 0) {
|
||||
return messages[0];
|
||||
}
|
||||
if (typeof messages === "string" && messages.trim().length > 0) {
|
||||
return messages;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const extractErrorMessage = (
|
||||
error: Error | unknown,
|
||||
fallbackMessage: string = "خطایی رخ داده است"
|
||||
): string => {
|
||||
try {
|
||||
const axiosError = error as { response?: { data?: IDanakError } };
|
||||
const data = axiosError?.response?.data;
|
||||
|
||||
if (!data) {
|
||||
return fallbackMessage;
|
||||
}
|
||||
|
||||
const directMessage = resolveMessage(data.message);
|
||||
if (directMessage) {
|
||||
return directMessage;
|
||||
}
|
||||
|
||||
if (typeof data.error === "object" && data.error !== null) {
|
||||
const nestedMessage = resolveMessage(data.error.message);
|
||||
if (nestedMessage) {
|
||||
return nestedMessage;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof data.error === "string" && data.error.trim().length > 0) {
|
||||
return data.error;
|
||||
}
|
||||
|
||||
return fallbackMessage;
|
||||
} catch {
|
||||
return fallbackMessage;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* تبدیل hex به RGB normalized (0-1)
|
||||
*/
|
||||
function hexToRgbNormalized(hex: string): { r: number; g: number; b: number } | null {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result
|
||||
? {
|
||||
r: parseInt(result[1], 16) / 255,
|
||||
g: parseInt(result[2], 16) / 255,
|
||||
b: parseInt(result[3], 16) / 255,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* تبدیل RGB normalized به oklch
|
||||
* استفاده از فرمول استاندارد OKLab/OKLCH
|
||||
*/
|
||||
function rgbToOklch(r: number, g: number, b: number): string {
|
||||
try {
|
||||
// تبدیل sRGB به linear RGB
|
||||
const toLinear = (c: number) => {
|
||||
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
||||
};
|
||||
|
||||
const rLinear = toLinear(r);
|
||||
const gLinear = toLinear(g);
|
||||
const bLinear = toLinear(b);
|
||||
|
||||
// تبدیل linear RGB به XYZ (D65 white point)
|
||||
const x = rLinear * 0.4124564 + gLinear * 0.3575761 + bLinear * 0.1804375;
|
||||
const y = rLinear * 0.2126729 + gLinear * 0.7151522 + bLinear * 0.072175;
|
||||
const z = rLinear * 0.0193339 + gLinear * 0.119192 + bLinear * 0.9503041;
|
||||
|
||||
// تبدیل XYZ به OKLab
|
||||
const l = 0.8189330101 * x + 0.3618667424 * y - 0.1288597137 * z;
|
||||
const m = 0.0329845436 * x + 0.9293118715 * y + 0.0361456387 * z;
|
||||
const s = 0.0482003018 * x + 0.2643662691 * y + 0.6338517070 * z;
|
||||
|
||||
const lCbrt = Math.cbrt(l);
|
||||
const mCbrt = Math.cbrt(m);
|
||||
const sCbrt = Math.cbrt(s);
|
||||
|
||||
const L = 0.2104542553 * lCbrt + 0.7936177850 * mCbrt - 0.0040720468 * sCbrt;
|
||||
const a = 1.9779984951 * lCbrt - 2.4285922050 * mCbrt + 0.4505937099 * sCbrt;
|
||||
const bLab = 0.0259040371 * lCbrt + 0.7827717662 * mCbrt - 0.8086757660 * sCbrt;
|
||||
|
||||
// تبدیل OKLab به OKLCH
|
||||
const C = Math.sqrt(a * a + bLab * bLab);
|
||||
let h = Math.atan2(bLab, a) * (180 / Math.PI);
|
||||
if (h < 0) h += 360;
|
||||
|
||||
// تبدیل L از 0-1 به درصد
|
||||
const LPercent = L * 100;
|
||||
|
||||
return `oklch(${LPercent.toFixed(2)}% ${C.toFixed(4)} ${h.toFixed(1)})`;
|
||||
} catch (error) {
|
||||
// Fallback در صورت خطا
|
||||
return `oklch(60% 0.15 25)`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* تبدیل hex color به oklch
|
||||
*/
|
||||
export function hexToOklch(hex: string): string | null {
|
||||
const rgb = hexToRgbNormalized(hex);
|
||||
if (!rgb) return null;
|
||||
return rgbToOklch(rgb.r, rgb.g, rgb.b);
|
||||
}
|
||||
|
||||
/**
|
||||
* محاسبه brightness برای تعیین رنگ foreground
|
||||
*/
|
||||
export function calculateBrightness(hex: string): number {
|
||||
const rgb = hexToRgbNormalized(hex);
|
||||
if (!rgb) return 0.5;
|
||||
return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { IconType, SmartIconType } from "@/types/iconType";
|
||||
import { SmartIcon } from '@/components/utils/SmartIcon';
|
||||
|
||||
export function isSmartIconType(icon: unknown): icon is SmartIconType {
|
||||
return (
|
||||
typeof icon === 'object' &&
|
||||
icon !== null &&
|
||||
'Name' in icon &&
|
||||
typeof (icon as SmartIconType).Name === 'string'
|
||||
);
|
||||
}
|
||||
|
||||
export function renderIcon(icon: IconType) {
|
||||
if (React.isValidElement(icon)) {
|
||||
return icon; // It's a React node
|
||||
} else if (typeof icon === 'function') {
|
||||
const IconComponent = icon; // It's a functional component
|
||||
|
||||
// Create a ref to pass to the forwardRef component
|
||||
const ref = React.createRef<SVGSVGElement>(); // Adjust the type based on your icon component
|
||||
|
||||
return <IconComponent ref={ref} size={20} className='stroke-foreground mb-0.5' />;
|
||||
} else if (isSmartIconType(icon)) {
|
||||
// Handle SmartIconType
|
||||
return (
|
||||
<SmartIcon icon={icon} />
|
||||
);
|
||||
}
|
||||
return null; // No valid icon provided
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Latin Digits (English)
|
||||
const latinDigits = [
|
||||
'\u0030', // 0
|
||||
'\u0031', // 1
|
||||
'\u0032', // 2
|
||||
'\u0033', // 3
|
||||
'\u0034', // 4
|
||||
'\u0035', // 5
|
||||
'\u0036', // 6
|
||||
'\u0037', // 7
|
||||
'\u0038', // 8
|
||||
'\u0039' // 9
|
||||
];
|
||||
|
||||
// Arabic Digits
|
||||
const arabicDigits = [
|
||||
'\u0660', // ٠
|
||||
'\u0661', // ١
|
||||
'\u0662', // ٢
|
||||
'\u0663', // ٣
|
||||
'\u0664', // ٤
|
||||
'\u0665', // ٥
|
||||
'\u0666', // ٦
|
||||
'\u0667', // ٧
|
||||
'\u0668', // ٨
|
||||
'\u0669' // ٩
|
||||
];
|
||||
|
||||
// Persian Digits
|
||||
const persianDigits = [
|
||||
'\u06F0', // ۰
|
||||
'\u06F1', // ۱
|
||||
'\u06F2', // ۲
|
||||
'\u06F3', // ۳
|
||||
'\u06F4', // ۴
|
||||
'\u06F5', // ۵
|
||||
'\u06F6', // ۶
|
||||
'\u06F7', // ۷
|
||||
'\u06F8', // ۸
|
||||
'\u06F9' // ۹
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param n latin/arabic numbers
|
||||
* @returns persian numbers
|
||||
*/
|
||||
function ef(n: string | number) {
|
||||
return n.toString().split("").map(c => persianDigits[latinDigits.indexOf(c)] || persianDigits[arabicDigits.indexOf(c)] || c).join("");
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param n latin/persian numbers
|
||||
* @returns arabic numbers
|
||||
*/
|
||||
function ea(n: string | number) {
|
||||
return n.toString().split("").map(c => arabicDigits[latinDigits.indexOf(c)] || arabicDigits[persianDigits.indexOf(c)] || c).join("");
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param n persian/arabic numbers
|
||||
* @returns latin numbers
|
||||
*/
|
||||
function ue(n: string | number) {
|
||||
return n.toString().split("").map(c => latinDigits[persianDigits.indexOf(c)] || latinDigits[arabicDigits.indexOf(c)] || c).join("");
|
||||
};
|
||||
|
||||
export { ef, ea, ue };
|
||||
@@ -0,0 +1,45 @@
|
||||
import { createInstance } from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next/initReactI18next';
|
||||
import resourcesToBackend from 'i18next-resources-to-backend';
|
||||
import i18nConfig from '../../i18nConfig';
|
||||
|
||||
export const getI18nObject = (t, x) => {
|
||||
return t(x, { returnObjects: true })
|
||||
}
|
||||
|
||||
export default async function initTranslations(
|
||||
locale,
|
||||
namespaces,
|
||||
i18nInstance,
|
||||
resources
|
||||
) {
|
||||
i18nInstance = i18nInstance || createInstance();
|
||||
|
||||
i18nInstance.use(initReactI18next);
|
||||
|
||||
if (!resources) {
|
||||
i18nInstance.use(
|
||||
resourcesToBackend(
|
||||
(language, namespace) =>
|
||||
import(`@/locales/${language}/${namespace}.json`)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
await i18nInstance.init({
|
||||
lng: locale,
|
||||
resources,
|
||||
fallbackLng: i18nConfig.defaultLocale,
|
||||
supportedLngs: i18nConfig.locales,
|
||||
defaultNS: namespaces[0],
|
||||
fallbackNS: namespaces[0],
|
||||
ns: namespaces,
|
||||
preload: resources ? [] : i18nConfig.locales
|
||||
});
|
||||
|
||||
return {
|
||||
i18n: i18nInstance,
|
||||
resources: { [locale]: i18nInstance.services.resourceStore.data[locale] },
|
||||
t: i18nInstance.t
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
Reference in New Issue
Block a user