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>
1876 lines
88 KiB
TypeScript
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">✓ <, >, <=, >=, !=</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;
|