codenuk_frontend_mine/src/components/admin/admin-templates-manager.tsx
2025-09-09 11:23:58 +05:30

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>
)
}