- 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
231 lines
10 KiB
TypeScript
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>`;
|