2026-04-05 18:31:16 -07:00
|
|
|
import React, { useState } from 'react';
|
|
|
|
|
import { usePages } from '../../state/PageContext';
|
2026-07-12 14:50:50 -07:00
|
|
|
import { clickableProps } from '../../utils/a11y';
|
2026-07-14 07:14:56 -07:00
|
|
|
import { PageSettingsModal } from './PageSettingsModal';
|
2026-04-05 18:31:16 -07:00
|
|
|
|
|
|
|
|
export const PagesPanel: React.FC = () => {
|
|
|
|
|
const {
|
|
|
|
|
pages,
|
|
|
|
|
activePageId,
|
|
|
|
|
isEditingHeader,
|
|
|
|
|
isEditingFooter,
|
|
|
|
|
switchPage,
|
|
|
|
|
editHeader,
|
|
|
|
|
editFooter,
|
|
|
|
|
addPage,
|
|
|
|
|
deletePage,
|
|
|
|
|
renamePage,
|
2026-07-14 07:36:00 -07:00
|
|
|
duplicatePage,
|
|
|
|
|
movePage,
|
|
|
|
|
setLandingPage,
|
2026-07-14 07:14:56 -07:00
|
|
|
updatePageSeo,
|
2026-04-05 18:31:16 -07:00
|
|
|
} = usePages();
|
|
|
|
|
const [isAdding, setIsAdding] = useState(false);
|
|
|
|
|
const [newName, setNewName] = useState('');
|
|
|
|
|
const [newSlug, setNewSlug] = useState('');
|
|
|
|
|
const [editingId, setEditingId] = useState<string | null>(null);
|
|
|
|
|
const [editName, setEditName] = useState('');
|
|
|
|
|
const [editSlug, setEditSlug] = useState('');
|
|
|
|
|
const [deleteConfirmId, setDeleteConfirmId] = useState<string | null>(null);
|
2026-07-14 07:14:56 -07:00
|
|
|
const [seoSettingsPageId, setSeoSettingsPageId] = useState<string | null>(null);
|
|
|
|
|
const seoSettingsPage = pages.find((p) => p.id === seoSettingsPageId) || null;
|
2026-04-05 18:31:16 -07:00
|
|
|
|
|
|
|
|
const handleAdd = () => {
|
|
|
|
|
if (!newName.trim()) return;
|
|
|
|
|
addPage(newName.trim(), newSlug.trim());
|
|
|
|
|
setNewName('');
|
|
|
|
|
setNewSlug('');
|
|
|
|
|
setIsAdding(false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleRename = (pageId: string) => {
|
|
|
|
|
if (!editName.trim()) return;
|
|
|
|
|
renamePage(pageId, editName.trim(), editSlug.trim());
|
|
|
|
|
setEditingId(null);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleDelete = (pageId: string) => {
|
|
|
|
|
deletePage(pageId);
|
|
|
|
|
setDeleteConfirmId(null);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const startEditing = (page: { id: string; name: string; slug: string }) => {
|
|
|
|
|
setEditingId(page.id);
|
|
|
|
|
setEditName(page.name);
|
|
|
|
|
setEditSlug(page.slug);
|
|
|
|
|
setDeleteConfirmId(null);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const autoSlug = (name: string): string => {
|
|
|
|
|
return name
|
|
|
|
|
.toLowerCase()
|
|
|
|
|
.trim()
|
|
|
|
|
.replace(/[^a-z0-9\s-]/g, '')
|
|
|
|
|
.replace(/\s+/g, '-')
|
|
|
|
|
.replace(/-+/g, '-');
|
|
|
|
|
};
|
|
|
|
|
|
2026-07-12 20:56:00 -07:00
|
|
|
/* ---------- Zone row style ----------
|
|
|
|
|
* Same neutral surface language as the page-list rows below (Item 11) --
|
|
|
|
|
* header/footer are just two more items in the tree, not a louder,
|
|
|
|
|
* differently-colored category. The active/editing state reuses the same
|
|
|
|
|
* accent-outline treatment the page list already uses for the active page,
|
|
|
|
|
* so there's one consistent "this is what's currently open" affordance. */
|
2026-07-14 07:36:00 -07:00
|
|
|
/* ---------- Per-page-row icon button ----------
|
|
|
|
|
* Shared 24x24 icon-button styling used by every action in the page row
|
|
|
|
|
* (SEO settings, rename, duplicate, reorder, set-as-home, delete) so a new
|
|
|
|
|
* action slots in looking identical to the pre-existing gear/pencil/trash
|
|
|
|
|
* buttons -- same dark-theme neutral treatment, `disabled` dims the icon
|
|
|
|
|
* (matching how `disabled` already reads elsewhere in this panel, e.g. the
|
|
|
|
|
* "Add Page" button), and `danger` reuses the existing delete-button
|
|
|
|
|
* accent color. */
|
|
|
|
|
const pageActionBtnStyle = (opts: { disabled?: boolean; danger?: boolean } = {}): React.CSSProperties => ({
|
|
|
|
|
width: 24,
|
|
|
|
|
height: 24,
|
|
|
|
|
display: 'inline-flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
fontSize: 11,
|
|
|
|
|
color: opts.disabled
|
|
|
|
|
? 'var(--color-text-dim)'
|
|
|
|
|
: opts.danger
|
|
|
|
|
? 'var(--color-danger)'
|
|
|
|
|
: 'var(--color-text-muted)',
|
|
|
|
|
background: 'transparent',
|
|
|
|
|
border: 'none',
|
|
|
|
|
borderRadius: 'var(--radius-sm)',
|
|
|
|
|
cursor: opts.disabled ? 'default' : 'pointer',
|
|
|
|
|
opacity: opts.disabled ? 0.5 : 1,
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-12 20:56:00 -07:00
|
|
|
const zoneRowStyle = (isActive: boolean): React.CSSProperties => ({
|
2026-04-05 18:31:16 -07:00
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
gap: 8,
|
|
|
|
|
width: '100%',
|
2026-07-12 20:56:00 -07:00
|
|
|
padding: '7px 10px',
|
2026-04-05 18:31:16 -07:00
|
|
|
fontSize: 12,
|
2026-07-12 20:56:00 -07:00
|
|
|
fontWeight: isActive ? 600 : 500,
|
|
|
|
|
color: isActive ? 'var(--color-accent)' : 'var(--color-text-muted)',
|
|
|
|
|
background: isActive ? 'var(--color-accent-subtle)' : 'var(--color-bg-elevated)',
|
|
|
|
|
border: `1px solid ${isActive ? 'var(--color-accent)' : 'var(--color-border)'}`,
|
2026-04-05 18:31:16 -07:00
|
|
|
borderRadius: 'var(--radius-md)',
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
transition: 'all var(--transition-fast)',
|
|
|
|
|
textAlign: 'left' as const,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
2026-07-12 20:56:00 -07:00
|
|
|
{/* Header/Footer zone rows */}
|
2026-04-05 18:31:16 -07:00
|
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginBottom: 8 }}>
|
|
|
|
|
<button
|
|
|
|
|
onClick={editHeader}
|
2026-07-12 20:56:00 -07:00
|
|
|
className="zone-row"
|
|
|
|
|
style={zoneRowStyle(isEditingHeader)}
|
|
|
|
|
title="Appears on all pages"
|
2026-04-05 18:31:16 -07:00
|
|
|
>
|
2026-07-12 20:56:00 -07:00
|
|
|
<i className="fa fa-window-maximize" style={{ fontSize: 12, width: 14, textAlign: 'center', flexShrink: 0 }} />
|
|
|
|
|
<span style={{ flex: 1 }}>Header</span>
|
|
|
|
|
{isEditingHeader ? (
|
|
|
|
|
<i className="fa fa-check-circle" style={{ fontSize: 11 }} aria-hidden />
|
|
|
|
|
) : (
|
|
|
|
|
<i className="fa fa-pencil zone-row-pencil" style={{ fontSize: 11 }} aria-hidden />
|
2026-04-05 18:31:16 -07:00
|
|
|
)}
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
onClick={editFooter}
|
2026-07-12 20:56:00 -07:00
|
|
|
className="zone-row"
|
|
|
|
|
style={zoneRowStyle(isEditingFooter)}
|
|
|
|
|
title="Appears on all pages"
|
2026-04-05 18:31:16 -07:00
|
|
|
>
|
2026-07-12 20:56:00 -07:00
|
|
|
<i className="fa fa-window-minimize" style={{ fontSize: 12, width: 14, textAlign: 'center', flexShrink: 0 }} />
|
|
|
|
|
<span style={{ flex: 1 }}>Footer</span>
|
|
|
|
|
{isEditingFooter ? (
|
|
|
|
|
<i className="fa fa-check-circle" style={{ fontSize: 11 }} aria-hidden />
|
|
|
|
|
) : (
|
|
|
|
|
<i className="fa fa-pencil zone-row-pencil" style={{ fontSize: 11 }} aria-hidden />
|
2026-04-05 18:31:16 -07:00
|
|
|
)}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Section label for pages */}
|
|
|
|
|
<div style={{
|
|
|
|
|
fontSize: 10,
|
|
|
|
|
fontWeight: 600,
|
|
|
|
|
textTransform: 'uppercase',
|
|
|
|
|
letterSpacing: '0.5px',
|
|
|
|
|
color: 'var(--color-text-dim)',
|
|
|
|
|
padding: '0 2px',
|
|
|
|
|
}}>
|
|
|
|
|
Pages
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Page list */}
|
2026-05-25 12:14:26 -07:00
|
|
|
{pages.map((page, pageIndex) => {
|
|
|
|
|
const isLanding = pageIndex === 0;
|
|
|
|
|
return (
|
2026-04-05 18:31:16 -07:00
|
|
|
<div key={page.id}>
|
|
|
|
|
{editingId === page.id ? (
|
|
|
|
|
/* Editing mode */
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
padding: 10,
|
|
|
|
|
background: 'var(--color-bg-elevated)',
|
|
|
|
|
borderRadius: 'var(--radius-md)',
|
|
|
|
|
border: '1px solid var(--color-accent)',
|
|
|
|
|
display: 'flex',
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
gap: 8,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
value={editName}
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
setEditName(e.target.value);
|
|
|
|
|
setEditSlug(autoSlug(e.target.value));
|
|
|
|
|
}}
|
|
|
|
|
placeholder="Page name"
|
|
|
|
|
className="control-input"
|
|
|
|
|
style={{ fontSize: 12 }}
|
|
|
|
|
autoFocus
|
|
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (e.key === 'Enter') handleRename(page.id);
|
|
|
|
|
if (e.key === 'Escape') setEditingId(null);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
2026-05-25 12:14:26 -07:00
|
|
|
{isLanding ? (
|
|
|
|
|
<div style={{
|
|
|
|
|
fontSize: 10,
|
|
|
|
|
color: 'var(--color-text-dim)',
|
|
|
|
|
padding: '4px 2px',
|
|
|
|
|
fontStyle: 'italic',
|
|
|
|
|
}}>
|
|
|
|
|
Landing page — URL locked to <code>/</code>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
value={editSlug}
|
|
|
|
|
onChange={(e) => setEditSlug(e.target.value)}
|
|
|
|
|
placeholder="page-slug"
|
|
|
|
|
className="control-input"
|
|
|
|
|
style={{ fontSize: 11 }}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
2026-04-05 18:31:16 -07:00
|
|
|
<div style={{ display: 'flex', gap: 6 }}>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => handleRename(page.id)}
|
|
|
|
|
style={{
|
|
|
|
|
flex: 1,
|
|
|
|
|
padding: '5px 10px',
|
|
|
|
|
fontSize: 11,
|
|
|
|
|
fontWeight: 600,
|
|
|
|
|
color: '#fff',
|
|
|
|
|
background: 'var(--color-accent)',
|
|
|
|
|
border: 'none',
|
|
|
|
|
borderRadius: 'var(--radius-sm)',
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Save
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setEditingId(null)}
|
|
|
|
|
style={{
|
|
|
|
|
flex: 1,
|
|
|
|
|
padding: '5px 10px',
|
|
|
|
|
fontSize: 11,
|
|
|
|
|
fontWeight: 600,
|
|
|
|
|
color: 'var(--color-text-muted)',
|
|
|
|
|
background: 'var(--color-bg-base)',
|
|
|
|
|
border: '1px solid var(--color-border)',
|
|
|
|
|
borderRadius: 'var(--radius-sm)',
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Cancel
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
) : deleteConfirmId === page.id ? (
|
|
|
|
|
/* Delete confirmation */
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
padding: 10,
|
|
|
|
|
background: 'var(--color-bg-elevated)',
|
|
|
|
|
borderRadius: 'var(--radius-md)',
|
|
|
|
|
border: '1px solid var(--color-danger)',
|
|
|
|
|
display: 'flex',
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
gap: 8,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div style={{ fontSize: 12, color: 'var(--color-text)' }}>
|
|
|
|
|
Delete "{page.name}"?
|
|
|
|
|
</div>
|
|
|
|
|
<div style={{ display: 'flex', gap: 6 }}>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => handleDelete(page.id)}
|
|
|
|
|
style={{
|
|
|
|
|
flex: 1,
|
|
|
|
|
padding: '5px 10px',
|
|
|
|
|
fontSize: 11,
|
|
|
|
|
fontWeight: 600,
|
|
|
|
|
color: '#fff',
|
|
|
|
|
background: 'var(--color-danger)',
|
|
|
|
|
border: 'none',
|
|
|
|
|
borderRadius: 'var(--radius-sm)',
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Delete
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setDeleteConfirmId(null)}
|
|
|
|
|
style={{
|
|
|
|
|
flex: 1,
|
|
|
|
|
padding: '5px 10px',
|
|
|
|
|
fontSize: 11,
|
|
|
|
|
fontWeight: 600,
|
|
|
|
|
color: 'var(--color-text-muted)',
|
|
|
|
|
background: 'var(--color-bg-base)',
|
|
|
|
|
border: '1px solid var(--color-border)',
|
|
|
|
|
borderRadius: 'var(--radius-sm)',
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Cancel
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
/* Normal page item */
|
|
|
|
|
<div
|
2026-07-12 14:50:50 -07:00
|
|
|
{...clickableProps(() => switchPage(page.id))}
|
2026-04-05 18:31:16 -07:00
|
|
|
style={{
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
|
padding: '8px 10px',
|
|
|
|
|
background:
|
|
|
|
|
page.id === activePageId
|
|
|
|
|
? 'var(--color-accent-subtle)'
|
|
|
|
|
: 'var(--color-bg-elevated)',
|
|
|
|
|
border: `1px solid ${page.id === activePageId ? 'var(--color-accent)' : 'var(--color-border)'}`,
|
|
|
|
|
borderRadius: 'var(--radius-md)',
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
transition: 'all var(--transition-fast)',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div style={{ flex: 1, minWidth: 0 }}>
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
fontWeight: page.id === activePageId ? 600 : 500,
|
|
|
|
|
color:
|
|
|
|
|
page.id === activePageId
|
|
|
|
|
? 'var(--color-accent)'
|
|
|
|
|
: 'var(--color-text)',
|
2026-05-25 12:14:26 -07:00
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
gap: 6,
|
2026-04-05 18:31:16 -07:00
|
|
|
overflow: 'hidden',
|
|
|
|
|
}}
|
|
|
|
|
>
|
2026-05-25 12:14:26 -07:00
|
|
|
<span style={{
|
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
textOverflow: 'ellipsis',
|
|
|
|
|
whiteSpace: 'nowrap',
|
|
|
|
|
}}>{page.name}</span>
|
|
|
|
|
{isLanding && (
|
|
|
|
|
<span
|
|
|
|
|
title="This is the landing page — published as the root URL (index.html)"
|
|
|
|
|
style={{
|
|
|
|
|
fontSize: 9,
|
2026-07-12 20:56:00 -07:00
|
|
|
fontWeight: 600,
|
|
|
|
|
letterSpacing: '0.3px',
|
|
|
|
|
color: 'var(--color-text-muted)',
|
|
|
|
|
background: 'transparent',
|
|
|
|
|
border: '1px solid var(--color-border-light)',
|
2026-05-25 12:14:26 -07:00
|
|
|
padding: '1px 5px',
|
|
|
|
|
borderRadius: 'var(--radius-sm)',
|
|
|
|
|
flexShrink: 0,
|
2026-07-12 21:12:09 -07:00
|
|
|
display: 'inline-flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
gap: 4,
|
2026-05-25 12:14:26 -07:00
|
|
|
}}
|
|
|
|
|
>
|
2026-07-12 21:12:09 -07:00
|
|
|
<i className="fa fa-home" aria-hidden="true" style={{ fontSize: 8, color: 'inherit' }} />
|
2026-07-12 20:56:00 -07:00
|
|
|
Landing
|
2026-05-25 12:14:26 -07:00
|
|
|
</span>
|
|
|
|
|
)}
|
2026-04-05 18:31:16 -07:00
|
|
|
</div>
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
fontSize: 10,
|
|
|
|
|
color: 'var(--color-text-dim)',
|
|
|
|
|
marginTop: 2,
|
|
|
|
|
}}
|
|
|
|
|
>
|
2026-05-25 12:14:26 -07:00
|
|
|
{isLanding ? '/' : '/' + page.slug}
|
2026-04-05 18:31:16 -07:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div
|
2026-07-14 07:36:00 -07:00
|
|
|
style={{ display: 'flex', gap: 2, flexShrink: 0, flexWrap: 'wrap', justifyContent: 'flex-end', maxWidth: 96 }}
|
2026-04-05 18:31:16 -07:00
|
|
|
onClick={(e) => e.stopPropagation()}
|
|
|
|
|
>
|
2026-07-14 07:36:00 -07:00
|
|
|
<button
|
|
|
|
|
onClick={() => movePage(page.id, 'up')}
|
|
|
|
|
disabled={pageIndex === 0}
|
|
|
|
|
data-tooltip="Move up"
|
|
|
|
|
aria-label={`Move ${page.name} up`}
|
|
|
|
|
style={pageActionBtnStyle({ disabled: pageIndex === 0 })}
|
|
|
|
|
>
|
|
|
|
|
<i className="fa fa-arrow-up" aria-hidden="true" />
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => movePage(page.id, 'down')}
|
|
|
|
|
disabled={pageIndex === pages.length - 1}
|
|
|
|
|
data-tooltip="Move down"
|
|
|
|
|
aria-label={`Move ${page.name} down`}
|
|
|
|
|
style={pageActionBtnStyle({ disabled: pageIndex === pages.length - 1 })}
|
|
|
|
|
>
|
|
|
|
|
<i className="fa fa-arrow-down" aria-hidden="true" />
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => duplicatePage(page.id)}
|
|
|
|
|
data-tooltip="Duplicate"
|
|
|
|
|
aria-label={`Duplicate ${page.name}`}
|
|
|
|
|
style={pageActionBtnStyle()}
|
|
|
|
|
>
|
|
|
|
|
<i className="fa fa-clone" aria-hidden="true" />
|
|
|
|
|
</button>
|
|
|
|
|
{isLanding ? (
|
|
|
|
|
<span
|
|
|
|
|
data-tooltip="This is the home page"
|
|
|
|
|
aria-label={`${page.name} is the home page`}
|
|
|
|
|
style={pageActionBtnStyle({ disabled: true })}
|
|
|
|
|
>
|
|
|
|
|
<i className="fa fa-home" aria-hidden="true" />
|
|
|
|
|
</span>
|
|
|
|
|
) : (
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setLandingPage(page.id)}
|
|
|
|
|
data-tooltip="Set as home page"
|
|
|
|
|
aria-label={`Set ${page.name} as the home page`}
|
|
|
|
|
style={pageActionBtnStyle()}
|
|
|
|
|
>
|
|
|
|
|
<i className="fa fa-home" aria-hidden="true" />
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
2026-07-14 07:14:56 -07:00
|
|
|
<button
|
|
|
|
|
onClick={() => setSeoSettingsPageId(page.id)}
|
|
|
|
|
data-tooltip="Page Settings (SEO)"
|
|
|
|
|
aria-label={`Page settings for ${page.name}`}
|
2026-07-14 07:36:00 -07:00
|
|
|
style={pageActionBtnStyle()}
|
2026-07-14 07:14:56 -07:00
|
|
|
>
|
|
|
|
|
<i className="fa fa-cog" aria-hidden="true" />
|
|
|
|
|
</button>
|
2026-04-05 18:31:16 -07:00
|
|
|
<button
|
|
|
|
|
onClick={() => startEditing(page)}
|
2026-07-12 20:56:00 -07:00
|
|
|
data-tooltip="Rename"
|
|
|
|
|
aria-label={`Rename ${page.name}`}
|
2026-07-14 07:36:00 -07:00
|
|
|
style={pageActionBtnStyle()}
|
2026-04-05 18:31:16 -07:00
|
|
|
>
|
2026-07-12 20:56:00 -07:00
|
|
|
<i className="fa fa-pencil" aria-hidden="true" />
|
2026-04-05 18:31:16 -07:00
|
|
|
</button>
|
2026-05-25 12:14:26 -07:00
|
|
|
{pages.length > 1 && !isLanding && (
|
2026-04-05 18:31:16 -07:00
|
|
|
<button
|
|
|
|
|
onClick={() => setDeleteConfirmId(page.id)}
|
2026-07-12 20:56:00 -07:00
|
|
|
data-tooltip="Delete"
|
|
|
|
|
aria-label={`Delete ${page.name}`}
|
2026-07-14 07:36:00 -07:00
|
|
|
style={pageActionBtnStyle({ danger: true })}
|
2026-04-05 18:31:16 -07:00
|
|
|
>
|
2026-07-12 20:22:11 -07:00
|
|
|
<i className="fa fa-trash" aria-hidden="true" />
|
2026-04-05 18:31:16 -07:00
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2026-05-25 12:14:26 -07:00
|
|
|
);
|
|
|
|
|
})}
|
2026-04-05 18:31:16 -07:00
|
|
|
|
|
|
|
|
{/* Add page section */}
|
|
|
|
|
{isAdding ? (
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
padding: 10,
|
|
|
|
|
background: 'var(--color-bg-elevated)',
|
|
|
|
|
borderRadius: 'var(--radius-md)',
|
|
|
|
|
border: '1px solid var(--color-border)',
|
|
|
|
|
display: 'flex',
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
gap: 8,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
value={newName}
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
setNewName(e.target.value);
|
|
|
|
|
setNewSlug(autoSlug(e.target.value));
|
|
|
|
|
}}
|
|
|
|
|
placeholder="Page name"
|
|
|
|
|
className="control-input"
|
|
|
|
|
style={{ fontSize: 12 }}
|
|
|
|
|
autoFocus
|
|
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (e.key === 'Enter') handleAdd();
|
|
|
|
|
if (e.key === 'Escape') {
|
|
|
|
|
setIsAdding(false);
|
|
|
|
|
setNewName('');
|
|
|
|
|
setNewSlug('');
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
value={newSlug}
|
|
|
|
|
onChange={(e) => setNewSlug(e.target.value)}
|
|
|
|
|
placeholder="page-slug"
|
|
|
|
|
className="control-input"
|
|
|
|
|
style={{ fontSize: 11 }}
|
|
|
|
|
/>
|
|
|
|
|
<div style={{ display: 'flex', gap: 6 }}>
|
|
|
|
|
<button
|
|
|
|
|
onClick={handleAdd}
|
|
|
|
|
disabled={!newName.trim()}
|
|
|
|
|
style={{
|
|
|
|
|
flex: 1,
|
|
|
|
|
padding: '5px 10px',
|
|
|
|
|
fontSize: 11,
|
|
|
|
|
fontWeight: 600,
|
|
|
|
|
color: '#fff',
|
|
|
|
|
background: newName.trim() ? 'var(--color-accent)' : 'var(--color-bg-active)',
|
|
|
|
|
border: 'none',
|
|
|
|
|
borderRadius: 'var(--radius-sm)',
|
|
|
|
|
cursor: newName.trim() ? 'pointer' : 'not-allowed',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Add Page
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => {
|
|
|
|
|
setIsAdding(false);
|
|
|
|
|
setNewName('');
|
|
|
|
|
setNewSlug('');
|
|
|
|
|
}}
|
|
|
|
|
style={{
|
|
|
|
|
flex: 1,
|
|
|
|
|
padding: '5px 10px',
|
|
|
|
|
fontSize: 11,
|
|
|
|
|
fontWeight: 600,
|
|
|
|
|
color: 'var(--color-text-muted)',
|
|
|
|
|
background: 'var(--color-bg-base)',
|
|
|
|
|
border: '1px solid var(--color-border)',
|
|
|
|
|
borderRadius: 'var(--radius-sm)',
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Cancel
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setIsAdding(true)}
|
|
|
|
|
style={{
|
|
|
|
|
width: '100%',
|
|
|
|
|
padding: '8px 12px',
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
fontWeight: 600,
|
|
|
|
|
color: 'var(--color-accent)',
|
|
|
|
|
background: 'var(--color-accent-subtle)',
|
|
|
|
|
border: '1px dashed var(--color-accent)',
|
|
|
|
|
borderRadius: 'var(--radius-md)',
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
transition: 'all var(--transition-fast)',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
+ Add Page
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
2026-07-14 07:14:56 -07:00
|
|
|
|
|
|
|
|
<PageSettingsModal
|
|
|
|
|
open={seoSettingsPageId !== null}
|
|
|
|
|
onClose={() => setSeoSettingsPageId(null)}
|
|
|
|
|
page={seoSettingsPage}
|
|
|
|
|
updatePageSeo={updatePageSeo}
|
|
|
|
|
/>
|
2026-04-05 18:31:16 -07:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|