Merge branch 'live-working'
This commit is contained in:
commit
c007095a05
221
Jenkinsfile
vendored
Normal file
221
Jenkinsfile
vendored
Normal file
@ -0,0 +1,221 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
environment {
|
||||
SSH_CREDENTIALS = 'cloudtopiaa'
|
||||
REMOTE_SERVER = 'ubuntu@160.187.166.39'
|
||||
REMOTE_WORKSPACE = '/home/ubuntu'
|
||||
PROJECT_NAME = 'codenuk-frontend-live'
|
||||
DEPLOY_PATH = '/var/www/html/codenuk-frontend-live'
|
||||
GIT_CREDENTIALS = 'git-cred'
|
||||
REPO_URL = 'https://git.tech4biz.wiki/Tech4Biz-Services/codenuk-frontend-live.git'
|
||||
NPM_PATH = '/home/ubuntu/.nvm/versions/node/v22.18.0/bin/npm'
|
||||
NODE_PATH = '/home/ubuntu/.nvm/versions/node/v22.18.0/bin/node'
|
||||
EMAIL_RECIPIENT = 'jassim.mohammed@tech4biz.io, chandini.pachigunta@tech4biz.org'
|
||||
}
|
||||
|
||||
options {
|
||||
timeout(time: 30, unit: 'MINUTES')
|
||||
retry(2)
|
||||
timestamps()
|
||||
buildDiscarder(logRotator(numToKeepStr: '10'))
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Preparation') {
|
||||
steps {
|
||||
script {
|
||||
echo "Starting ${PROJECT_NAME} deployment pipeline"
|
||||
echo "Server: ${REMOTE_SERVER}"
|
||||
echo "Deploy Path: ${DEPLOY_PATH}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Git Operations on Remote Server') {
|
||||
steps {
|
||||
script {
|
||||
sshagent(credentials: [SSH_CREDENTIALS]) {
|
||||
withCredentials([usernamePassword(credentialsId: GIT_CREDENTIALS, usernameVariable: 'GIT_USER', passwordVariable: 'GIT_PASS')]) {
|
||||
sh """
|
||||
ssh -o StrictHostKeyChecking=no ${REMOTE_SERVER} '
|
||||
set -e
|
||||
|
||||
echo "Checking Git repo..."
|
||||
if [ -d "${DEPLOY_PATH}/.git" ]; then
|
||||
echo "Pulling latest code..."
|
||||
cd ${DEPLOY_PATH}
|
||||
|
||||
# Fix ownership issues
|
||||
sudo chown -R ubuntu:ubuntu ${DEPLOY_PATH}
|
||||
git config --global --add safe.directory ${DEPLOY_PATH}
|
||||
|
||||
git reset --hard
|
||||
git clean -fd
|
||||
git config pull.rebase false
|
||||
git pull https://${GIT_USER}:${GIT_PASS}@git.tech4biz.wiki/Tech4Biz-Services/codenuk-frontend-live.git main
|
||||
else
|
||||
echo "Cloning fresh repo..."
|
||||
sudo rm -rf ${DEPLOY_PATH}
|
||||
sudo mkdir -p /var/www/html
|
||||
sudo git clone https://${GIT_USER}:${GIT_PASS}@git.tech4biz.wiki/Tech4Biz-Services/codenuk-frontend-live.git ${DEPLOY_PATH}
|
||||
sudo chown -R ubuntu:ubuntu ${DEPLOY_PATH}
|
||||
git config --global --add safe.directory ${DEPLOY_PATH}
|
||||
fi
|
||||
|
||||
cd ${DEPLOY_PATH}
|
||||
echo "Commit: \$(git rev-parse HEAD)"
|
||||
'
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Verify Node.js Environment') {
|
||||
steps {
|
||||
script {
|
||||
sshagent(credentials: [SSH_CREDENTIALS]) {
|
||||
sh """
|
||||
ssh -o StrictHostKeyChecking=no ${REMOTE_SERVER} '
|
||||
set -e
|
||||
export PATH="/home/ubuntu/.nvm/versions/node/v22.18.0/bin:\$PATH"
|
||||
cd ${DEPLOY_PATH}
|
||||
|
||||
echo "Node: \$(${NODE_PATH} -v)"
|
||||
echo "NPM: \$(${NPM_PATH} -v)"
|
||||
${NPM_PATH} cache clean --force
|
||||
'
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Install Dependencies') {
|
||||
steps {
|
||||
sshagent(credentials: [SSH_CREDENTIALS]) {
|
||||
sh """
|
||||
ssh -o StrictHostKeyChecking=no ${REMOTE_SERVER} '
|
||||
set -e
|
||||
export PATH="/home/ubuntu/.nvm/versions/node/v22.18.0/bin:\$PATH"
|
||||
cd ${DEPLOY_PATH}
|
||||
echo "Installing dependencies..."
|
||||
rm -rf node_modules package-lock.json
|
||||
${NPM_PATH} install --force
|
||||
'
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build Application') {
|
||||
steps {
|
||||
sshagent(credentials: [SSH_CREDENTIALS]) {
|
||||
sh """
|
||||
ssh -o StrictHostKeyChecking=no ${REMOTE_SERVER} '
|
||||
set -e
|
||||
export PATH="/home/ubuntu/.nvm/versions/node/v22.18.0/bin:\$PATH"
|
||||
cd ${DEPLOY_PATH}
|
||||
echo "Building..."
|
||||
${NPM_PATH} run build
|
||||
echo "Build directory contents:"
|
||||
ls -la dist || ls -la build || echo "No build/dist directory found"
|
||||
'
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Deploy Static Files') {
|
||||
steps {
|
||||
sshagent(credentials: [SSH_CREDENTIALS]) {
|
||||
sh """
|
||||
ssh -o StrictHostKeyChecking=no ${REMOTE_SERVER} '
|
||||
set -e
|
||||
cd ${DEPLOY_PATH}
|
||||
echo "Setting up static files for nginx..."
|
||||
|
||||
# Ensure proper ownership and permissions
|
||||
sudo chown -R www-data:www-data ${DEPLOY_PATH}
|
||||
sudo find ${DEPLOY_PATH} -type d -exec chmod 755 {} \\;
|
||||
sudo find ${DEPLOY_PATH} -type f -exec chmod 644 {} \\;
|
||||
|
||||
echo "Static files prepared for nginx serving"
|
||||
'
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Restart Nginx') {
|
||||
steps {
|
||||
sshagent(credentials: [SSH_CREDENTIALS]) {
|
||||
sh """
|
||||
ssh -o StrictHostKeyChecking=no ${REMOTE_SERVER} '
|
||||
set -e
|
||||
echo "Testing nginx configuration..."
|
||||
sudo nginx -t
|
||||
|
||||
echo "Restarting nginx..."
|
||||
sudo systemctl restart nginx
|
||||
|
||||
echo "Checking nginx status..."
|
||||
sudo systemctl status nginx --no-pager -l
|
||||
'
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Health Check') {
|
||||
steps {
|
||||
sshagent(credentials: [SSH_CREDENTIALS]) {
|
||||
sh """
|
||||
ssh -o StrictHostKeyChecking=no ${REMOTE_SERVER} '
|
||||
set -e
|
||||
echo "Verifying deployment..."
|
||||
echo "Directory structure:"
|
||||
ls -la ${DEPLOY_PATH}
|
||||
|
||||
echo "Build files:"
|
||||
ls -la ${DEPLOY_PATH}/dist || ls -la ${DEPLOY_PATH}/build || echo "No build directory found"
|
||||
|
||||
echo "Nginx status:"
|
||||
sudo systemctl is-active nginx
|
||||
'
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
cleanWs()
|
||||
}
|
||||
success {
|
||||
mail to: "${EMAIL_RECIPIENT}",
|
||||
subject: "✅ Jenkins - ${PROJECT_NAME} Deployment Successful",
|
||||
body: """The deployment of '${PROJECT_NAME}' to ${REMOTE_SERVER} was successful.
|
||||
|
||||
Build Number: ${BUILD_NUMBER}
|
||||
URL: ${BUILD_URL}
|
||||
Time: ${new Date()}
|
||||
|
||||
The static files have been deployed and nginx has been restarted.
|
||||
"""
|
||||
}
|
||||
failure {
|
||||
mail to: "${EMAIL_RECIPIENT}",
|
||||
subject: "❌ Jenkins - ${PROJECT_NAME} Deployment Failed",
|
||||
body: """Deployment failed. Please review logs at:
|
||||
|
||||
${BUILD_URL}console
|
||||
|
||||
Time: ${new Date()}
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -137,7 +137,7 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddAIAnalyzed = async (payload: { name: string; description: string; complexity: 'low' | 'medium' | 'high' }) => {
|
||||
const handleAddAIAnalyzed = async (payload: { name: string; description: string; complexity: 'low' | 'medium' | 'high'; logic_rules?: string[] }) => {
|
||||
// Check if template ID is valid UUID format
|
||||
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
||||
if (!uuidRegex.test(template.id)) {
|
||||
@ -153,6 +153,7 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio
|
||||
complexity: payload.complexity,
|
||||
is_default: true,
|
||||
created_by_user: true,
|
||||
logic_rules: payload.logic_rules,
|
||||
})
|
||||
await load()
|
||||
} catch (error) {
|
||||
|
||||
176
src/components/admin/admin-layout.tsx
Normal file
176
src/components/admin/admin-layout.tsx
Normal file
@ -0,0 +1,176 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from 'react'
|
||||
import { AdminSidebar } from './admin-sidebar'
|
||||
import { AdminTemplatesManager } from './admin-templates-manager'
|
||||
import { AdminDashboard } from './admin-dashboard'
|
||||
import { TemplateFeaturesManager } from './template-features-manager'
|
||||
import { AdminNotificationsPanel } from './admin-notifications-panel'
|
||||
|
||||
type AdminView =
|
||||
| 'dashboard'
|
||||
| 'templates'
|
||||
| 'features'
|
||||
| 'ai-features'
|
||||
| 'users'
|
||||
| 'notifications'
|
||||
| 'analytics'
|
||||
| 'settings'
|
||||
| 'create-template'
|
||||
| 'add-feature'
|
||||
| 'ai-analyze'
|
||||
|
||||
export function AdminLayout() {
|
||||
const [currentView, setCurrentView] = useState<AdminView>('dashboard')
|
||||
const [featuresDialogOpen, setFeaturesDialogOpen] = useState(false)
|
||||
|
||||
const renderMainContent = () => {
|
||||
switch (currentView) {
|
||||
case 'dashboard':
|
||||
return <AdminDashboard />
|
||||
|
||||
case 'templates':
|
||||
case 'create-template':
|
||||
return <AdminTemplatesManager />
|
||||
|
||||
case 'features':
|
||||
case 'add-feature':
|
||||
const defaultTemplate = {
|
||||
id: "default",
|
||||
type: "default",
|
||||
title: "Default Template",
|
||||
description: "Default template for feature management",
|
||||
complexity: "medium" as const,
|
||||
approved: true,
|
||||
usage_count: 0,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
is_custom: false,
|
||||
status: "approved" as const
|
||||
}
|
||||
return (
|
||||
<TemplateFeaturesManager
|
||||
template={defaultTemplate}
|
||||
open={true}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setCurrentView('dashboard')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
case 'ai-features':
|
||||
case 'ai-analyze':
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto space-y-6">
|
||||
<div className="text-center space-y-4">
|
||||
<h1 className="text-4xl font-bold text-white">AI Feature Analysis</h1>
|
||||
<p className="text-xl text-white/60">AI-powered feature creation and optimization</p>
|
||||
</div>
|
||||
<div className="bg-white/5 border border-white/10 rounded-xl p-8 text-center">
|
||||
<div className="text-6xl mb-4">🤖</div>
|
||||
<h3 className="text-2xl font-bold text-white mb-2">AI Feature Creator</h3>
|
||||
<p className="text-white/60 mb-6">Analyze project requirements and generate optimized features</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="bg-white/5 rounded-lg p-4">
|
||||
<h4 className="font-semibold text-white mb-2">Smart Analysis</h4>
|
||||
<p className="text-white/60 text-sm">AI analyzes your project type and suggests relevant features</p>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-lg p-4">
|
||||
<h4 className="font-semibold text-white mb-2">Essential Features</h4>
|
||||
<p className="text-white/60 text-sm">Creates essential features that integrate with your template</p>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-lg p-4">
|
||||
<h4 className="font-semibold text-white mb-2">Auto-Optimization</h4>
|
||||
<p className="text-white/60 text-sm">Optimizes complexity and requirements automatically</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
case 'users':
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto space-y-6">
|
||||
<div className="text-center space-y-4">
|
||||
<h1 className="text-4xl font-bold text-white">User Management</h1>
|
||||
<p className="text-xl text-white/60">Manage user accounts and permissions</p>
|
||||
</div>
|
||||
<div className="bg-white/5 border border-white/10 rounded-xl p-8 text-center">
|
||||
<div className="text-6xl mb-4">👥</div>
|
||||
<h3 className="text-2xl font-bold text-white mb-2">User Administration</h3>
|
||||
<p className="text-white/60">User management features coming soon</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
case 'notifications':
|
||||
return <AdminNotificationsPanel open={true} onOpenChange={() => {}} />
|
||||
|
||||
case 'analytics':
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto space-y-6">
|
||||
<div className="text-center space-y-4">
|
||||
<h1 className="text-4xl font-bold text-white">Analytics Dashboard</h1>
|
||||
<p className="text-xl text-white/60">Usage statistics and performance metrics</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div className="bg-white/5 border border-white/10 rounded-xl p-6">
|
||||
<h3 className="text-lg font-semibold text-white mb-2">Total Templates</h3>
|
||||
<div className="text-3xl font-bold text-orange-400">24</div>
|
||||
<p className="text-white/60 text-sm">+3 this week</p>
|
||||
</div>
|
||||
<div className="bg-white/5 border border-white/10 rounded-xl p-6">
|
||||
<h3 className="text-lg font-semibold text-white mb-2">Active Users</h3>
|
||||
<div className="text-3xl font-bold text-emerald-400">156</div>
|
||||
<p className="text-white/60 text-sm">+12 this week</p>
|
||||
</div>
|
||||
<div className="bg-white/5 border border-white/10 rounded-xl p-6">
|
||||
<h3 className="text-lg font-semibold text-white mb-2">Features Created</h3>
|
||||
<div className="text-3xl font-bold text-blue-400">89</div>
|
||||
<p className="text-white/60 text-sm">+7 this week</p>
|
||||
</div>
|
||||
<div className="bg-white/5 border border-white/10 rounded-xl p-6">
|
||||
<h3 className="text-lg font-semibold text-white mb-2">AI Analyses</h3>
|
||||
<div className="text-3xl font-bold text-purple-400">42</div>
|
||||
<p className="text-white/60 text-sm">+8 this week</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
case 'settings':
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto space-y-6">
|
||||
<div className="text-center space-y-4">
|
||||
<h1 className="text-4xl font-bold text-white">System Settings</h1>
|
||||
<p className="text-xl text-white/60">Configure system preferences and options</p>
|
||||
</div>
|
||||
<div className="bg-white/5 border border-white/10 rounded-xl p-8 text-center">
|
||||
<div className="text-6xl mb-4">⚙️</div>
|
||||
<h3 className="text-2xl font-bold text-white mb-2">Configuration Panel</h3>
|
||||
<p className="text-white/60">System settings panel coming soon</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
default:
|
||||
return <AdminDashboard />
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-black flex">
|
||||
<AdminSidebar
|
||||
currentView={currentView}
|
||||
onViewChange={(view) => setCurrentView(view as AdminView)}
|
||||
/>
|
||||
<main className="flex-1 overflow-y-auto">
|
||||
<div className="p-8">
|
||||
{renderMainContent()}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
246
src/components/admin/admin-sidebar.tsx
Normal file
246
src/components/admin/admin-sidebar.tsx
Normal file
@ -0,0 +1,246 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import {
|
||||
LayoutDashboard,
|
||||
FileText,
|
||||
Settings,
|
||||
Users,
|
||||
Bell,
|
||||
Plus,
|
||||
Search,
|
||||
Filter,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Home,
|
||||
Database,
|
||||
Layers,
|
||||
Zap,
|
||||
BarChart3,
|
||||
Globe,
|
||||
Code
|
||||
} from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface AdminSidebarProps {
|
||||
currentView: string
|
||||
onViewChange: (view: string) => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function AdminSidebar({ currentView, onViewChange, className }: AdminSidebarProps) {
|
||||
const [isCollapsed, setIsCollapsed] = useState(false)
|
||||
|
||||
const navigationItems = [
|
||||
{
|
||||
id: 'dashboard',
|
||||
label: 'Dashboard',
|
||||
icon: LayoutDashboard,
|
||||
description: 'Overview & Analytics',
|
||||
badge: null
|
||||
},
|
||||
{
|
||||
id: 'templates',
|
||||
label: 'Templates',
|
||||
icon: FileText,
|
||||
description: 'Manage Templates',
|
||||
badge: null
|
||||
},
|
||||
{
|
||||
id: 'features',
|
||||
label: 'Features',
|
||||
icon: Layers,
|
||||
description: 'Feature Management',
|
||||
badge: null
|
||||
},
|
||||
{
|
||||
id: 'ai-features',
|
||||
label: 'AI Features',
|
||||
icon: Zap,
|
||||
description: 'AI-Powered Creation',
|
||||
badge: 'NEW'
|
||||
},
|
||||
{
|
||||
id: 'users',
|
||||
label: 'Users',
|
||||
icon: Users,
|
||||
description: 'User Management',
|
||||
badge: null
|
||||
},
|
||||
{
|
||||
id: 'notifications',
|
||||
label: 'Notifications',
|
||||
icon: Bell,
|
||||
description: 'System Alerts',
|
||||
badge: '3'
|
||||
},
|
||||
{
|
||||
id: 'analytics',
|
||||
label: 'Analytics',
|
||||
icon: BarChart3,
|
||||
description: 'Usage Statistics',
|
||||
badge: null
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
label: 'Settings',
|
||||
icon: Settings,
|
||||
description: 'System Configuration',
|
||||
badge: null
|
||||
}
|
||||
]
|
||||
|
||||
const quickActions = [
|
||||
{
|
||||
id: 'create-template',
|
||||
label: 'New Template',
|
||||
icon: Plus,
|
||||
action: () => onViewChange('create-template')
|
||||
},
|
||||
{
|
||||
id: 'add-feature',
|
||||
label: 'Add Feature',
|
||||
icon: Layers,
|
||||
action: () => onViewChange('add-feature')
|
||||
},
|
||||
{
|
||||
id: 'ai-analyze',
|
||||
label: 'AI Analyze',
|
||||
icon: Zap,
|
||||
action: () => onViewChange('ai-analyze')
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
"bg-gray-900/50 border-r border-white/10 flex flex-col transition-all duration-300",
|
||||
isCollapsed ? "w-16" : "w-64",
|
||||
className
|
||||
)}>
|
||||
{/* Header */}
|
||||
<div className="p-4 border-b border-white/10">
|
||||
<div className="flex items-center justify-between">
|
||||
{!isCollapsed && (
|
||||
<div>
|
||||
<h2 className="text-white font-semibold text-lg">Admin Panel</h2>
|
||||
<p className="text-white/60 text-sm">CodeNuk Management</p>
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||
className="text-white/60 hover:text-white hover:bg-white/10"
|
||||
>
|
||||
{isCollapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="p-2">
|
||||
{!isCollapsed && (
|
||||
<div className="text-white/40 text-xs font-medium uppercase tracking-wider px-3 py-2">
|
||||
Navigation
|
||||
</div>
|
||||
)}
|
||||
<nav className="space-y-1">
|
||||
{navigationItems.map((item) => {
|
||||
const Icon = item.icon
|
||||
const isActive = currentView === item.id
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={item.id}
|
||||
variant="ghost"
|
||||
onClick={() => onViewChange(item.id)}
|
||||
className={cn(
|
||||
"w-full justify-start text-left transition-colors",
|
||||
isActive
|
||||
? "bg-orange-500/20 text-orange-400 border-r-2 border-orange-400"
|
||||
: "text-white/70 hover:text-white hover:bg-white/10",
|
||||
isCollapsed ? "px-2" : "px-3"
|
||||
)}
|
||||
>
|
||||
<Icon className={cn("h-4 w-4", isCollapsed ? "" : "mr-3")} />
|
||||
{!isCollapsed && (
|
||||
<>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium">{item.label}</div>
|
||||
<div className="text-xs text-white/50">{item.description}</div>
|
||||
</div>
|
||||
{item.badge && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"text-xs",
|
||||
item.badge === 'NEW'
|
||||
? "bg-emerald-500/20 text-emerald-300 border-emerald-500/30"
|
||||
: "bg-orange-500/20 text-orange-300 border-orange-500/30"
|
||||
)}
|
||||
>
|
||||
{item.badge}
|
||||
</Badge>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
{!isCollapsed && (
|
||||
<div className="p-2 border-t border-white/10 mt-4">
|
||||
<div className="text-white/40 text-xs font-medium uppercase tracking-wider px-3 py-2">
|
||||
Quick Actions
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{quickActions.map((action) => {
|
||||
const Icon = action.icon
|
||||
return (
|
||||
<Button
|
||||
key={action.id}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={action.action}
|
||||
className="w-full justify-start border-white/20 text-white/70 hover:text-white hover:bg-white/10"
|
||||
>
|
||||
<Icon className="h-4 w-4 mr-2" />
|
||||
{action.label}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="p-4 border-t border-white/10">
|
||||
{!isCollapsed ? (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-white/60">System Status</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-2 h-2 bg-emerald-400 rounded-full"></div>
|
||||
<span className="text-emerald-400 text-xs">Online</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-white/40">
|
||||
Backend: Connected • DB: Healthy
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center">
|
||||
<div className="w-2 h-2 bg-emerald-400 rounded-full"></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -178,20 +178,35 @@ export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps
|
||||
// Get category icon
|
||||
const getCategoryIcon = (category: string) => {
|
||||
switch (category.toLowerCase()) {
|
||||
case 'marketing':
|
||||
return Zap
|
||||
case 'software':
|
||||
return Code
|
||||
case 'seo':
|
||||
return BarChart3
|
||||
case 'food delivery':
|
||||
return ShoppingCart
|
||||
case 'e-commerce':
|
||||
case 'ecommerce':
|
||||
return ShoppingCart
|
||||
case 'portfolio':
|
||||
case 'saas platform':
|
||||
return Code
|
||||
case 'mobile app':
|
||||
return Code
|
||||
case 'dashboard':
|
||||
return BarChart3
|
||||
case 'crm system':
|
||||
return Briefcase
|
||||
case 'business':
|
||||
return Briefcase
|
||||
case 'education':
|
||||
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
|
||||
}
|
||||
@ -199,37 +214,69 @@ export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps
|
||||
|
||||
// Get category stats for filters
|
||||
const getCategoryStats = () => {
|
||||
// Start with "All Templates"
|
||||
const categoryStats = [
|
||||
{ id: 'all', name: 'All Templates', count: templates.length, icon: Globe },
|
||||
{ id: 'marketing', name: 'Marketing', count: 0, icon: Zap },
|
||||
{ id: 'software', name: 'Software', count: 0, icon: Code },
|
||||
{ id: 'seo', name: 'SEO', count: 0, icon: BarChart3 },
|
||||
{ id: 'ecommerce', name: 'E-commerce', count: 0, icon: ShoppingCart },
|
||||
{ id: 'portfolio', name: 'Portfolio', count: 0, icon: Briefcase },
|
||||
{ id: 'business', name: 'Business', count: 0, icon: Briefcase },
|
||||
{ id: 'education', name: 'Education', count: 0, icon: GraduationCap }
|
||||
{ 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 categoryId = template.category?.toLowerCase()
|
||||
const categoryItem = categoryStats.find(cat => cat.id === categoryId)
|
||||
if (categoryItem) {
|
||||
categoryItem.count++
|
||||
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
|
||||
// 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())
|
||||
|
||||
return matchesSearch
|
||||
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 }) => (
|
||||
@ -428,30 +475,7 @@ export function AdminTemplatesList({ onTemplateSelect }: AdminTemplatesListProps
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Category Filters */}
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{getCategoryStats().map((category) => {
|
||||
const Icon = category.icon
|
||||
const active = categoryFilter === category.id
|
||||
return (
|
||||
<button
|
||||
key={category.id}
|
||||
onClick={() => setCategoryFilter(category.id)}
|
||||
className={`flex items-center space-x-2 px-4 py-2 rounded-lg border transition-all ${
|
||||
active
|
||||
? "bg-orange-500 text-black border-orange-500"
|
||||
: "bg-white/5 text-white/80 border-white/10 hover:bg-white/10"
|
||||
}`}
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
<span className="font-medium">{category.name}</span>
|
||||
<Badge variant="secondary" className="ml-1 bg-white/10 text-white">
|
||||
{category.count}
|
||||
</Badge>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{/* Category Filters removed; using only dropdown above */}
|
||||
|
||||
{/* Create Template Modal */}
|
||||
<Dialog open={showCreateModal} onOpenChange={setShowCreateModal}>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user