diff --git a/frontend/src/pages/Automations.tsx b/frontend/src/pages/Automations.tsx index 1cae19a3..302ffa48 100644 --- a/frontend/src/pages/Automations.tsx +++ b/frontend/src/pages/Automations.tsx @@ -59,6 +59,7 @@ export default function Automations() { const restoreAll = useRestoreAllFlows(); const [showFlowsMenu, setShowFlowsMenu] = useState(false); const [confirmRestore, setConfirmRestore] = useState<{ type: 'all' | string; name: string } | null>(null); + const [isProcessing, setIsProcessing] = useState(false); // Fetch embed token for Activepieces const { @@ -255,13 +256,21 @@ export default function Automations() { onClose={() => setConfirmRestore(null)} onConfirm={() => { const refreshIframe = () => { - // Reset iframe state and increment key to force remount - initSentRef.current = false; - setAuthenticated(false); - setIframeReady(false); - setRefreshKey((k) => k + 1); - refetch(); - setConfirmRestore(null); + // Show processing state while waiting for Activepieces + setIsProcessing(true); + + // Wait for Activepieces async processing to complete (~2 seconds) + // before refreshing the iframe to show updated flow status + setTimeout(() => { + // Reset iframe state and increment key to force remount + initSentRef.current = false; + setAuthenticated(false); + setIframeReady(false); + setRefreshKey((k) => k + 1); + refetch(); + setConfirmRestore(null); + setIsProcessing(false); + }, 2500); }; if (confirmRestore?.type === 'all') { @@ -287,7 +296,7 @@ export default function Automations() { } confirmText={t('automations.restore.confirm', 'Restore')} variant="warning" - isLoading={restoreFlow.isPending || restoreAll.isPending} + isLoading={restoreFlow.isPending || restoreAll.isPending || isProcessing} /> {/* Header */} diff --git a/frontend/src/pages/HelpGuide.tsx b/frontend/src/pages/HelpGuide.tsx index 0624f76a..f1a9e0a1 100644 --- a/frontend/src/pages/HelpGuide.tsx +++ b/frontend/src/pages/HelpGuide.tsx @@ -21,6 +21,7 @@ import { Mail, CreditCard, Puzzle, + Workflow, Settings, ChevronRight, HelpCircle, @@ -77,8 +78,9 @@ const HelpGuide: React.FC = () => { }, { title: 'Extend', - description: 'Add functionality with plugins', + description: 'Add functionality with automations and plugins', links: [ + { label: 'Automations', path: '/dashboard/help/automations', icon: }, { label: 'Plugins', path: '/dashboard/help/plugins', icon: }, ], }, diff --git a/frontend/src/pages/help/HelpAutomationDocs.tsx b/frontend/src/pages/help/HelpAutomationDocs.tsx index 3d7cf638..f374958a 100644 --- a/frontend/src/pages/help/HelpAutomationDocs.tsx +++ b/frontend/src/pages/help/HelpAutomationDocs.tsx @@ -1,124 +1,70 @@ -import React, { useState, useEffect } from 'react'; +/** + * Comprehensive Automation Documentation + * + * Full documentation for the Activepieces-powered automation system. + */ + +import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { ArrowLeft, + Workflow, + Zap, + Clock, + Mail, + CreditCard, + Bell, + ChevronRight, + ChevronDown, + Play, + Settings, + BookOpen, + Sparkles, + RotateCcw, + Calendar, + Users, + FileText, + Send, + Search, + Puzzle, + Code, Copy, Check, - ChevronDown, - ChevronRight, - Zap, AlertCircle, - Code, - Calendar, - Shield, - Cpu, + Info, + Lightbulb, + ExternalLink, } from 'lucide-react'; -// ============================================================================= -// TYPES & CONSTANTS -// ============================================================================= - -type CodeLanguage = 'python' | 'json'; - -interface LanguageConfig { - label: string; - icon: string; -} - -const LANGUAGES: Record = { - python: { label: 'Python', icon: 'py' }, - json: { label: 'JSON', icon: '{}' }, -}; - -// ============================================================================= -// SYNTAX HIGHLIGHTING -// ============================================================================= - -const highlightSyntax = (code: string, language: CodeLanguage): React.ReactNode => { - const patterns: Record> = { - json: [ - { pattern: /"([^"\\]|\\.)*"(?=\s*:)/g, className: 'text-purple-400' }, - { pattern: /"([^"\\]|\\.)*"(?!\s*:)/g, className: 'text-green-400' }, - { pattern: /\b(true|false|null)\b/g, className: 'text-orange-400' }, - { pattern: /\b(-?\d+\.?\d*)\b/g, className: 'text-cyan-400' }, - ], - python: [ - { pattern: /#.*/g, className: 'text-gray-500 italic' }, - { pattern: /\b(import|from|class|def|return|if|else|elif|for|while|try|except|with|as|None|True|False|self|async|await|in|and|or|not|is)\b/g, className: 'text-purple-400' }, - { pattern: /('([^'\\]|\\.)*'|"([^"\\]|\\.)*"|f"([^"\\]|\\.)*"|f'([^'\\]|\\.)*')/g, className: 'text-green-400' }, - { pattern: /\b(\d+\.?\d*)\b/g, className: 'text-cyan-400' }, - { pattern: /@\w+/g, className: 'text-pink-400' }, - ], - }; - - const langPatterns = patterns[language] || []; - - if (langPatterns.length === 0) { - return {code}; - } - - const lines = code.split('\n'); +// Collapsible section component +const CollapsibleSection: React.FC<{ + title: string; + icon?: React.ReactNode; + defaultOpen?: boolean; + children: React.ReactNode; +}> = ({ title, icon, defaultOpen = false, children }) => { + const [isOpen, setIsOpen] = useState(defaultOpen); return ( - <> - {lines.map((line, lineIndex) => { - let result: Array<{ text: string; className?: string; start: number }> = [{ text: line, start: 0 }]; - - langPatterns.forEach(({ pattern, className }) => { - const newResult: typeof result = []; - - result.forEach(segment => { - if (segment.className) { - newResult.push(segment); - return; - } - - const text = segment.text; - const regex = new RegExp(pattern.source, pattern.flags); - let lastIndex = 0; - let match; - - while ((match = regex.exec(text)) !== null) { - if (match.index > lastIndex) { - newResult.push({ text: text.slice(lastIndex, match.index), start: segment.start + lastIndex }); - } - newResult.push({ text: match[0], className, start: segment.start + match.index }); - lastIndex = match.index + match[0].length; - - if (match[0].length === 0) break; - } - - if (lastIndex < text.length) { - newResult.push({ text: text.slice(lastIndex), start: segment.start + lastIndex }); - } - }); - - result = newResult.length > 0 ? newResult : result; - }); - - return ( - - {result.map((segment, i) => ( - {segment.text} - ))} - {lineIndex < lines.length - 1 && '\n'} - - ); - })} - +
+ + {isOpen &&
{children}
} +
); }; -// ============================================================================= -// CODE BLOCK COMPONENTS -// ============================================================================= - -const CodeBlock: React.FC<{ code: string; language?: CodeLanguage; title?: string }> = ({ - code, - language = 'json', - title -}) => { +// Code block component +const CodeBlock: React.FC<{ code: string; language?: string }> = ({ code, language = 'json' }) => { const [copied, setCopied] = useState(false); const handleCopy = () => { @@ -128,2985 +74,954 @@ const CodeBlock: React.FC<{ code: string; language?: CodeLanguage; title?: strin }; return ( -
- {title && ( -
- {title} - {LANGUAGES[language]?.label || language} +
+
+        {code}
+      
+ +
+ ); +}; + +// Info box component +const InfoBox: React.FC<{ type: 'info' | 'warning' | 'tip'; title: string; children: React.ReactNode }> = ({ + type, + title, + children, +}) => { + const styles = { + info: 'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800', + warning: 'bg-amber-50 dark:bg-amber-900/20 border-amber-200 dark:border-amber-800', + tip: 'bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800', + }; + const icons = { + info: , + warning: , + tip: , + }; + + return ( +
+
+ {icons[type]} +
+

