/** * Create Automation Page * * Allows businesses to create custom automations with code editor, * category selection, and visibility options. */ import React, { useState, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { Code, Save, Eye, EyeOff, ArrowLeft, Info, CheckCircle, AlertTriangle, Mail, BarChart3, Users, Calendar, Link as LinkIcon, Bot, Package, Image, HelpCircle, } from 'lucide-react'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; import api from '../api/client'; import { AutomationCategory } from '../types'; import { usePlanFeatures } from '../hooks/usePlanFeatures'; // Category icon mapping const categoryIcons: Record = { EMAIL: , REPORTS: , CUSTOMER: , BOOKING: , INTEGRATION: , AUTOMATION: , OTHER: , }; // Category descriptions const categoryDescriptions: Record = { EMAIL: 'Email notifications and automated messaging', REPORTS: 'Analytics, reports, and data exports', CUSTOMER: 'Customer engagement and retention', BOOKING: 'Scheduling and booking automation', INTEGRATION: 'Third-party service integrations', AUTOMATION: 'General business automation', OTHER: 'Miscellaneous automations', }; // Default automation code template const DEFAULT_AUTOMATION_CODE = `# My Custom Automation # # This automation runs on a schedule and can interact with your business data. # Use template variables to make your automation configurable. # # Available template variables: # {{PROMPT:variable_name:default_value:description}} # {{CONTEXT:context_type}} - Access business context (CUSTOMERS, EVENTS, etc.) # {{DATE:format}} - Current date in specified format # Example: Get all customers who haven't booked in 30 days inactive_days = int("{{PROMPT:inactive_days:30:Days of inactivity}}") # Access customer data customers = {{CONTEXT:CUSTOMERS}} # Filter inactive customers from datetime import datetime, timedelta cutoff_date = datetime.now() - timedelta(days=inactive_days) inactive_customers = [ c for c in customers if not c.get('last_booking') or datetime.fromisoformat(c['last_booking']) < cutoff_date ] # Return results (will be logged) result = { 'inactive_count': len(inactive_customers), 'customers': inactive_customers[:10], # First 10 for preview 'message': f"Found {len(inactive_customers)} inactive customers" } `; interface FormData { name: string; shortDescription: string; description: string; category: AutomationCategory; automationCode: string; version: string; logoUrl: string; visibility: 'PRIVATE' | 'PUBLIC'; } const CreateAutomation: React.FC = () => { const { t } = useTranslation(); const navigate = useNavigate(); const queryClient = useQueryClient(); const { canUse } = usePlanFeatures(); const [formData, setFormData] = useState({ name: '', shortDescription: '', description: '', category: 'AUTOMATION', automationCode: DEFAULT_AUTOMATION_CODE, version: '1.0.0', logoUrl: '', visibility: 'PRIVATE', }); const [showPreview, setShowPreview] = useState(false); const [extractedVariables, setExtractedVariables] = useState([]); // Extract template variables from code const extractVariables = useCallback((code: string) => { const promptPattern = /\{\{PROMPT:([^:}]+):([^:}]*):([^}]*)\}\}/g; const contextPattern = /\{\{CONTEXT:([^}]+)\}\}/g; const datePattern = /\{\{DATE:([^}]+)\}\}/g; const variables: any[] = []; let match; while ((match = promptPattern.exec(code)) !== null) { variables.push({ type: 'PROMPT', name: match[1], default: match[2], description: match[3], }); } while ((match = contextPattern.exec(code)) !== null) { variables.push({ type: 'CONTEXT', name: match[1], }); } while ((match = datePattern.exec(code)) !== null) { variables.push({ type: 'DATE', format: match[1], }); } return variables; }, []); // Update extracted variables when code changes const handleCodeChange = (e: React.ChangeEvent) => { const newCode = e.target.value; setFormData(prev => ({ ...prev, automationCode: newCode })); setExtractedVariables(extractVariables(newCode)); }; // Create automation mutation const createMutation = useMutation({ mutationFn: async (data: FormData) => { const payload = { name: data.name, short_description: data.shortDescription, description: data.description, category: data.category, automation_code: data.automationCode, version: data.version, logo_url: data.logoUrl || undefined, visibility: data.visibility, }; const response = await api.post('/automation-templates/', payload); return response.data; }, onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ['automation-templates'] }); navigate('/dashboard/automations/my-automations'); }, }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); createMutation.mutate(formData); }; // Check if user can create automations const canCreateAutomations = canUse('can_create_automations'); if (!canCreateAutomations) { return (

{t('automations.upgradeRequired', 'Upgrade Required')}

{t('automations.upgradeToCreate', 'Automation creation is available on higher-tier plans. Upgrade your subscription to create custom automations.')}

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

{t('automations.createAutomation', 'Create Custom Automation')}

{t('automations.createAutomationDescription', 'Build a custom automation for your business')}

{t('automations.viewDocs', 'View Documentation')}
{/* Left Column - Basic Info */}
{/* Automation Name */}

{t('automations.basicInfo', 'Basic Information')}

setFormData(prev => ({ ...prev, name: e.target.value }))} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-500" placeholder="e.g., Win Back Inactive Customers" required />
setFormData(prev => ({ ...prev, shortDescription: e.target.value }))} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-500" placeholder="Brief summary for marketplace listing" maxLength={200} required />

{formData.shortDescription.length}/200