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,80 @@
.container {
position: fixed;
bottom: 24px;
left: 24px;
z-index: 300;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
pointer-events: none;
}
.toast {
pointer-events: auto;
min-width: 220px;
max-width: 360px;
padding: 12px 16px;
font-size: 13px;
font-weight: 500;
line-height: 1.4;
color: var(--text-primary);
background: rgba(255, 255, 255, 0.55);
backdrop-filter: blur(var(--blur-glass));
-webkit-backdrop-filter: blur(var(--blur-glass));
border: 1px solid var(--glass-border);
border-radius: var(--radius-sm);
box-shadow: var(--glass-shadow);
}
.toastIn {
animation: toastIn 0.22s ease forwards;
}
.toastOut {
animation: toastOut 0.2s ease forwards;
}
.success {
border-color: rgba(22, 163, 74, 0.35);
}
.error {
border-color: rgba(239, 68, 68, 0.35);
}
@keyframes toastIn {
from {
opacity: 0;
transform: translateY(8px) scale(0.98);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes toastOut {
from {
opacity: 1;
transform: translateY(0) scale(1);
}
to {
opacity: 0;
transform: translateY(6px) scale(0.98);
}
}
@media (max-width: 480px) {
.container {
left: 16px;
right: 16px;
bottom: 16px;
align-items: stretch;
}
.toast {
min-width: 0;
max-width: none;
}
}
@@ -0,0 +1,98 @@
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
type ReactNode,
} from 'react'
import styles from './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
}