648 lines
26 KiB
TypeScript
648 lines
26 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect } from 'react'
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
|
import { Input } from '@/components/ui/input'
|
|
import { Label } from '@/components/ui/label'
|
|
import { Textarea } from '@/components/ui/textarea'
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
|
import {
|
|
CheckCircle,
|
|
XCircle,
|
|
Clock,
|
|
RefreshCw,
|
|
AlertCircle,
|
|
Copy,
|
|
Filter,
|
|
Search,
|
|
Edit,
|
|
Plus,
|
|
Save,
|
|
Files,
|
|
Settings
|
|
} from 'lucide-react'
|
|
import { adminApi, formatDate, getStatusColor, getComplexityColor } from '@/lib/api/admin'
|
|
import { AdminTemplate, AdminStats } from '@/types/admin.types'
|
|
import { TemplateEditDialog } from './template-edit-dialog'
|
|
import { RejectDialog } from './reject-dialog'
|
|
import { TemplateFeaturesManager } from './template-features-manager'
|
|
import { AdminTemplatesList } from './admin-templates-list'
|
|
import { useAdminNotifications } from '@/contexts/AdminNotificationContext'
|
|
|
|
export function AdminTemplatesManager() {
|
|
const [activeTab, setActiveTab] = useState("admin-templates")
|
|
const [customTemplates, setCustomTemplates] = useState<AdminTemplate[]>([])
|
|
const [templateStats, setTemplateStats] = useState<AdminStats | null>(null)
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
const [selectedTemplate, setSelectedTemplate] = useState<AdminTemplate | null>(null)
|
|
const [showTemplateEditDialog, setShowTemplateEditDialog] = useState(false)
|
|
const [showRejectDialog, setShowRejectDialog] = useState(false)
|
|
const [rejectItem, setRejectItem] = useState<{ id: string; name: string; type: 'template' } | null>(null)
|
|
const [searchQuery, setSearchQuery] = useState('')
|
|
const [statusFilter, setStatusFilter] = useState<string>('all')
|
|
const [showFeaturesManager, setShowFeaturesManager] = useState(false)
|
|
const [selectedTemplateForFeatures, setSelectedTemplateForFeatures] = useState<AdminTemplate | null>(null)
|
|
|
|
// Create template form state
|
|
const [newTemplate, setNewTemplate] = useState({
|
|
title: '',
|
|
description: '',
|
|
category: '',
|
|
type: '',
|
|
complexity: 'low',
|
|
icon: '',
|
|
gradient: '',
|
|
border: '',
|
|
text: '',
|
|
subtext: ''
|
|
})
|
|
|
|
const { removeByReference } = useAdminNotifications()
|
|
|
|
// Load templates data
|
|
const loadTemplatesData = async () => {
|
|
try {
|
|
setLoading(true)
|
|
setError(null)
|
|
|
|
console.log('Loading templates data...')
|
|
|
|
const [templatesResponse, templateStatsResponse] = await Promise.all([
|
|
adminApi.getCustomTemplates(), // Try without status filter first
|
|
adminApi.getTemplateStats()
|
|
])
|
|
|
|
console.log('Templates response:', templatesResponse)
|
|
console.log('Template stats response:', templateStatsResponse)
|
|
|
|
setCustomTemplates(templatesResponse || [])
|
|
setTemplateStats(templateStatsResponse)
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Failed to load templates data')
|
|
console.error('Error loading templates data:', err)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
loadTemplatesData()
|
|
}, [])
|
|
|
|
// Handle template review
|
|
const handleTemplateReview = async (templateId: string, reviewData: { status: 'pending' | 'approved' | 'rejected' | 'duplicate'; admin_notes?: string }) => {
|
|
try {
|
|
await adminApi.reviewTemplate(templateId, reviewData)
|
|
|
|
// Update the template in the list
|
|
setCustomTemplates(prev => prev.map(t =>
|
|
t.id === templateId ? { ...t, status: reviewData.status as AdminTemplate['status'], admin_notes: reviewData.admin_notes } : t
|
|
))
|
|
|
|
// Reload stats
|
|
const newStats = await adminApi.getTemplateStats()
|
|
setTemplateStats(newStats)
|
|
|
|
} catch (err) {
|
|
console.error('Error reviewing template:', err)
|
|
alert('Error reviewing template')
|
|
}
|
|
}
|
|
|
|
// Handle template update
|
|
const handleTemplateUpdate = (templateId: string, updatedTemplate: AdminTemplate) => {
|
|
setCustomTemplates(prev => prev.filter(t => t.id !== templateId))
|
|
}
|
|
|
|
// Handle reject action
|
|
const handleReject = async (adminNotes: string) => {
|
|
if (!rejectItem) return
|
|
|
|
try {
|
|
await adminApi.rejectTemplate(rejectItem.id, adminNotes)
|
|
setCustomTemplates(prev => prev.map(t =>
|
|
t.id === rejectItem.id ? { ...t, status: 'rejected', admin_notes: adminNotes } : t
|
|
))
|
|
// Reload template stats
|
|
const newStats = await adminApi.getTemplateStats()
|
|
setTemplateStats(newStats)
|
|
} catch (err) {
|
|
console.error('Error rejecting template:', err)
|
|
alert('Error rejecting template')
|
|
}
|
|
}
|
|
|
|
// Handle approve action
|
|
const handleApprove = async (templateId: string) => {
|
|
// Optimistic UI: remove immediately
|
|
setCustomTemplates(prev => prev.filter(t => t.id !== templateId))
|
|
|
|
try {
|
|
// Get the template details first
|
|
const template = customTemplates.find(t => t.id === templateId)
|
|
if (template) {
|
|
// Create new approved template in main templates table
|
|
await adminApi.createApprovedTemplate(templateId, {
|
|
title: template.title || '',
|
|
description: template.description,
|
|
category: template.category || '',
|
|
type: template.type || '',
|
|
icon: template.icon,
|
|
gradient: template.gradient,
|
|
border: template.border,
|
|
text: template.text,
|
|
subtext: template.subtext
|
|
})
|
|
|
|
// Update the custom template status to approved
|
|
await adminApi.reviewTemplate(templateId, { status: 'approved', admin_notes: 'Approved and created in main templates' })
|
|
|
|
// Remove related notifications for this template
|
|
removeByReference('custom_template', templateId)
|
|
}
|
|
|
|
// Reload template stats
|
|
const newStats = await adminApi.getTemplateStats()
|
|
setTemplateStats(newStats)
|
|
} catch (err) {
|
|
console.error('Error approving template:', err)
|
|
// Recover UI by reloading if optimistic removal was wrong
|
|
await loadTemplatesData()
|
|
}
|
|
}
|
|
|
|
// Handle create template
|
|
const handleCreateTemplate = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
|
|
try {
|
|
// Validate required fields
|
|
if (!newTemplate.title || !newTemplate.description || !newTemplate.category || !newTemplate.type) {
|
|
alert('Please fill in all required fields (Title, Description, Category, Type)')
|
|
return
|
|
}
|
|
|
|
// Call API to create new custom template using the correct endpoint
|
|
await adminApi.createCustomTemplate({
|
|
title: newTemplate.title,
|
|
description: newTemplate.description,
|
|
category: newTemplate.category,
|
|
type: newTemplate.type,
|
|
complexity: newTemplate.complexity,
|
|
icon: newTemplate.icon,
|
|
gradient: newTemplate.gradient,
|
|
border: newTemplate.border,
|
|
text: newTemplate.text,
|
|
subtext: newTemplate.subtext
|
|
})
|
|
|
|
// Reset form
|
|
setNewTemplate({
|
|
title: '',
|
|
description: '',
|
|
category: '',
|
|
type: '',
|
|
complexity: 'low',
|
|
icon: '',
|
|
gradient: '',
|
|
border: '',
|
|
text: '',
|
|
subtext: ''
|
|
})
|
|
|
|
// Reload templates
|
|
await loadTemplatesData()
|
|
|
|
// Switch to manage tab
|
|
setActiveTab("manage")
|
|
|
|
alert('Template created successfully!')
|
|
} catch (err) {
|
|
console.error('Error creating template:', err)
|
|
const errorMessage = err instanceof Error ? err.message : 'Failed to create template'
|
|
alert(`Error creating template: ${errorMessage}`)
|
|
}
|
|
}
|
|
|
|
// Filter templates based on search and status
|
|
const filteredTemplates = customTemplates.filter(template => {
|
|
const matchesSearch = template.title?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
template.description?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
template.category?.toLowerCase().includes(searchQuery.toLowerCase())
|
|
|
|
const matchesStatus = statusFilter === 'all' || template.status === statusFilter
|
|
|
|
return matchesSearch && matchesStatus
|
|
})
|
|
|
|
// Get status counts for templates
|
|
const getTemplateStatusCount = (status: string) => {
|
|
return (templateStats as AdminStats & { templates?: Array<{ status: string; count: number }> })?.templates?.find((s) => s.status === status)?.count || 0
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center min-h-[400px]">
|
|
<div className="flex items-center space-x-2">
|
|
<RefreshCw className="h-6 w-6 animate-spin" />
|
|
<span>Loading templates...</span>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="flex items-center justify-center min-h-[400px]">
|
|
<Card className="w-full max-w-md">
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-center space-x-2 text-red-600">
|
|
<AlertCircle className="h-6 w-6" />
|
|
<span>Error loading templates</span>
|
|
</div>
|
|
<p className="mt-2 text-sm text-gray-600">{error}</p>
|
|
<Button onClick={loadTemplatesData} className="mt-4">
|
|
<RefreshCw className="h-4 w-4 mr-2" />
|
|
Retry
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-white">Templates Management</h1>
|
|
<p className="text-white/70">Manage templates and create new ones</p>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<Button onClick={loadTemplatesData} className="bg-orange-500 text-black hover:bg-orange-600">
|
|
<RefreshCw className="h-4 w-4 mr-2" />
|
|
Refresh
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stats Cards */}
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
|
<Card className="bg-gray-900 border-gray-800">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium text-white">Total Templates</CardTitle>
|
|
<Files className="h-4 w-4 text-white/60" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-white">{customTemplates.length}</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="bg-gray-900 border-gray-800">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium text-white">Pending</CardTitle>
|
|
<Clock className="h-4 w-4 text-yellow-600" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-white">{getTemplateStatusCount('pending')}</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="bg-gray-900 border-gray-800">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium text-white">Approved</CardTitle>
|
|
<CheckCircle className="h-4 w-4 text-green-500" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-white">{getTemplateStatusCount('approved')}</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="bg-gray-900 border-gray-800">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium text-white">Rejected</CardTitle>
|
|
<XCircle className="h-4 w-4 text-red-500" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-white">{getTemplateStatusCount('rejected')}</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Main Content */}
|
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-4">
|
|
<TabsList className="bg-gray-900 border-gray-800">
|
|
<TabsTrigger value="admin-templates" className="flex items-center space-x-2 data-[state=active]:bg-orange-500 data-[state=active]:text-black text-white/70">
|
|
<Settings className="h-4 w-4" />
|
|
<span>Admin Templates</span>
|
|
</TabsTrigger>
|
|
<TabsTrigger value="manage" className="flex items-center space-x-2 data-[state=active]:bg-orange-500 data-[state=active]:text-black text-white/70">
|
|
<Filter className="h-4 w-4" />
|
|
<span>Custom Templates ({customTemplates.length})</span>
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="admin-templates" className="space-y-4">
|
|
<AdminTemplatesList />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="manage" className="space-y-4">
|
|
{/* Filters */}
|
|
<div className="flex items-center space-x-4">
|
|
<div className="flex-1">
|
|
<div className="relative">
|
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
|
<Input
|
|
placeholder="Search templates..."
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
className="pl-10"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
|
<SelectTrigger className="w-48">
|
|
<SelectValue placeholder="Filter by status" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="all">All Status</SelectItem>
|
|
<SelectItem value="pending">Pending</SelectItem>
|
|
<SelectItem value="approved">Approved</SelectItem>
|
|
<SelectItem value="rejected">Rejected</SelectItem>
|
|
<SelectItem value="duplicate">Duplicate</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* Templates List */}
|
|
<div className="space-y-4">
|
|
{filteredTemplates.length === 0 ? (
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
<div className="text-center text-gray-500">
|
|
<Copy className="h-12 w-12 mx-auto mb-4 text-gray-300" />
|
|
<p>No templates found</p>
|
|
<p className="text-sm">All templates have been reviewed or no templates match your filters.</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
) : (
|
|
filteredTemplates.map((template) => (
|
|
<Card key={template.id} className="hover:shadow-md transition-shadow">
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<div className="flex items-center space-x-2 mb-2">
|
|
<h3 className="text-lg font-semibold">{template.title}</h3>
|
|
<Badge className={getStatusColor(template.status)}>
|
|
{template.status}
|
|
</Badge>
|
|
<Badge className={getComplexityColor(template.complexity)}>
|
|
{template.complexity}
|
|
</Badge>
|
|
{template.category && (
|
|
<Badge variant="outline">{template.category}</Badge>
|
|
)}
|
|
</div>
|
|
|
|
{template.description && (
|
|
<p className="text-gray-600 mb-2">{template.description}</p>
|
|
)}
|
|
|
|
<div className="flex items-center space-x-4 text-sm text-gray-500">
|
|
<span>Type: {template.type || 'Unknown'}</span>
|
|
<span>Submitted: {formatDate(template.created_at)}</span>
|
|
<span>Usage: {template.usage_count || 0}</span>
|
|
</div>
|
|
|
|
{template.admin_notes && (
|
|
<div className="mt-2 p-2 bg-gray-50 rounded">
|
|
<p className="text-sm text-gray-600">
|
|
<strong>Admin Notes:</strong> {template.admin_notes}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex items-center space-x-2">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => handleApprove(template.id)}
|
|
className="text-green-600 hover:text-green-700 hover:bg-green-50"
|
|
>
|
|
<CheckCircle className="h-4 w-4 mr-1" />
|
|
Approve
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => {
|
|
setRejectItem({ id: template.id, name: template.title, type: 'template' })
|
|
setShowRejectDialog(true)
|
|
}}
|
|
className="text-red-600 hover:text-red-700 hover:bg-red-50"
|
|
>
|
|
<XCircle className="h-4 w-4 mr-1" />
|
|
Reject
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => {
|
|
setSelectedTemplateForFeatures(template)
|
|
setShowFeaturesManager(true)
|
|
}}
|
|
className="text-green-600 hover:text-green-700 hover:bg-green-50"
|
|
>
|
|
<Settings className="h-4 w-4 mr-1" />
|
|
Features
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => {
|
|
setSelectedTemplate(template)
|
|
setShowTemplateEditDialog(true)
|
|
}}
|
|
className="text-blue-600 hover:text-blue-700 hover:bg-blue-50"
|
|
>
|
|
<Edit className="h-4 w-4 mr-1" />
|
|
Edit
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))
|
|
)}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="create" className="space-y-4">
|
|
<Card className="bg-gray-900 border-gray-800">
|
|
<CardHeader>
|
|
<CardTitle className="text-white">Create New Template</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<form onSubmit={handleCreateTemplate} className="space-y-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="title" className="text-white">Template Title</Label>
|
|
<Input
|
|
id="title"
|
|
value={newTemplate.title}
|
|
onChange={(e) => setNewTemplate(prev => ({ ...prev, title: e.target.value }))}
|
|
className="bg-white/5 border-white/10 text-white"
|
|
placeholder="Enter template title"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="category" className="text-white">Category</Label>
|
|
<Select value={newTemplate.category} onValueChange={(value) => setNewTemplate(prev => ({ ...prev, category: value }))}>
|
|
<SelectTrigger className="bg-white/5 border-white/10 text-white">
|
|
<SelectValue placeholder="Select category" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="marketing">Marketing</SelectItem>
|
|
<SelectItem value="software">Software</SelectItem>
|
|
<SelectItem value="seo">SEO</SelectItem>
|
|
<SelectItem value="ecommerce">E-commerce</SelectItem>
|
|
<SelectItem value="portfolio">Portfolio</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="description" className="text-white">Description</Label>
|
|
<Textarea
|
|
id="description"
|
|
value={newTemplate.description}
|
|
onChange={(e) => setNewTemplate(prev => ({ ...prev, description: e.target.value }))}
|
|
className="bg-white/5 border-white/10 text-white"
|
|
placeholder="Enter template description"
|
|
rows={3}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="type" className="text-white">Type</Label>
|
|
<Select value={newTemplate.type} onValueChange={(value) => setNewTemplate(prev => ({ ...prev, type: value }))}>
|
|
<SelectTrigger className="bg-white/5 border-white/10 text-white">
|
|
<SelectValue placeholder="Select template type" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="Business Website">Business Website</SelectItem>
|
|
<SelectItem value="E-commerce Store">E-commerce Store</SelectItem>
|
|
<SelectItem value="Landing Page">Landing Page</SelectItem>
|
|
<SelectItem value="Blog Platform">Blog Platform</SelectItem>
|
|
<SelectItem value="Portfolio Site">Portfolio Site</SelectItem>
|
|
<SelectItem value="SaaS Platform">SaaS Platform</SelectItem>
|
|
<SelectItem value="Mobile App">Mobile App</SelectItem>
|
|
<SelectItem value="Web Application">Web Application</SelectItem>
|
|
<SelectItem value="Marketing Site">Marketing Site</SelectItem>
|
|
<SelectItem value="Corporate Website">Corporate Website</SelectItem>
|
|
<SelectItem value="Educational Platform">Educational Platform</SelectItem>
|
|
<SelectItem value="Social Media App">Social Media App</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="complexity" className="text-white">Complexity</Label>
|
|
<Select value={newTemplate.complexity} onValueChange={(value) => setNewTemplate(prev => ({ ...prev, complexity: value }))}>
|
|
<SelectTrigger className="bg-white/5 border-white/10 text-white">
|
|
<SelectValue placeholder="Select complexity" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="low">Low</SelectItem>
|
|
<SelectItem value="medium">Medium</SelectItem>
|
|
<SelectItem value="high">High</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="icon" className="text-white">Icon</Label>
|
|
<Input
|
|
id="icon"
|
|
value={newTemplate.icon}
|
|
onChange={(e) => setNewTemplate(prev => ({ ...prev, icon: e.target.value }))}
|
|
className="bg-white/5 border-white/10 text-white"
|
|
placeholder="Icon name or URL"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="gradient" className="text-white">Gradient</Label>
|
|
<Input
|
|
id="gradient"
|
|
value={newTemplate.gradient}
|
|
onChange={(e) => setNewTemplate(prev => ({ ...prev, gradient: e.target.value }))}
|
|
className="bg-white/5 border-white/10 text-white"
|
|
placeholder="CSS gradient value"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-end space-x-2">
|
|
<Button type="button" variant="outline" onClick={() => setActiveTab("manage")}>
|
|
Cancel
|
|
</Button>
|
|
<Button type="submit" className="bg-orange-500 text-black hover:bg-orange-600">
|
|
<Save className="h-4 w-4 mr-2" />
|
|
Create Template
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
</Tabs>
|
|
|
|
{/* Template Edit Dialog */}
|
|
{selectedTemplate && (
|
|
<TemplateEditDialog
|
|
template={selectedTemplate}
|
|
open={showTemplateEditDialog}
|
|
onOpenChange={setShowTemplateEditDialog}
|
|
onUpdate={handleTemplateUpdate}
|
|
/>
|
|
)}
|
|
|
|
{/* Reject Dialog */}
|
|
{rejectItem && (
|
|
<RejectDialog
|
|
open={showRejectDialog}
|
|
onOpenChange={setShowRejectDialog}
|
|
onReject={handleReject}
|
|
title="Reject Template"
|
|
itemName={rejectItem.name}
|
|
itemType={rejectItem.type}
|
|
/>
|
|
)}
|
|
|
|
{/* Template Features Manager */}
|
|
{selectedTemplateForFeatures && (
|
|
<TemplateFeaturesManager
|
|
template={selectedTemplateForFeatures}
|
|
open={showFeaturesManager}
|
|
onOpenChange={setShowFeaturesManager}
|
|
/>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|