mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 22:04:56 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user