feat: product page layout toggle (flat/card), fix email shortcode rendering

- Add layout_style setting (flat default) to product appearance
  - AppearanceController: sanitize & persist layout_style, add to default settings
  - Admin SPA: Layout Style select in Appearance > Product
  - Customer SPA: useEffect targets <main> bg-white in flat mode (full-width),
    card mode uses per-section white floating cards on gray background
  - Accordion sections styled per mode: flat=border-t dividers, card=white cards

- Fix email shortcode gaps (EmailRenderer, EmailManager)
  - Add missing variables: return_url, contact_url, account_url (alias),
    payment_error_reason, order_items_list (alias for order_items_table)
  - Fix customer_note extra_data key mismatch (note → customer_note)
  - Pass low_stock_threshold via extra_data in low_stock email send
This commit is contained in:
Dwindi Ramadhana
2026-03-04 01:14:56 +07:00
parent 7ff429502d
commit 90169b508d
46 changed files with 2337 additions and 1278 deletions

View File

@@ -39,6 +39,13 @@ class TemplateRegistry
'description' => 'Simple contact page with a form and address details.',
'icon' => 'mail',
'sections' => self::get_contact_structure()
],
[
'id' => 'single-post',
'label' => 'Single Post / CPT',
'description' => 'A dynamic layout for blog posts or custom post types with a hero, featured image, and body content.',
'icon' => 'layout',
'sections' => self::get_single_post_structure()
]
]);
}
@@ -166,4 +173,73 @@ class TemplateRegistry
]
];
}
private static function get_single_post_structure()
{
return [
// ── Section 1: Article Hero ─────────────────────────────────────
[
'id' => self::generate_id(),
'type' => 'hero',
'layoutVariant' => 'centered',
'colorScheme' => 'default',
'props' => [
'title' => ['type' => 'dynamic', 'source' => 'post_title'],
'subtitle' => ['type' => 'dynamic', 'source' => 'post_author'],
'image' => ['type' => 'static', 'value' => ''],
'cta_text' => ['type' => 'static', 'value' => ''],
'cta_url' => ['type' => 'static', 'value' => ''],
],
// dynamicBackground tells the API to resolve styles.backgroundImage
// from 'post_featured_image' at render time (falls back to '' if no featured image)
'styles' => [
'contentWidth' => 'contained',
'heightPreset' => 'medium',
'dynamicBackground' => 'post_featured_image',
'backgroundOverlay' => 50,
],
],
// ── Section 2: Article Body ─────────────────────────────────────
[
'id' => self::generate_id(),
'type' => 'content',
'layoutVariant' => 'narrow',
'colorScheme' => 'default',
'props' => [
'content' => ['type' => 'dynamic', 'source' => 'post_content'],
'cta_text' => ['type' => 'static', 'value' => ''],
'cta_url' => ['type' => 'static', 'value' => ''],
],
'styles' => ['contentWidth' => 'contained', 'heightPreset' => 'default'],
],
// ── Section 3: Related Posts ────────────────────────────────────
[
'id' => self::generate_id(),
'type' => 'feature-grid',
'layoutVariant' => 'grid-3',
'colorScheme' => 'muted',
'props' => [
'heading' => ['type' => 'static', 'value' => 'Related Articles'],
'features' => ['type' => 'dynamic', 'source' => 'related_posts'],
],
'styles' => ['contentWidth' => 'contained', 'heightPreset' => 'default'],
],
// ── Section 4: CTA Banner ───────────────────────────────────────
[
'id' => self::generate_id(),
'type' => 'cta-banner',
'colorScheme' => 'gradient',
'props' => [
'title' => ['type' => 'static', 'value' => 'Enjoyed this article?'],
'text' => ['type' => 'static', 'value' => 'Subscribe to our newsletter and never miss an update.'],
'button_text' => ['type' => 'static', 'value' => 'Subscribe Now'],
'button_url' => ['type' => 'static', 'value' => '/subscribe'],
],
'styles' => ['contentWidth' => 'contained', 'heightPreset' => 'medium'],
],
];
}
}