revert fb8e77bb68
Local Build and Deploy / deploy (push) Successful in 1m11s

revert style: clean up code with prettier and eslint
This commit is contained in:
remco committed 2026-07-12 21:02:03 +02:00
1 parent 27de078f54
commit e85e4d74ea
378 files changed
+20710 -22428

No files matched your search

@@ -1,4 +1,4 @@
"use client";
'use client'
import {
AlertCircle,
@@ -10,127 +10,133 @@ import {
Package,
Save,
Settings,
} from "lucide-react";
import Link from "next/link";
import { useCallback, useEffect, useMemo, useState } from "react";
import { toast } from "sonner";
import { updateCatalogPage } from "@/actions/catalog";
import type { CatalogItemData } from "@/app/admin/catalog/[id]/catalog-items-table";
import { CatalogItemsTable } from "@/app/admin/catalog/[id]/catalog-items-table";
import { CatalogTranslateTab } from "@/app/admin/catalog/[id]/catalog-translate-tab";
import { BulkImportItems } from "@/components/admin/catalog/bulk-import-items";
import { CatalogImagePicker } from "@/components/admin/catalog/catalog-image-picker";
import { IconPicker } from "@/components/admin/catalog/icon-picker";
import { ItemsShopPreview } from "@/components/admin/catalog/items-shop-preview";
import { QuickAddFurni } from "@/components/admin/catalog/quick-add-furni";
import { CatalogIcon, LAYOUT_COLORS } from "@/components/admin/catalog-tree";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea";
import { CATALOG_LAYOUTS, type CatalogLayout, LAYOUT_DESCRIPTIONS } from "@/lib/catalog-layouts";
import { cn } from "@/lib/utils";
import { ImagePreview } from "./image-preview";
import { useCatalogManager } from "./use-catalog-store";
} from 'lucide-react'
import Link from 'next/link'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { toast } from 'sonner'
import { updateCatalogPage } from '@/actions/catalog'
import type { CatalogItemData } from '@/app/admin/catalog/[id]/catalog-items-table'
import { CatalogItemsTable } from '@/app/admin/catalog/[id]/catalog-items-table'
import { CatalogTranslateTab } from '@/app/admin/catalog/[id]/catalog-translate-tab'
import { BulkImportItems } from '@/components/admin/catalog/bulk-import-items'
import { CatalogImagePicker } from '@/components/admin/catalog/catalog-image-picker'
import { IconPicker } from '@/components/admin/catalog/icon-picker'
import { ItemsShopPreview } from '@/components/admin/catalog/items-shop-preview'
import { QuickAddFurni } from '@/components/admin/catalog/quick-add-furni'
import { CatalogIcon, LAYOUT_COLORS } from '@/components/admin/catalog-tree'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { Switch } from '@/components/ui/switch'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Textarea } from '@/components/ui/textarea'
import { CATALOG_LAYOUTS, type CatalogLayout, LAYOUT_DESCRIPTIONS } from '@/lib/catalog-layouts'
import { cn } from '@/lib/utils'
import { ImagePreview } from './image-preview'
import { useCatalogManager } from './use-catalog-store'
interface PageDetail {
id: number;
caption: string;
parentId: number;
pageLayout: string;
enabled: string;
visible: string;
minRank: number;
clubOnly: string;
orderNum: number;
iconImage: number;
iconColor: number;
pageHeadline: string;
pageTeaser: string;
pageSpecial: string | null;
pageText1: string | null;
pageText2: string | null;
pageTextDetails: string | null;
pageTextTeaser: string | null;
itemCount?: number;
id: number
caption: string
parentId: number
pageLayout: string
enabled: string
visible: string
minRank: number
clubOnly: string
orderNum: number
iconImage: number
iconColor: number
pageHeadline: string
pageTeaser: string
pageSpecial: string | null
pageText1: string | null
pageText2: string | null
pageTextDetails: string | null
pageTextTeaser: string | null
itemCount?: number
}
interface ItemsData {
items: CatalogItemData[];
baseItems: { id: number; publicName: string; itemName: string; spriteId: number; type: string }[];
catalogNameMap: Record<number, string>;
furniDataIdList: number[];
furniDescriptionMap: Record<number, string>;
furniRevisionMap: Record<number, { classname: string; revision: number }>;
interactionTypes: string[];
allPages: { id: number; caption: string }[];
items: CatalogItemData[]
baseItems: { id: number; publicName: string; itemName: string; spriteId: number; type: string }[]
catalogNameMap: Record<number, string>
furniDataIdList: number[]
furniDescriptionMap: Record<number, string>
furniRevisionMap: Record<number, { classname: string; revision: number }>
interactionTypes: string[]
allPages: { id: number; caption: string }[]
}
export interface PageUpdateData {
id: number;
caption: string;
enabled: string;
visible: string;
pageLayout: string;
iconImage: number;
id: number
caption: string
enabled: string
visible: string
pageLayout: string
iconImage: number
}
interface InlineEditorProps {
pageId: number | null;
onSaved: (updated: PageUpdateData) => void;
pageId: number | null
onSaved: (updated: PageUpdateData) => void
}
type FormState = Record<string, string | number | null>;
type FormState = Record<string, string | number | null>
export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
const { dispatch } = useCatalogManager();
const [page, setPage] = useState<PageDetail | null>(null);
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [form, setForm] = useState<FormState>({});
const [original, setOriginal] = useState<FormState>({});
const [showTexts, setShowTexts] = useState(false);
const [activeTab, setActiveTab] = useState("settings");
const [showPreview, setShowPreview] = useState(false);
const { dispatch } = useCatalogManager()
const [page, setPage] = useState<PageDetail | null>(null)
const [loading, setLoading] = useState(false)
const [saving, setSaving] = useState(false)
const [form, setForm] = useState<FormState>({})
const [original, setOriginal] = useState<FormState>({})
const [showTexts, setShowTexts] = useState(false)
const [activeTab, setActiveTab] = useState('settings')
const [showPreview, setShowPreview] = useState(false)
// Items/translate data (lazy loaded)
const [itemsData, setItemsData] = useState<ItemsData | null>(null);
const [itemsLoading, setItemsLoading] = useState(false);
const [itemsLoaded, setItemsLoaded] = useState(false);
const [itemsData, setItemsData] = useState<ItemsData | null>(null)
const [itemsLoading, setItemsLoading] = useState(false)
const [itemsLoaded, setItemsLoaded] = useState(false)
// ── Dirty tracking ──────────────────────────────────────────
const isDirty = useMemo(() => {
return Object.keys(form).some((k) => form[k] !== original[k]);
}, [form, original]);
return Object.keys(form).some((k) => form[k] !== original[k])
}, [form, original])
useEffect(() => {
dispatch({ type: "SET_DIRTY", dirty: isDirty });
}, [isDirty, dispatch]);
dispatch({ type: 'SET_DIRTY', dirty: isDirty })
}, [isDirty, dispatch])
// ── Ctrl+S handler ──────────────────────────────────────────
useEffect(() => {
function onKeyDown(e: KeyboardEvent) {
if ((e.metaKey || e.ctrlKey) && e.key === "s") {
e.preventDefault();
if (isDirty && !saving && page) handleSave();
if ((e.metaKey || e.ctrlKey) && e.key === 's') {
e.preventDefault()
if (isDirty && !saving && page) handleSave()
}
}
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
});
document.addEventListener('keydown', onKeyDown)
return () => document.removeEventListener('keydown', onKeyDown)
}) // eslint-disable-line react-hooks/exhaustive-deps
const loadPage = useCallback(async (id: number) => {
setLoading(true);
setLoading(true)
try {
const res = await fetch(`/api/admin/catalog/tree?pageId=${id}`);
if (!res.ok) throw new Error();
const data = await res.json();
const p = data.page as PageDetail;
setPage(p);
const res = await fetch(`/api/admin/catalog/tree?pageId=${id}`)
if (!res.ok) throw new Error()
const data = await res.json()
const p = data.page as PageDetail
setPage(p)
const formData: FormState = {
caption: p.caption,
pageLayout: p.pageLayout,
@@ -141,84 +147,84 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
orderNum: p.orderNum,
iconImage: p.iconImage,
iconColor: p.iconColor,
pageHeadline: p.pageHeadline || "",
pageTeaser: p.pageTeaser || "",
pageSpecial: p.pageSpecial || "",
pageText1: p.pageText1 || "",
pageText2: p.pageText2 || "",
pageTextDetails: p.pageTextDetails || "",
pageTextTeaser: p.pageTextTeaser || "",
};
setForm(formData);
setOriginal(formData);
pageHeadline: p.pageHeadline || '',
pageTeaser: p.pageTeaser || '',
pageSpecial: p.pageSpecial || '',
pageText1: p.pageText1 || '',
pageText2: p.pageText2 || '',
pageTextDetails: p.pageTextDetails || '',
pageTextTeaser: p.pageTextTeaser || '',
}
setForm(formData)
setOriginal(formData)
} catch {
toast.error("Failed to load page");
setPage(null);
toast.error('Failed to load page')
setPage(null)
} finally {
setLoading(false);
setLoading(false)
}
}, []);
}, [])
const loadItemsData = useCallback(async (id: number) => {
setItemsLoading(true);
setItemsLoading(true)
try {
const res = await fetch(`/api/admin/catalog/items?pageId=${id}`);
if (!res.ok) throw new Error();
const data = await res.json();
setItemsData(data as ItemsData);
setItemsLoaded(true);
const res = await fetch(`/api/admin/catalog/items?pageId=${id}`)
if (!res.ok) throw new Error()
const data = await res.json()
setItemsData(data as ItemsData)
setItemsLoaded(true)
} catch {
toast.error("Failed to load items");
toast.error('Failed to load items')
} finally {
setItemsLoading(false);
setItemsLoading(false)
}
}, []);
}, [])
const reloadItems = useCallback(() => {
if (pageId && pageId > 0) loadItemsData(pageId);
}, [pageId, loadItemsData]);
if (pageId && pageId > 0) loadItemsData(pageId)
}, [pageId, loadItemsData])
useEffect(() => {
if (pageId && pageId > 0) {
loadPage(pageId);
setItemsData(null);
setItemsLoaded(false);
setActiveTab("settings");
setShowPreview(false);
loadPage(pageId)
setItemsData(null)
setItemsLoaded(false)
setActiveTab('settings')
setShowPreview(false)
} else {
setPage(null);
setForm({});
setOriginal({});
setItemsData(null);
setItemsLoaded(false);
setPage(null)
setForm({})
setOriginal({})
setItemsData(null)
setItemsLoaded(false)
}
}, [pageId, loadPage]);
}, [pageId, loadPage])
// Lazy-load items
useEffect(() => {
if (
(activeTab === "items" || activeTab === "translate") &&
(activeTab === 'items' || activeTab === 'translate') &&
!itemsLoaded &&
!itemsLoading &&
pageId &&
pageId > 0
) {
loadItemsData(pageId);
loadItemsData(pageId)
}
}, [activeTab, itemsLoaded, itemsLoading, pageId, loadItemsData]);
}, [activeTab, itemsLoaded, itemsLoading, pageId, loadItemsData])
function update(key: string, value: string | number | null) {
setForm((prev) => ({ ...prev, [key]: value }));
setForm((prev) => ({ ...prev, [key]: value }))
}
async function handleSave() {
if (!page) return;
setSaving(true);
if (!page) return
setSaving(true)
try {
const result = await updateCatalogPage({ id: page.id, ...form });
if (!result.ok) throw new Error(result.error || "Failed to save");
toast.success("Page updated & catalog synced");
setOriginal({ ...form });
const result = await updateCatalogPage({ id: page.id, ...form })
if (!result.ok) throw new Error(result.error || 'Failed to save')
toast.success('Page updated & catalog synced')
setOriginal({ ...form })
setPage((prev) =>
prev
? {
@@ -230,7 +236,7 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
iconImage: (form.iconImage as number) ?? prev.iconImage,
}
: prev,
);
)
onSaved({
id: page.id,
caption: String(form.caption || page.caption),
@@ -238,11 +244,11 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
visible: String(form.visible ?? page.visible),
pageLayout: String(form.pageLayout || page.pageLayout),
iconImage: (form.iconImage as number) ?? page.iconImage,
});
})
} catch (e) {
toast.error(e instanceof Error ? e.message : "Failed to save");
toast.error(e instanceof Error ? e.message : 'Failed to save')
} finally {
setSaving(false);
setSaving(false)
}
}
@@ -251,7 +257,7 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
<div className="flex-1 flex items-center justify-center text-muted-foreground text-sm">
Select a page from the tree to edit
</div>
);
)
}
if (loading) {
@@ -259,12 +265,12 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
<div className="flex-1 flex items-center justify-center">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
);
)
}
if (!page) return null;
if (!page) return null
const layoutColor = LAYOUT_COLORS[String(form.pageLayout)] || "bg-muted text-muted-foreground";
const layoutColor = LAYOUT_COLORS[String(form.pageLayout)] || 'bg-muted text-muted-foreground'
return (
<div className="flex-1 flex flex-col min-h-0">
@@ -275,13 +281,13 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
<div>
<div className="flex items-center gap-2">
<h3 className="text-sm font-semibold">{(form.caption as string) || page.caption}</h3>
<Badge className={cn("h-4 px-1 text-[8px] font-mono border-0", layoutColor)}>
<Badge className={cn('h-4 px-1 text-[8px] font-mono border-0', layoutColor)}>
{form.pageLayout as string}
</Badge>
{isDirty && (
<Badge
variant="outline"
className="h-4 px-1.5 text-[9px] text-[var(--admin-warning)] border-[var(--admin-warning-border)] bg-[var(--admin-warning-subtle)]"
className="h-4 px-1.5 text-[9px] text-[var(--admin-warning)] border-[var(--admin-warning-border)] bg-[var(--admin-warning-subtle)]"
>
<AlertCircle className="h-2.5 w-2.5 mr-0.5" />
unsaved
@@ -289,7 +295,7 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
)}
</div>
<p className="text-xs text-muted-foreground">
#{page.id} &middot; Parent: {page.parentId === -1 ? "Root" : `#${page.parentId}`}
#{page.id} &middot; Parent: {page.parentId === -1 ? 'Root' : `#${page.parentId}`}
{page.itemCount !== undefined && ` · ${page.itemCount} items`}
</p>
</div>
@@ -307,8 +313,9 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
<div className="flex items-center gap-2 px-4 py-1.5 bg-[var(--admin-warning-subtle)] border-b border-[var(--admin-warning-border)] text-xs text-[var(--admin-warning)] shrink-0">
<AlertCircle className="h-3.5 w-3.5 shrink-0" />
<span className="flex-1">
Unsaved changes — press{" "}
<kbd className="rounded border bg-background px-1 font-mono text-[10px]">Ctrl+S</kbd> to save
Unsaved changes — press{' '}
<kbd className="rounded border bg-background px-1 font-mono text-[10px]">Ctrl+S</kbd> to
save
</span>
<Button
size="sm"
@@ -317,7 +324,11 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
onClick={handleSave}
disabled={saving}
>
{saving ? <Loader2 className="mr-1 h-3 w-3 animate-spin" /> : <Save className="mr-1 h-3 w-3" />}
{saving ? (
<Loader2 className="mr-1 h-3 w-3 animate-spin" />
) : (
<Save className="mr-1 h-3 w-3" />
)}
Save
</Button>
</div>
@@ -329,7 +340,7 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
<TabsTrigger value="settings" className="gap-1.5 text-xs">
<Settings className="h-3.5 w-3.5" />
Settings
{isDirty && <span className="ml-0.5 h-1.5 w-1.5 rounded-full bg-[var(--admin-warning)]" />}
{isDirty && <span className="ml-0.5 h-1.5 w-1.5 rounded-full bg-[var(--admin-warning)]" />}
</TabsTrigger>
<TabsTrigger value="items" className="gap-1.5 text-xs">
<Package className="h-3.5 w-3.5" />
@@ -345,13 +356,13 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
Translate
{itemsData &&
(() => {
const furniSet = new Set(itemsData.furniDataIdList);
const missing = itemsData.baseItems.filter((b) => !furniSet.has(b.id)).length;
const furniSet = new Set(itemsData.furniDataIdList)
const missing = itemsData.baseItems.filter((b) => !furniSet.has(b.id)).length
return missing > 0 ? (
<span className="ml-0.5 rounded-full bg-[var(--admin-warning)] text-[var(--admin-warning-foreground)] text-[9px] px-1 py-0.5 leading-none font-medium">
<span className="ml-0.5 rounded-full bg-[var(--admin-warning)] text-[var(--admin-warning-foreground)] text-[9px] px-1 py-0.5 leading-none font-medium">
{missing}
</span>
) : null;
) : null
})()}
</TabsTrigger>
</TabsList>
@@ -364,33 +375,36 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
<div className="space-y-1.5">
<Label className="text-xs">Caption</Label>
<Input
value={String(form.caption || "")}
onChange={(e) => update("caption", e.target.value)}
value={String(form.caption || '')}
onChange={(e) => update('caption', e.target.value)}
className="h-8 text-sm"
/>
</div>
<div className="space-y-1.5">
<Label className="text-xs">Layout</Label>
<Select value={String(form.pageLayout || "")} onValueChange={(v) => update("pageLayout", v)}>
<Select
value={String(form.pageLayout || '')}
onValueChange={(v) => update('pageLayout', v)}
>
<SelectTrigger className="h-8 text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
{CATALOG_LAYOUTS.map((l) => {
const color = LAYOUT_COLORS[l] || "bg-muted text-muted-foreground";
const color = LAYOUT_COLORS[l] || 'bg-muted text-muted-foreground'
return (
<SelectItem key={l} value={l}>
<span className="flex items-center gap-2">
<span className={cn("inline-block h-2 w-2 rounded-full", color)} />
<span className={cn('inline-block h-2 w-2 rounded-full', color)} />
{l}
</span>
</SelectItem>
);
)
})}
</SelectContent>
</Select>
<p className="text-[10px] text-muted-foreground">
{LAYOUT_DESCRIPTIONS[String(form.pageLayout) as CatalogLayout] ?? "Custom layout"}
{LAYOUT_DESCRIPTIONS[String(form.pageLayout) as CatalogLayout] ?? 'Custom layout'}
</p>
</div>
</div>
@@ -402,7 +416,7 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
<Input
type="number"
value={form.orderNum as number}
onChange={(e) => update("orderNum", parseInt(e.target.value, 10) || 0)}
onChange={(e) => update('orderNum', parseInt(e.target.value, 10) || 0)}
className="h-8 text-sm"
/>
</div>
@@ -411,7 +425,7 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
<Input
type="number"
value={form.minRank as number}
onChange={(e) => update("minRank", parseInt(e.target.value, 10) || 1)}
onChange={(e) => update('minRank', parseInt(e.target.value, 10) || 1)}
className="h-8 text-sm"
/>
</div>
@@ -419,7 +433,7 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
<Label className="text-xs">Icon</Label>
<IconPicker
value={(form.iconImage as number) ?? 0}
onChange={(v) => update("iconImage", v)}
onChange={(v) => update('iconImage', v)}
/>
</div>
</div>
@@ -430,22 +444,22 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
id="ie-enabled"
label="Enabled"
description="Active in the catalog"
checked={form.enabled === "1"}
onChange={(v) => update("enabled", v ? "1" : "0")}
checked={form.enabled === '1'}
onChange={(v) => update('enabled', v ? '1' : '0')}
/>
<ToggleRow
id="ie-visible"
label="Visible"
description="Shown in navigation"
checked={form.visible === "1"}
onChange={(v) => update("visible", v ? "1" : "0")}
checked={form.visible === '1'}
onChange={(v) => update('visible', v ? '1' : '0')}
/>
<ToggleRow
id="ie-club"
label="Club Only"
description="Requires HC subscription"
checked={form.clubOnly === "1"}
onChange={(v) => update("clubOnly", v ? "1" : "0")}
checked={form.clubOnly === '1'}
onChange={(v) => update('clubOnly', v ? '1' : '0')}
/>
</div>
@@ -453,24 +467,24 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
<div className="space-y-1.5">
<Label className="text-xs">Headline</Label>
<CatalogImagePicker
value={String(form.pageHeadline || "")}
onChange={(v) => update("pageHeadline", v)}
value={String(form.pageHeadline || '')}
onChange={(v) => update('pageHeadline', v)}
type="header"
label="Headline image"
/>
<ImagePreview value={String(form.pageHeadline || "")} height={50} />
<ImagePreview value={String(form.pageHeadline || '')} height={50} />
</div>
{/* Teaser image picker */}
<div className="space-y-1.5">
<Label className="text-xs">Teaser</Label>
<CatalogImagePicker
value={String(form.pageTeaser || "")}
onChange={(v) => update("pageTeaser", v)}
value={String(form.pageTeaser || '')}
onChange={(v) => update('pageTeaser', v)}
type="teaser"
label="Teaser image"
/>
<ImagePreview value={String(form.pageTeaser || "")} height={50} />
<ImagePreview value={String(form.pageTeaser || '')} height={50} />
</div>
{/* Collapsible text fields */}
@@ -479,7 +493,11 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
onClick={() => setShowTexts(!showTexts)}
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
>
{showTexts ? <ChevronDown className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
{showTexts ? (
<ChevronDown className="h-3.5 w-3.5" />
) : (
<ChevronRight className="h-3.5 w-3.5" />
)}
Text fields
{(form.pageText1 || form.pageText2 || form.pageTextDetails) && (
<Badge variant="secondary" className="h-4 px-1 text-[9px]">
@@ -494,8 +512,8 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
<Label className="text-xs">Text 1</Label>
<Textarea
rows={2}
value={String(form.pageText1 || "")}
onChange={(e) => update("pageText1", e.target.value || null)}
value={String(form.pageText1 || '')}
onChange={(e) => update('pageText1', e.target.value || null)}
className="text-sm"
/>
</div>
@@ -503,8 +521,8 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
<Label className="text-xs">Text 2</Label>
<Textarea
rows={2}
value={String(form.pageText2 || "")}
onChange={(e) => update("pageText2", e.target.value || null)}
value={String(form.pageText2 || '')}
onChange={(e) => update('pageText2', e.target.value || null)}
className="text-sm"
/>
</div>
@@ -512,16 +530,16 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
<Label className="text-xs">Text Details</Label>
<Textarea
rows={2}
value={String(form.pageTextDetails || "")}
onChange={(e) => update("pageTextDetails", e.target.value || null)}
value={String(form.pageTextDetails || '')}
onChange={(e) => update('pageTextDetails', e.target.value || null)}
className="text-sm"
/>
</div>
<div className="space-y-1.5">
<Label className="text-xs">Special</Label>
<Input
value={String(form.pageSpecial || "")}
onChange={(e) => update("pageSpecial", e.target.value || null)}
value={String(form.pageSpecial || '')}
onChange={(e) => update('pageSpecial', e.target.value || null)}
className="h-8 text-sm"
/>
</div>
@@ -560,7 +578,7 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
onClick={() => setShowPreview(!showPreview)}
className="ml-auto"
>
{showPreview ? "Hide Preview" : "Shop Preview"}
{showPreview ? 'Hide Preview' : 'Shop Preview'}
</Button>
</div>
@@ -607,7 +625,7 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
</TabsContent>
</Tabs>
</div>
);
)
}
// ── Toggle row (inline) ─────────────────────────────────────
@@ -619,11 +637,11 @@ function ToggleRow({
checked,
onChange,
}: {
id: string;
label: string;
description: string;
checked: boolean;
onChange: (v: boolean) => void;
id: string
label: string
description: string
checked: boolean
onChange: (v: boolean) => void
}) {
return (
<div className="flex items-start justify-between gap-3">
@@ -635,5 +653,5 @@ function ToggleRow({
</div>
<Switch id={id} checked={checked} onCheckedChange={onChange} size="sm" />
</div>
);
)
}