Files
smoothschedule/frontend/src/pages/HelpPluginDocs.tsx
poduck ecfdbdefe0 refactor: Replace "Platform" with "SmoothSchedule" in licensing section
Updated all references to "Platform" in the licensing documentation to use
"SmoothSchedule" for better branding consistency:

- Changed "Platform Rights" to "SmoothSchedule Rights"
- Changed "Platform Service Rights" to "SmoothSchedule Service Rights"
- Changed "Platform Use" table header to "SmoothSchedule Use"
- Updated all inline references from "Platform" to "SmoothSchedule"

This makes the licensing terms more specific and branded while maintaining
the same legal structure and protections.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 21:07:09 -05:00

1876 lines
88 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import {
ArrowLeft,
Copy,
Check,
ChevronDown,
ChevronRight,
Zap,
AlertCircle,
Code,
Calendar,
Shield,
Cpu,
} from 'lucide-react';
// =============================================================================
// TYPES & CONSTANTS
// =============================================================================
type CodeLanguage = 'python' | 'json';
interface LanguageConfig {
label: string;
icon: string;
}
const LANGUAGES: Record<CodeLanguage, LanguageConfig> = {
python: { label: 'Python', icon: 'py' },
json: { label: 'JSON', icon: '{}' },
};
// =============================================================================
// SYNTAX HIGHLIGHTING
// =============================================================================
const highlightSyntax = (code: string, language: CodeLanguage): React.ReactNode => {
const patterns: Record<string, Array<{ pattern: RegExp; className: string }>> = {
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 <span>{code}</span>;
}
const lines = code.split('\n');
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 (
<React.Fragment key={lineIndex}>
{result.map((segment, i) => (
<span key={i} className={segment.className}>{segment.text}</span>
))}
{lineIndex < lines.length - 1 && '\n'}
</React.Fragment>
);
})}
</>
);
};
// =============================================================================
// CODE BLOCK COMPONENTS
// =============================================================================
const CodeBlock: React.FC<{ code: string; language?: CodeLanguage; title?: string }> = ({
code,
language = 'json',
title
}) => {
const [copied, setCopied] = useState(false);
const handleCopy = () => {
navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<div className="rounded-lg overflow-hidden bg-[#0a0a0f] mb-4">
{title && (
<div className="px-4 py-2 text-xs text-gray-400 border-b border-gray-800 flex items-center justify-between">
<span>{title}</span>
<span className="text-gray-500">{LANGUAGES[language]?.label || language}</span>
</div>
)}
<div className="relative">
<pre className="p-4 overflow-x-auto text-sm font-mono leading-relaxed text-gray-300">
<code>{highlightSyntax(code, language)}</code>
</pre>
<button
onClick={handleCopy}
className="absolute top-2 right-2 p-1.5 rounded bg-gray-800 hover:bg-gray-700 text-gray-400 transition-colors"
title="Copy to clipboard"
>
{copied ? <Check size={14} /> : <Copy size={14} />}
</button>
</div>
</div>
);
};
// 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<string[]>(['plugins', 'api', 'examples', 'security']);
const toggleSection = (id: string) => {
setExpandedSections(prev =>
prev.includes(id) ? prev.filter(s => s !== id) : [...prev, id]
);
};
return (
<nav className="w-64 flex-shrink-0 border-r border-gray-200 dark:border-gray-800 sticky top-[65px] h-[calc(100vh-65px)] overflow-y-auto">
<div className="p-4 space-y-1">
{navSections.map(section => (
<div key={section.id}>
{section.items ? (
<>
<button
onClick={() => toggleSection(section.id)}
className="w-full flex items-center justify-between px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md"
>
{section.titleKey}
{expandedSections.includes(section.id) ? (
<ChevronDown size={16} />
) : (
<ChevronRight size={16} />
)}
</button>
{expandedSections.includes(section.id) && (
<div className="ml-3 mt-1 space-y-0.5">
{section.items.map(item => (
<button
key={item.id}
onClick={() => onSectionClick(item.id)}
className={`w-full flex items-center gap-2 px-3 py-1.5 text-sm rounded-md transition-colors ${
activeSection === item.id
? 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300'
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
>
<span className="truncate">{item.titleKey}</span>
</button>
))}
</div>
)}
</>
) : (
<button
onClick={() => onSectionClick(section.id)}
className={`w-full flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors ${
activeSection === section.id
? 'bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300'
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
>
{section.titleKey}
</button>
)}
</div>
))}
</div>
</nav>
);
};
// =============================================================================
// API SECTION COMPONENT (Stripe-style split pane)
// =============================================================================
interface ApiSectionProps {
id: string;
children: React.ReactNode;
}
const ApiSection: React.FC<ApiSectionProps> = ({ id, children }) => {
return (
<section id={id} className="py-12 border-b border-gray-200 dark:border-gray-800 last:border-b-0 scroll-mt-24">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{children}
</div>
</section>
);
};
const ApiContent: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="prose prose-gray dark:prose-invert max-w-none">
{children}
</div>
);
const ApiExample: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<div className="lg:sticky lg:top-[81px] z-10">
{children}
</div>
);
// =============================================================================
// ATTRIBUTE TABLE
// =============================================================================
interface Attribute {
name: string;
type: string;
description: string;
required?: boolean;
}
const AttributeTable: React.FC<{ attributes: Attribute[] }> = ({ attributes }) => (
<div className="mt-4 space-y-3">
{attributes.map(attr => (
<div key={attr.name} className="border-b border-gray-100 dark:border-gray-800 pb-3 last:border-b-0">
<div className="flex items-center gap-2">
<code className="text-sm font-mono text-purple-600 dark:text-purple-400">{attr.name}</code>
<span className="text-xs text-gray-500">{attr.type}</span>
{attr.required && (
<span className="text-xs text-red-500 font-medium">required</span>
)}
</div>
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">{attr.description}</p>
</div>
))}
</div>
);
// =============================================================================
// MAIN COMPONENT
// =============================================================================
const HelpPluginDocs: 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 (
<div className="min-h-screen bg-white dark:bg-gray-900">
{/* Header */}
<header className="sticky top-0 z-50 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800">
<div className="flex items-center justify-between px-6 py-4">
<div className="flex items-center gap-4">
<button
onClick={() => navigate(-1)}
className="flex items-center gap-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors"
>
<ArrowLeft size={20} />
<span className="text-sm">{t('common.back', 'Back')}</span>
</button>
<div className="h-6 w-px bg-gray-200 dark:bg-gray-700" />
<h1 className="text-lg font-semibold text-gray-900 dark:text-white">
Automation Plugins
</h1>
</div>
</div>
</header>
<div className="flex relative">
{/* Sidebar */}
<Sidebar activeSection={activeSection} onSectionClick={handleSectionClick} />
{/* Main Content */}
<main className="flex-1 min-w-0 px-8 lg:px-12">
{/* Introduction */}
<ApiSection id="introduction">
<ApiContent>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mt-0">
Automation Plugins
</h2>
<p className="text-gray-600 dark:text-gray-300">
Automate your business with powerful plugins that run on schedules. Send emails,
generate reports, and create custom workflows - all without writing complex code.
</p>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Key Features
</h3>
<ul className="text-gray-600 dark:text-gray-300">
<li><strong>Safe Scripting</strong> - Write Python-like code with if/else, loops, variables</li>
<li><strong>Flexible Scheduling</strong> - Run daily, weekly, hourly, or on cron schedules</li>
<li><strong>Resource Protected</strong> - Automatic limits prevent abuse</li>
<li><strong>Pre-built Templates</strong> - Start with ready-made plugins</li>
</ul>
</ApiContent>
<ApiExample>
<div className="p-6 bg-gradient-to-br from-blue-50 to-purple-50 dark:from-blue-900/20 dark:to-purple-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
What You Can Automate
</h3>
<ul className="space-y-3 text-gray-700 dark:text-gray-300">
<li className="flex items-start gap-2">
<Zap size={16} className="text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" />
<span>Send weekly summary reports to managers</span>
</li>
<li className="flex items-start gap-2">
<Zap size={16} className="text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" />
<span>Re-engage customers who haven't booked in 60 days</span>
</li>
<li className="flex items-start gap-2">
<Zap size={16} className="text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" />
<span>Alert when bookings are unusually low</span>
</li>
<li className="flex items-start gap-2">
<Zap size={16} className="text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" />
<span>Send birthday wishes with discount codes</span>
</li>
<li className="flex items-start gap-2">
<Zap size={16} className="text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" />
<span>Generate custom analytics and export data</span>
</li>
<li className="flex items-start gap-2">
<Zap size={16} className="text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" />
<span>Integrate with external services via webhooks</span>
</li>
</ul>
</div>
</ApiExample>
</ApiSection>
{/* Quick Start */}
<ApiSection id="quick-start">
<ApiContent>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mt-0">
Quick Start
</h2>
<p className="text-gray-600 dark:text-gray-300">
Create your first automation in 3 simple steps:
</p>
<ol className="text-gray-600 dark:text-gray-300">
<li><strong>Write your script</strong> - Use simple Python code to access your data</li>
<li><strong>Set a schedule</strong> - Choose when and how often to run</li>
<li><strong>Activate & monitor</strong> - Your automation runs automatically</li>
</ol>
</ApiContent>
<ApiExample>
<CodeBlock
title="SIMPLE EXAMPLE"
language="python"
code={simplePluginExample}
/>
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg border border-green-200 dark:border-green-800">
<p className="text-sm text-green-800 dark:text-green-200">
<strong>That's it!</strong> This script counts scheduled appointments and logs the result.
It runs automatically on your schedule.
</p>
</div>
</ApiExample>
</ApiSection>
{/* How It Works */}
<ApiSection id="how-it-works">
<ApiContent>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mt-0">
How It Works
</h2>
<p className="text-gray-600 dark:text-gray-300">
Your scripts run in a secure sandbox with access to your business data through
a simple API object.
</p>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Available API Methods
</h3>
<ul className="text-gray-600 dark:text-gray-300 text-sm">
<li><code>api.get_appointments()</code> - Get your appointments</li>
<li><code>api.get_customers()</code> - Get your customers</li>
<li><code>api.send_email()</code> - Send emails</li>
<li><code>api.create_appointment()</code> - Create appointments</li>
<li><code>api.log()</code> - Debug logging</li>
</ul>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Execution Flow
</h3>
<ol className="text-gray-600 dark:text-gray-300 text-sm">
<li>Script is validated for safety</li>
<li>Loop guards injected to prevent infinite loops</li>
<li>Executed with resource limits (30s timeout, 50 API calls)</li>
<li>Results logged for audit trail</li>
<li>Next run automatically scheduled</li>
</ol>
</ApiContent>
<ApiExample>
<CodeBlock
title="EXECUTION FLOW"
language="python"
code={`# 1. Your script starts
appointments = api.get_appointments(status='SCHEDULED')
# 2. Loop protection active
for apt in appointments: # ← Monitored for infinite loops
api.log(f"Processing: {apt['title']}")
# 3. Resource limits enforced
# - Max 30 seconds execution time
# - Max 50 API calls
# - Max 10,000 loop iterations
# 4. Result returned
result = {'processed': len(appointments)}
# 5. Execution logged and next run scheduled`}
/>
</ApiExample>
</ApiSection>
{/* Built-in Plugins */}
<ApiSection id="builtin-plugins">
<ApiContent>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mt-0">
Built-in Plugins
</h2>
<p className="text-gray-600 dark:text-gray-300">
Ready-to-use plugins for common business tasks. Just configure and activate.
</p>
<AttributeTable
attributes={[
{ name: 'client_reengagement', type: 'marketing', description: 'Reach out to customers who haven\'t booked recently with personalized discount codes.' },
{ name: 'daily_report', type: 'reporting', description: 'Generate and email daily summary reports with appointment stats and metrics.' },
{ name: 'low_show_rate_alert', type: 'monitoring', description: 'Monitor cancellation rates and alert when they exceed your threshold.' },
{ name: 'webhook', type: 'integration', description: 'Call external APIs and integrate with third-party services.' },
]}
/>
</ApiContent>
<ApiExample>
<div className="p-6 bg-gradient-to-br from-purple-50 to-pink-50 dark:from-purple-900/20 dark:to-pink-900/20 rounded-lg border border-purple-200 dark:border-purple-800">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
How to Use Built-in Plugins
</h3>
<ol className="space-y-3 text-sm text-gray-700 dark:text-gray-300">
<li className="flex items-start gap-2">
<span className="text-purple-600 dark:text-purple-400 font-semibold">1.</span>
<span>Navigate to Automation in your dashboard</span>
</li>
<li className="flex items-start gap-2">
<span className="text-purple-600 dark:text-purple-400 font-semibold">2.</span>
<span>Click "Create New Task"</span>
</li>
<li className="flex items-start gap-2">
<span className="text-purple-600 dark:text-purple-400 font-semibold">3.</span>
<span>Select a built-in plugin from the dropdown</span>
</li>
<li className="flex items-start gap-2">
<span className="text-purple-600 dark:text-purple-400 font-semibold">4.</span>
<span>Configure the plugin settings</span>
</li>
<li className="flex items-start gap-2">
<span className="text-purple-600 dark:text-purple-400 font-semibold">5.</span>
<span>Set your schedule and activate</span>
</li>
</ol>
</div>
</ApiExample>
</ApiSection>
{/* Custom Scripts */}
<ApiSection id="custom-scripts">
<ApiContent>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mt-0">
Custom Scripts
</h2>
<p className="text-gray-600 dark:text-gray-300">
Write your own automation logic with Python. Access your data, write if/else logic,
use loops, and create powerful custom workflows.
</p>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
What You Can Use
</h3>
<ul className="text-gray-600 dark:text-gray-300 text-sm grid grid-cols-2 gap-2">
<li> if/else statements</li>
<li> for/while loops</li>
<li> Variables & lists</li>
<li> Dictionaries</li>
<li> String formatting</li>
<li> Math operations</li>
</ul>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
What's Blocked
</h3>
<ul className="text-gray-600 dark:text-gray-300 text-sm grid grid-cols-2 gap-2">
<li>✗ import statements</li>
<li>✗ File system access</li>
<li>✗ eval/exec</li>
<li>✗ Network access*</li>
</ul>
<p className="text-xs text-gray-500 dark:text-gray-400">
*Except approved API calls via api.http_get()
</p>
</ApiContent>
<ApiExample>
<CodeBlock
title="CUSTOM SCRIPT EXAMPLE"
language="python"
code={`# Get last 7 days of appointments
from datetime import datetime, timedelta
end = datetime.now().strftime('%Y-%m-%d')
start = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d')
appointments = api.get_appointments(
start_date=start,
end_date=end
)
# Group by status
stats = {}
for apt in appointments:
status = apt['status']
stats[status] = stats.get(status, 0) + 1
# Build report
report = f"Weekly Report ({start} to {end})\\n\\n"
for status, count in stats.items():
report += f"{status}: {count}\\n"
report += f"\\nTotal: {len(appointments)}"
# Email it
api.send_email(
to='manager@business.com',
subject='Weekly Report',
body=report
)
result = {'total': len(appointments), 'by_status': stats}`}
/>
</ApiExample>
</ApiSection>
{/* Template Variables */}
<ApiSection id="template-variables">
<ApiContent>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mt-0">
Template Variables
</h2>
<p className="text-gray-600 dark:text-gray-300">
Make your plugins reusable and shareable with powerful template variables. The system supports
four types of templates for maximum flexibility.
</p>
{/* 1. PROMPT Variables */}
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mt-6">
1. User Input (PROMPT)
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
Prompt users for custom values during plugin installation. The system automatically
generates a configuration form based on your template variables.
</p>
<div className="mt-2 space-y-2 text-sm">
<div>
<code className="text-purple-600 dark:text-purple-400">{'{{PROMPT:variable|description}}'}</code>
<span className="text-gray-500 ml-2">- Required field</span>
</div>
<div>
<code className="text-purple-600 dark:text-purple-400">{'{{PROMPT:variable|description|default}}'}</code>
<span className="text-gray-500 ml-2">- Optional field with default value</span>
</div>
</div>
<div className="mt-3 p-3 bg-blue-50 dark:bg-blue-900/20 rounded border border-blue-200 dark:border-blue-800">
<p className="text-sm text-blue-800 dark:text-blue-200">
<strong>Example:</strong> <code className="text-purple-600">{'{{PROMPT:manager_email|Manager email address}}'}</code>
</p>
<p className="text-sm text-blue-800 dark:text-blue-200 mt-1">
<strong>With default:</strong> <code className="text-purple-600">{'{{PROMPT:discount|Discount code|SAVE20}}'}</code>
</p>
</div>
{/* 2. CONTEXT Variables */}
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mt-6">
2. Business Context (CONTEXT)
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
Automatically filled with business information from the system. No user input required!
</p>
<div className="mt-2 space-y-1 text-sm font-mono">
<div className="text-green-600 dark:text-green-400">✓ {'{{CONTEXT:business_name}}'} - Business name</div>
<div className="text-green-600 dark:text-green-400">✓ {'{{CONTEXT:owner_email}}'} - Owner email</div>
<div className="text-green-600 dark:text-green-400">✓ {'{{CONTEXT:owner_name}}'} - Owner full name</div>
<div className="text-green-600 dark:text-green-400">✓ {'{{CONTEXT:contact_email}}'} - Contact email</div>
<div className="text-green-600 dark:text-green-400">✓ {'{{CONTEXT:phone}}'} - Business phone</div>
</div>
<div className="mt-3 p-3 bg-green-50 dark:bg-green-900/20 rounded border border-green-200 dark:border-green-800">
<p className="text-sm text-green-800 dark:text-green-200">
<strong>Benefit:</strong> Users don't need to manually enter data that's already in the system!
</p>
</div>
{/* 3. DATE Helpers */}
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mt-6">
3. Date Helpers (DATE)
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
Pre-calculated dates without writing datetime code. All dates return YYYY-MM-DD format.
</p>
<div className="mt-2 space-y-1 text-sm font-mono">
<div className="text-blue-600 dark:text-blue-400">{'{{DATE:today}}'} or {'{{DATE:now}}'} - Current date</div>
<div className="text-blue-600 dark:text-blue-400">{'{{DATE:tomorrow}}'} - Tomorrow's date</div>
<div className="text-blue-600 dark:text-blue-400">{'{{DATE:yesterday}}'} - Yesterday's date</div>
<div className="text-blue-600 dark:text-blue-400">{'{{DATE:+7d}}'} - 7 days from now</div>
<div className="text-blue-600 dark:text-blue-400">{'{{DATE:-30d}}'} - 30 days ago</div>
<div className="text-blue-600 dark:text-blue-400">{'{{DATE:+2w}}'} - 2 weeks from now</div>
<div className="text-blue-600 dark:text-blue-400">{'{{DATE:monday}}'} - Next Monday</div>
<div className="text-blue-600 dark:text-blue-400">{'{{DATE:friday}}'} - Next Friday</div>
</div>
{/* 4. Validation & Types */}
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mt-6">
4. Automatic Validation
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
The system automatically detects field types and validates input:
</p>
<div className="mt-2 space-y-1 text-sm">
<div><strong>Email:</strong> Variables with "email" in name/description are validated</div>
<div><strong>Number:</strong> Variables with "count", "days", "hours", "limit" are numeric</div>
<div><strong>URL:</strong> Variables with "url", "webhook", "endpoint" are validated</div>
<div><strong>Textarea:</strong> Variables with "message", "body", "content" use multi-line input</div>
</div>
<div className="mt-6 p-4 bg-purple-50 dark:bg-purple-900/20 rounded-lg border border-purple-200 dark:border-purple-800">
<p className="text-sm text-purple-800 dark:text-purple-200">
<strong>Pro Tip:</strong> Combine all template types for maximum power! Use CONTEXT
for business info, DATE for time logic, and PROMPT only when user input is truly needed.
</p>
</div>
</ApiContent>
<ApiExample>
{/* Visual Mockup of Configuration Form */}
<div className="mb-6 p-6 bg-white dark:bg-gray-800 rounded-lg border-2 border-indigo-200 dark:border-indigo-800 shadow-lg">
<div className="flex items-center justify-between mb-4 pb-3 border-b border-gray-200 dark:border-gray-700">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Configure Plugin
</h3>
<span className="text-xs text-gray-500 dark:text-gray-400">Step 2 of 3</span>
</div>
<div className="space-y-4">
{/* Field 1: Required, no default */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Days Inactive <span className="text-red-500">*</span>
</label>
<p className="text-xs text-gray-500 dark:text-gray-400 mb-2">
Number of days before a customer is considered inactive
</p>
<input
type="number"
placeholder="Enter days inactive"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white text-sm"
value=""
readOnly
/>
</div>
{/* Field 2: Optional with default */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Discount Code
</label>
<p className="text-xs text-gray-500 dark:text-gray-400 mb-2">
Promotional discount code to offer
</p>
<input
type="text"
placeholder="Enter discount code"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white text-sm"
value="SAVE20"
readOnly
/>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1 italic">
Default: SAVE20
</p>
</div>
{/* Field 3: Email type (auto-detected) */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Manager Email <span className="text-red-500">*</span>
</label>
<p className="text-xs text-gray-500 dark:text-gray-400 mb-2">
Email address for reports and notifications
</p>
<input
type="email"
placeholder="manager@example.com"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white text-sm"
value=""
readOnly
/>
<p className="text-xs text-green-600 dark:text-green-400 mt-1 flex items-center gap-1">
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd"/>
</svg>
Email validation enabled
</p>
</div>
{/* Field 4: Textarea (auto-detected) */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Email Message
</label>
<p className="text-xs text-gray-500 dark:text-gray-400 mb-2">
Custom message to include in re-engagement emails
</p>
<textarea
rows={3}
placeholder="Enter email message"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white text-sm resize-none"
value="We miss you! Come back and save 20%"
readOnly
/>
</div>
</div>
{/* Info Box */}
<div className="mt-4 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
<p className="text-xs text-blue-800 dark:text-blue-200">
<strong>Auto-filled values:</strong> business_name, owner_email, phone
(no input needed)
</p>
</div>
{/* Buttons */}
<div className="mt-6 flex gap-3">
<button className="flex-1 px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg text-sm font-medium hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors">
Back
</button>
<button className="flex-1 px-4 py-2 bg-indigo-600 text-white rounded-lg text-sm font-medium hover:bg-indigo-700 transition-colors">
Install Plugin →
</button>
</div>
</div>
<CodeBlock
title="PLUGIN CODE WITH PROMPT VARIABLES"
language="python"
code={`# The plugin code that generates the form above
# Required field (no default)
days = {{PROMPT:days_inactive|Number of days before a customer is considered inactive}}
# Optional field (has default)
discount = {{PROMPT:discount_code|Promotional discount code to offer|SAVE20}}
# Email type (auto-detected and validated)
manager = {{PROMPT:manager_email|Email address for reports and notifications}}
# Textarea type (auto-detected)
custom_msg = {{PROMPT:email_message|Custom message to include in re-engagement emails}}
# Context variables (auto-filled, no form needed)
business = {{CONTEXT:business_name}}
owner = {{CONTEXT:owner_email}}
phone = {{CONTEXT:phone}}`}
/>
<CodeBlock
title="ALL TEMPLATE TYPES EXAMPLE"
language="python"
code={`# Advanced re-engagement plugin with all template types
from datetime import datetime, timedelta
# DATE helper - no code needed!
today = {{DATE:today}}
cutoff_date = {{DATE:-60d}} # 60 days ago
# PROMPT with default value
days = {{PROMPT:days_inactive|Days before inactive|60}}
discount = {{PROMPT:discount_code|Promotional code|SAVE20}}
# Get inactive customers
customers = api.get_customers(has_email=True)
# Send personalized emails
for customer in customers[:30]:
message = f'''Hi {customer['name']},
We miss you at {{CONTEXT:business_name}}!
Come back before {{DATE:+7d}} and use code {discount}
for 20% off your next appointment.
Questions? Call {{CONTEXT:phone}} or email {{CONTEXT:contact_email}}
Best regards,
{{CONTEXT:owner_name}}'''
api.send_email(
to=customer['email'],
subject={{PROMPT:subject|Email subject|We Miss You!}},
body=message
)
result = {'sent': 30, 'cutoff': cutoff_date}`}
/>
<div className="mt-4 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
<h3 className="font-semibold text-blue-900 dark:text-blue-200 mb-2">
What Gets Replaced
</h3>
<div className="space-y-2 text-sm">
<div className="grid grid-cols-2 gap-2">
<div className="text-blue-700 dark:text-blue-300 font-mono text-xs">{'{{CONTEXT:business_name}}'}</div>
<div className="text-blue-800 dark:text-blue-200">→ "Acme Spa" (auto-filled)</div>
</div>
<div className="grid grid-cols-2 gap-2">
<div className="text-blue-700 dark:text-blue-300 font-mono text-xs">{'{{DATE:today}}'}</div>
<div className="text-blue-800 dark:text-blue-200">→ "2025-01-15" (calculated)</div>
</div>
<div className="grid grid-cols-2 gap-2">
<div className="text-blue-700 dark:text-blue-300 font-mono text-xs">{'{{DATE:-60d}}'}</div>
<div className="text-blue-800 dark:text-blue-200">→ "2024-11-16" (60 days ago)</div>
</div>
<div className="grid grid-cols-2 gap-2">
<div className="text-blue-700 dark:text-blue-300 font-mono text-xs">{'{{PROMPT:discount_code|...|SAVE20}}'}</div>
<div className="text-blue-800 dark:text-blue-200">→ "SAVE20" (default used)</div>
</div>
</div>
</div>
<div className="mt-4 space-y-3">
<div className="p-3 bg-green-50 dark:bg-green-900/20 rounded border border-green-200 dark:border-green-800">
<h4 className="font-semibold text-green-900 dark:text-green-200 text-sm mb-1">
Benefits of CONTEXT Variables
</h4>
<ul className="text-xs text-green-800 dark:text-green-200 space-y-1">
<li>✓ No user input required - fully automatic</li>
<li>✓ Always up-to-date with current business info</li>
<li>✓ Prevents typos in business name/email</li>
<li>✓ Faster plugin installation</li>
</ul>
</div>
<div className="p-3 bg-orange-50 dark:bg-orange-900/20 rounded border border-orange-200 dark:border-orange-800">
<h4 className="font-semibold text-orange-900 dark:text-orange-200 text-sm mb-1">
Benefits of DATE Helpers
</h4>
<ul className="text-xs text-orange-800 dark:text-orange-200 space-y-1">
<li>✓ No datetime imports or code needed</li>
<li>✓ Simpler, more readable scripts</li>
<li>✓ Consistent date formatting (YYYY-MM-DD)</li>
<li>✓ Human-friendly expressions (+7d, monday)</li>
</ul>
</div>
<div className="p-3 bg-purple-50 dark:bg-purple-900/20 rounded border border-purple-200 dark:border-purple-800">
<h4 className="font-semibold text-purple-900 dark:text-purple-200 text-sm mb-1">
Benefits of Default Values
</h4>
<ul className="text-xs text-purple-800 dark:text-purple-200 space-y-1">
<li>✓ Faster setup with pre-filled sensible defaults</li>
<li>✓ Users can accept defaults or customize</li>
<li>✓ Makes fields optional instead of required</li>
<li>✓ Great for expert/beginner modes</li>
</ul>
</div>
</div>
</ApiExample>
</ApiSection>
{/* API Methods */}
<ApiSection id="api-methods">
<ApiContent>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mt-0">
API Methods Reference
</h2>
<p className="text-gray-600 dark:text-gray-300">
All scripts have access to the <code>api</code> object with these methods:
</p>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mt-4">
Business Data Methods
</h3>
<AttributeTable
attributes={[
{ name: 'api.get_appointments(**filters)', type: 'method', description: 'Retrieve appointments for your business. Filters: status, start_date, end_date, limit (max 1000)' },
{ name: 'api.get_customers(**filters)', type: 'method', description: 'Retrieve customers for your business. Filters: has_email, limit (max 1000)' },
{ name: 'api.send_email(to, subject, body)', type: 'method', description: 'Send an email to a customer. Parameters: to (email or customer ID), subject (max 200 chars), body (max 10,000 chars)' },
{ name: 'api.create_appointment(...)', type: 'method', description: 'Create a new appointment. Parameters: title, start_time, end_time, notes (optional)' },
{ name: 'api.log(message)', type: 'method', description: 'Log a message for debugging. Visible in execution logs.' },
]}
/>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mt-6">
External HTTP Methods (Whitelist Required)
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm mb-3">
All external HTTP requests require URL whitelisting. See warning below for details.
</p>
<AttributeTable
attributes={[
{ name: 'api.http_get(url, headers=None)', type: 'HTTP GET', description: 'Fetch data from external API. Returns response text. Timeout: 10 seconds.' },
{ name: 'api.http_post(url, data=None, headers=None)', type: 'HTTP POST', description: 'Send data to external API. Accepts JSON or form data. Returns response text.' },
{ name: 'api.http_put(url, data=None, headers=None)', type: 'HTTP PUT', description: 'Update resource on external API. Accepts JSON or form data. Returns response text.' },
{ name: 'api.http_patch(url, data=None, headers=None)', type: 'HTTP PATCH', description: 'Partially update resource on external API. Accepts JSON or form data. Returns response text.' },
{ name: 'api.http_delete(url, headers=None)', type: 'HTTP DELETE', description: 'Delete resource on external API. Returns response text.' },
]}
/>
<div className="mt-6 p-4 bg-red-50 dark:bg-red-900/20 rounded-lg border-2 border-red-200 dark:border-red-800">
<h3 className="font-semibold text-red-900 dark:text-red-200 mb-2 flex items-center gap-2">
<AlertCircle className="w-5 h-5" />
Important: URL Whitelisting Required
</h3>
<p className="text-sm text-red-800 dark:text-red-200 mb-3">
If your plugin makes HTTP requests to external APIs (using <code>api.http_get()</code>,
<code>api.http_post()</code>, or any other HTTP method), all URLs must be
whitelisted <strong>before</strong> you upload your plugin to the marketplace.
</p>
<div className="text-sm text-red-800 dark:text-red-200 space-y-2">
<p><strong>To request URL whitelisting:</strong></p>
<ol className="list-decimal list-inside space-y-1 ml-2">
<li>Contact support at <a href="mailto:pluginaccess@smoothschedule.com" className="underline font-mono">pluginaccess@smoothschedule.com</a></li>
<li>Include the exact URL(s) you need whitelisted</li>
<li>Specify which HTTP methods you need (GET, POST, PUT, DELETE, etc.)</li>
<li>Explain why each URL is safe and necessary for your plugin</li>
<li>Provide an <strong>exact copy</strong> of the code you intend to upload</li>
</ol>
<div className="mt-3 p-3 bg-red-100 dark:bg-red-900/40 rounded border border-red-300 dark:border-red-700">
<p className="font-semibold">⚠️ Code Verification</p>
<p className="mt-1">
If you change your plugin code after it's been whitelisted, it will <strong>fail to upload</strong>.
The system verifies that uploaded code matches the whitelisted version exactly.
</p>
</div>
</div>
</div>
</ApiContent>
<ApiExample>
<CodeBlock
title="BUSINESS DATA EXAMPLES"
language="python"
code={`# Get appointments
appointments = api.get_appointments(
status='SCHEDULED',
start_date='2025-01-01',
limit=50
)
# Get customers with emails
customers = api.get_customers(has_email=True)
# Send email
api.send_email(
to='customer@example.com',
subject='Hello!',
body='Thanks for being a great customer!'
)
# Create appointment
apt = api.create_appointment(
title='Follow-up',
start_time='2025-02-01T10:00:00',
end_time='2025-02-01T11:00:00',
notes='Auto-created'
)
# Log for debugging
api.log(f'Processed {len(appointments)} appointments')`}
/>
<CodeBlock
title="HTTP METHODS EXAMPLES"
language="python"
code={`# HTTP GET - Fetch data from external API
response = api.http_get(
url='https://api.example.com/data',
headers={'Authorization': 'Bearer token123'}
)
# HTTP POST - Send data to webhook
result = api.http_post(
url='https://hooks.slack.com/webhook',
data={'text': 'New appointment scheduled!'},
headers={'Content-Type': 'application/json'}
)
# HTTP PUT - Update resource
api.http_put(
url='https://api.example.com/resource/123',
data={'status': 'completed'},
headers={'Authorization': 'Bearer token123'}
)
# HTTP PATCH - Partial update
api.http_patch(
url='https://api.example.com/resource/123',
data={'notes': 'Updated notes'},
headers={'Authorization': 'Bearer token123'}
)
# HTTP DELETE - Remove resource
api.http_delete(
url='https://api.example.com/resource/123',
headers={'Authorization': 'Bearer token123'}
)
# All HTTP methods require whitelisting!
# See warning below for approval process.`}
/>
</ApiExample>
</ApiSection>
{/* Command Reference */}
<ApiSection id="command-reference">
<ApiContent>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mt-0">
Command Reference
</h2>
<p className="text-gray-600 dark:text-gray-300">
Scripts run in a secure sandbox with a restricted set of Python commands.
This prevents malicious code while giving you the power to write useful automations.
</p>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Allowed Control Flow & Operators
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
All standard Python control flow statements and operators are supported:
</p>
<div className="grid grid-cols-2 gap-2 text-sm font-mono mb-4">
<div className="text-green-600 dark:text-green-400"> if / elif / else</div>
<div className="text-green-600 dark:text-green-400"> for loops</div>
<div className="text-green-600 dark:text-green-400"> while loops</div>
<div className="text-green-600 dark:text-green-400"> break / continue</div>
<div className="text-green-600 dark:text-green-400"> try / except / finally</div>
<div className="text-green-600 dark:text-green-400"> with statements</div>
<div className="text-green-600 dark:text-green-400"> and / or / not</div>
<div className="text-green-600 dark:text-green-400"> in / is / ==</div>
<div className="text-green-600 dark:text-green-400"> +, -, *, /, //, %</div>
<div className="text-green-600 dark:text-green-400"> &lt;, &gt;, &lt;=, &gt;=, !=</div>
<div className="text-green-600 dark:text-green-400"> List comprehensions</div>
<div className="text-green-600 dark:text-green-400"> Dict comprehensions</div>
</div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Allowed Built-in Functions
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
These Python built-in functions are available in your scripts:
</p>
<div className="grid grid-cols-2 gap-2 text-sm font-mono">
<div className="text-green-600 dark:text-green-400"> len()</div>
<div className="text-green-600 dark:text-green-400"> range()</div>
<div className="text-green-600 dark:text-green-400"> min()</div>
<div className="text-green-600 dark:text-green-400"> max()</div>
<div className="text-green-600 dark:text-green-400"> sum()</div>
<div className="text-green-600 dark:text-green-400"> abs()</div>
<div className="text-green-600 dark:text-green-400"> round()</div>
<div className="text-green-600 dark:text-green-400"> int()</div>
<div className="text-green-600 dark:text-green-400"> float()</div>
<div className="text-green-600 dark:text-green-400"> str()</div>
<div className="text-green-600 dark:text-green-400"> bool()</div>
<div className="text-green-600 dark:text-green-400"> list()</div>
<div className="text-green-600 dark:text-green-400"> dict()</div>
<div className="text-green-600 dark:text-green-400"> enumerate()</div>
<div className="text-green-600 dark:text-green-400"> zip()</div>
<div className="text-green-600 dark:text-green-400"> sorted()</div>
<div className="text-green-600 dark:text-green-400"> reversed()</div>
<div className="text-green-600 dark:text-green-400"> any()</div>
<div className="text-green-600 dark:text-green-400"> all()</div>
</div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mt-6">
Blocked Operations
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
These operations are disabled for security:
</p>
<div className="grid grid-cols-2 gap-2 text-sm font-mono">
<div className="text-red-600 dark:text-red-400"> import statements</div>
<div className="text-red-600 dark:text-red-400"> exec()</div>
<div className="text-red-600 dark:text-red-400"> eval()</div>
<div className="text-red-600 dark:text-red-400"> compile()</div>
<div className="text-red-600 dark:text-red-400"> __import__()</div>
<div className="text-red-600 dark:text-red-400"> class definitions</div>
<div className="text-red-600 dark:text-red-400"> function definitions</div>
<div className="text-red-600 dark:text-red-400"> file operations</div>
</div>
<div className="mt-4 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
<p className="text-sm text-blue-800 dark:text-blue-200">
<strong>Note:</strong> While you cannot use <code>import</code>,
the <code>datetime</code> module is available for use in your scripts
via <code>from datetime import datetime, timedelta</code>. This is
pre-imported in the execution context.
</p>
</div>
</ApiContent>
<ApiExample>
<div className="space-y-4">
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg border border-green-200 dark:border-green-800">
<h3 className="font-semibold text-green-900 dark:text-green-200 mb-2">
Allowed: Basic Python Operations
</h3>
<CodeBlock
language="python"
code={`# ✓ Variables and data structures
count = 0
my_list = [1, 2, 3]
my_dict = {'key': 'value'}
# ✓ Loops and conditionals
for item in my_list:
if item > 1:
count += 1
# ✓ List comprehensions
filtered = [x for x in my_list if x > 1]
# ✓ String operations
message = f"Count: {count}"
# ✓ Math operations
total = sum([1, 2, 3])
avg = total / len(my_list)`}
/>
</div>
<div className="p-4 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:border-red-800">
<h3 className="font-semibold text-red-900 dark:text-red-200 mb-2">
Blocked: Dangerous Operations
</h3>
<CodeBlock
language="python"
code={`# ✗ Import statements
import os # Error!
# ✗ Code execution
eval('1 + 1') # Error!
exec('print("hi")') # Error!
# ✗ Function definitions
def my_function(): # Error!
pass
# ✗ Class definitions
class MyClass: # Error!
pass`}
/>
</div>
</div>
</ApiExample>
</ApiSection>
{/* Schedule Types */}
<ApiSection id="schedule-types">
<ApiContent>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mt-0">
Schedule Types
</h2>
<p className="text-gray-600 dark:text-gray-300">
Choose when and how often your automation runs.
</p>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Cron Expression
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
Flexible scheduling using cron syntax:
</p>
<ul className="text-gray-600 dark:text-gray-300 text-sm">
<li><code>0 9 * * 1</code> - Every Monday at 9 AM</li>
<li><code>0 0 * * *</code> - Daily at midnight</li>
<li><code>0 */2 * * *</code> - Every 2 hours</li>
</ul>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Fixed Interval
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
Run every N minutes: <code>interval_minutes: 60</code>
</p>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
One-Time
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
Run once at a specific datetime: <code>run_at: "2025-02-01T10:00:00Z"</code>
</p>
</ApiContent>
<ApiExample>
<div className="p-6 bg-gradient-to-br from-green-50 to-teal-50 dark:from-green-900/20 dark:to-teal-900/20 rounded-lg border border-green-200 dark:border-green-800">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
Common Schedule Examples
</h3>
<div className="space-y-4 text-sm">
<div className="p-3 bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700">
<div className="font-mono text-purple-600 dark:text-purple-400 mb-1">0 9 * * 1</div>
<div className="text-gray-600 dark:text-gray-400">Every Monday at 9:00 AM</div>
</div>
<div className="p-3 bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700">
<div className="font-mono text-purple-600 dark:text-purple-400 mb-1">0 0 * * *</div>
<div className="text-gray-600 dark:text-gray-400">Every day at midnight</div>
</div>
<div className="p-3 bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700">
<div className="font-mono text-purple-600 dark:text-purple-400 mb-1">0 */4 * * *</div>
<div className="text-gray-600 dark:text-gray-400">Every 4 hours</div>
</div>
<div className="p-3 bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700">
<div className="font-mono text-purple-600 dark:text-purple-400 mb-1">Interval: 60 minutes</div>
<div className="text-gray-600 dark:text-gray-400">Every hour</div>
</div>
</div>
</div>
</ApiExample>
</ApiSection>
{/* Manage Tasks */}
<ApiSection id="manage-tasks">
<ApiContent>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mt-0">
Manage Tasks
</h2>
<p className="text-gray-600 dark:text-gray-300">
Create, update, and monitor your scheduled automation tasks through the dashboard.
</p>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Dashboard Actions
</h3>
<ul className="text-gray-600 dark:text-gray-300 text-sm space-y-2">
<li><strong>Create:</strong> Click "New Automation" to create a task</li>
<li><strong>Edit:</strong> Click on any task to modify its settings</li>
<li><strong>Pause/Resume:</strong> Toggle tasks on/off without deleting</li>
<li><strong>Delete:</strong> Remove tasks you no longer need</li>
<li><strong>View Logs:</strong> See execution history and results</li>
<li><strong>Test Run:</strong> Execute manually to test your script</li>
</ul>
</ApiContent>
<ApiExample>
<div className="p-6 bg-gradient-to-br from-indigo-50 to-blue-50 dark:from-indigo-900/20 dark:to-blue-900/20 rounded-lg border border-indigo-200 dark:border-indigo-800">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
Task Management Tips
</h3>
<ul className="space-y-3 text-sm text-gray-700 dark:text-gray-300">
<li className="flex items-start gap-2">
<span className="text-indigo-600 dark:text-indigo-400"></span>
<span>Always test your script with "Run Now" before scheduling</span>
</li>
<li className="flex items-start gap-2">
<span className="text-indigo-600 dark:text-indigo-400"></span>
<span>Check execution logs regularly to catch errors early</span>
</li>
<li className="flex items-start gap-2">
<span className="text-indigo-600 dark:text-indigo-400"></span>
<span>Pause tasks during maintenance or high-traffic periods</span>
</li>
<li className="flex items-start gap-2">
<span className="text-indigo-600 dark:text-indigo-400"></span>
<span>Use descriptive names to identify tasks at a glance</span>
</li>
</ul>
</div>
</ApiExample>
</ApiSection>
{/* Real-World Example: Re-engagement */}
<ApiSection id="example-reengagement">
<ApiContent>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mt-0">
Example: Win Back Lost Customers
</h2>
<p className="text-gray-600 dark:text-gray-300">
Automatically identify customers who haven't booked in 60 days and send them
a personalized discount code to win them back.
</p>
<div className="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
<h3 className="font-semibold text-gray-900 dark:text-white mb-2">
Expected Results
</h3>
<ul className="text-sm text-gray-700 dark:text-gray-300">
<li>• 15-20% of contacted customers return</li>
<li>• $3,000-5,000/month in recovered revenue</li>
<li>• Runs automatically every Monday</li>
</ul>
</div>
</ApiContent>
<ApiExample>
<CodeBlock
title="RE-ENGAGEMENT SCRIPT"
language="python"
code={reengagementExample}
/>
</ApiExample>
</ApiSection>
{/* Real-World Example: Alerts */}
<ApiSection id="example-alerts">
<ApiContent>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mt-0">
Example: Low Booking Alerts
</h2>
<p className="text-gray-600 dark:text-gray-300">
Get notified when upcoming bookings are unusually low so you can take action
before it impacts revenue.
</p>
<div className="p-4 bg-orange-50 dark:bg-orange-900/20 rounded-lg border border-orange-200 dark:border-orange-800">
<h3 className="font-semibold text-gray-900 dark:text-white mb-2">
Use Case
</h3>
<p className="text-sm text-gray-700 dark:text-gray-300">
Spa manager gets daily email if next week has less than 10 bookings,
prompting them to run a promotion or send reminders to past customers.
</p>
</div>
</ApiContent>
<ApiExample>
<CodeBlock
title="BOOKING ALERT SCRIPT"
language="python"
code={alertExample}
/>
</ApiExample>
</ApiSection>
{/* Example: Weekly Reports */}
<ApiSection id="example-reports">
<ApiContent>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mt-0">
Example: Weekly Reports
</h2>
<p className="text-gray-600 dark:text-gray-300">
Automatically generate and email weekly summary reports with appointment statistics.
</p>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
What It Does
</h3>
<ul className="text-gray-600 dark:text-gray-300 text-sm">
<li>Counts appointments by status (scheduled, completed, canceled)</li>
<li>Calculates revenue from completed appointments</li>
<li>Emails formatted report to manager</li>
<li>Runs every Monday at 9 AM</li>
</ul>
</ApiContent>
<ApiExample>
<CodeBlock
title="WEEKLY REPORT SCRIPT"
language="python"
code={`# Get last 7 days of appointments
from datetime import datetime, timedelta
end = datetime.now().strftime('%Y-%m-%d')
start = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d')
appointments = api.get_appointments(
start_date=start,
end_date=end
)
# Group by status
stats = {}
for apt in appointments:
status = apt['status']
stats[status] = stats.get(status, 0) + 1
# Build report
report = f'''Weekly Report ({start} to {end})
Scheduled: {stats.get('SCHEDULED', 0)}
Completed: {stats.get('COMPLETED', 0)}
Canceled: {stats.get('CANCELED', 0)}
Total: {len(appointments)}'''
# Email it
api.send_email(
to='manager@business.com',
subject='Weekly Report',
body=report
)
result = {'total': len(appointments), 'by_status': stats}`}
/>
</ApiExample>
</ApiSection>
{/* Safety Features */}
<ApiSection id="safety">
<ApiContent>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mt-0">
Safety Features
</h2>
<p className="text-gray-600 dark:text-gray-300">
Multiple layers of protection ensure your data and our servers stay safe.
</p>
<AttributeTable
attributes={[
{ name: 'Sandboxed Execution', type: 'security', description: 'Scripts run in isolated environments with no access to system resources' },
{ name: 'Code Validation', type: 'security', description: 'All scripts validated before execution to prevent dangerous operations' },
{ name: 'Data Isolation', type: 'security', description: 'Each script only accesses its own business\'s data - complete multi-tenant isolation' },
{ name: 'No File Access', type: 'security', description: 'Scripts cannot read or write files on the server' },
{ name: 'No Code Injection', type: 'security', description: 'eval() and exec() are blocked to prevent code injection attacks' },
]}
/>
</ApiContent>
<ApiExample>
<div className="space-y-4">
<div className="p-4 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:border-red-800">
<h3 className="font-semibold text-red-900 dark:text-red-200 mb-2">
Blocked: File System Access
</h3>
<CodeBlock
language="python"
code={`# ❌ This will fail
open('/etc/passwd', 'r') # Error!
import os # Error!`}
/>
</div>
<div className="p-4 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:border-red-800">
<h3 className="font-semibold text-red-900 dark:text-red-200 mb-2">
Blocked: Code Injection
</h3>
<CodeBlock
language="python"
code={`# ❌ This will fail
eval('1 + 1') # Error!
exec('print("hi")') # Error!`}
/>
</div>
</div>
</ApiExample>
</ApiSection>
{/* Plugin Licensing */}
<ApiSection id="licensing">
<ApiContent>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mt-0">
Plugin Licensing
</h2>
<p className="text-gray-600 dark:text-gray-300">
Understand the licensing terms for plugins you create and share on SmoothSchedule.
</p>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Private Plugins
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
Plugins you create for personal use remain <strong>private</strong>. You retain full ownership and control:
</p>
<ul className="text-gray-600 dark:text-gray-300 text-sm">
<li>Only accessible within your business account</li>
<li>Not visible in the public marketplace</li>
<li>You choose any license you want (or none)</li>
<li>SmoothSchedule can execute your code to provide the service</li>
</ul>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Marketplace Plugins (Public Sharing)
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
When you <strong>publish a plugin to the marketplace</strong>, you agree to the{' '}
<span className="font-semibold">SmoothSchedule Community Plugin License</span>:
</p>
<div className="bg-purple-50 dark:bg-purple-900/20 border-2 border-purple-200 dark:border-purple-800 rounded-lg p-4 my-4">
<h4 className="font-semibold text-purple-900 dark:text-purple-100 mb-2">
SmoothSchedule Community Plugin License (SCPL)
</h4>
<div className="text-sm text-purple-800 dark:text-purple-200 space-y-2">
<p>
<strong>You grant the following rights:</strong>
</p>
<ol className="ml-4 space-y-1">
<li>
<strong>SmoothSchedule Rights:</strong> SmoothSchedule may use, execute, host, and distribute your plugin code
to provide the marketplace service
</li>
<li>
<strong>User Rights:</strong> Other SmoothSchedule users may install, use, and modify your plugin
for their own business purposes
</li>
<li>
<strong>Attribution:</strong> Your authorship will be credited in the marketplace listing
</li>
<li>
<strong>Modifications:</strong> Users may adapt your code to their needs, but cannot republish
modified versions to the marketplace without your permission
</li>
<li>
<strong>Revocation:</strong> You may unpublish your plugin at any time. Existing installations
will continue to function, but new installs will be disabled
</li>
</ol>
<p className="mt-3">
<strong>You retain:</strong> Copyright ownership, ability to license elsewhere, right to unpublish
</p>
<p className="mt-2 text-xs italic">
Similar to: MIT License + SmoothSchedule Service Rights
</p>
</div>
</div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Code Verification & Security
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
When you upload a plugin (private or marketplace):
</p>
<ul className="text-gray-600 dark:text-gray-300 text-sm">
<li>Original code is stored for security verification</li>
<li>Changing code after URL whitelisting will fail upload</li>
<li>SmoothSchedule may review code for security compliance</li>
<li>Malicious code will result in account suspension</li>
</ul>
</ApiContent>
<ApiExample>
<div className="space-y-4">
{/* Comparison Table */}
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
<div className="p-4 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700">
<h3 className="font-semibold text-gray-900 dark:text-white">
Private vs. Marketplace Plugins
</h3>
</div>
<div className="divide-y divide-gray-200 dark:divide-gray-700">
<div className="grid grid-cols-3 gap-4 p-3 text-xs">
<div className="font-semibold text-gray-700 dark:text-gray-300">Feature</div>
<div className="font-semibold text-gray-700 dark:text-gray-300">Private</div>
<div className="font-semibold text-gray-700 dark:text-gray-300">Marketplace</div>
</div>
<div className="grid grid-cols-3 gap-4 p-3 text-xs">
<div className="text-gray-600 dark:text-gray-400">Visibility</div>
<div className="text-gray-900 dark:text-white">Your business only</div>
<div className="text-gray-900 dark:text-white">All users</div>
</div>
<div className="grid grid-cols-3 gap-4 p-3 text-xs">
<div className="text-gray-600 dark:text-gray-400">License</div>
<div className="text-gray-900 dark:text-white">Any (or none)</div>
<div className="text-gray-900 dark:text-white">SCPL (required)</div>
</div>
<div className="grid grid-cols-3 gap-4 p-3 text-xs">
<div className="text-gray-600 dark:text-gray-400">Ownership</div>
<div className="text-green-600 dark:text-green-400"> You retain all rights</div>
<div className="text-green-600 dark:text-green-400"> You retain copyright</div>
</div>
<div className="grid grid-cols-3 gap-4 p-3 text-xs">
<div className="text-gray-600 dark:text-gray-400">Attribution</div>
<div className="text-gray-900 dark:text-white">N/A</div>
<div className="text-green-600 dark:text-green-400"> Required</div>
</div>
<div className="grid grid-cols-3 gap-4 p-3 text-xs">
<div className="text-gray-600 dark:text-gray-400">Can Unpublish</div>
<div className="text-green-600 dark:text-green-400"> Delete anytime</div>
<div className="text-green-600 dark:text-green-400"> Hide from marketplace</div>
</div>
<div className="grid grid-cols-3 gap-4 p-3 text-xs">
<div className="text-gray-600 dark:text-gray-400">SmoothSchedule Use</div>
<div className="text-green-600 dark:text-green-400"> Execute only</div>
<div className="text-green-600 dark:text-green-400"> Execute + Distribute</div>
</div>
</div>
</div>
{/* Important Notice */}
<div className="bg-blue-50 dark:bg-blue-900/20 border-2 border-blue-200 dark:border-blue-800 rounded-lg p-4">
<h3 className="flex items-center gap-2 font-semibold text-blue-900 dark:text-blue-100 mb-2">
<AlertCircle size={18} />
Important to Know
</h3>
<ul className="text-sm text-blue-800 dark:text-blue-200 space-y-2">
<li>
<strong>SmoothSchedule Service Rights:</strong> By uploading any plugin, you grant SmoothSchedule
the right to execute your code to provide the automation service
</li>
<li>
<strong>No Warranty:</strong> Plugins are provided "as-is" without warranty. Test thoroughly
before deploying to production
</li>
<li>
<strong>Compliance:</strong> You are responsible for ensuring your plugin complies with all
applicable laws and regulations
</li>
<li>
<strong>Data Privacy:</strong> Handle customer data responsibly and in compliance with GDPR,
CCPA, and other privacy laws
</li>
</ul>
</div>
{/* Quick Decision Guide */}
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
<h3 className="font-semibold text-gray-900 dark:text-white mb-3 text-sm">
Should I Publish to Marketplace?
</h3>
<div className="space-y-3 text-xs">
<div>
<div className="font-semibold text-green-700 dark:text-green-400 mb-1"> Publish if:</div>
<ul className="ml-4 space-y-1 text-gray-600 dark:text-gray-400">
<li>You want to help the community</li>
<li>Your plugin solves a common problem</li>
<li>You're comfortable with the SCPL terms</li>
<li>You want recognition as the author</li>
</ul>
</div>
<div>
<div className="font-semibold text-red-700 dark:text-red-400 mb-1">✗ Keep Private if:</div>
<ul className="ml-4 space-y-1 text-gray-600 dark:text-gray-400">
<li>Contains business-specific logic</li>
<li>Uses proprietary algorithms</li>
<li>Integrates with private internal systems</li>
<li>You want to restrict access</li>
</ul>
</div>
</div>
</div>
</div>
</ApiExample>
</ApiSection>
{/* Resource Limits */}
<ApiSection id="limits">
<ApiContent>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mt-0">
Resource Limits
</h2>
<p className="text-gray-600 dark:text-gray-300">
Automatic limits prevent resource abuse and keep the platform fast for everyone.
</p>
<AttributeTable
attributes={[
{ name: 'Execution Time', type: '30 seconds', description: 'Maximum execution time per run' },
{ name: 'API Calls', type: '50 calls', description: 'Maximum API calls per execution' },
{ name: 'Loop Iterations', type: '10,000', description: 'Prevents infinite loops' },
{ name: 'Memory', type: '50 MB', description: 'Maximum memory usage' },
]}
/>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
What Happens When Limits Are Hit?
</h3>
<ol className="text-gray-600 dark:text-gray-300 text-sm">
<li>Script stops gracefully with clear error message</li>
<li>Execution logged with failure status</li>
<li>You receive notification in your dashboard</li>
<li>Task will retry on next scheduled run</li>
</ol>
</ApiContent>
<ApiExample>
<div className="p-6 bg-gradient-to-br from-yellow-50 to-orange-50 dark:from-yellow-900/20 dark:to-orange-900/20 rounded-lg border border-yellow-200 dark:border-yellow-800">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-3">
Optimization Tips
</h3>
<ul className="space-y-2 text-sm text-gray-700 dark:text-gray-300">
<li className="flex items-start gap-2">
<span className="text-orange-600 dark:text-orange-400"></span>
<span>Use filters to reduce data fetched (limit, status, dates)</span>
</li>
<li className="flex items-start gap-2">
<span className="text-orange-600 dark:text-orange-400"></span>
<span>Avoid nested loops where possible</span>
</li>
<li className="flex items-start gap-2">
<span className="text-orange-600 dark:text-orange-400"></span>
<span>Cache API results in variables instead of repeated calls</span>
</li>
<li className="flex items-start gap-2">
<span className="text-orange-600 dark:text-orange-400"></span>
<span>Break large tasks into smaller scheduled tasks</span>
</li>
</ul>
</div>
</ApiExample>
</ApiSection>
{/* Footer padding */}
<div className="h-24" />
</main>
</div>
</div>
);
};
export default HelpPluginDocs;