{title}

+
{children}
- )} -
-
-          {highlightSyntax(code, language)}
-        
-
); }; -// Removed TabbedCodeBlock - we only show Python examples since users write scripts in the UI - -// ============================================================================= -// SIDEBAR NAVIGATION -// ============================================================================= - -interface NavSection { - titleKey: string; - id: string; - items?: { titleKey: string; id: string }[]; -} - -const navSections: NavSection[] = [ - { titleKey: 'Introduction', id: 'introduction' }, - { titleKey: 'Quick Start', id: 'quick-start' }, - { titleKey: 'How It Works', id: 'how-it-works' }, - { - titleKey: 'Available Plugins', - id: 'plugins', - items: [ - { titleKey: 'Built-in Plugins', id: 'builtin-plugins' }, - { titleKey: 'Custom Scripts', id: 'custom-scripts' }, - { titleKey: 'Template Variables', id: 'template-variables' }, - ], - }, - { - titleKey: 'API Reference', - id: 'api', - items: [ - { titleKey: 'API Methods', id: 'api-methods' }, - { titleKey: 'Command Reference', id: 'command-reference' }, - { titleKey: 'Schedule Types', id: 'schedule-types' }, - { titleKey: 'Manage Tasks', id: 'manage-tasks' }, - ], - }, - { - titleKey: 'Examples', - id: 'examples', - items: [ - { titleKey: 'Win Back Customers', id: 'example-reengagement' }, - { titleKey: 'Booking Alerts', id: 'example-alerts' }, - { titleKey: 'Weekly Reports', id: 'example-reports' }, - ], - }, - { - titleKey: 'Security', - id: 'security', - items: [ - { titleKey: 'Safety Features', id: 'safety' }, - { titleKey: 'Resource Limits', id: 'limits' }, - ], - }, - { titleKey: 'Plugin Licensing', id: 'licensing' }, -]; - -const Sidebar: React.FC<{ - activeSection: string; - onSectionClick: (id: string) => void; -}> = ({ activeSection, onSectionClick }) => { - const [expandedSections, setExpandedSections] = useState(['plugins', 'api', 'examples', 'security']); - - const toggleSection = (id: string) => { - setExpandedSections(prev => - prev.includes(id) ? prev.filter(s => s !== id) : [...prev, id] - ); - }; - - return ( - - ); -}; - -// ============================================================================= -// API SECTION COMPONENT (Stripe-style split pane) -// ============================================================================= - -interface ApiSectionProps { - id: string; - children: React.ReactNode; -} - -const ApiSection: React.FC = ({ id, children }) => { - return ( -
-
- {children} -
-
- ); -}; - -const ApiContent: React.FC<{ children: React.ReactNode }> = ({ children }) => ( -
- {children} -
-); - -const ApiExample: React.FC<{ children: React.ReactNode }> = ({ children }) => ( -
- {children} -
-); - -// ============================================================================= -// ATTRIBUTE TABLE -// ============================================================================= - -interface Attribute { - name: string; - type: string; - description: string; - required?: boolean; -} - -const AttributeTable: React.FC<{ attributes: Attribute[] }> = ({ attributes }) => ( -
- {attributes.map(attr => ( -
-
- {attr.name} - {attr.type} - {attr.required && ( - required - )} -
-

{attr.description}

-
- ))} -
-); - -// ============================================================================= -// MAIN COMPONENT -// ============================================================================= - -const HelpPluginDocs: React.FC = () => { +const HelpAutomationDocs: React.FC = () => { const { t } = useTranslation(); const navigate = useNavigate(); - const [activeSection, setActiveSection] = useState('introduction'); - - const handleSectionClick = (id: string) => { - setActiveSection(id); - const element = document.getElementById(id); - if (element) { - element.scrollIntoView({ - behavior: 'smooth', - block: 'start' - }); - } - }; - - // Track active section on scroll - useEffect(() => { - const handleScroll = () => { - const sections = document.querySelectorAll('section[id]'); - let current = 'introduction'; - const headerHeight = 72; - const threshold = headerHeight + 32; - - sections.forEach(section => { - const rect = section.getBoundingClientRect(); - if (rect.top <= threshold) { - current = section.id; - } - }); - - setActiveSection(current); - }; - - window.addEventListener('scroll', handleScroll); - return () => window.removeEventListener('scroll', handleScroll); - }, []); - - // ============================================================================= - // CODE SNIPPETS - // ============================================================================= - - const simplePluginExample = `# Get scheduled appointments -appointments = api.get_appointments(status='SCHEDULED') - -# Count them -count = len(appointments) - -# Log the result -api.log(f'Found {count} scheduled appointments') - -# Return result -result = {'total': count}`; - - const reengagementExample = `# Get customers who haven't booked in 60 days -from datetime import datetime, timedelta - -cutoff = (datetime.now() - timedelta(days=60)).strftime('%Y-%m-%d') -customers = api.get_customers(has_email=True, limit=50) - -# Send re-engagement emails -sent = 0 -for customer in customers[:30]: # Limit to 30 per run - message = f'''Hi {customer['name']}, - -We miss you! It's been a while since your last visit. - -Get 20% off your next appointment with code: COMEBACK20 - -Hope to see you soon!''' - - success = api.send_email( - to=customer['email'], - subject='We Miss You! 20% Off', - body=message - ) - - if success: - sent += 1 - -result = {'emails_sent': sent}`; - - const alertExample = `# Get appointments for next 7 days -from datetime import datetime, timedelta - -today = datetime.now().strftime('%Y-%m-%d') -next_week = (datetime.now() + timedelta(days=7)).strftime('%Y-%m-%d') - -upcoming = api.get_appointments( - start_date=today, - end_date=next_week, - status='SCHEDULED' -) - -# Alert if bookings are low -if len(upcoming) < 10: - alert = f'''⚠️ LOW BOOKING ALERT - -Only {len(upcoming)} appointments for next 7 days. - -Recommendation: Run a promotion!''' - - api.send_email( - to='manager@business.com', - subject='⚠️ Low Bookings', - body=alert - ) - - result = {'alert_sent': True, 'count': len(upcoming)} -else: - result = {'alert_sent': False, 'count': len(upcoming)}`; return ( -
- {/* Header */} -
-
-
- -
-

