feat: Page Editor Phase 2 - Admin UI

- Add AppearancePages component with 3-column layout
- Add PageSidebar for listing structural pages and CPT templates
- Add SectionEditor with add/delete/reorder functionality
- Add PageSettings with layout/color scheme and static/dynamic toggle
- Add CreatePageModal for creating new structural pages
- Add route at /appearance/pages in admin App.tsx
- Build admin-spa successfully
This commit is contained in:
Dwindi Ramadhana
2026-01-11 22:44:00 +07:00
parent 749cfb3f92
commit bdded61221
6 changed files with 1051 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
import React from 'react';
import { __ } from '@/lib/i18n';
import { cn } from '@/lib/utils';
import { FileText, Layout, Loader2 } from 'lucide-react';
interface PageItem {
id?: number;
type: 'page' | 'template';
cpt?: string;
slug?: string;
title: string;
has_template?: boolean;
permalink_base?: string;
}
interface PageSidebarProps {
pages: PageItem[];
selectedPage: PageItem | null;
onSelectPage: (page: PageItem) => void;
isLoading: boolean;
}
export function PageSidebar({ pages, selectedPage, onSelectPage, isLoading }: PageSidebarProps) {
const structuralPages = pages.filter(p => p.type === 'page');
const templates = pages.filter(p => p.type === 'template');
if (isLoading) {
return (
<div className="w-60 border-r bg-white flex items-center justify-center">
<Loader2 className="w-6 h-6 animate-spin text-gray-400" />
</div>
);
}
return (
<div className="w-60 border-r bg-white flex flex-col">
<div className="flex-1 overflow-y-auto">
<div className="p-4">
{/* Structural Pages */}
<div className="mb-6">
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2 flex items-center gap-2">
<FileText className="w-3.5 h-3.5" />
{__('Structural Pages')}
</h3>
<div className="space-y-1">
{structuralPages.length === 0 ? (
<p className="text-sm text-gray-400 italic">{__('No pages yet')}</p>
) : (
structuralPages.map((page) => (
<button
key={`page-${page.id}`}
onClick={() => onSelectPage(page)}
className={cn(
'w-full text-left px-3 py-2 rounded-lg text-sm transition-colors',
'hover:bg-gray-100',
selectedPage?.id === page.id && selectedPage?.type === 'page'
? 'bg-primary/10 text-primary font-medium'
: 'text-gray-700'
)}
>
{page.title}
</button>
))
)}
</div>
</div>
{/* Templates */}
<div>
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2 flex items-center gap-2">
<Layout className="w-3.5 h-3.5" />
{__('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>
))}
</div>
</div>
</div>
</div>
</div>
);
}