import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { Package, Plus, Trash2, Star, RefreshCw, CheckCircle, XCircle, Mail, BarChart3, Users, Calendar, Link as LinkIcon, Bot, Package as PackageIcon, X, AlertTriangle, Clock, Settings } from 'lucide-react'; import api from '../api/client'; import { PluginInstallation, PluginCategory } from '../types'; import EmailTemplateSelector from '../components/EmailTemplateSelector'; // Category icon mapping const categoryIcons: Record = { EMAIL: , REPORTS: , CUSTOMER: , BOOKING: , INTEGRATION: , AUTOMATION: , OTHER: , }; // Category colors const categoryColors: Record = { EMAIL: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300', REPORTS: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300', CUSTOMER: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300', BOOKING: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-300', INTEGRATION: 'bg-cyan-100 text-cyan-700 dark:bg-cyan-900/30 dark:text-cyan-300', AUTOMATION: 'bg-pink-100 text-pink-700 dark:bg-pink-900/30 dark:text-pink-300', OTHER: 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300', }; const MyPlugins: React.FC = () => { const { t } = useTranslation(); const navigate = useNavigate(); const queryClient = useQueryClient(); const [selectedPlugin, setSelectedPlugin] = useState(null); const [showUninstallModal, setShowUninstallModal] = useState(false); const [showRatingModal, setShowRatingModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false); const [rating, setRating] = useState(0); const [review, setReview] = useState(''); const [configValues, setConfigValues] = useState>({}); // Fetch installed plugins const { data: plugins = [], isLoading, error } = useQuery({ queryKey: ['plugin-installations'], queryFn: async () => { const { data } = await api.get('/plugin-installations/'); return data.map((p: any) => ({ id: String(p.id), template: String(p.template), templateName: p.template_name || p.templateName, templateDescription: p.template_description || p.templateDescription, category: p.category, version: p.version, authorName: p.author_name || p.authorName, logoUrl: p.logo_url || p.logoUrl, templateVariables: p.template_variables || p.templateVariables || {}, configValues: p.config_values || p.configValues || {}, isActive: p.is_active !== undefined ? p.is_active : p.isActive, installedAt: p.installed_at || p.installedAt, hasUpdate: p.has_update !== undefined ? p.has_update : p.hasUpdate || false, rating: p.rating, review: p.review, })); }, }); // Uninstall plugin mutation const uninstallMutation = useMutation({ mutationFn: async (pluginId: string) => { await api.delete(`/plugin-installations/${pluginId}/`); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['plugin-installations'] }); setShowUninstallModal(false); setSelectedPlugin(null); }, }); // Rate plugin mutation const rateMutation = useMutation({ mutationFn: async ({ pluginId, rating, review }: { pluginId: string; rating: number; review: string }) => { const { data } = await api.post(`/plugin-installations/${pluginId}/rate/`, { rating, review, }); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['plugin-installations'] }); setShowRatingModal(false); setSelectedPlugin(null); setRating(0); setReview(''); }, }); // Update plugin mutation const updateMutation = useMutation({ mutationFn: async (pluginId: string) => { const { data } = await api.post(`/plugin-installations/${pluginId}/update/`); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['plugin-installations'] }); }, }); // Edit config mutation const editConfigMutation = useMutation({ mutationFn: async ({ pluginId, configValues }: { pluginId: string; configValues: Record }) => { const { data } = await api.patch(`/plugin-installations/${pluginId}/`, { config_values: configValues, }); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['plugin-installations'] }); setShowEditModal(false); setSelectedPlugin(null); setConfigValues({}); }, }); const handleUninstall = (plugin: PluginInstallation) => { setSelectedPlugin(plugin); setShowUninstallModal(true); }; const confirmUninstall = () => { if (selectedPlugin) { uninstallMutation.mutate(selectedPlugin.id); } }; const handleRating = (plugin: PluginInstallation) => { setSelectedPlugin(plugin); setRating(plugin.rating || 0); setReview(plugin.review || ''); setShowRatingModal(true); }; const submitRating = () => { if (selectedPlugin && rating > 0) { rateMutation.mutate({ pluginId: selectedPlugin.id, rating, review, }); } }; const handleUpdate = (plugin: PluginInstallation) => { updateMutation.mutate(plugin.id); }; const unescapeString = (str: string): string => { return str .replace(/\\n/g, '\n') .replace(/\\r/g, '\r') .replace(/\\t/g, '\t') .replace(/\\'/g, "'") .replace(/\\"/g, '"') .replace(/\\\\/g, '\\'); }; const handleEdit = (plugin: PluginInstallation) => { setSelectedPlugin(plugin); // Convert escape sequences to actual characters for display const displayValues = { ...plugin.configValues || {} }; if (plugin.templateVariables) { Object.entries(plugin.templateVariables).forEach(([key, variable]: [string, any]) => { if (displayValues[key]) { displayValues[key] = unescapeString(displayValues[key]); } }); } setConfigValues(displayValues); setShowEditModal(true); }; const escapeString = (str: string): string => { return str .replace(/\\/g, '\\\\') // Escape backslashes first .replace(/\n/g, '\\n') .replace(/\r/g, '\\r') .replace(/\t/g, '\\t') .replace(/'/g, "\\'") .replace(/"/g, '\\"'); }; const submitConfigEdit = () => { if (selectedPlugin) { // Convert actual characters back to escape sequences for storage const storageValues = { ...configValues }; if (selectedPlugin.templateVariables) { Object.entries(selectedPlugin.templateVariables).forEach(([key, variable]: [string, any]) => { if (storageValues[key]) { storageValues[key] = escapeString(storageValues[key]); } }); } editConfigMutation.mutate({ pluginId: selectedPlugin.id, configValues: storageValues, }); } }; if (isLoading) { return (
); } if (error) { return (

{t('common.error')}: {error instanceof Error ? error.message : 'Unknown error'}

); } return (
{/* Header */}

{t('plugins.myPlugins', 'My Plugins')}

{t('plugins.myPluginsDescription', 'Manage your installed plugins')}

{/* Plugin List */} {plugins.length === 0 ? (

{t('plugins.noPluginsInstalled', 'No plugins installed yet')}

) : (
{plugins.map((plugin) => (
handleEdit(plugin)} >
{/* Plugin Info */}
{plugin.logoUrl && ( {`${plugin.templateName} { // Hide image if it fails to load e.currentTarget.style.display = 'none'; }} /> )}

{plugin.templateName}

{categoryIcons[plugin.category]} {plugin.category} {plugin.hasUpdate && ( Update Available )}

{plugin.templateDescription}

{plugin.authorName && (
{t('plugins.author', 'Author')}: {plugin.authorName}
)}
{t('plugins.version', 'Version')}: {plugin.version}
{t('plugins.installedOn', 'Installed on')}: {new Date(plugin.installedAt).toLocaleDateString()}
{plugin.isActive ? ( <> {t('plugins.active', 'Active')} ) : ( <> {t('plugins.inactive', 'Inactive')} )}
{plugin.rating && (
{plugin.rating}/5
)}
{/* Actions */}
{/* Schedule button - only if not already scheduled */} {!plugin.scheduledTaskId && ( )} {/* Already scheduled indicator */} {plugin.scheduledTaskId && ( {t('plugins.scheduled', 'Scheduled')} )} {plugin.hasUpdate && ( )}
))}
)} {/* Info Box */}

{t('plugins.needCustomPlugin', 'Need a custom plugin?')}

{t('plugins.customPluginDescription', 'Create your own custom plugins to extend your business functionality with specific features tailored to your needs.')}

{/* Uninstall Confirmation Modal */} {showUninstallModal && selectedPlugin && (
{/* Modal Header */}

{t('plugins.confirmUninstall', 'Confirm Uninstall')}

{/* Modal Body */}

{t('plugins.uninstallWarning', 'Are you sure you want to uninstall')} {selectedPlugin.templateName}?

{t('plugins.uninstallNote', 'This action cannot be undone. Your plugin data and settings will be removed.')}

{/* Modal Footer */}
)} {/* Rating Modal */} {showRatingModal && selectedPlugin && (
{/* Modal Header */}

{t('plugins.ratePlugin', 'Rate Plugin')}

{/* Modal Body */}

{selectedPlugin.templateName}

{t('plugins.rateDescription', 'Share your experience with this plugin')}

{/* Star Rating */}
{[1, 2, 3, 4, 5].map((star) => ( ))}
{/* Review */}