- Automation Plugins -

-
+
+ {/* Back Button */} + + + {/* Header */} +
+
+
+ +
+
+

+ Automation Documentation +

+

+ Complete reference for SmoothSchedule automations +

-
- -
- {/* Sidebar */} - - - {/* Main Content */} -
- {/* Introduction */} - - -

- Automation Plugins -

-

- Automate your business with powerful plugins that run on schedules. Send emails, - generate reports, and create custom workflows - all without writing complex code. -

-

- Key Features -

-
    -
  • Safe Scripting - Write Python-like code with if/else, loops, variables
  • -
  • Flexible Scheduling - Run daily, weekly, hourly, or on cron schedules
  • -
  • Resource Protected - Automatic limits prevent abuse
  • -
  • Pre-built Templates - Start with ready-made plugins
  • -
-
- -
-

- What You Can Automate -

-
    -
  • - - Send weekly summary reports to managers -
  • -
  • - - Re-engage customers who haven't booked in 60 days -
  • -
  • - - Alert when bookings are unusually low -
  • -
  • - - Send birthday wishes with discount codes -
  • -
  • - - Generate custom analytics and export data -
  • -
  • - - Integrate with external services via webhooks -
  • -
-
-
-
- - {/* Quick Start */} - - -

- Quick Start -

