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

@@ -14,7 +14,7 @@ interface PageSidebarProps {
export function PageSidebar({ pages, selectedPage, onSelectPage, isLoading }: PageSidebarProps) {
const structuralPages = pages.filter(p => p.type === 'page');
const templates = pages.filter(p => p.type === 'template');
const templates = pages.filter(p => p.type === 'template' && p.has_template);
if (isLoading) {
return (
@@ -69,24 +69,28 @@ export function PageSidebar({ pages, selectedPage, onSelectPage, isLoading }: Pa
{__('Templates')}
</h3>
<div className="space-y-1">
{templates.map((template) => (
<button
key={`template-${template.cpt}`}
onClick={() => onSelectPage(template)}
className={cn(
'w-full text-left px-3 py-2 rounded-lg text-sm transition-colors',
'hover:bg-gray-100',
selectedPage?.cpt === template.cpt && selectedPage?.type === 'template'
? 'bg-primary/10 text-primary font-medium'
: 'text-gray-700'
)}
>
<span className="block">{template.title}</span>
{template.permalink_base && (
<span className="text-xs text-gray-400">{template.permalink_base}*</span>
)}
</button>
))}
{templates.length === 0 ? (
<p className="text-sm text-gray-400 italic">{__('No templates yet')}</p>
) : (
templates.map((template) => (
<button
key={`template-${template.cpt}`}
onClick={() => onSelectPage(template)}
className={cn(
'w-full text-left px-3 py-2 rounded-lg text-sm transition-colors',
'hover:bg-gray-100',
selectedPage?.cpt === template.cpt && selectedPage?.type === 'template'
? 'bg-primary/10 text-primary font-medium'
: 'text-gray-700'
)}
>
<span className="block">{template.title}</span>
{template.permalink_base && (
<span className="text-xs text-gray-400">{template.permalink_base}*</span>
)}
</button>
))
)}
</div>
</div>
</div>