Add store-product Excel bulk edit and polish the business dashboard.

Export/import price and stock from Store Products, keep card order after saves, localize variations, and drop unused website nav items.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-19 01:02:43 +03:30
co-authored by Cursor
parent 7b51dda82a
commit 832ac32a09
16 changed files with 617 additions and 181 deletions
@@ -1,8 +1,12 @@
import { useEffect, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import { Plus, X } from 'lucide-react'
import { useLocale } from '@meshkee/dashboard-ui'
import { useT } from '../i18n/useT'
import type { BusinessMessageKey } from '../i18n/messages'
import type { Variation, VariationType } from '../types/variation'
import { COLOR_OPTIONS, SIZE_OPTIONS } from '../types/variation'
import { Tooltip } from './Tooltip'
import styles from './VariationsModal.module.css'
interface AddVariationModalProps {
@@ -14,6 +18,9 @@ interface AddVariationModalProps {
const ANIMATION_MS = 220
export function AddVariationModal({ open, onClose, onSubmit }: AddVariationModalProps) {
const t = useT()
const { locale } = useLocale()
const isFa = locale === 'fa'
const [mounted, setMounted] = useState(open)
const [closing, setClosing] = useState(false)
const [type, setType] = useState<VariationType>('color')
@@ -98,11 +105,11 @@ export function AddVariationModal({ open, onClose, onSubmit }: AddVariationModal
e.preventDefault()
if (type === 'color') {
onSubmit({ name: 'Color', type: 'color', values: selectedColors })
onSubmit({ name: t('categories.variations.type.color'), type: 'color', values: selectedColors })
} else if (type === 'size') {
onSubmit({ name: 'Size', type: 'size', values: selectedSizes })
onSubmit({ name: t('categories.variations.type.size'), type: 'size', values: selectedSizes })
} else {
const name = customName.trim() || 'Custom'
const name = customName.trim() || t('categories.variations.type.custom')
const values = customValues.map((v) => v.trim()).filter(Boolean)
onSubmit({ name, type: 'custom', values })
}
@@ -126,33 +133,35 @@ export function AddVariationModal({ open, onClose, onSubmit }: AddVariationModal
role="dialog"
aria-modal="true"
aria-labelledby="add-variation-title"
lang={isFa ? 'fa' : 'en'}
dir={isFa ? 'rtl' : 'ltr'}
>
<div className={styles.header}>
<h3 id="add-variation-title" className={styles.title}>
Add Variation
{t('categories.variations.addTitle')}
</h3>
<button className={styles.closeBtn} onClick={onClose} aria-label="Close">
<button className={styles.closeBtn} onClick={onClose} aria-label={t('common.close')}>
<X size={20} />
</button>
</div>
<form className={styles.body} onSubmit={handleSubmit}>
<div className={styles.field}>
<label htmlFor="variationType">Type</label>
<label htmlFor="variationType">{t('categories.variations.type')}</label>
<select
id="variationType"
value={type}
onChange={(e) => setType(e.target.value as VariationType)}
>
<option value="color">Color</option>
<option value="size">Size</option>
<option value="custom">Your own</option>
<option value="color">{t('categories.variations.type.color')}</option>
<option value="size">{t('categories.variations.type.size')}</option>
<option value="custom">{t('categories.variations.type.custom')}</option>
</select>
</div>
{type === 'color' && (
<div className={styles.field}>
<label>Select colors</label>
<label>{t('categories.variations.selectColors')}</label>
<div className={styles.chipGrid}>
{COLOR_OPTIONS.map((color) => (
<button
@@ -161,7 +170,7 @@ export function AddVariationModal({ open, onClose, onSubmit }: AddVariationModal
className={`${styles.chip} ${selectedColors.includes(color) ? styles.chipSelected : ''}`}
onClick={() => toggleOption(color, selectedColors, setSelectedColors)}
>
{color}
{t(`categories.variations.color.${color}` as BusinessMessageKey)}
</button>
))}
</div>
@@ -170,7 +179,7 @@ export function AddVariationModal({ open, onClose, onSubmit }: AddVariationModal
{type === 'size' && (
<div className={styles.field}>
<label>Select sizes</label>
<label>{t('categories.variations.selectSizes')}</label>
<div className={styles.chipGrid}>
{SIZE_OPTIONS.map((size) => (
<button
@@ -189,17 +198,17 @@ export function AddVariationModal({ open, onClose, onSubmit }: AddVariationModal
{type === 'custom' && (
<>
<div className={styles.field}>
<label htmlFor="customName">Variation name</label>
<label htmlFor="customName">{t('categories.variations.name')}</label>
<input
id="customName"
type="text"
placeholder="e.g. Guarantee, Material..."
placeholder={t('categories.variations.namePlaceholder')}
value={customName}
onChange={(e) => setCustomName(e.target.value)}
/>
</div>
<div className={styles.field}>
<label>Values</label>
<label>{t('categories.variations.values')}</label>
<div className={styles.customValues}>
{customValues.map((value, index) => (
<div key={index} className={styles.customRow}>
@@ -208,26 +217,28 @@ export function AddVariationModal({ open, onClose, onSubmit }: AddVariationModal
valueInputRefs.current[index] = el
}}
type="text"
placeholder={`Value ${index + 1}`}
placeholder={t('categories.variations.valueN', { n: index + 1 })}
value={value}
onChange={(e) => updateCustomValue(index, e.target.value)}
onKeyDown={(e) => handleValueKeyDown(index, e)}
/>
{customValues.length > 1 && (
<button
type="button"
className={styles.removeValueBtn}
onClick={() => removeCustomRow(index)}
aria-label="Remove value"
>
<X size={16} />
</button>
<Tooltip label={t('categories.variations.removeValue')}>
<button
type="button"
className={styles.removeValueBtn}
onClick={() => removeCustomRow(index)}
aria-label={t('categories.variations.removeValue')}
>
<X size={16} />
</button>
</Tooltip>
)}
</div>
))}
<button type="button" className={styles.addValueBtn} onClick={addCustomRow}>
<Plus size={14} />
Add value
{t('categories.variations.addValue')}
</button>
</div>
</div>
@@ -236,10 +247,10 @@ export function AddVariationModal({ open, onClose, onSubmit }: AddVariationModal
<div className={styles.actions}>
<button type="button" className={styles.cancelBtn} onClick={onClose}>
Cancel
{t('categories.modal.cancel')}
</button>
<button type="submit" className={styles.submitBtn} disabled={!canSubmit}>
Add
{t('categories.variations.submit')}
</button>
</div>
</form>