-

- Create your first automation in 3 simple steps: -

-
    -
  1. Write your script - Use simple Python code to access your data
  2. -
  3. Set a schedule - Choose when and how often to run
  4. -
  5. Activate & monitor - Your automation runs automatically
  6. -
-
- - -
-

- That's it! This script counts scheduled appointments and logs the result. - It runs automatically on your schedule. -

-
-
-
- - {/* How It Works */} - - -

- How It Works -

-

- Your scripts run in a secure sandbox with access to your business data through - a simple API object. -

-

- Available API Methods -

-
    -
  • api.get_appointments() - Get your appointments
  • -
  • api.get_customers() - Get your customers
  • -
  • api.send_email() - Send emails
  • -
  • api.create_appointment() - Create appointments
  • -
  • api.log() - Debug logging
  • -
-

- Execution Flow -

-
    -
  1. Script is validated for safety
  2. -
  3. Loop guards injected to prevent infinite loops
  4. -
  5. Executed with resource limits (30s timeout, 50 API calls)
  6. -
  7. Results logged for audit trail
  8. -
  9. Next run automatically scheduled
  10. -
-
- - - -
- - {/* Built-in Plugins */} - - -

- Built-in Plugins -

-

- Ready-to-use plugins for common business tasks. Just configure and activate. -

