diff --git a/frontends/nextjs/src/components/misc/data/generic-page/Preview.tsx b/frontends/nextjs/src/components/misc/data/generic-page/Preview.tsx new file mode 100644 index 000000000..65836e4ab --- /dev/null +++ b/frontends/nextjs/src/components/misc/data/generic-page/Preview.tsx @@ -0,0 +1,120 @@ +import { Badge, Card, CardContent, CardDescription, CardHeader, CardTitle, Separator } from '@/components/ui' +import { PageDefinition } from '@/lib/rendering/page/page-renderer' +import { Eye, Layout, ShieldCheck } from '@phosphor-icons/react' + +interface GenericPagePreviewProps { + page: PageDefinition + updatedAt?: string + footerText?: string +} + +const layoutCopy: Record = { + default: 'Default layout with header and footer', + sidebar: 'Sidebar layout with navigation', + dashboard: 'Dashboard layout with widgets', + blank: 'Blank canvas for custom layouts' +} + +export function Preview({ page, updatedAt, footerText }: GenericPagePreviewProps) { + const showHeader = page.metadata?.showHeader !== false + const showFooter = page.metadata?.showFooter !== false + + return ( + + + + + Page preview + + + + {layoutCopy[page.layout]} + + + +
+
+

{page.description || 'No description provided.'}

+
+ + Level {page.level} + + {page.components.length} components + {page.permissions?.requiresAuth && ( + + + Auth required{page.permissions?.requiredRole ? ` (${page.permissions.requiredRole})` : ''} + + )} + {updatedAt && Last updated {updatedAt}} +
+
+ {page.metadata?.headerTitle || page.title} +
+ +
+ {showHeader && ( +
+ Header + {page.metadata?.headerTitle || 'Default title'} +
+ )} + +
+ {page.layout === 'sidebar' && ( +
+ Sidebar navigation +
+ )} + +
+

Component tree

+
+ {page.components.slice(0, 4).map(component => ( +
+
+ {component.type} + {component.children && component.children.length > 0 && ( + {component.children.length} children + )} +
+ {component.props?.className &&

{component.props.className}

} +
+ ))} + {page.components.length === 0 && ( +

Add components to see them previewed here.

+ )} +
+
+
+ + {showFooter && ( +
+ Footer + {footerText || 'Configured in metadata'} +
+ )} +
+ + + +
+
+

Lua hooks

+

onLoad: {page.luaScripts?.onLoad || 'Not configured'}

+

onUnload: {page.luaScripts?.onUnload || 'Not configured'}

+
+
+

Metadata

+

Header actions: {page.metadata?.headerActions?.length ?? 0}

+

Sidebar items: {page.metadata?.sidebarItems?.length ?? 0}

+
+
+
+
+ ) +} diff --git a/frontends/nextjs/src/components/misc/data/generic-page/SectionList.tsx b/frontends/nextjs/src/components/misc/data/generic-page/SectionList.tsx new file mode 100644 index 000000000..4dd8ce0f7 --- /dev/null +++ b/frontends/nextjs/src/components/misc/data/generic-page/SectionList.tsx @@ -0,0 +1,101 @@ +import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, ScrollArea, Separator } from '@/components/ui' +import { ListNumbers, Plus, PushPinSimple, SquaresFour } from '@phosphor-icons/react' + +export interface PageSection { + id: string + title: string + description?: string + componentCount?: number + status?: 'draft' | 'review' | 'published' + updatedAt?: string +} + +interface SectionListProps { + sections: PageSection[] + selectedSectionId?: string + onSelectSection?: (section: PageSection) => void + onCreateSection?: () => void +} + +const statusVariant: Record, 'default' | 'secondary' | 'outline'> = { + draft: 'secondary', + review: 'outline', + published: 'default' +} + +export function SectionList({ sections, selectedSectionId, onSelectSection, onCreateSection }: SectionListProps) { + return ( + + +
+ + + Sections + + Outline the sections that make up your generic page. +
+ +
+ + {sections.length === 0 ? ( +
+

No sections yet. Create your first section to start building the page.

+
+ ) : ( + +
+ {sections.map(section => ( + + ))} +
+
+ )} +
+
+ ) +} diff --git a/frontends/nextjs/src/components/misc/data/quick-guide/MediaPane.tsx b/frontends/nextjs/src/components/misc/data/quick-guide/MediaPane.tsx new file mode 100644 index 000000000..7f7f2dd27 --- /dev/null +++ b/frontends/nextjs/src/components/misc/data/quick-guide/MediaPane.tsx @@ -0,0 +1,69 @@ +import Image from 'next/image' +import { Badge, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Label } from '@/components/ui' +import { FilmSlate, ImageSquare } from '@phosphor-icons/react' + +interface MediaPaneProps { + thumbnailUrl?: string + videoUrl?: string + onThumbnailChange?: (value: string) => void + onVideoChange?: (value: string) => void +} + +export function MediaPane({ thumbnailUrl, videoUrl, onThumbnailChange, onVideoChange }: MediaPaneProps) { + return ( + + + + + Media + + Optional visuals to make the quick guide easier to follow. + + +
+ + onThumbnailChange?.(e.target.value)} + placeholder="https://images.example.com/quick-guide.png" + /> +

Shown in dashboards and previews.

+ {thumbnailUrl && ( +
+ Quick guide thumbnail +
+ )} +
+ +
+ + onVideoChange?.(e.target.value)} + placeholder="YouTube or direct MP4 link" + /> +

Embed a short clip that shows the flow in action.

+ {videoUrl && ( +
+ + + Preview + +
+