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:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user