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
+149
View File
@@ -0,0 +1,149 @@
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
type ReactNode,
} from 'react'
import { ApiError, getAccessToken, isAbortError } from '../lib/api'
import {
setActiveBusinessById,
} from '../lib/businessContext'
import { getBusinessDomain } from '../lib/config'
import {
fetchCurrentUser,
login as loginRequest,
logout as logoutRequest,
} from '../services/authService'
import { resolveTenantByDomain } from '../services/tenantService'
import type { AuthUser } from '../types/auth'
interface AuthContextValue {
user: AuthUser | null
isLoading: boolean
login: (cellNumber: string, password: string) => Promise<void>
logout: () => void
setUser: (user: AuthUser) => void
}
const AuthContext = createContext<AuthContextValue | null>(null)
const BUSINESS_ACCESS_MESSAGE =
'This account cannot access the business dashboard. Only business owners and staff can sign in here.'
function isSuperAdminUser(user: AuthUser) {
return user.isSuperAdmin === true || user.roles.includes('super_admin')
}
function canAccessBusinessDashboard(user: AuthUser) {
if (isSuperAdminUser(user)) {
return true
}
return user.dashboard === 'business' && user.businesses.length > 0
}
async function prepareBusinessContext(user: AuthUser) {
const domain = getBusinessDomain()
if (isSuperAdminUser(user)) {
const tenant = await resolveTenantByDomain(domain)
setActiveBusinessById(String(tenant.id), tenant.domain)
return
}
const membership =
user.businesses.find((business) => business.slug === tenantSlugFromDomain(domain)) ??
user.businesses[0]
if (!membership) {
throw new ApiError('No business membership found for this account.', 403)
}
setActiveBusinessById(String(membership.id), domain)
}
function tenantSlugFromDomain(domain: string) {
return domain.split('.')[0]?.toLowerCase() ?? domain
}
async function assertBusinessAccess(user: AuthUser) {
if (!canAccessBusinessDashboard(user)) {
logoutRequest()
throw new ApiError(BUSINESS_ACCESS_MESSAGE, 403)
}
await prepareBusinessContext(user)
}
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<AuthUser | null>(null)
const [isLoading, setIsLoading] = useState(true)
const logout = useCallback(() => {
logoutRequest()
setUser(null)
}, [])
useEffect(() => {
const controller = new AbortController()
async function init() {
if (!getAccessToken()) {
if (!controller.signal.aborted) setIsLoading(false)
return
}
try {
const { user: currentUser } = await fetchCurrentUser(controller.signal)
if (controller.signal.aborted) return
if (!canAccessBusinessDashboard(currentUser)) {
logout()
return
}
await prepareBusinessContext(currentUser)
if (controller.signal.aborted) return
setUser(currentUser)
} catch (err) {
if (isAbortError(err) || controller.signal.aborted) return
logout()
} finally {
if (!controller.signal.aborted) setIsLoading(false)
}
}
void init()
return () => {
controller.abort()
}
}, [logout])
const login = useCallback(async (cellNumber: string, password: string) => {
const data = await loginRequest(cellNumber, password)
await assertBusinessAccess(data.user)
setUser(data.user)
}, [])
const value = useMemo(
() => ({ user, isLoading, login, logout, setUser }),
[user, isLoading, login, logout],
)
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}
export function useAuth() {
const context = useContext(AuthContext)
if (!context) {
throw new Error('useAuth must be used within AuthProvider')
}
return context
}
export { BUSINESS_ACCESS_MESSAGE }