From de09293051499e381da7fcb1d6448e63543f9b73 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 12 Jul 2026 19:19:53 -0400 Subject: [PATCH] x --- CUSTOM_BLOCK_FLOW_GUIDE.md | 6 ++- .../page-builder/CustomBlockForm.tsx | 2 +- apps/admin/src/pages/PageBuilderPage.tsx | 12 ++++-- apps/mcp-server/section-toolkit.ts | 2 +- .../components/custom/BakeryCategories.tsx | 37 +++++++++++++++++-- .../components/custom/BakeryCategories.tsx | 37 +++++++++++++++++-- 6 files changed, 80 insertions(+), 16 deletions(-) diff --git a/CUSTOM_BLOCK_FLOW_GUIDE.md b/CUSTOM_BLOCK_FLOW_GUIDE.md index db4e639..cbc8874 100644 --- a/CUSTOM_BLOCK_FLOW_GUIDE.md +++ b/CUSTOM_BLOCK_FLOW_GUIDE.md @@ -76,6 +76,7 @@ Renderer จะอ่าน `componentId` แล้วไปหา component จ "ko": "매일 구운 식빵, 사워도우, 부드러운 샌드위치 브레드." }, "href": "/shop", + "icon": "bakery", "imageUrl": "https://example.com/bread.jpg" } ] @@ -88,6 +89,7 @@ Renderer จะอ่าน `componentId` แล้วไปหา component จ - ใช้ language keys ชุดเดียวกับระบบ: `th`, `en`, `ja`, `zh`, `vi`, `ko` - field ที่เป็นข้อความควรรองรับ localized object เสมอ - field ที่เป็น route หรือ URL เช่น `href`, `imageUrl` เป็น string ปกติ +- `icon` เป็น optional string สำหรับเลือก line icon ในวงกลม เช่น `dessert`, `bakery`, `cake`, `gift`, `cookie`, `bread` - `imageUrl` เป็น optional ถ้าไม่ใส่ component ต้องมี fallback ที่หน้าไม่พัง ## Step 2: เพิ่ม Shared Type @@ -353,6 +355,7 @@ type BakeryCategory = { description?: string | Record href: string imageUrl?: string + icon?: string } ``` @@ -381,7 +384,7 @@ apps/mcp-server/section-toolkit.ts ```ts CUSTOM: { required: ["componentId"], - example: `{ "componentId": "BakeryCategories", "props": { "title": ${localizedStringExample}, "subtitle": ${localizedStringExample}, "layout": "horizontal", "items": [{ "title": ${localizedStringExample}, "description": ${localizedStringExample}, "href": "/shop" }] } }` + example: `{ "componentId": "BakeryCategories", "props": { "title": ${localizedStringExample}, "subtitle": ${localizedStringExample}, "layout": "horizontal", "items": [{ "title": ${localizedStringExample}, "description": ${localizedStringExample}, "href": "/shop", "icon": "bakery" }] } }` } ``` @@ -512,4 +515,3 @@ Admin Page Builder -> BakeryCategories resolves language -> UI renders cards ``` - diff --git a/apps/admin/src/components/page-builder/CustomBlockForm.tsx b/apps/admin/src/components/page-builder/CustomBlockForm.tsx index c26b726..20195d0 100644 --- a/apps/admin/src/components/page-builder/CustomBlockForm.tsx +++ b/apps/admin/src/components/page-builder/CustomBlockForm.tsx @@ -89,7 +89,7 @@ export default function CustomBlockForm({ defaultValue={formatProps(editingSection.payload?.props)} onChange={(e) => updateProps(e.target.value)} className="admin-input w-full resize-y font-mono text-sm" - placeholder={'{\n "title": { "th": "หมวดหมู่เบเกอรี่", "en": "Bakery Categories" },\n "subtitle": { "th": "เลือกซื้อขนมอบตามหมวดหมู่ที่คุณชอบ", "en": "Explore fresh-baked favorites by collection." },\n "layout": "horizontal",\n "items": []\n}'} + placeholder={'{\n "title": { "th": "หมวดหมู่เบเกอรี่", "en": "Bakery Categories" },\n "subtitle": { "th": "เลือกซื้อขนมอบตามหมวดหมู่ที่คุณชอบ", "en": "Explore fresh-baked favorites by collection." },\n "layout": "horizontal",\n "items": [\n { "title": { "th": "ขนมไทย", "en": "Thai Dessert" }, "href": "/shop", "icon": "dessert" }\n ]\n}'} /> {jsonError && (

{jsonError}

