copy base dmenu to dkala

This commit is contained in:
hamid zarghami
2026-02-07 15:31:22 +03:30
commit c9e37f6177
521 changed files with 57786 additions and 0 deletions
+11
View File
@@ -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
}
+6
View File
@@ -0,0 +1,6 @@
import { api } from "../axiosInstance";
export const fetchMe = async () => {
const res = await api.get('/user')
return res.data
}
+17
View File
@@ -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;
};
+10
View File
@@ -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
}
+12
View File
@@ -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
}
+11
View File
@@ -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
}
+10
View File
@@ -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
}
+11
View File
@@ -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
}
+119
View File
@@ -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 };
+31
View File
@@ -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);
};
+62
View File
@@ -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' },
]
}
}
+53
View File
@@ -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;
}
};
+80
View File
@@ -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;
}
+31
View File
@@ -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
}
+70
View File
@@ -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 };
+45
View File
@@ -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
};
}
+6
View File
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}