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,342 @@
import { useEffect, useState } from 'react'
import { ChevronDown, ChevronUp, Plus, Trash2 } from 'lucide-react'
import { Breadcrumbs } from '../components/Breadcrumbs'
import { StepColorPicker } from '../components/StepColorPicker'
import { Switch } from '../components/Switch'
import { Tooltip } from '../components/Tooltip'
import { useToast } from '../context/ToastContext'
import { ApiError } from '../lib/api'
import {
getSettings,
updateStoreSettings,
DEFAULT_ORDER_PROCESS_STEPS,
type OrderProcessStep,
type StoreSettings,
} from '../services/settingsService'
import { createId } from '../utils/id'
import {
defaultStepColorForId,
normalizeStepColor,
type StepColorPreset,
} from '../utils/stepColors'
import controlStyles from '../components/CategoryRow.module.css'
import removeStyles from '../components/VariationsModal.module.css'
import pageStyles from '../components/PageContent.module.css'
import sharedStyles from './ProductSettingsPage.module.css'
import styles from './StoreSettingsPage.module.css'
const DEFAULT_STORE_SETTINGS: StoreSettings = {
onlineSellEnabled: true,
orderProcessSteps: DEFAULT_ORDER_PROCESS_STEPS,
}
function normalizeSteps(steps: OrderProcessStep[]) {
return steps.map((step, index) => ({
id: step.id,
label: step.label.trim(),
color: normalizeStepColor(step.color, defaultStepColorForId(step.id, index)),
}))
}
function stepsAreEqual(a: OrderProcessStep[], b: OrderProcessStep[]) {
if (a.length !== b.length) return false
return a.every((step, index) => {
const other = b[index]
return (
step.id === other.id && step.label === other.label && step.color === other.color
)
})
}
export function StoreSettingsPage() {
const { showToast } = useToast()
const [settings, setSettings] = useState<StoreSettings>(DEFAULT_STORE_SETTINGS)
const [draftSteps, setDraftSteps] = useState<OrderProcessStep[]>(
DEFAULT_STORE_SETTINGS.orderProcessSteps,
)
const [isLoading, setIsLoading] = useState(true)
const [savingKey, setSavingKey] = useState<string | null>(null)
const [error, setError] = useState('')
useEffect(() => {
const controller = new AbortController()
void loadSettings(controller.signal)
return () => controller.abort()
}, [])
async function loadSettings(signal?: AbortSignal) {
setIsLoading(true)
setError('')
try {
const data = await getSettings(signal)
setSettings(data.settings.store)
setDraftSteps(data.settings.store.orderProcessSteps)
} catch (err) {
if (err instanceof DOMException && err.name === 'AbortError') return
if (err instanceof ApiError) {
setError(err.message)
} else {
setError('Unable to load settings.')
}
} finally {
setIsLoading(false)
}
}
async function handleOnlineSellChange(checked: boolean) {
setSavingKey('onlineSell')
setError('')
try {
const data = await updateStoreSettings({ onlineSellEnabled: checked })
setSettings(data.settings.store)
showToast('Settings saved.', 'success')
} catch (err) {
if (err instanceof ApiError) {
setError(err.message)
} else {
setError('Unable to save settings.')
}
} finally {
setSavingKey(null)
}
}
function updateStepLabel(id: string, label: string) {
setDraftSteps((current) =>
current.map((step) => (step.id === id ? { ...step, label } : step)),
)
}
function updateStepColor(id: string, color: StepColorPreset) {
setDraftSteps((current) =>
current.map((step) => (step.id === id ? { ...step, color } : step)),
)
}
function addStep() {
setDraftSteps((current) => {
const id = createId()
return [
...current,
{
id,
label: '',
color: defaultStepColorForId(id, current.length),
},
]
})
}
function removeStep(id: string) {
setDraftSteps((current) => current.filter((step) => step.id !== id))
}
function moveStep(id: string, direction: -1 | 1) {
setDraftSteps((current) => {
const index = current.findIndex((step) => step.id === id)
if (index < 0) return current
const targetIndex = index + direction
if (targetIndex < 0 || targetIndex >= current.length) return current
const next = [...current]
const [item] = next.splice(index, 1)
next.splice(targetIndex, 0, item)
return next
})
}
async function handleSaveSteps() {
const normalized = normalizeSteps(draftSteps)
const hasEmptyLabel = normalized.some((step) => !step.label)
if (!normalized.length) {
setError('Add at least one order process step.')
return
}
if (hasEmptyLabel) {
setError('Every order step needs a label.')
return
}
setSavingKey('orderSteps')
setError('')
try {
const data = await updateStoreSettings({ orderProcessSteps: normalized })
setSettings(data.settings.store)
setDraftSteps(data.settings.store.orderProcessSteps)
showToast('Order process steps saved.', 'success')
} catch (err) {
if (err instanceof ApiError) {
setError(err.message)
} else {
setError('Unable to save order process steps.')
}
} finally {
setSavingKey(null)
}
}
const stepsDirty = !stepsAreEqual(
normalizeSteps(draftSteps),
normalizeSteps(settings.orderProcessSteps),
)
const canSaveSteps =
stepsDirty &&
draftSteps.length > 0 &&
draftSteps.every((step) => step.label.trim().length > 0)
return (
<main className={pageStyles.content}>
<Breadcrumbs
items={[
{ label: 'Dashboard', href: '/' },
{ label: 'Store', href: '/store' },
{ label: 'Settings' },
]}
/>
<div className={pageStyles.pageHeader}>
<div>
<h2 className={pageStyles.pageTitle}>Store settings</h2>
<p className={pageStyles.pageSubtitle}>
Control online sales and define how orders move through fulfillment.
</p>
</div>
</div>
{error && (
<div className={sharedStyles.alertError} role="alert">
{error}
</div>
)}
<section className={sharedStyles.panel}>
<h3 className={sharedStyles.sectionTitle}>Sales</h3>
{isLoading ? (
<p className={sharedStyles.status}>Loading settings...</p>
) : (
<div className={sharedStyles.list}>
<div className={sharedStyles.row}>
<div className={sharedStyles.rowText}>
<label htmlFor="online-sell" className={sharedStyles.rowLabel}>
Online sell
</label>
<p className={sharedStyles.rowDescription}>
When disabled, all sales on your website are turned off. Customers
will not be able to place new orders online.
</p>
</div>
<Switch
id="online-sell"
checked={settings.onlineSellEnabled}
disabled={savingKey === 'onlineSell'}
aria-label="Online sell"
onChange={(checked) => void handleOnlineSellChange(checked)}
/>
</div>
</div>
)}
</section>
<section className={`${sharedStyles.panel} ${styles.stepsPanel}`}>
<div className={styles.stepsHeader}>
<div>
<h3 className={sharedStyles.sectionTitle}>Order process</h3>
<p className={styles.stepsDescription}>
Define the steps an order can move through for example: under
processing, ready for shipping, shipped, delivered.
</p>
</div>
</div>
{isLoading ? (
<p className={sharedStyles.status}>Loading settings...</p>
) : (
<>
<div className={styles.stepsList}>
{draftSteps.map((step, index) => (
<div key={step.id} className={styles.stepRow}>
<span className={styles.stepIndex}>{index + 1}</span>
<StepColorPicker
value={normalizeStepColor(step.color, defaultStepColorForId(step.id, index))}
onChange={(color) => updateStepColor(step.id, color)}
ariaLabel={`Color for step ${index + 1}`}
/>
<input
type="text"
className={styles.stepInput}
value={step.label}
placeholder="Step label"
aria-label={`Order step ${index + 1}`}
onChange={(e) => updateStepLabel(step.id, e.target.value)}
/>
<div className={styles.stepControls}>
<Tooltip label="Move step up">
<button
type="button"
className={controlStyles.controlBtn}
onClick={() => moveStep(step.id, -1)}
disabled={index === 0}
aria-label="Move step up"
>
<ChevronUp size={16} />
</button>
</Tooltip>
<Tooltip label="Move step down">
<button
type="button"
className={controlStyles.controlBtn}
onClick={() => moveStep(step.id, 1)}
disabled={index === draftSteps.length - 1}
aria-label="Move step down"
>
<ChevronDown size={16} />
</button>
</Tooltip>
<Tooltip label="Remove step">
<button
type="button"
className={removeStyles.removeRowBtn}
onClick={() => removeStep(step.id)}
disabled={draftSteps.length <= 1}
aria-label="Remove step"
>
<Trash2 size={15} />
</button>
</Tooltip>
</div>
</div>
))}
{!draftSteps.length && (
<p className={styles.emptyText}>
No order steps yet. Add the first step to define your workflow.
</p>
)}
</div>
<button type="button" className={styles.addStepBtn} onClick={addStep}>
<Plus size={18} />
Add step
</button>
<div className={styles.stepsActions}>
<button
type="button"
className={styles.saveBtn}
onClick={() => void handleSaveSteps()}
disabled={!canSaveSteps || savingKey === 'orderSteps'}
>
{savingKey === 'orderSteps' ? 'Saving...' : 'Save steps'}
</button>
</div>
</>
)}
</section>
</main>
)
}