- -
- -
-

- How to Use Built-in Plugins -

-
    -
  1. - 1. - Navigate to Automation in your dashboard -
  2. -
  3. - 2. - Click "Create New Task" -
  4. -
  5. - 3. - Select a built-in plugin from the dropdown -
  6. -
  7. - 4. - Configure the plugin settings -
  8. -
  9. - 5. - Set your schedule and activate -
  10. -
-
-
-
- - {/* Custom Scripts */} - - -

- Custom Scripts -

-

- Write your own automation logic with Python. Access your data, write if/else logic, - use loops, and create powerful custom workflows. -

-

- What You Can Use -

-
    -
  • ✓ if/else statements
  • -
  • ✓ for/while loops
  • -
  • ✓ Variables & lists
  • -
  • ✓ Dictionaries
  • -
  • ✓ String formatting
  • -
  • ✓ Math operations
  • -
-

- What's Blocked -

-
    -
  • ✗ import statements
  • -
  • ✗ File system access
  • -
  • ✗ eval/exec
  • -
  • ✗ Network access*
  • -
-

- *Except approved API calls via api.http_get() -

-
- - - -
- - {/* Template Variables */} - - -

- Template Variables -

-

- Make your plugins reusable and shareable with powerful template variables. The system supports - four types of templates for maximum flexibility. -

- - {/* 1. PROMPT Variables */} -

- 1. User Input (PROMPT) -

-

- Prompt users for custom values during plugin installation. The system automatically - generates a configuration form based on your template variables. -

-
-
- {'{{PROMPT:variable|description}}'} - - Required field -
-
- {'{{PROMPT:variable|description|default}}'} - - Optional field with default value -
-
- {'{{PROMPT:variable|description|default|textarea}}'} - - Multi-line text input (for email bodies, long messages) -
-
-
-

- Field Type Detection -

-

- The system automatically detects field types from variable names and descriptions: email for email validation, - number for numeric inputs, message/body/content for textareas, url/webhook for URLs. - You can override this by explicitly specifying the type as the 4th parameter. -

-
-
-

- Example: {'{{PROMPT:manager_email|Manager email address}}'} -

-

- With default: {'{{PROMPT:discount|Discount code|SAVE20}}'} -

-
- - {/* 2. CONTEXT Variables */} -

- 2. Business Context (CONTEXT) -

-

- Automatically filled with business information from the system. No user input required! -