diff --git a/apps/admin/src/pages/PageBuilderPage.tsx b/apps/admin/src/pages/PageBuilderPage.tsx index 5990464..51934fb 100644 --- a/apps/admin/src/pages/PageBuilderPage.tsx +++ b/apps/admin/src/pages/PageBuilderPage.tsx @@ -699,7 +699,8 @@ export default function PageBuilderPage() { th: 'ขนมปังอบสดใหม่ทุกวัน', en: 'Daily loaves, sourdough, and soft sandwich breads.' }, - href: '/shop' + href: '/shop', + icon: 'bakery' }, { title: { th: 'พาสทรี', en: 'Pastries' }, @@ -707,7 +708,8 @@ export default function PageBuilderPage() { th: 'พาสทรีเนยหอมกรอบสำหรับมื้อเช้าและของฝาก', en: 'Layered butter pastries for breakfast and gifting.' }, - href: '/shop' + href: '/shop', + icon: 'dessert' }, { title: { th: 'เค้ก', en: 'Cakes' }, @@ -715,7 +717,8 @@ export default function PageBuilderPage() { th: 'เค้กสำหรับวันพิเศษและช่วงเวลาพักผ่อน', en: 'Celebration cakes and slices for quiet afternoons.' }, - href: '/shop' + href: '/shop', + icon: 'cake' }, { title: { th: 'คุกกี้', en: 'Cookies' }, @@ -723,7 +726,8 @@ export default function PageBuilderPage() { th: 'คุกกี้อบใหม่ กรอบนอกนุ่มใน', en: 'Small-batch cookies with crisp edges and soft centers.' }, - href: '/shop' + href: '/shop', + icon: 'cookie' } ] } diff --git a/apps/mcp-server/section-toolkit.ts b/apps/mcp-server/section-toolkit.ts index 3f5ee0a..444f1bc 100644 --- a/apps/mcp-server/section-toolkit.ts +++ b/apps/mcp-server/section-toolkit.ts @@ -93,7 +93,7 @@ const ADD_PAGE_SECTION_GUIDANCE: Record = { + bakery: Croissant, + bread: Croissant, + cake: CakeSlice, + cakes: CakeSlice, + dessert: Cookie, + cookie: Cookie, + cookies: Cookie, + gift: Gift, + gifts: Gift, }; const defaultItems: BakeryCategory[] = [ @@ -24,21 +39,25 @@ const defaultItems: BakeryCategory[] = [ title: 'Breads', description: 'Daily loaves, sourdough, and soft sandwich breads.', href: '/shop', + icon: 'bakery', }, { title: 'Pastries', description: 'Layered butter pastries for breakfast and gifting.', href: '/shop', + icon: 'dessert', }, { title: 'Cakes', description: 'Celebration cakes and slices for quiet afternoons.', href: '/shop', + icon: 'cake', }, { title: 'Cookies', description: 'Small-batch cookies with crisp edges and soft centers.', href: '/shop', + icon: 'cookie', }, ]; @@ -66,6 +85,7 @@ function readItems(value: unknown, lang: string): ResolvedBakeryCategory[] { description: readLocalized(record.description, lang, ''), href: readString(record.href, '/shop'), imageUrl: typeof record.imageUrl === 'string' ? record.imageUrl : undefined, + icon: readString(record.icon, 'bakery'), }); return acc; }, []); @@ -101,7 +121,7 @@ export default function BakeryCategories({ props }: CustomBlockComponentProps) { to={item.href} className="ui-shell-card ui-shell-card-hover group overflow-hidden" > -
+
{item.imageUrl ? ( )}
-
-

{item.title}

-

{item.description}

+
+
+ {(() => { + const Icon = iconMap[item.icon] ?? Croissant; + return ; + })()} +
+
+

{item.title}

+

{item.description}

+
+
))} diff --git a/apps/website/src/components/custom/BakeryCategories.tsx b/apps/website/src/components/custom/BakeryCategories.tsx index 4c749af..83e7cfe 100644 --- a/apps/website/src/components/custom/BakeryCategories.tsx +++ b/apps/website/src/components/custom/BakeryCategories.tsx @@ -1,4 +1,5 @@ import Image from 'next/image' +import { ArrowRight, CakeSlice, Cookie, Croissant, Gift, type LucideIcon } from 'lucide-react' import Link from '../LocalizedLink' import type { CustomBlockComponentProps } from '../../config/customRegistry' import { resolveLocalized } from '../../lib/localization' @@ -10,6 +11,7 @@ type BakeryCategory = { description?: LocalizedValue href: string imageUrl?: string + icon?: string } type ResolvedBakeryCategory = { @@ -17,6 +19,19 @@ type ResolvedBakeryCategory = { description: string href: string imageUrl?: string + icon: string +} + +const iconMap: Record = { + bakery: Croissant, + bread: Croissant, + cake: CakeSlice, + cakes: CakeSlice, + dessert: Cookie, + cookie: Cookie, + cookies: Cookie, + gift: Gift, + gifts: Gift, } const defaultItems: BakeryCategory[] = [ @@ -24,21 +39,25 @@ const defaultItems: BakeryCategory[] = [ title: 'Breads', description: 'Daily loaves, sourdough, and soft sandwich breads.', href: '/shop', + icon: 'bakery', }, { title: 'Pastries', description: 'Layered butter pastries for breakfast and gifting.', href: '/shop', + icon: 'dessert', }, { title: 'Cakes', description: 'Celebration cakes and slices for quiet afternoons.', href: '/shop', + icon: 'cake', }, { title: 'Cookies', description: 'Small-batch cookies with crisp edges and soft centers.', href: '/shop', + icon: 'cookie', }, ] @@ -66,6 +85,7 @@ function readItems(value: unknown, lang: string): ResolvedBakeryCategory[] { description: readLocalized(record.description, lang, ''), href: readString(record.href, '/shop'), imageUrl: typeof record.imageUrl === 'string' ? record.imageUrl : undefined, + icon: readString(record.icon, 'bakery'), }) return acc }, []) @@ -97,7 +117,7 @@ export default function BakeryCategories({ props, lang }: CustomBlockComponentPr href={item.href} className="group overflow-hidden rounded-2xl border border-line-primary/20 bg-surface-card shadow-card transition hover:-translate-y-1 hover:shadow-card-hover" > -
+
{item.imageUrl ? ( )}
-
-

{item.title}

-

{item.description}

+
+
+ {(() => { + const Icon = iconMap[item.icon] ?? Croissant + return + })()} +
+
+

{item.title}

+

{item.description}

+
+
))}