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
@@ -0,0 +1,96 @@
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
type ReactNode,
} from 'react'
import { ApiError, getAccessToken, isAbortError } from '../lib/api'
import { fetchCurrentUser, login as loginRequest, logout as logoutRequest } from '../services/authService'
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)
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 (currentUser.dashboard !== 'super_admin') {
logout()
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)
if (data.user.dashboard !== 'super_admin') {
logoutRequest()
throw new ApiError(
'This account does not have super admin access. Use the correct dashboard for your role.',
403,
)
}
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
}
@@ -0,0 +1,98 @@
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
type ReactNode,
} from 'react'
import styles from '../components/Toast.module.css'
export type ToastVariant = 'success' | 'error' | 'info'
interface ToastItem {
id: number
message: string
variant: ToastVariant
}
interface ToastContextValue {
showToast: (message: string, variant?: ToastVariant) => void
}
const ToastContext = createContext<ToastContextValue | null>(null)
const TOAST_DURATION_MS = 3200
const ANIMATION_MS = 200
export function ToastProvider({ children }: { children: ReactNode }) {
const [toast, setToast] = useState<ToastItem | null>(null)
const [closing, setClosing] = useState(false)
const idRef = useRef(0)
const dismissTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const clearTimers = useCallback(() => {
if (dismissTimerRef.current) {
clearTimeout(dismissTimerRef.current)
dismissTimerRef.current = null
}
if (closeTimerRef.current) {
clearTimeout(closeTimerRef.current)
closeTimerRef.current = null
}
}, [])
const dismissToast = useCallback(() => {
setClosing(true)
closeTimerRef.current = setTimeout(() => {
setToast(null)
setClosing(false)
}, ANIMATION_MS)
}, [])
const showToast = useCallback(
(message: string, variant: ToastVariant = 'info') => {
clearTimers()
idRef.current += 1
setClosing(false)
setToast({ id: idRef.current, message, variant })
dismissTimerRef.current = setTimeout(() => {
dismissToast()
}, TOAST_DURATION_MS)
},
[clearTimers, dismissToast],
)
useEffect(() => clearTimers, [clearTimers])
const value = useMemo(() => ({ showToast }), [showToast])
return (
<ToastContext.Provider value={value}>
{children}
<div className={styles.container} aria-live="polite" aria-atomic="true">
{toast && (
<div
key={toast.id}
className={`${styles.toast} ${styles[toast.variant]} ${closing ? styles.toastOut : styles.toastIn}`}
role="status"
>
{toast.message}
</div>
)}
</div>
</ToastContext.Provider>
)
}
export function useToast() {
const context = useContext(ToastContext)
if (!context) {
throw new Error('useToast must be used within ToastProvider')
}
return context
}