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,297 @@
import { useEffect, useMemo, useState } from 'react'
import { RotateCcw, Search } from 'lucide-react'
import { Breadcrumbs } from '../components/Breadcrumbs'
import { ContactSubmissionDetailModal } from '../components/ContactSubmissionDetailModal'
import { ApiError, isAbortError } from '../lib/api'
import { formatCellForDisplay } from '../lib/cellNumber'
import {
listContactSubmissions,
} from '../services/contactSubmissionService'
import type {
ContactSubmission,
ContactSubmissionsListResponse,
} from '../types/contactSubmission'
import filterStyles from '../components/ListFiltersPanel.module.css'
import pageStyles from '../components/PageContent.module.css'
import styles from './WebsiteContactPage.module.css'
const PAGE_SIZE = 20
const COLUMN_COUNT = 6
function buildPageNumbers(page: number, totalPages: number) {
const start = Math.max(1, page - 2)
const end = Math.min(totalPages, page + 2)
const numbers: number[] = []
for (let i = start; i <= end; i++) numbers.push(i)
return numbers
}
function formatDateTime(value: string) {
const d = new Date(value)
if (Number.isNaN(d.getTime())) return { date: value, time: '' }
return {
date: d.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: '2-digit' }),
time: d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }),
}
}
export function WebsiteContactPage() {
const [data, setData] = useState<ContactSubmissionsListResponse | null>(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const [appliedQuery, setAppliedQuery] = useState('')
const [draftQuery, setDraftQuery] = useState('')
const [page, setPage] = useState(1)
const [selected, setSelected] = useState<ContactSubmission | null>(null)
useEffect(() => {
const controller = new AbortController()
async function load() {
setLoading(true)
setError('')
try {
const result = await listContactSubmissions(
{
page,
pageSize: PAGE_SIZE,
...(appliedQuery ? { q: appliedQuery } : {}),
},
controller.signal,
)
if (controller.signal.aborted) return
setData(result)
} catch (err) {
if (isAbortError(err) || controller.signal.aborted) return
setError(err instanceof ApiError ? err.message : 'Unable to load contact submissions.')
} finally {
if (!controller.signal.aborted) setLoading(false)
}
}
void load()
return () => {
controller.abort()
}
}, [page, appliedQuery])
const totalPages = useMemo(() => {
const total = data?.total ?? 0
return Math.max(1, Math.ceil(total / PAGE_SIZE))
}, [data?.total])
const pageNumbers = useMemo(() => buildPageNumbers(page, totalPages), [page, totalPages])
const showingFrom = useMemo(() => {
if (!data || data.total === 0) return 0
return (page - 1) * PAGE_SIZE + 1
}, [data, page])
const showingTo = useMemo(() => {
if (!data) return 0
return Math.min(data.total, page * PAGE_SIZE)
}, [data, page])
function applyFilters() {
setPage(1)
setAppliedQuery(draftQuery.trim())
}
function clearFilters() {
setDraftQuery('')
setAppliedQuery('')
setPage(1)
}
return (
<main className={pageStyles.content}>
<Breadcrumbs
items={[
{ label: 'Dashboard', href: '/' },
{ label: 'Website', href: '/website' },
{ label: 'Contact Us Form' },
]}
/>
<div className={pageStyles.pageHeader}>
<div>
<h2 className={pageStyles.pageTitle}>Contact us form</h2>
<p className={pageStyles.pageSubtitle}>
Submissions received from your website contact form.
</p>
</div>
</div>
<div className={filterStyles.filtersPanel}>
<div className={filterStyles.filtersTitle}>Filters</div>
<div className={filterStyles.filtersGrid}>
<div className={filterStyles.filtersInputs}>
<div className={`${filterStyles.field} ${filterStyles.fieldCol4}`}>
<label htmlFor="filter-contact-q">Search</label>
<input
id="filter-contact-q"
value={draftQuery}
onChange={(e) => setDraftQuery(e.target.value)}
placeholder="Title, name, email, cell number, or message"
onKeyDown={(e) => {
if (e.key === 'Enter') applyFilters()
}}
/>
</div>
</div>
<div className={filterStyles.filterActions}>
<button
type="button"
className={`${filterStyles.iconActionBtn} ${filterStyles.iconActionBtnPrimary}`}
onClick={applyFilters}
disabled={loading}
aria-label="Search"
title="Search"
>
<Search size={18} />
</button>
<button
type="button"
className={`${filterStyles.iconActionBtn} ${filterStyles.iconActionBtnGhost}`}
onClick={clearFilters}
disabled={loading}
aria-label="Clear filters"
title="Clear filters"
>
<RotateCcw size={18} />
</button>
</div>
</div>
</div>
<div className={styles.tablePanel}>
<div className={styles.tableWrap}>
<div className={styles.tableHeader}>
<div className={styles.tableHeaderTitle}>Submissions</div>
<div className={styles.meta}>
{data ? (
data.total > 0 ? (
<>
Showing {showingFrom} - {showingTo} of {data.total}
</>
) : (
'No submissions'
)
) : (
' '
)}
</div>
</div>
{error && <div className={styles.errorBanner}>{error}</div>}
<div className={styles.tableScroll}>
<table className={styles.table}>
<thead>
<tr>
<th className={styles.th}>Title</th>
<th className={styles.th}>Name</th>
<th className={styles.th}>Email</th>
<th className={styles.th}>Cell number</th>
<th className={styles.th}>Date</th>
<th className={styles.th}>Time</th>
</tr>
</thead>
<tbody>
{loading && (
<tr>
<td className={styles.td} colSpan={COLUMN_COUNT}>
Loading...
</td>
</tr>
)}
{!loading && data?.items.length === 0 && (
<tr>
<td className={styles.td} colSpan={COLUMN_COUNT}>
No submissions found.
</td>
</tr>
)}
{!loading &&
data?.items.map((item) => {
const { date, time } = formatDateTime(item.createdAt)
return (
<tr
key={item.id}
className={styles.clickableRow}
onClick={() => setSelected(item)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
setSelected(item)
}
}}
tabIndex={0}
role="button"
aria-label={`View submission from ${item.name}`}
>
<td className={styles.td}>{item.title}</td>
<td className={styles.td}>{item.name}</td>
<td className={styles.td}>{item.email ?? '—'}</td>
<td className={styles.td}>
{item.cellNumber ? formatCellForDisplay(item.cellNumber) : '—'}
</td>
<td className={styles.td}>{date}</td>
<td className={styles.td}>{time || '—'}</td>
</tr>
)
})}
</tbody>
</table>
</div>
<div className={styles.pagination}>
<div>
Page {page} / {totalPages}
</div>
<div className={styles.pagerBtns}>
<button
type="button"
className={styles.pageBtn}
onClick={() => setPage((p) => Math.max(1, p - 1))}
disabled={page <= 1 || loading}
>
Prev
</button>
{pageNumbers.map((n) => (
<button
key={n}
type="button"
className={`${styles.pageBtn} ${n === page ? styles.pageBtnActive : ''}`}
onClick={() => setPage(n)}
disabled={loading}
>
{n}
</button>
))}
<button
type="button"
className={styles.pageBtn}
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
disabled={page >= totalPages || loading}
>
Next
</button>
</div>
</div>
</div>
</div>
<ContactSubmissionDetailModal
open={selected !== null}
submission={selected}
onClose={() => setSelected(null)}
/>
</main>
)
}