Jake Shore f8e0b3246f feat: Complete Acuity Scheduling MCP server with 40+ tools and 14 React apps
- Full API client with Basic Auth and OAuth2 support
- 40+ tools across 10 categories (appointments, availability, clients, calendars, products, forms, labels, webhooks, coupons, blocks)
- 14 interactive React-based MCP apps for rich UI experiences
- Comprehensive error handling and pagination
- TypeScript implementation with full type definitions
- Complete documentation and examples
2026-02-12 17:41:55 -05:00

231 lines
10 KiB
TypeScript

export default `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Booking Flow</title>
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.container { max-width: 800px; margin: 0 auto; }
.booking-card { background: white; border-radius: 8px; padding: 30px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
h1 { color: #333; margin-bottom: 10px; }
.subtitle { color: #666; margin-bottom: 30px; }
.progress-bar { display: flex; justify-content: space-between; margin-bottom: 40px; }
.progress-step { flex: 1; text-align: center; position: relative; }
.progress-step::after { content: ''; position: absolute; top: 15px; left: 50%; width: 100%; height: 2px; background: #E5E7EB; z-index: -1; }
.progress-step:last-child::after { display: none; }
.step-circle { width: 30px; height: 30px; border-radius: 50%; background: #E5E7EB; color: #9CA3AF; display: inline-flex; align-items: center; justify-content: center; font-weight: 600; margin-bottom: 8px; }
.progress-step.active .step-circle { background: #4F46E5; color: white; }
.progress-step.completed .step-circle { background: #10B981; color: white; }
.step-label { font-size: 13px; color: #666; }
.section-title { font-size: 20px; font-weight: 600; color: #333; margin-bottom: 20px; }
.service-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px; margin-bottom: 30px; }
.service-card { border: 2px solid #E5E7EB; border-radius: 8px; padding: 20px; cursor: pointer; transition: all 0.2s; }
.service-card:hover { border-color: #4F46E5; }
.service-card.selected { border-color: #4F46E5; background: #EEF2FF; }
.service-name { font-weight: 600; color: #333; margin-bottom: 5px; }
.service-duration { font-size: 13px; color: #666; }
.service-price { font-size: 16px; font-weight: 700; color: #4F46E5; margin-top: 10px; }
.form-group { margin-bottom: 20px; }
.form-group label { display: block; font-size: 14px; font-weight: 500; color: #333; margin-bottom: 8px; }
.form-group input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; box-sizing: border-box; }
.actions { display: flex; justify-content: space-between; margin-top: 30px; }
button { padding: 12px 24px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; }
.btn-primary { background: #4F46E5; color: white; }
.btn-primary:hover { background: #4338CA; }
.btn-secondary { background: #E5E7EB; color: #374151; }
.btn-secondary:hover { background: #D1D5DB; }
.time-slots { display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 10px; margin-bottom: 30px; }
.time-slot { padding: 12px; border: 1px solid #E5E7EB; border-radius: 6px; text-align: center; cursor: pointer; transition: all 0.2s; }
.time-slot:hover { border-color: #4F46E5; }
.time-slot.selected { border-color: #4F46E5; background: #EEF2FF; font-weight: 600; }
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState } = React;
function BookingFlow() {
const [step, setStep] = useState(1);
const [selectedService, setSelectedService] = useState(null);
const [selectedDate, setSelectedDate] = useState(null);
const [selectedTime, setSelectedTime] = useState(null);
const [formData, setFormData] = useState({
firstName: '',
lastName: '',
email: '',
phone: ''
});
const services = [
{ id: 1, name: 'Initial Consultation', duration: '60 min', price: '$150' },
{ id: 2, name: 'Follow-up Visit', duration: '30 min', price: '$100' },
{ id: 3, name: 'Comprehensive Assessment', duration: '90 min', price: '$225' },
];
const timeSlots = ['09:00 AM', '10:00 AM', '11:00 AM', '01:00 PM', '02:00 PM', '03:00 PM', '04:00 PM'];
const handleNext = () => {
if (step === 1 && !selectedService) {
alert('Please select a service');
return;
}
if (step === 2 && (!selectedDate || !selectedTime)) {
alert('Please select a date and time');
return;
}
if (step === 3) {
if (!formData.firstName || !formData.lastName || !formData.email || !formData.phone) {
alert('Please fill in all fields');
return;
}
handleConfirm();
return;
}
setStep(step + 1);
};
const handleBack = () => {
setStep(step - 1);
};
const handleConfirm = () => {
alert(\`Booking confirmed!
Service: \${services.find(s => s.id === selectedService)?.name}
Date: \${selectedDate}
Time: \${selectedTime}
Name: \${formData.firstName} \${formData.lastName}
Email: \${formData.email}\`);
};
return (
<div className="container">
<div className="booking-card">
<h1>Book an Appointment</h1>
<p className="subtitle">Complete the steps below to schedule your visit</p>
<div className="progress-bar">
<div className={\`progress-step \${step >= 1 ? 'active' : ''} \${step > 1 ? 'completed' : ''}\`}>
<div className="step-circle">1</div>
<div className="step-label">Select Service</div>
</div>
<div className={\`progress-step \${step >= 2 ? 'active' : ''} \${step > 2 ? 'completed' : ''}\`}>
<div className="step-circle">2</div>
<div className="step-label">Choose Date & Time</div>
</div>
<div className={\`progress-step \${step >= 3 ? 'active' : ''}\`}>
<div className="step-circle">3</div>
<div className="step-label">Your Information</div>
</div>
</div>
{step === 1 && (
<div>
<div className="section-title">Select a Service</div>
<div className="service-grid">
{services.map(service => (
<div
key={service.id}
className={\`service-card \${selectedService === service.id ? 'selected' : ''}\`}
onClick={() => setSelectedService(service.id)}
>
<div className="service-name">{service.name}</div>
<div className="service-duration">{service.duration}</div>
<div className="service-price">{service.price}</div>
</div>
))}
</div>
</div>
)}
{step === 2 && (
<div>
<div className="section-title">Choose Date & Time</div>
<div className="form-group">
<label>Select Date</label>
<input
type="date"
value={selectedDate || ''}
onChange={(e) => setSelectedDate(e.target.value)}
min={new Date().toISOString().split('T')[0]}
/>
</div>
{selectedDate && (
<div>
<label style={{ display: 'block', marginBottom: '10px', fontWeight: 500 }}>Select Time</label>
<div className="time-slots">
{timeSlots.map(time => (
<div
key={time}
className={\`time-slot \${selectedTime === time ? 'selected' : ''}\`}
onClick={() => setSelectedTime(time)}
>
{time}
</div>
))}
</div>
</div>
)}
</div>
)}
{step === 3 && (
<div>
<div className="section-title">Your Information</div>
<div className="form-group">
<label>First Name</label>
<input
type="text"
value={formData.firstName}
onChange={(e) => setFormData({...formData, firstName: e.target.value})}
/>
</div>
<div className="form-group">
<label>Last Name</label>
<input
type="text"
value={formData.lastName}
onChange={(e) => setFormData({...formData, lastName: e.target.value})}
/>
</div>
<div className="form-group">
<label>Email</label>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
/>
</div>
<div className="form-group">
<label>Phone</label>
<input
type="tel"
value={formData.phone}
onChange={(e) => setFormData({...formData, phone: e.target.value})}
/>
</div>
</div>
)}
<div className="actions">
<button className="btn-secondary" onClick={handleBack} disabled={step === 1}>
Back
</button>
<button className="btn-primary" onClick={handleNext}>
{step === 3 ? 'Confirm Booking' : 'Next'}
</button>
</div>
</div>
</div>
);
}
ReactDOM.render(<BookingFlow />, document.getElementById('root'));
</script>
</body>
</html>`;