-
-
✓ {'{{CONTEXT:business_name}}'} - Business name
-
✓ {'{{CONTEXT:owner_email}}'} - Owner email
-
✓ {'{{CONTEXT:owner_name}}'} - Owner full name
-
✓ {'{{CONTEXT:contact_email}}'} - Contact email
-
✓ {'{{CONTEXT:phone}}'} - Business phone
-
-
-

- Benefit: Users don't need to manually enter data that's already in the system! -

-
- - {/* 3. DATE Helpers */} -

- 3. Date Helpers (DATE) -

-

- Pre-calculated dates without writing datetime code. All dates return YYYY-MM-DD format. -

-
-
{'{{DATE:today}}'} or {'{{DATE:now}}'} - Current date
-
{'{{DATE:tomorrow}}'} - Tomorrow's date
-
{'{{DATE:yesterday}}'} - Yesterday's date
-
{'{{DATE:+7d}}'} - 7 days from now
-
{'{{DATE:-30d}}'} - 30 days ago
-
{'{{DATE:+2w}}'} - 2 weeks from now
-
{'{{DATE:monday}}'} - Next Monday
-
{'{{DATE:friday}}'} - Next Friday
-
- - {/* 4. Insertion Codes */} -

- 4. Insertion Codes (Dynamic Content) -

-

- Use insertion codes within your PROMPT template text (like email bodies) to inject dynamic content - at runtime. These are automatically replaced with actual values when the plugin executes. -

-
-

- Business Information: -

-
-
{'{{BUSINESS_NAME}}'} - Your business name
-
{'{{BUSINESS_EMAIL}}'} - Business contact email
-
{'{{BUSINESS_PHONE}}'} - Business phone number
-
-
-
-

- Customer & Appointment Data: -

-
-
{'{{CUSTOMER_NAME}}'} - Customer's name (in appointment contexts)
-
{'{{CUSTOMER_EMAIL}}'} - Customer's email address
-
{'{{APPOINTMENT_TIME}}'} - Full appointment date and time
-
{'{{APPOINTMENT_DATE}}'} - Appointment date only
-
{'{{APPOINTMENT_SERVICE}}'} - Service name
-
-
-
-

- Date & Time: -

-
-
{'{{TODAY}}'} - Today's date (YYYY-MM-DD)
-
{'{{NOW}}'} - Current date and time
-
-
-
-

Example: Email Template with Insertion Codes

- -{`email_body = '{{PROMPT:email_body|Email Message|Hi {{CUSTOMER_NAME}}, - -This is a reminder about your appointment: - -Date/Time: {{APPOINTMENT_TIME}} -Service: {{APPOINTMENT_SERVICE}} - -If you have questions, contact us at {{BUSINESS_EMAIL}} - -Best regards, -{{BUSINESS_NAME}}||textarea}}'`} - -
- - {/* 5. Validation & Types */} -

- 5. Automatic Validation -

-

- The system automatically detects field types and validates input: -

-
-
Email: Variables with "email" in name/description are validated
-
Number: Variables with "count", "days", "hours", "limit" are numeric
-
URL: Variables with "url", "webhook", "endpoint" are validated
-
Textarea: Variables with "message", "body", "content" use multi-line input
-
- -
-

- Pro Tip: Combine all template types for maximum power! Use CONTEXT - for business info, DATE for time logic, PROMPT for user configuration, and Insertion Codes - within your email templates for personalized dynamic content. -

-
-
- - - {/* Visual Mockup of Configuration Form */} -
-
-

- Configure Plugin -

- Step 2 of 3 -
- -
- {/* Field 1: Required, no default */} -
- -

- Number of days before a customer is considered inactive -

- -
- - {/* Field 2: Optional with default */} -
- -

- Promotional discount code to offer -

- -

- Default: SAVE20 -

-
- - {/* Field 3: Email type (auto-detected) */} -
- -

- Email address for reports and notifications -

- -

- - - - Email validation enabled -

-
- - {/* Field 4: Textarea (auto-detected) */} -
- -

- Custom message to include in re-engagement emails -

-