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 @@
import { API_BASE_URL } from './config'
const ACCESS_TOKEN_KEY = 'meshkee_business_access_token'
const REFRESH_TOKEN_KEY = 'meshkee_business_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
}
+38
View File
@@ -0,0 +1,38 @@
import type { AuthUser } from '../types/auth'
import { getBusinessDomain } from './config'
const ACTIVE_BUSINESS_ID_KEY = 'meshkee_active_business_id'
const ACTIVE_BUSINESS_DOMAIN_KEY = 'meshkee_active_business_domain'
export function setActiveBusiness(user: AuthUser) {
const domain = getBusinessDomain()
localStorage.setItem(ACTIVE_BUSINESS_DOMAIN_KEY, domain)
if (user.businesses.length > 0) {
localStorage.setItem(ACTIVE_BUSINESS_ID_KEY, String(user.businesses[0].id))
}
}
export function setActiveBusinessById(businessId: string, domain?: string) {
localStorage.setItem(ACTIVE_BUSINESS_ID_KEY, businessId)
localStorage.setItem(ACTIVE_BUSINESS_DOMAIN_KEY, domain ?? getBusinessDomain())
}
export function getActiveBusinessId(): string | null {
return localStorage.getItem(ACTIVE_BUSINESS_ID_KEY)
}
export function getActiveBusinessDomain(): string {
return localStorage.getItem(ACTIVE_BUSINESS_DOMAIN_KEY) ?? getBusinessDomain()
}
export function clearActiveBusiness() {
localStorage.removeItem(ACTIVE_BUSINESS_ID_KEY)
localStorage.removeItem(ACTIVE_BUSINESS_DOMAIN_KEY)
}
export const BUSINESS_PROFILE_UPDATED_EVENT = 'meshkee:business-profile-updated'
export function dispatchBusinessProfileUpdated() {
window.dispatchEvent(new Event(BUSINESS_PROFILE_UPDATED_EVENT))
}
+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
}
+36
View File
@@ -0,0 +1,36 @@
import {
BUSINESS_SUBDOMAIN_PREFIX,
getBaseDomainFromHost,
getBusinessDashboardHost,
isBusinessDashboardHost,
} from '@meshkee/dashboard-core'
export const API_BASE_URL =
import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:3000/api/v1'
export { BUSINESS_SUBDOMAIN_PREFIX }
/** Base tenant domain without dashboard prefix (e.g. sanihome.ir). */
export function getBaseBusinessDomain(hostname = window.location.hostname): string {
return getBaseDomainFromHost(hostname, import.meta.env.VITE_BUSINESS_DOMAIN)
}
/** Expected business admin dashboard host (e.g. business.sanihome.ir). */
export function getBusinessDashboardHostForApp(baseDomain?: string): string {
return getBusinessDashboardHost(baseDomain ?? getBaseBusinessDomain())
}
export function isAllowedBusinessHost(hostname = window.location.hostname): boolean {
const host = hostname.toLowerCase().trim()
if (host === 'localhost' || host === '127.0.0.1') {
return true
}
return isBusinessDashboardHost(host)
}
/** Domain sent to tenant resolution APIs. */
export function getBusinessDomain(): string {
return getBaseBusinessDomain()
}
+47
View File
@@ -0,0 +1,47 @@
import type { RouteTitleRule } from '@meshkee/dashboard-core'
export const BUSINESS_DASHBOARD_NAME = 'Business Dashboard'
export const businessRouteTitleRules: RouteTitleRule[] = [
{ match: '/login', labels: ['Sign in'] },
{ match: '/business-profile', labels: ['Business Profile'] },
{ match: '/products/categories', labels: ['Products', 'Categories'] },
{ match: '/products/brands', labels: ['Products', 'Brands'] },
{ match: '/products/new', labels: ['Products', 'Add New Product'] },
{ match: /^\/products\/edit\/[^/]+$/, labels: ['Products', 'Edit Product'] },
{ match: '/products/list', labels: ['Products', 'My Products'] },
{ match: /^\/products\/detail\/[^/]+$/, labels: ['Products', 'Product Details'] },
{ match: '/products/settings', labels: ['Products', 'Settings'] },
{ match: '/products', labels: ['Products'] },
{ match: '/store/items', labels: ['Store', 'My Store Items'] },
{ match: '/store/orders', labels: ['Store', 'My Orders'] },
{ match: '/store/cards', labels: ['Store', 'Shopping Cards'] },
{ match: '/store/settings', labels: ['Store', 'Settings'] },
{ match: '/store', labels: ['Store'] },
{ match: '/customers', labels: ['Customers'] },
{ match: '/blog/list', labels: ['Blog', 'My Blogs'] },
{ match: /^\/blog\/detail\/[^/]+$/, labels: ['Blog', 'Blog Details'] },
{ match: '/blog/new', labels: ['Blog', 'Add New Blog'] },
{ match: /^\/blog\/edit\/[^/]+$/, labels: ['Blog', 'Edit Blog'] },
{ match: '/blog/categories', labels: ['Blog', 'Categories'] },
{ match: '/blog/settings', labels: ['Blog', 'Settings'] },
{ match: '/blog', labels: ['Blog'] },
{ match: '/portfolios/list', labels: ['Portfolios', 'My Portfolios'] },
{ match: /^\/portfolios\/detail\/[^/]+$/, labels: ['Portfolios', 'Portfolio Details'] },
{ match: '/portfolios/new', labels: ['Portfolios', 'Add New Portfolio'] },
{ match: /^\/portfolios\/edit\/[^/]+$/, labels: ['Portfolios', 'Edit Portfolio'] },
{ match: '/portfolios/categories', labels: ['Portfolios', 'Categories'] },
{ match: '/portfolios/settings', labels: ['Portfolios', 'Settings'] },
{ match: '/portfolios', labels: ['Portfolios'] },
{ match: '/website/sliders', labels: ['Website', 'Sliders'] },
{ match: '/website/special-categories', labels: ['Website', 'Special Categories'] },
{ match: '/website/special-brands', labels: ['Website', 'Special Brands'] },
{ match: '/website/special-items', labels: ['Website', 'Special Items'] },
{ match: '/website/contact', labels: ['Website', 'Contact Us Form'] },
{ match: '/website/subscriptions', labels: ['Website', 'Subscriptions'] },
{ match: '/website/faq', labels: ['Website', 'FAQ'] },
{ match: '/website/badges', labels: ['Website', 'Badges'] },
{ match: '/website/e-payment', labels: ['Website', 'E-Payment'] },
{ match: '/website', labels: ['Website'] },
{ match: '/', labels: ['Home'] },
]