Initial commit: Meshkee dashboards monorepo.

Includes business, customer, and super-admin apps with shared packages and production deploy scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-07-22 13:48:53 +03:30
co-authored by Cursor
commit f566387c61
509 changed files with 62690 additions and 0 deletions
+122
View File
@@ -0,0 +1,122 @@
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:3000/api/v1'
const ACCESS_TOKEN_KEY = 'meshkee_access_token'
const REFRESH_TOKEN_KEY = 'meshkee_refresh_token'
export class ApiError extends Error {
status: number
constructor(message: string, status: number) {
super(message)
this.name = 'ApiError'
this.status = status
}
}
export function isAbortError(err: unknown): boolean {
return err instanceof DOMException && err.name === 'AbortError'
}
export function getAccessToken() {
return localStorage.getItem(ACCESS_TOKEN_KEY)
}
export function getRefreshToken() {
return localStorage.getItem(REFRESH_TOKEN_KEY)
}
export function setTokens(accessToken: string, refreshToken: string) {
localStorage.setItem(ACCESS_TOKEN_KEY, accessToken)
localStorage.setItem(REFRESH_TOKEN_KEY, refreshToken)
}
export function clearTokens() {
localStorage.removeItem(ACCESS_TOKEN_KEY)
localStorage.removeItem(REFRESH_TOKEN_KEY)
}
function parseErrorMessage(payload: unknown, fallback: string) {
if (!payload || typeof payload !== 'object') {
return fallback
}
const message = (payload as { message?: string | string[] }).message
if (Array.isArray(message)) {
return message.join(', ')
}
if (typeof message === 'string') {
return message
}
return fallback
}
async function refreshAccessToken() {
const refreshToken = getRefreshToken()
if (!refreshToken) {
return false
}
const response = await fetch(`${API_BASE_URL}/auth/refresh`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken }),
})
if (!response.ok) {
clearTokens()
return false
}
const data = await response.json()
setTokens(data.accessToken, data.refreshToken)
return true
}
interface RequestOptions extends Omit<RequestInit, 'body'> {
body?: unknown
auth?: boolean
}
export async function apiRequest<T>(path: string, options: RequestOptions = {}): Promise<T> {
const { body, auth = false, headers, ...rest } = options
const requestHeaders = new Headers(headers)
if (body !== undefined) {
requestHeaders.set('Content-Type', 'application/json')
}
if (auth) {
const accessToken = getAccessToken()
if (accessToken) {
requestHeaders.set('Authorization', `Bearer ${accessToken}`)
}
}
const response = await fetch(`${API_BASE_URL}${path}`, {
...rest,
headers: requestHeaders,
body: body !== undefined ? JSON.stringify(body) : undefined,
})
if (response.status === 401 && auth) {
const refreshed = await refreshAccessToken()
if (refreshed) {
return apiRequest<T>(path, options)
}
}
const payload = await response.json().catch(() => null)
if (!response.ok) {
throw new ApiError(
parseErrorMessage(payload, `Request failed with status ${response.status}`),
response.status,
)
}
return payload as T
}
+30
View File
@@ -0,0 +1,30 @@
/** Normalize Iranian/local input to E.164 (e.g. +989121111111). */
export function toE164CellNumber(input: string): string {
const digits = input.replace(/\D/g, '')
if (!digits) {
return ''
}
if (digits.startsWith('98')) {
return `+${digits}`
}
if (digits.startsWith('0')) {
return `+98${digits.slice(1)}`
}
if (digits.length === 10 && digits.startsWith('9')) {
return `+98${digits}`
}
return `+${digits}`
}
/** Display E.164 Iranian numbers as local format (e.g. 0912...). */
export function formatCellForDisplay(cellNumber: string): string {
if (cellNumber.startsWith('+98')) {
return `0${cellNumber.slice(3)}`
}
return cellNumber
}
+10
View File
@@ -0,0 +1,10 @@
/** Super admin host (meshkee.app in production). Defaults to current hostname. */
export function getAdminDomain(): string {
const fromEnv = import.meta.env.VITE_ADMIN_DOMAIN
if (fromEnv) return fromEnv
return window.location.hostname
}
export function isAllowedAdminHost(hostname = window.location.hostname): boolean {
return hostname === getAdminDomain()
}