Merge pull request #223 from johndoe6345789/codex/create-routestable,-routeeditor,-and-preview-components

Refactor page routes manager into modular components
This commit is contained in:
2025-12-27 18:33:50 +00:00
committed by GitHub
4 changed files with 288 additions and 187 deletions

View File

@@ -1,29 +1,39 @@
import { useState, useEffect } from 'react'
import { Button } from '@/components/ui'
import { Input } from '@/components/ui'
import { Label } from '@/components/ui'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui'
import { Badge } from '@/components/ui'
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui'
import { Plus, Pencil, Trash, Eye, LockKey } from '@phosphor-icons/react'
import { useEffect, useState } from 'react'
import {
Button,
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui'
import { Plus } from '@phosphor-icons/react'
import { Database } from '@/lib/database'
import type { PageConfig } from '@/lib/level-types'
import { toast } from 'sonner'
import type { PageConfig, UserRole, AppLevel } from '@/lib/level-types'
import { Switch } from '@/components/ui'
import { RoutesTable } from './page-routes/RoutesTable'
import { Preview } from './page-routes/Preview'
import { RouteEditor, RouteFormData } from './page-routes/RouteEditor'
const defaultFormData: RouteFormData = {
path: '/',
title: '',
level: 1,
requiresAuth: false,
componentTree: [],
}
export function PageRoutesManager() {
const [pages, setPages] = useState<PageConfig[]>([])
const [isDialogOpen, setIsDialogOpen] = useState(false)
const [editingPage, setEditingPage] = useState<PageConfig | null>(null)
const [formData, setFormData] = useState<Partial<PageConfig>>({
path: '/',
title: '',
level: 1,
requiresAuth: false,
componentTree: [],
})
const [formData, setFormData] = useState<RouteFormData>({ ...defaultFormData })
useEffect(() => {
loadPages()
@@ -40,13 +50,7 @@ export function PageRoutesManager() {
setFormData(page)
} else {
setEditingPage(null)
setFormData({
path: '/',
title: '',
level: 1,
requiresAuth: false,
componentTree: [],
})
setFormData({ ...defaultFormData })
}
setIsDialogOpen(true)
}
@@ -54,13 +58,7 @@ export function PageRoutesManager() {
const handleCloseDialog = () => {
setIsDialogOpen(false)
setEditingPage(null)
setFormData({
path: '/',
title: '',
level: 1,
requiresAuth: false,
componentTree: [],
})
setFormData({ ...defaultFormData })
}
const handleSavePage = async () => {
@@ -98,18 +96,6 @@ export function PageRoutesManager() {
}
}
const getLevelBadgeColor = (level: AppLevel) => {
switch (level) {
case 1: return 'bg-blue-500'
case 2: return 'bg-green-500'
case 3: return 'bg-orange-500'
case 4: return 'bg-sky-500'
case 5: return 'bg-purple-500'
case 6: return 'bg-rose-500'
default: return 'bg-gray-500'
}
}
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
@@ -124,94 +110,23 @@ export function PageRoutesManager() {
New Page Route
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl">
<DialogContent className="max-w-4xl">
<DialogHeader>
<DialogTitle>{editingPage ? 'Edit Page Route' : 'Create New Page Route'}</DialogTitle>
<DialogDescription>
Configure the route path, access level, and authentication requirements
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="path">Route Path *</Label>
<Input
id="path"
placeholder="/home"
value={formData.path || ''}
onChange={(e) => setFormData({ ...formData, path: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="title">Page Title *</Label>
<Input
id="title"
placeholder="Home Page"
value={formData.title || ''}
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="level">Application Level</Label>
<Select
value={String(formData.level)}
onValueChange={(value) => setFormData({ ...formData, level: Number(value) as AppLevel })}
>
<SelectTrigger>
<SelectValue placeholder="Select level" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">Level 1 - Public</SelectItem>
<SelectItem value="2">Level 2 - User Area</SelectItem>
<SelectItem value="3">Level 3 - Moderator Desk</SelectItem>
<SelectItem value="4">Level 4 - Admin Panel</SelectItem>
<SelectItem value="5">Level 5 - God Builder</SelectItem>
<SelectItem value="6">Level 6 - Supergod Console</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="requiredRole">Required Role (if auth)</Label>
<Select
value={formData.requiredRole || 'public'}
onValueChange={(value) => setFormData({ ...formData, requiredRole: value as UserRole })}
>
<SelectTrigger>
<SelectValue placeholder="Select role" />
</SelectTrigger>
<SelectContent>
<SelectItem value="public">Public</SelectItem>
<SelectItem value="user">User</SelectItem>
<SelectItem value="moderator">Moderator</SelectItem>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="god">God</SelectItem>
<SelectItem value="supergod">Supergod</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="flex items-center space-x-2">
<Switch
id="requiresAuth"
checked={formData.requiresAuth}
onCheckedChange={(checked) => setFormData({ ...formData, requiresAuth: checked })}
/>
<Label htmlFor="requiresAuth" className="cursor-pointer">Requires Authentication</Label>
</div>
<div className="grid gap-6 md:grid-cols-2">
<RouteEditor
formData={formData}
onChange={setFormData}
onSave={handleSavePage}
onCancel={handleCloseDialog}
isEdit={Boolean(editingPage)}
/>
<Preview formData={formData} />
</div>
<DialogFooter>
<Button variant="outline" onClick={handleCloseDialog}>Cancel</Button>
<Button onClick={handleSavePage}>
{editingPage ? 'Update Page' : 'Create Page'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
@@ -222,67 +137,11 @@ export function PageRoutesManager() {
<CardDescription>All page routes in your application</CardDescription>
</CardHeader>
<CardContent>
{pages.length === 0 ? (
<div className="text-center py-12 text-muted-foreground">
<p>No pages configured yet. Create your first page route!</p>
</div>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead>Path</TableHead>
<TableHead>Title</TableHead>
<TableHead>Level</TableHead>
<TableHead>Auth</TableHead>
<TableHead>Required Role</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{pages.map((page) => (
<TableRow key={page.id}>
<TableCell className="font-mono text-sm">{page.path}</TableCell>
<TableCell>{page.title}</TableCell>
<TableCell>
<Badge className={getLevelBadgeColor(page.level)}>
Level {page.level}
</Badge>
</TableCell>
<TableCell>
{page.requiresAuth ? (
<LockKey className="text-orange-500" weight="fill" />
) : (
<Eye className="text-green-500" weight="fill" />
)}
</TableCell>
<TableCell>
<Badge variant="outline">
{page.requiredRole || 'public'}
</Badge>
</TableCell>
<TableCell className="text-right">
<div className="flex justify-end gap-2">
<Button
size="sm"
variant="ghost"
onClick={() => handleOpenDialog(page)}
>
<Pencil />
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => handleDeletePage(page.id)}
>
<Trash />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
<RoutesTable
pages={pages}
onEdit={handleOpenDialog}
onDelete={handleDeletePage}
/>
</CardContent>
</Card>
</div>

View File

@@ -0,0 +1,37 @@
import { Badge, Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui'
import { Eye, LockKey } from '@phosphor-icons/react'
import type { PageConfig } from '@/lib/level-types'
interface PreviewProps {
formData: Partial<PageConfig>
}
export function Preview({ formData }: PreviewProps) {
return (
<Card className="h-full">
<CardHeader>
<CardTitle>Route Preview</CardTitle>
<CardDescription>Quick glance at the route details</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div>
<p className="text-xs uppercase text-muted-foreground">Path</p>
<p className="font-mono">{formData.path || '/your-path'}</p>
</div>
<div>
<p className="text-xs uppercase text-muted-foreground">Title</p>
<p className="font-semibold">{formData.title || 'Untitled Page'}</p>
</div>
<div className="flex items-center gap-3">
<Badge variant="outline">Level {formData.level || 1}</Badge>
<Badge variant="secondary">{formData.requiredRole || 'public'}</Badge>
{formData.requiresAuth ? (
<LockKey className="text-orange-500" weight="fill" />
) : (
<Eye className="text-green-500" weight="fill" />
)}
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,107 @@
import {
Button,
Input,
Label,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
Switch,
} from '@/components/ui'
import type { PageConfig, UserRole, AppLevel } from '@/lib/level-types'
export type RouteFormData = Partial<PageConfig>
interface RouteEditorProps {
formData: RouteFormData
onChange: (value: RouteFormData) => void
onSave: () => void
onCancel: () => void
isEdit: boolean
}
export function RouteEditor({ formData, onChange, onSave, onCancel, isEdit }: RouteEditorProps) {
return (
<div className="space-y-4 py-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="path">Route Path *</Label>
<Input
id="path"
placeholder="/home"
value={formData.path || ''}
onChange={(e) => onChange({ ...formData, path: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="title">Page Title *</Label>
<Input
id="title"
placeholder="Home Page"
value={formData.title || ''}
onChange={(e) => onChange({ ...formData, title: e.target.value })}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="level">Application Level</Label>
<Select
value={formData.level ? String(formData.level) : ''}
onValueChange={(value) => onChange({ ...formData, level: Number(value) as AppLevel })}
>
<SelectTrigger>
<SelectValue placeholder="Select level" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">Level 1 - Public</SelectItem>
<SelectItem value="2">Level 2 - User Area</SelectItem>
<SelectItem value="3">Level 3 - Moderator Desk</SelectItem>
<SelectItem value="4">Level 4 - Admin Panel</SelectItem>
<SelectItem value="5">Level 5 - God Builder</SelectItem>
<SelectItem value="6">Level 6 - Supergod Console</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="requiredRole">Required Role (if auth)</Label>
<Select
value={formData.requiredRole || 'public'}
onValueChange={(value) => onChange({ ...formData, requiredRole: value as UserRole })}
>
<SelectTrigger>
<SelectValue placeholder="Select role" />
</SelectTrigger>
<SelectContent>
<SelectItem value="public">Public</SelectItem>
<SelectItem value="user">User</SelectItem>
<SelectItem value="moderator">Moderator</SelectItem>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="god">God</SelectItem>
<SelectItem value="supergod">Supergod</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="flex items-center space-x-2">
<Switch
id="requiresAuth"
checked={formData.requiresAuth}
onCheckedChange={(checked) => onChange({ ...formData, requiresAuth: checked })}
/>
<Label htmlFor="requiresAuth" className="cursor-pointer">Requires Authentication</Label>
</div>
<div className="flex gap-2 justify-end">
<Button type="button" variant="outline" onClick={onCancel}>Cancel</Button>
<Button type="button" onClick={onSave}>
{isEdit ? 'Update Page' : 'Create Page'}
</Button>
</div>
</div>
)
}

View File

@@ -0,0 +1,98 @@
import {
Badge,
Button,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui'
import { Eye, LockKey, Pencil, Trash } from '@phosphor-icons/react'
import type { PageConfig, AppLevel } from '@/lib/level-types'
interface RoutesTableProps {
pages: PageConfig[]
onEdit: (page: PageConfig) => void
onDelete: (pageId: string) => void
}
const getLevelBadgeColor = (level: AppLevel) => {
switch (level) {
case 1: return 'bg-blue-500'
case 2: return 'bg-green-500'
case 3: return 'bg-orange-500'
case 4: return 'bg-sky-500'
case 5: return 'bg-purple-500'
case 6: return 'bg-rose-500'
default: return 'bg-gray-500'
}
}
export function RoutesTable({ pages, onEdit, onDelete }: RoutesTableProps) {
if (pages.length === 0) {
return (
<div className="text-center py-12 text-muted-foreground">
<p>No pages configured yet. Create your first page route!</p>
</div>
)
}
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>Path</TableHead>
<TableHead>Title</TableHead>
<TableHead>Level</TableHead>
<TableHead>Auth</TableHead>
<TableHead>Required Role</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{pages.map((page) => (
<TableRow key={page.id}>
<TableCell className="font-mono text-sm">{page.path}</TableCell>
<TableCell>{page.title}</TableCell>
<TableCell>
<Badge className={getLevelBadgeColor(page.level)}>
Level {page.level}
</Badge>
</TableCell>
<TableCell>
{page.requiresAuth ? (
<LockKey className="text-orange-500" weight="fill" />
) : (
<Eye className="text-green-500" weight="fill" />
)}
</TableCell>
<TableCell>
<Badge variant="outline">
{page.requiredRole || 'public'}
</Badge>
</TableCell>
<TableCell className="text-right">
<div className="flex justify-end gap-2">
<Button
size="sm"
variant="ghost"
onClick={() => onEdit(page)}
>
<Pencil />
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => onDelete(page.id)}
>
<Trash />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
}