import { defineRouteConfig } from "@medusajs/admin-sdk" import { Adjustments, Trash, Pencil, Plus, Check } from "@medusajs/icons" import { Container, Heading, Text, Button, Input, Table, Badge, toast } from "@medusajs/ui" import { useState, useEffect } from "react" export type MeasurementPreset = { id: string name: string description?: string } const DEFAULT_PRESETS: MeasurementPreset[] = [ { id: "p_bust", name: "Bust", description: "Chest/bust circumference at fullest point" }, { id: "p_waist", name: "Waist", description: "Natural waist circumference" }, { id: "p_hips", name: "Hips", description: "Hip circumference at widest point" }, { id: "p_shoulder", name: "Shoulder Width", description: "Distance between shoulder points across the back" }, { id: "p_top_length", name: "Top Length", description: "Length from high shoulder point to hem" }, { id: "p_pant_length", name: "Pant Length", description: "Outseam length from waist to pant hem" }, { id: "p_thigh", name: "Thigh Circumference", description: "Widest part of upper thigh" }, { id: "p_height", name: "Height (cm)", description: "Total body height in centimeters" }, { id: "p_weight", name: "Weight (kg)", description: "Total body weight in kilograms" }, ] const MeasurementPresetsPage = () => { const [store, setStore] = useState(null) const [presets, setPresets] = useState([]) const [isLoading, setIsLoading] = useState(true) const [isSaving, setIsSaving] = useState(false) // New preset form const [newName, setNewName] = useState("") const [newDescription, setNewDescription] = useState("") // Editing state const [editingId, setEditingId] = useState(null) const [editName, setEditName] = useState("") const [editDescription, setEditDescription] = useState("") const loadData = async () => { try { setIsLoading(true) const res = await fetch("/admin/stores?fields=+metadata", { credentials: "include" }) if (!res.ok) throw new Error("Failed to fetch store metadata") const data = await res.json() const currentStore = data.stores?.[0] setStore(currentStore) if (currentStore?.metadata?.measurement_presets) { let raw = currentStore.metadata.measurement_presets if (typeof raw === "string") { try { raw = JSON.parse(raw) } catch {} } if (Array.isArray(raw) && raw.length > 0) { const normalized: MeasurementPreset[] = raw.map((item: any, idx: number) => { if (typeof item === "string") { return { id: `preset_${idx}`, name: item, description: "" } } return { id: item.id || `preset_${idx}_${Date.now()}`, name: item.name || "", description: item.description || "", } }) setPresets(normalized) return } } setPresets(DEFAULT_PRESETS) } catch (err: any) { toast.error("Error", { description: err.message || "Failed to load presets." }) } finally { setIsLoading(false) } } useEffect(() => { loadData() }, []) const handleAdd = () => { const trimmed = newName.trim() if (!trimmed) return if (presets.some((p) => p.name.toLowerCase() === trimmed.toLowerCase())) { toast.error("Duplicate field", { description: `A preset named "${trimmed}" already exists.` }) return } const newPreset: MeasurementPreset = { id: `p_${Date.now()}`, name: trimmed, description: newDescription.trim(), } setPresets((prev) => [...prev, newPreset]) setNewName("") setNewDescription("") toast.success("Added", { description: `"${trimmed}" added. Remember to click "Save Changes".` }) } const handleDelete = (id: string) => { setPresets((prev) => prev.filter((p) => p.id !== id)) } const startEdit = (preset: MeasurementPreset) => { setEditingId(preset.id) setEditName(preset.name) setEditDescription(preset.description || "") } const saveEdit = () => { if (!editingId) return const trimmed = editName.trim() if (!trimmed) return setPresets((prev) => prev.map((p) => p.id === editingId ? { ...p, name: trimmed, description: editDescription.trim() } : p ) ) setEditingId(null) } const handleSaveToStore = async () => { if (!store?.id) { toast.error("Error", { description: "Store ID not available." }) return } setIsSaving(true) try { const response = await fetch(`/admin/stores/${store.id}`, { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ metadata: { ...store.metadata, measurement_presets: presets, }, }), }) if (!response.ok) { throw new Error(`Failed to save: ${response.statusText}`) } toast.success("Success", { description: "Measurement preset fields updated successfully!", }) } catch (err: any) { toast.error("Error", { description: err.message || "Failed to save configuration.", }) } finally { setIsSaving(false) } } const handleResetDefaults = () => { setPresets(DEFAULT_PRESETS) toast.info("Reset", { description: "Reset to default preset fields. Click Save to persist." }) } return (
Measurement Presets Configuration Manage the reusable measurement fields (e.g. Bust, Waist, Length) that appear as quick-add options on product custom sizing widgets.
{/* Add New Preset Form */}
Add New Measurement Field:
setNewName(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault() handleAdd() } }} /> setNewDescription(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault() handleAdd() } }} />
{/* Presets List Table */}
Current Configured Fields {presets.length} fields
These fields will be available for quick selection in all product details.
Field Name Description / Measurement Guide Actions {presets.length === 0 ? ( {/* Table.Cell's props type omits colSpan, so it is spread in */} No presets configured yet. Add fields above or click "Reset to Defaults". ) : ( presets.map((preset) => { const isEditing = editingId === preset.id return ( {isEditing ? ( setEditName(e.target.value)} autoFocus /> ) : ( {preset.name} )} {isEditing ? ( setEditDescription(e.target.value)} /> ) : ( {preset.description || "—"} )}
{isEditing ? ( <> ) : ( <> )}
) }) )}
) } export const config = defineRouteConfig({ label: "Measurement Presets", icon: Adjustments, }) export default MeasurementPresetsPage