Add event status trigger, improve test coverage, and UI enhancements
- Add event-status-changed trigger for SmoothSchedule Activepieces piece - Add comprehensive test coverage for payments, tickets, messaging, mobile - Add test coverage for core services, signals, consumers, and views - Improve Activepieces UI: templates, billing hooks, project hooks - Update marketing automation showcase and workflow visual components - Add public API endpoints for availability 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -291,6 +291,7 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
|
||||
label={t('nav.automations', 'Automations')}
|
||||
isCollapsed={isCollapsed}
|
||||
locked={!canUse('automations')}
|
||||
badgeElement={<UnfinishedBadge />}
|
||||
/>
|
||||
</SidebarSection>
|
||||
)}
|
||||
|
||||
@@ -1,197 +1,161 @@
|
||||
import React, { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Mail, Calendar, Bell, ArrowRight, Zap, CheckCircle2, Code, LayoutGrid } from 'lucide-react';
|
||||
import { Mail, Calendar, Bell, ArrowRight, Zap, CheckCircle2 } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import CodeBlock from './CodeBlock';
|
||||
import WorkflowVisual from './WorkflowVisual';
|
||||
|
||||
const AutomationShowcase: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const [viewMode, setViewMode] = useState<'marketplace' | 'code'>('marketplace');
|
||||
const { t } = useTranslation();
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
|
||||
const examples = [
|
||||
{
|
||||
id: 'winback',
|
||||
icon: Mail,
|
||||
title: t('marketing.plugins.examples.winback.title'),
|
||||
description: t('marketing.plugins.examples.winback.description'),
|
||||
stats: [t('marketing.plugins.examples.winback.stats.retention'), t('marketing.plugins.examples.winback.stats.revenue')],
|
||||
marketplaceImage: 'bg-gradient-to-br from-pink-500 to-rose-500',
|
||||
code: t('marketing.plugins.examples.winback.code'),
|
||||
},
|
||||
{
|
||||
id: 'noshow',
|
||||
icon: Bell,
|
||||
title: t('marketing.plugins.examples.noshow.title'),
|
||||
description: t('marketing.plugins.examples.noshow.description'),
|
||||
stats: [t('marketing.plugins.examples.noshow.stats.reduction'), t('marketing.plugins.examples.noshow.stats.utilization')],
|
||||
marketplaceImage: 'bg-gradient-to-br from-blue-500 to-cyan-500',
|
||||
code: t('marketing.plugins.examples.noshow.code'),
|
||||
},
|
||||
{
|
||||
id: 'report',
|
||||
icon: Calendar,
|
||||
title: t('marketing.plugins.examples.report.title'),
|
||||
description: t('marketing.plugins.examples.report.description'),
|
||||
stats: [t('marketing.plugins.examples.report.stats.timeSaved'), t('marketing.plugins.examples.report.stats.visibility')],
|
||||
marketplaceImage: 'bg-gradient-to-br from-purple-500 to-indigo-500',
|
||||
code: t('marketing.plugins.examples.report.code'),
|
||||
},
|
||||
];
|
||||
const examples = [
|
||||
{
|
||||
id: 'winback',
|
||||
icon: Mail,
|
||||
title: t('marketing.plugins.examples.winback.title'),
|
||||
description: t('marketing.plugins.examples.winback.description'),
|
||||
stats: [
|
||||
t('marketing.plugins.examples.winback.stats.retention'),
|
||||
t('marketing.plugins.examples.winback.stats.revenue'),
|
||||
],
|
||||
variant: 'winback' as const,
|
||||
},
|
||||
{
|
||||
id: 'noshow',
|
||||
icon: Bell,
|
||||
title: t('marketing.plugins.examples.noshow.title'),
|
||||
description: t('marketing.plugins.examples.noshow.description'),
|
||||
stats: [
|
||||
t('marketing.plugins.examples.noshow.stats.reduction'),
|
||||
t('marketing.plugins.examples.noshow.stats.utilization'),
|
||||
],
|
||||
variant: 'noshow' as const,
|
||||
},
|
||||
{
|
||||
id: 'report',
|
||||
icon: Calendar,
|
||||
title: t('marketing.plugins.examples.report.title'),
|
||||
description: t('marketing.plugins.examples.report.description'),
|
||||
stats: [
|
||||
t('marketing.plugins.examples.report.stats.timeSaved'),
|
||||
t('marketing.plugins.examples.report.stats.visibility'),
|
||||
],
|
||||
variant: 'report' as const,
|
||||
},
|
||||
];
|
||||
|
||||
const CurrentIcon = examples[activeTab].icon;
|
||||
|
||||
return (
|
||||
<section className="py-24 bg-gray-50 dark:bg-gray-900 overflow-hidden">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid lg:grid-cols-2 gap-16 items-center">
|
||||
|
||||
{/* Left Column: Content */}
|
||||
<div>
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-brand-100 dark:bg-brand-900/30 text-brand-600 dark:text-brand-400 text-sm font-medium mb-6">
|
||||
<Zap className="w-4 h-4" />
|
||||
<span>{t('marketing.plugins.badge')}</span>
|
||||
</div>
|
||||
|
||||
<h2 className="text-4xl font-bold text-gray-900 dark:text-white mb-6">
|
||||
{t('marketing.plugins.headline')}
|
||||
</h2>
|
||||
|
||||
<p className="text-lg text-gray-600 dark:text-gray-400 mb-10">
|
||||
{t('marketing.plugins.subheadline')}
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
{examples.map((example, index) => (
|
||||
<button
|
||||
key={example.id}
|
||||
onClick={() => setActiveTab(index)}
|
||||
className={`w-full text-left p-4 rounded-xl transition-all duration-200 border ${activeTab === index
|
||||
? 'bg-white dark:bg-gray-800 border-brand-500 shadow-lg scale-[1.02]'
|
||||
: 'bg-transparent border-transparent hover:bg-white/50 dark:hover:bg-gray-800/50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className={`p-2 rounded-lg ${activeTab === index
|
||||
? 'bg-brand-100 text-brand-600 dark:bg-brand-900/50 dark:text-brand-400'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-400'
|
||||
}`}>
|
||||
<example.icon className="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className={`font-semibold mb-1 ${activeTab === index ? 'text-gray-900 dark:text-white' : 'text-gray-600 dark:text-gray-400'
|
||||
}`}>
|
||||
{example.title}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-500">
|
||||
{example.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column: Visuals */}
|
||||
<div className="relative">
|
||||
{/* Background Decor */}
|
||||
<div className="absolute -inset-4 bg-gradient-to-r from-brand-500/20 to-purple-500/20 rounded-3xl blur-2xl opacity-50" />
|
||||
|
||||
{/* View Toggle */}
|
||||
<div className="absolute -top-12 right-0 flex bg-gray-100 dark:bg-gray-800 p-1 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
<button
|
||||
onClick={() => setViewMode('marketplace')}
|
||||
className={`flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium transition-all ${viewMode === 'marketplace'
|
||||
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
|
||||
: 'text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
<LayoutGrid className="w-4 h-4" />
|
||||
{t('marketing.plugins.viewToggle.marketplace')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode('code')}
|
||||
className={`flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium transition-all ${viewMode === 'code'
|
||||
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-sm'
|
||||
: 'text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
<Code className="w-4 h-4" />
|
||||
{t('marketing.plugins.viewToggle.developer')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={`${activeTab}-${viewMode}`}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="relative mt-8" // Added margin top for toggle
|
||||
>
|
||||
{/* Stats Cards */}
|
||||
<div className="flex gap-4 mb-6">
|
||||
{examples[activeTab].stats.map((stat, i) => (
|
||||
<div key={i} className="flex items-center gap-2 px-4 py-2 bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
|
||||
<CheckCircle2 className="w-4 h-4 text-green-500" />
|
||||
<span className="text-sm font-medium text-gray-900 dark:text-white">{stat}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{viewMode === 'marketplace' ? (
|
||||
// Marketplace Card View
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 shadow-xl overflow-hidden">
|
||||
<div className={`h-32 ${examples[activeTab].marketplaceImage} flex items-center justify-center`}>
|
||||
<CurrentIcon className="w-16 h-16 text-white opacity-90" />
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-gray-900 dark:text-white">{examples[activeTab].title}</h3>
|
||||
<div className="text-sm text-gray-500">{t('marketing.plugins.marketplaceCard.author')}</div>
|
||||
</div>
|
||||
<button className="px-4 py-2 bg-brand-600 text-white rounded-lg font-medium text-sm hover:bg-brand-700 transition-colors">
|
||||
{t('marketing.plugins.marketplaceCard.installButton')}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-6">
|
||||
{examples[activeTab].description}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-500">
|
||||
<div className="flex -space-x-2">
|
||||
{[1, 2, 3].map(i => (
|
||||
<div key={i} className="w-6 h-6 rounded-full bg-gray-300 border-2 border-white dark:border-gray-800" />
|
||||
))}
|
||||
</div>
|
||||
<span>{t('marketing.plugins.marketplaceCard.usedBy')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
// Code View
|
||||
<CodeBlock
|
||||
code={examples[activeTab].code}
|
||||
filename={`${examples[activeTab].id}_automation.py`}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* CTA */}
|
||||
<div className="mt-6 text-right">
|
||||
<a href="/features" className="inline-flex items-center gap-2 text-brand-600 dark:text-brand-400 font-medium hover:underline">
|
||||
{t('marketing.plugins.cta')} <ArrowRight className="w-4 h-4" />
|
||||
</a>
|
||||
</div>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
return (
|
||||
<section className="py-24 bg-gray-50 dark:bg-gray-900 overflow-hidden">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid lg:grid-cols-2 gap-16 items-center">
|
||||
{/* Left Column: Content */}
|
||||
<div>
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-brand-100 dark:bg-brand-900/30 text-brand-600 dark:text-brand-400 text-sm font-medium mb-6">
|
||||
<Zap className="w-4 h-4" />
|
||||
<span>{t('marketing.plugins.badge')}</span>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
<h2 className="text-4xl font-bold text-gray-900 dark:text-white mb-6">
|
||||
{t('marketing.plugins.headline')}
|
||||
</h2>
|
||||
|
||||
<p className="text-lg text-gray-600 dark:text-gray-400 mb-10">
|
||||
{t('marketing.plugins.subheadline')}
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
{examples.map((example, index) => (
|
||||
<button
|
||||
key={example.id}
|
||||
onClick={() => setActiveTab(index)}
|
||||
className={`w-full text-left p-4 rounded-xl transition-all duration-200 border ${
|
||||
activeTab === index
|
||||
? 'bg-white dark:bg-gray-800 border-brand-500 shadow-lg scale-[1.02]'
|
||||
: 'bg-transparent border-transparent hover:bg-white/50 dark:hover:bg-gray-800/50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div
|
||||
className={`p-2 rounded-lg ${
|
||||
activeTab === index
|
||||
? 'bg-brand-100 text-brand-600 dark:bg-brand-900/50 dark:text-brand-400'
|
||||
: 'bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-400'
|
||||
}`}
|
||||
>
|
||||
<example.icon className="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3
|
||||
className={`font-semibold mb-1 ${
|
||||
activeTab === index
|
||||
? 'text-gray-900 dark:text-white'
|
||||
: 'text-gray-600 dark:text-gray-400'
|
||||
}`}
|
||||
>
|
||||
{example.title}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-500">
|
||||
{example.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column: Workflow Visual */}
|
||||
<div className="relative">
|
||||
{/* Background Decor */}
|
||||
<div className="absolute -inset-4 bg-gradient-to-r from-brand-500/20 to-purple-500/20 rounded-3xl blur-2xl opacity-50" />
|
||||
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={activeTab}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="relative"
|
||||
>
|
||||
{/* Stats Cards */}
|
||||
<div className="flex gap-4 mb-6">
|
||||
{examples[activeTab].stats.map((stat, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700"
|
||||
>
|
||||
<CheckCircle2 className="w-4 h-4 text-green-500" />
|
||||
<span className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{stat}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Workflow Visual */}
|
||||
<WorkflowVisual
|
||||
variant={examples[activeTab].variant}
|
||||
trigger={examples[activeTab].title}
|
||||
actions={[]}
|
||||
/>
|
||||
|
||||
{/* CTA */}
|
||||
<div className="mt-6 text-right">
|
||||
<a
|
||||
href="/features"
|
||||
className="inline-flex items-center gap-2 text-brand-600 dark:text-brand-400 font-medium hover:underline"
|
||||
>
|
||||
{t('marketing.plugins.cta')} <ArrowRight className="w-4 h-4" />
|
||||
</a>
|
||||
</div>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default AutomationShowcase;
|
||||
|
||||
171
frontend/src/components/marketing/WorkflowVisual.tsx
Normal file
171
frontend/src/components/marketing/WorkflowVisual.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Calendar,
|
||||
Mail,
|
||||
MessageSquare,
|
||||
Clock,
|
||||
Search,
|
||||
FileText,
|
||||
Sparkles,
|
||||
ChevronRight,
|
||||
} from 'lucide-react';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
|
||||
interface WorkflowBlock {
|
||||
icon: LucideIcon;
|
||||
label: string;
|
||||
type: 'trigger' | 'action';
|
||||
}
|
||||
|
||||
interface WorkflowVisualProps {
|
||||
trigger: string;
|
||||
actions: string[];
|
||||
variant?: 'winback' | 'noshow' | 'report';
|
||||
}
|
||||
|
||||
const getWorkflowConfig = (
|
||||
variant: WorkflowVisualProps['variant']
|
||||
): WorkflowBlock[] => {
|
||||
switch (variant) {
|
||||
case 'winback':
|
||||
return [
|
||||
{ icon: Clock, label: 'Schedule: Weekly', type: 'trigger' },
|
||||
{ icon: Search, label: 'Find Inactive Customers', type: 'action' },
|
||||
{ icon: Mail, label: 'Send Email', type: 'action' },
|
||||
];
|
||||
case 'noshow':
|
||||
return [
|
||||
{ icon: Calendar, label: 'Event Created', type: 'trigger' },
|
||||
{ icon: Clock, label: 'Wait 2 Hours Before', type: 'action' },
|
||||
{ icon: MessageSquare, label: 'Send SMS', type: 'action' },
|
||||
];
|
||||
case 'report':
|
||||
return [
|
||||
{ icon: Clock, label: 'Daily at 6 PM', type: 'trigger' },
|
||||
{ icon: FileText, label: "Get Tomorrow's Schedule", type: 'action' },
|
||||
{ icon: Mail, label: 'Send Summary', type: 'action' },
|
||||
];
|
||||
default:
|
||||
return [
|
||||
{ icon: Calendar, label: 'Event Created', type: 'trigger' },
|
||||
{ icon: Clock, label: 'Wait', type: 'action' },
|
||||
{ icon: Mail, label: 'Send Notification', type: 'action' },
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
const WorkflowVisual: React.FC<WorkflowVisualProps> = ({
|
||||
variant = 'noshow',
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const blocks = getWorkflowConfig(variant);
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 shadow-xl overflow-hidden">
|
||||
{/* AI Copilot Input */}
|
||||
<div className="p-4 bg-gradient-to-r from-purple-50 to-brand-50 dark:from-purple-900/20 dark:to-brand-900/20 border-b border-gray-200 dark:border-gray-700">
|
||||
<div className="flex items-center gap-3 bg-white dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-600 px-4 py-3 shadow-sm">
|
||||
<Sparkles className="w-5 h-5 text-purple-500" />
|
||||
<span className="text-gray-400 dark:text-gray-500 text-sm flex-1">
|
||||
{t('marketing.plugins.aiCopilot.placeholder')}
|
||||
</span>
|
||||
<motion.div
|
||||
animate={{ opacity: [0.5, 1, 0.5] }}
|
||||
transition={{ duration: 1.5, repeat: Infinity }}
|
||||
className="w-2 h-5 bg-purple-500 rounded-sm"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-2 ml-1">
|
||||
{t('marketing.plugins.aiCopilot.examples')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Workflow Visualization */}
|
||||
<div className="p-6">
|
||||
<div className="flex flex-col gap-3">
|
||||
{blocks.map((block, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{/* Block */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: index * 0.15 }}
|
||||
className={`flex items-center gap-3 p-3 rounded-lg border ${
|
||||
block.type === 'trigger'
|
||||
? 'bg-gradient-to-r from-brand-50 to-purple-50 dark:from-brand-900/30 dark:to-purple-900/30 border-brand-200 dark:border-brand-800'
|
||||
: 'bg-gray-50 dark:bg-gray-900/50 border-gray-200 dark:border-gray-700'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`p-2 rounded-lg ${
|
||||
block.type === 'trigger'
|
||||
? 'bg-brand-100 dark:bg-brand-900/50 text-brand-600 dark:text-brand-400'
|
||||
: 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400'
|
||||
}`}
|
||||
>
|
||||
<block.icon className="w-5 h-5" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<span
|
||||
className={`text-xs font-medium uppercase tracking-wide ${
|
||||
block.type === 'trigger'
|
||||
? 'text-brand-600 dark:text-brand-400'
|
||||
: 'text-gray-500 dark:text-gray-400'
|
||||
}`}
|
||||
>
|
||||
{block.type === 'trigger' ? 'When' : 'Then'}
|
||||
</span>
|
||||
<p className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{block.label}
|
||||
</p>
|
||||
</div>
|
||||
<ChevronRight className="w-4 h-4 text-gray-400" />
|
||||
</motion.div>
|
||||
|
||||
{/* Connector */}
|
||||
{index < blocks.length - 1 && (
|
||||
<div className="flex items-center justify-center h-4">
|
||||
<div className="relative w-0.5 h-full bg-gray-200 dark:bg-gray-700">
|
||||
<motion.div
|
||||
className="absolute w-2 h-2 bg-brand-500 rounded-full left-1/2 -translate-x-1/2"
|
||||
animate={{ y: [0, 12, 0] }}
|
||||
transition={{
|
||||
duration: 1,
|
||||
repeat: Infinity,
|
||||
delay: index * 0.3,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Integration badges */}
|
||||
<div className="mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
||||
{t('marketing.plugins.integrations.description')}
|
||||
</p>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{['Gmail', 'Slack', 'Sheets', 'Twilio'].map((app) => (
|
||||
<span
|
||||
key={app}
|
||||
className="px-2 py-1 text-xs bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 rounded-md"
|
||||
>
|
||||
{app}
|
||||
</span>
|
||||
))}
|
||||
<span className="px-2 py-1 text-xs text-gray-400 dark:text-gray-500">
|
||||
+1000 more
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WorkflowVisual;
|
||||
@@ -2444,14 +2444,14 @@
|
||||
"pageTitle": "Built for Developers, Designed for Business",
|
||||
"pageSubtitle": "SmoothSchedule isn't just cloud software. It's a programmable platform that adapts to your unique business logic.",
|
||||
"automationEngine": {
|
||||
"badge": "Automation Engine",
|
||||
"title": "Automated Task Manager",
|
||||
"description": "Most schedulers only book appointments. SmoothSchedule runs your business. Our \"Automated Task Manager\" executes internal tasks without blocking your calendar.",
|
||||
"badge": "AI-Powered Automation",
|
||||
"title": "Visual Workflow Builder with AI Copilot",
|
||||
"description": "Most schedulers only book appointments. SmoothSchedule runs your business. Create powerful automations with our visual builder or just describe what you want.",
|
||||
"features": {
|
||||
"recurringJobs": "Run recurring jobs (e.g., \"Every Monday at 9am\")",
|
||||
"customLogic": "Execute custom logic securely",
|
||||
"fullContext": "Access full customer and event context",
|
||||
"zeroInfrastructure": "Zero infrastructure management"
|
||||
"visualBuilder": "Visual drag-and-drop workflow builder",
|
||||
"aiCopilot": "AI Copilot creates flows from natural language",
|
||||
"integrations": "Connect to 1000+ apps (Gmail, Slack, Sheets, etc.)",
|
||||
"templates": "Pre-built templates for common automations"
|
||||
}
|
||||
},
|
||||
"multiTenancy": {
|
||||
@@ -2558,7 +2558,7 @@
|
||||
"0": "Unlimited Users",
|
||||
"1": "Unlimited Appointments",
|
||||
"2": "Unlimited Automations",
|
||||
"3": "Custom Python Scripts",
|
||||
"3": "AI-Powered Workflow Builder",
|
||||
"4": "Custom Domain (White-Label)",
|
||||
"5": "Dedicated Support",
|
||||
"6": "API Access"
|
||||
@@ -2638,9 +2638,9 @@
|
||||
},
|
||||
"faq": {
|
||||
"title": "Frequently Asked Questions",
|
||||
"needPython": {
|
||||
"question": "Do I need to know Python to use SmoothSchedule?",
|
||||
"answer": "Not at all! You can use our pre-built plugins from the marketplace for common tasks like email reminders and reports. Python is only needed if you want to write custom scripts."
|
||||
"needCoding": {
|
||||
"question": "Do I need to know how to code to create automations?",
|
||||
"answer": "Not at all! Our visual workflow builder lets you create automations by dragging and dropping blocks. Even better, just describe what you want in plain English and our AI Copilot will build the workflow for you."
|
||||
},
|
||||
"exceedLimits": {
|
||||
"question": "What happens if I exceed my plan's limits?",
|
||||
@@ -2939,19 +2939,14 @@
|
||||
"copyright": "Smooth Schedule Inc. All rights reserved."
|
||||
},
|
||||
"plugins": {
|
||||
"badge": "Limitless Automation",
|
||||
"headline": "Choose from our Marketplace, or build your own.",
|
||||
"subheadline": "Browse hundreds of pre-built automations to streamline your workflows instantly. Need something custom? Developers can write Python scripts to extend the platform endlessly.",
|
||||
"viewToggle": {
|
||||
"marketplace": "Marketplace",
|
||||
"developer": "Developer"
|
||||
"badge": "Visual Automation Builder",
|
||||
"headline": "Build automations visually, or just describe what you want.",
|
||||
"subheadline": "Create powerful workflows with our drag-and-drop builder. No coding required. Just describe what you want and our AI Copilot will build it for you.",
|
||||
"aiCopilot": {
|
||||
"placeholder": "Describe your automation...",
|
||||
"examples": "e.g., \"Send a reminder 2 hours before each appointment\""
|
||||
},
|
||||
"marketplaceCard": {
|
||||
"author": "by SmoothSchedule Team",
|
||||
"installButton": "Install Automation",
|
||||
"usedBy": "Used by 1,200+ businesses"
|
||||
},
|
||||
"cta": "Explore the Marketplace",
|
||||
"cta": "Try the Automation Builder",
|
||||
"examples": {
|
||||
"winback": {
|
||||
"title": "Client Win-Back",
|
||||
@@ -2960,7 +2955,8 @@
|
||||
"retention": "+15% Retention",
|
||||
"revenue": "$4k/mo Revenue"
|
||||
},
|
||||
"code": "# Win back lost customers\ndays_inactive = 60\ndiscount = \"20%\"\n\n# Find inactive customers\ninactive = api.get_customers(\n last_visit_lt=days_ago(days_inactive)\n)\n\n# Send personalized offer\nfor customer in inactive:\n api.send_email(\n to=customer.email,\n subject=\"We miss you!\",\n body=f\"Come back for {discount} off!\"\n )"
|
||||
"trigger": "Schedule: Every Monday",
|
||||
"actions": ["Find inactive customers", "Send personalized email"]
|
||||
},
|
||||
"noshow": {
|
||||
"title": "No-Show Prevention",
|
||||
@@ -2969,7 +2965,8 @@
|
||||
"reduction": "-40% No-Shows",
|
||||
"utilization": "Better Utilization"
|
||||
},
|
||||
"code": "# Prevent no-shows\nhours_before = 2\n\n# Find upcoming appointments\nupcoming = api.get_appointments(\n start_time__within=hours(hours_before)\n)\n\n# Send SMS reminder\nfor appt in upcoming:\n api.send_sms(\n to=appt.customer.phone,\n body=f\"Reminder: Appointment in 2h at {appt.time}\"\n )"
|
||||
"trigger": "Event: Appointment Created",
|
||||
"actions": ["Wait 2 hours before", "Send SMS reminder"]
|
||||
},
|
||||
"report": {
|
||||
"title": "Daily Reports",
|
||||
@@ -2978,8 +2975,13 @@
|
||||
"timeSaved": "Save 30min/day",
|
||||
"visibility": "Full Visibility"
|
||||
},
|
||||
"code": "# Daily Manager Report\ntomorrow = date.today() + timedelta(days=1)\n\n# Get schedule stats\nstats = api.get_schedule_stats(date=tomorrow)\nrevenue = api.forecast_revenue(date=tomorrow)\n\n# Email manager\napi.send_email(\n to=\"manager@business.com\",\n subject=f\"Schedule for {tomorrow}\",\n body=f\"Bookings: {stats.count}, Est. Rev: ${revenue}\"\n)"
|
||||
"trigger": "Schedule: Daily at 6 PM",
|
||||
"actions": ["Get tomorrow's schedule", "Send email summary"]
|
||||
}
|
||||
},
|
||||
"integrations": {
|
||||
"title": "Connect to 1000+ Apps",
|
||||
"description": "Gmail, Slack, Google Sheets, and more"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
@@ -2993,8 +2995,8 @@
|
||||
"description": "Handle complex resources like staff, rooms, and equipment with concurrency limits."
|
||||
},
|
||||
"automationEngine": {
|
||||
"title": "Automation Engine",
|
||||
"description": "Install automations from our marketplace or build your own to automate tasks."
|
||||
"title": "AI-Powered Automations",
|
||||
"description": "Build visual workflows with AI assistance. Connect to 1000+ apps with no code."
|
||||
},
|
||||
"multiTenant": {
|
||||
"title": "Enterprise Security",
|
||||
@@ -3023,7 +3025,7 @@
|
||||
},
|
||||
"testimonials": {
|
||||
"winBack": {
|
||||
"quote": "I installed the 'Client Win-Back' plugin and recovered $2k in bookings the first week. No setup required.",
|
||||
"quote": "I set up the 'Client Win-Back' automation in 2 minutes using the AI Copilot. Recovered $2k in bookings the first week.",
|
||||
"author": "Alex Rivera",
|
||||
"role": "Owner",
|
||||
"company": "TechSalon"
|
||||
|
||||
@@ -10,32 +10,15 @@ import {
|
||||
CheckCircle2,
|
||||
FileSignature,
|
||||
FileCheck,
|
||||
Scale
|
||||
Scale,
|
||||
Sparkles
|
||||
} from 'lucide-react';
|
||||
import CodeBlock from '../../components/marketing/CodeBlock';
|
||||
import WorkflowVisual from '../../components/marketing/WorkflowVisual';
|
||||
import CTASection from '../../components/marketing/CTASection';
|
||||
|
||||
const FeaturesPage: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const pluginExample = `# Custom Webhook Plugin
|
||||
import requests
|
||||
|
||||
def execute(context):
|
||||
event = context['event']
|
||||
|
||||
# Send data to external CRM
|
||||
response = requests.post(
|
||||
'https://api.crm.com/leads',
|
||||
json={
|
||||
'name': event.customer.name,
|
||||
'email': event.customer.email,
|
||||
'source': 'SmoothSchedule'
|
||||
}
|
||||
)
|
||||
|
||||
return response.status_code == 200`;
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-gray-900 min-h-screen pt-24">
|
||||
|
||||
@@ -55,7 +38,7 @@ def execute(context):
|
||||
<div className="grid lg:grid-cols-2 gap-16 items-center">
|
||||
<div>
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-purple-100 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400 text-sm font-medium mb-6">
|
||||
<Zap className="w-4 h-4" />
|
||||
<Sparkles className="w-4 h-4" />
|
||||
<span>{t('marketing.features.automationEngine.badge')}</span>
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-6">
|
||||
@@ -67,10 +50,10 @@ def execute(context):
|
||||
|
||||
<ul className="space-y-4">
|
||||
{[
|
||||
t('marketing.features.automationEngine.features.recurringJobs'),
|
||||
t('marketing.features.automationEngine.features.customLogic'),
|
||||
t('marketing.features.automationEngine.features.fullContext'),
|
||||
t('marketing.features.automationEngine.features.zeroInfrastructure')
|
||||
t('marketing.features.automationEngine.features.visualBuilder'),
|
||||
t('marketing.features.automationEngine.features.aiCopilot'),
|
||||
t('marketing.features.automationEngine.features.integrations'),
|
||||
t('marketing.features.automationEngine.features.templates')
|
||||
].map((item) => (
|
||||
<li key={item} className="flex items-center gap-3">
|
||||
<CheckCircle2 className="w-5 h-5 text-green-500" />
|
||||
@@ -82,7 +65,7 @@ def execute(context):
|
||||
|
||||
<div className="relative">
|
||||
<div className="absolute -inset-4 bg-purple-500/20 rounded-3xl blur-2xl" />
|
||||
<CodeBlock code={pluginExample} filename="webhook_plugin.py" />
|
||||
<WorkflowVisual variant="noshow" trigger="" actions={[]} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user