683 lines
24 KiB
TypeScript
683 lines
24 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 { Input } from '@/components/ui/input'
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
|
|
import {
|
|
Search,
|
|
Settings,
|
|
RefreshCw,
|
|
AlertCircle,
|
|
Zap,
|
|
Globe,
|
|
BarChart3,
|
|
Code,
|
|
ShoppingCart,
|
|
Briefcase,
|
|
GraduationCap,
|
|
Plus,
|
|
Save,
|
|
X
|
|
} from 'lucide-react'
|
|
import { adminApi, formatDate, getComplexityColor } from '@/lib/api/admin'
|
|
import { BACKEND_URL } from '@/config/backend'
|
|
import { AdminTemplate, AdminStats } from '@/types/admin.types'
|
|
import { AdminFeatureSelection } from './admin-feature-selection'
|
|
|
|
interface AdminTemplatesListProps {
|
|
onTemplateSelect?: (template: AdminTemplate) => void
|
|
}
|
|
|
|
export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps) {
|
|
const [templates, setTemplates] = useState<AdminTemplate[]>([])
|
|
const [stats, setStats] = useState<AdminStats | null>(null)
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
const [searchQuery, setSearchQuery] = useState('')
|
|
const [categoryFilter, setCategoryFilter] = useState<string>('all')
|
|
const [showFeaturesManager, setShowFeaturesManager] = useState(false)
|
|
const [selectedTemplate, setSelectedTemplate] = useState<any | null>(null)
|
|
const [showFeatureSelection, setShowFeatureSelection] = useState(false)
|
|
const [showCreateModal, setShowCreateModal] = useState(false)
|
|
|
|
// Create template form state
|
|
const [newTemplate, setNewTemplate] = useState({
|
|
type: '',
|
|
title: '',
|
|
description: '',
|
|
category: '',
|
|
icon: '',
|
|
gradient: '',
|
|
border: '',
|
|
text: '',
|
|
subtext: ''
|
|
})
|
|
|
|
const categories = [
|
|
"Food Delivery",
|
|
"E-commerce",
|
|
"SaaS Platform",
|
|
"Mobile App",
|
|
"Dashboard",
|
|
"CRM System",
|
|
"Learning Platform",
|
|
"Healthcare",
|
|
"Real Estate",
|
|
"Travel",
|
|
"Entertainment",
|
|
"Finance",
|
|
"Social Media",
|
|
"Marketplace",
|
|
"Other"
|
|
]
|
|
|
|
// Load templates and stats
|
|
const loadData = async () => {
|
|
try {
|
|
setLoading(true)
|
|
setError(null)
|
|
|
|
console.log('Loading admin templates data...')
|
|
|
|
const [templatesResponse, statsResponse] = await Promise.all([
|
|
adminApi.getAdminTemplates(50, 0, categoryFilter, searchQuery),
|
|
adminApi.getAdminTemplateStats()
|
|
])
|
|
|
|
console.log('Admin templates response:', templatesResponse)
|
|
console.log('Admin template stats response:', statsResponse)
|
|
|
|
setTemplates(templatesResponse || [])
|
|
setStats(statsResponse)
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Failed to load admin templates')
|
|
console.error('Error loading admin templates:', err)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
loadData()
|
|
}, [categoryFilter, searchQuery])
|
|
|
|
// Handle template selection for features
|
|
const handleManageFeatures = (template: AdminTemplate) => {
|
|
// Convert AdminTemplate to Template format for feature management
|
|
const templateForFeatures = {
|
|
id: template.id,
|
|
title: template.title,
|
|
description: template.description,
|
|
type: template.type,
|
|
category: template.category,
|
|
icon: template.icon,
|
|
gradient: template.gradient,
|
|
border: template.border,
|
|
text: template.text,
|
|
subtext: template.subtext
|
|
}
|
|
setSelectedTemplate(templateForFeatures)
|
|
setShowFeatureSelection(true)
|
|
}
|
|
|
|
// Handle create template form submission
|
|
const handleCreateTemplate = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
try {
|
|
setLoading(true)
|
|
|
|
// Create template payload
|
|
const templateData = {
|
|
...newTemplate,
|
|
created_at: new Date().toISOString(),
|
|
updated_at: new Date().toISOString()
|
|
}
|
|
|
|
// Call API to create template (you'll need to implement this endpoint)
|
|
const response = await fetch(`${BACKEND_URL}/api/templates`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(templateData)
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to create template')
|
|
}
|
|
|
|
// Reset form and hide it
|
|
setNewTemplate({
|
|
type: '',
|
|
title: '',
|
|
description: '',
|
|
category: '',
|
|
icon: '',
|
|
gradient: '',
|
|
border: '',
|
|
text: '',
|
|
subtext: ''
|
|
})
|
|
setShowCreateModal(false)
|
|
|
|
// Reload data to show new template
|
|
await loadData()
|
|
|
|
} catch (error) {
|
|
console.error('Error creating template:', error)
|
|
setError(error instanceof Error ? error.message : 'Failed to create template')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
// Get category icon
|
|
const getCategoryIcon = (category: string) => {
|
|
switch (category.toLowerCase()) {
|
|
case 'food delivery':
|
|
return ShoppingCart
|
|
case 'e-commerce':
|
|
case 'ecommerce':
|
|
return ShoppingCart
|
|
case 'saas platform':
|
|
return Code
|
|
case 'mobile app':
|
|
return Code
|
|
case 'dashboard':
|
|
return BarChart3
|
|
case 'crm system':
|
|
return Briefcase
|
|
case 'learning platform':
|
|
return GraduationCap
|
|
case 'healthcare':
|
|
return AlertCircle
|
|
case 'real estate':
|
|
return Globe
|
|
case 'travel':
|
|
return Globe
|
|
case 'entertainment':
|
|
return Zap
|
|
case 'finance':
|
|
return BarChart3
|
|
case 'social media':
|
|
return Zap
|
|
case 'marketplace':
|
|
return ShoppingCart
|
|
default:
|
|
return Globe
|
|
}
|
|
}
|
|
|
|
// Get category stats for filters
|
|
const getCategoryStats = () => {
|
|
// Start with "All Templates"
|
|
const categoryStats = [
|
|
{ id: 'all', name: 'All Templates', count: templates.length, icon: Globe }
|
|
]
|
|
|
|
// Add categories based on your actual categories array
|
|
categories.forEach(category => {
|
|
if (category !== 'Other') { // Skip 'Other' as it will be handled separately
|
|
categoryStats.push({
|
|
id: category.toLowerCase().replace(/\s+/g, '-'),
|
|
name: category,
|
|
count: 0,
|
|
icon: getCategoryIcon(category)
|
|
})
|
|
}
|
|
})
|
|
|
|
// Add 'Other' category at the end
|
|
categoryStats.push({
|
|
id: 'other',
|
|
name: 'Other',
|
|
count: 0,
|
|
icon: Globe
|
|
})
|
|
|
|
// Count templates by category
|
|
templates.forEach(template => {
|
|
const templateCategory = template.category
|
|
if (templateCategory) {
|
|
const categoryItem = categoryStats.find(cat =>
|
|
cat.name.toLowerCase() === templateCategory.toLowerCase() ||
|
|
cat.id === templateCategory.toLowerCase().replace(/\s+/g, '-')
|
|
)
|
|
if (categoryItem) {
|
|
categoryItem.count++
|
|
} else {
|
|
// If category doesn't match any predefined category, add to 'Other'
|
|
const otherCategory = categoryStats.find(cat => cat.id === 'other')
|
|
if (otherCategory) {
|
|
otherCategory.count++
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
return categoryStats
|
|
}
|
|
|
|
// Filter templates based on search and category
|
|
const filteredTemplates = templates.filter(template => {
|
|
const matchesSearch = !searchQuery ||
|
|
template.title?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
template.description?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
template.type?.toLowerCase().includes(searchQuery.toLowerCase())
|
|
|
|
const matchesCategory = categoryFilter === 'all' ||
|
|
template.category?.toLowerCase() === categoryFilter.toLowerCase() ||
|
|
template.category?.toLowerCase().replace(/\s+/g, '-') === categoryFilter ||
|
|
(categoryFilter === 'other' && !categories.some(cat =>
|
|
cat.toLowerCase() === template.category?.toLowerCase()
|
|
))
|
|
|
|
return matchesSearch && matchesCategory
|
|
})
|
|
|
|
const TemplateCard = ({ template }: { template: AdminTemplate }) => (
|
|
<Card className="group hover:shadow-md transition-all bg-gray-900 border-gray-800">
|
|
<CardHeader className="pb-3">
|
|
<div className="flex items-start justify-between">
|
|
<div className="space-y-2 flex-1">
|
|
<CardTitle className="text-lg text-white group-hover:text-orange-400 transition-colors">
|
|
{template.title}
|
|
</CardTitle>
|
|
<div className="flex items-center space-x-2">
|
|
<Badge variant="outline" className="text-xs bg-white/5 border-white/10 text-white/80">
|
|
{template.type}
|
|
</Badge>
|
|
<Badge variant="outline" className="text-xs bg-white/5 border-white/10 text-white/70">
|
|
{template.category}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => handleManageFeatures(template)}
|
|
className="text-green-400 hover:text-green-300 border-green-400/30 hover:border-green-300/50"
|
|
>
|
|
<Settings className="h-4 w-4 mr-1" />
|
|
Features
|
|
</Button>
|
|
</div>
|
|
{template.description && (
|
|
<p className="text-white/70 text-sm">{template.description}</p>
|
|
)}
|
|
</CardHeader>
|
|
|
|
<CardContent className="space-y-3 text-white/80">
|
|
<div className="flex items-center justify-between text-sm">
|
|
<div className="flex items-center space-x-1">
|
|
<Zap className="h-4 w-4 text-orange-400" />
|
|
<span>{(template as any).feature_count || 0} features</span>
|
|
</div>
|
|
<div className="text-white/60">
|
|
{template.created_at && formatDate(template.created_at)}
|
|
</div>
|
|
</div>
|
|
|
|
{template.gradient && (
|
|
<div className="text-xs text-white/60">
|
|
<span className="font-medium">Style:</span> {template.gradient}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
|
|
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 text-orange-400" />
|
|
<span className="text-white">Loading admin templates...</span>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="flex items-center justify-center min-h-[400px]">
|
|
<Card className="w-full max-w-md bg-gray-900 border-gray-800">
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-center space-x-2 text-red-400">
|
|
<AlertCircle className="h-6 w-6" />
|
|
<span>Error loading admin templates</span>
|
|
</div>
|
|
<p className="mt-2 text-sm text-gray-400">{error}</p>
|
|
<Button onClick={loadData} className="mt-4 bg-orange-500 text-black hover:bg-orange-600">
|
|
<RefreshCw className="h-4 w-4 mr-2" />
|
|
Retry
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Show feature selection view if a template is selected
|
|
if (showFeatureSelection && selectedTemplate) {
|
|
return (
|
|
<AdminFeatureSelection
|
|
template={selectedTemplate}
|
|
onBack={() => {
|
|
setShowFeatureSelection(false)
|
|
setSelectedTemplate(null)
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-white">Admin Templates</h1>
|
|
<p className="text-white/70">Manage features for your templates</p>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<Button
|
|
onClick={() => setShowCreateModal(true)}
|
|
className="bg-green-500 text-black hover:bg-green-600"
|
|
>
|
|
<Plus className="h-4 w-4 mr-2" />
|
|
Create Template
|
|
</Button>
|
|
<Button onClick={loadData} className="bg-orange-500 text-black hover:bg-orange-600">
|
|
<RefreshCw className="h-4 w-4 mr-2" />
|
|
Refresh
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stats Cards */}
|
|
{stats && (
|
|
<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>
|
|
<Globe className="h-4 w-4 text-white/60" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-white">{(stats as any).total_templates || templates.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">Categories</CardTitle>
|
|
<BarChart3 className="h-4 w-4 text-white/60" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-white">{(stats as any).total_categories || categories.length - 1}</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">Avg Features</CardTitle>
|
|
<Zap className="h-4 w-4 text-orange-400" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-white">
|
|
{Math.round((stats as any).avg_features_per_template || 0)}
|
|
</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">With Features</CardTitle>
|
|
<Settings className="h-4 w-4 text-green-400" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-white">{(stats as any).templates_with_features || 0}</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)}
|
|
|
|
{/* 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 bg-white/5 border-white/10 text-white"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<Select value={categoryFilter} onValueChange={setCategoryFilter}>
|
|
<SelectTrigger className="w-48 bg-white/5 border-white/10 text-white">
|
|
<SelectValue placeholder="Filter by category" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{getCategoryStats().map((category) => (
|
|
<SelectItem key={category.id} value={category.id}>
|
|
<div className="flex items-center space-x-2">
|
|
<category.icon className="h-4 w-4" />
|
|
<span>{category.name} ({category.count})</span>
|
|
</div>
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* Category Filters removed; using only dropdown above */}
|
|
|
|
{/* Create Template Modal */}
|
|
<Dialog open={showCreateModal} onOpenChange={setShowCreateModal}>
|
|
<DialogContent className="bg-gray-900 border-gray-800 text-white max-w-2xl max-h-[90vh] overflow-y-auto">
|
|
<DialogHeader>
|
|
<DialogTitle className="text-white">Create New Template</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
<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 className="text-sm font-medium text-white">Template Type *</label>
|
|
<Input
|
|
placeholder="e.g., multi_restaurant_food_delivery"
|
|
value={newTemplate.type}
|
|
onChange={(e) => setNewTemplate(prev => ({ ...prev, type: e.target.value }))}
|
|
className="bg-white/5 border-white/10 text-white placeholder:text-white/40"
|
|
required
|
|
/>
|
|
<p className="text-xs text-white/60">Unique identifier for the template</p>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-white">Title *</label>
|
|
<Input
|
|
placeholder="e.g., Multi-Restaurant Food Delivery App"
|
|
value={newTemplate.title}
|
|
onChange={(e) => setNewTemplate(prev => ({ ...prev, title: e.target.value }))}
|
|
className="bg-white/5 border-white/10 text-white placeholder:text-white/40"
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-white">Description *</label>
|
|
<textarea
|
|
placeholder="Describe your template and its key features..."
|
|
value={newTemplate.description}
|
|
onChange={(e) => setNewTemplate(prev => ({ ...prev, description: e.target.value }))}
|
|
className="w-full px-3 py-2 bg-white/5 border border-white/10 text-white placeholder:text-white/40 rounded-md min-h-[100px]"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium 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 a category" />
|
|
</SelectTrigger>
|
|
<SelectContent className="bg-gray-900 border-white/10">
|
|
{categories.map((category, index) => (
|
|
<SelectItem key={`category-${index}`} value={category} className="text-white">
|
|
{category}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-white">Icon (optional)</label>
|
|
<Input
|
|
placeholder="e.g., restaurant, shopping-cart, users"
|
|
value={newTemplate.icon}
|
|
onChange={(e) => setNewTemplate(prev => ({ ...prev, icon: e.target.value }))}
|
|
className="bg-white/5 border-white/10 text-white placeholder:text-white/40"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-white">Gradient (optional)</label>
|
|
<Input
|
|
placeholder="e.g., from-orange-400 to-red-500"
|
|
value={newTemplate.gradient}
|
|
onChange={(e) => setNewTemplate(prev => ({ ...prev, gradient: e.target.value }))}
|
|
className="bg-white/5 border-white/10 text-white placeholder:text-white/40"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-white">Border (optional)</label>
|
|
<Input
|
|
placeholder="e.g., border-orange-500"
|
|
value={newTemplate.border}
|
|
onChange={(e) => setNewTemplate(prev => ({ ...prev, border: e.target.value }))}
|
|
className="bg-white/5 border-white/10 text-white placeholder:text-white/40"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-white">Text Color (optional)</label>
|
|
<Input
|
|
placeholder="e.g., text-orange-500"
|
|
value={newTemplate.text}
|
|
onChange={(e) => setNewTemplate(prev => ({ ...prev, text: e.target.value }))}
|
|
className="bg-white/5 border-white/10 text-white placeholder:text-white/40"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium text-white">Subtext (optional)</label>
|
|
<Input
|
|
placeholder="e.g., Perfect for food delivery startups"
|
|
value={newTemplate.subtext}
|
|
onChange={(e) => setNewTemplate(prev => ({ ...prev, subtext: e.target.value }))}
|
|
className="bg-white/5 border-white/10 text-white placeholder:text-white/40"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex justify-end space-x-3 pt-4">
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => setShowCreateModal(false)}
|
|
className="border-white/20 text-white hover:bg-white/10 cursor-pointer"
|
|
disabled={loading}
|
|
>
|
|
<X className="mr-2 h-4 w-4" />
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
className="bg-orange-500 hover:bg-orange-400 text-black font-semibold cursor-pointer"
|
|
disabled={loading}
|
|
>
|
|
<Save className="mr-2 h-4 w-4" />
|
|
{loading ? "Creating..." : "Create Template"}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Feature Selection View */}
|
|
{showFeatureSelection && selectedTemplate && (
|
|
<AdminFeatureSelection
|
|
template={selectedTemplate}
|
|
onBack={() => {
|
|
setShowFeatureSelection(false)
|
|
setSelectedTemplate(null)
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{/* Features Manager Modal */}
|
|
{showFeaturesManager && selectedTemplate && (
|
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50">
|
|
<div className="bg-gray-900 rounded-lg max-w-6xl w-full max-h-[90vh] overflow-y-auto">
|
|
<div className="p-6">
|
|
<div className="flex items-center justify-between mb-6">
|
|
<h2 className="text-2xl font-bold text-white">Manage Features - {selectedTemplate.title}</h2>
|
|
<Button
|
|
onClick={() => {
|
|
setShowFeaturesManager(false)
|
|
setSelectedTemplate(null)
|
|
}}
|
|
variant="outline"
|
|
className="text-white border-gray-600 hover:bg-gray-800"
|
|
>
|
|
Close
|
|
</Button>
|
|
</div>
|
|
<AdminFeatureSelection
|
|
template={selectedTemplate}
|
|
onBack={() => {
|
|
setShowFeaturesManager(false)
|
|
setSelectedTemplate(null)
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Templates Grid */}
|
|
<div className="space-y-4">
|
|
{filteredTemplates.length === 0 ? (
|
|
<Card className="bg-gray-900 border-gray-800">
|
|
<CardContent className="pt-6">
|
|
<div className="text-center text-gray-400">
|
|
<Globe className="h-12 w-12 mx-auto mb-4 text-gray-600" />
|
|
<p>No templates found</p>
|
|
<p className="text-sm">Try adjusting your search or filters.</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
) : (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{filteredTemplates.map((template) => (
|
|
<TemplateCard key={template.id} template={template} />
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
</div>
|
|
)
|
|
}
|