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

185 lines
6.9 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>Coupon Manager</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: 1200px; margin: 0 auto; }
h1 { color: #333; margin-bottom: 20px; }
.toolbar { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; display: flex; justify-content: space-between; }
button { padding: 10px 20px; background: #4F46E5; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; }
button:hover { background: #4338CA; }
.coupons-list { background: white; border-radius: 8px; overflow: hidden; }
.coupon-item { padding: 20px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; }
.coupon-item:last-child { border-bottom: none; }
.coupon-main { flex: 1; }
.coupon-code { font-size: 20px; font-weight: 700; color: #4F46E5; font-family: monospace; margin-bottom: 5px; }
.coupon-details { font-size: 14px; color: #666; }
.coupon-stats { display: flex; gap: 20px; margin-top: 10px; }
.stat { font-size: 13px; }
.stat-label { color: #666; }
.stat-value { font-weight: 600; color: #333; }
.coupon-status { padding: 6px 12px; border-radius: 12px; font-size: 12px; font-weight: 500; }
.status-active { background: #D1FAE5; color: #065F46; }
.status-expired { background: #FEE2E2; color: #991B1B; }
.status-maxed { background: #F3F4F6; color: #6B7280; }
.coupon-actions { display: flex; gap: 10px; }
.btn-small { padding: 6px 12px; font-size: 13px; }
.btn-secondary { background: #6B7280; }
.btn-secondary:hover { background: #4B5563; }
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect } = React;
function CouponManager() {
const [coupons, setCoupons] = useState([]);
useEffect(() => {
fetchCoupons();
}, []);
const fetchCoupons = async () => {
// Mock data
const mockCoupons = [
{
id: 1,
code: 'WELCOME20',
name: 'New Client Welcome',
percentageOff: 20,
validFrom: '2024-01-01',
validTo: '2024-12-31',
maxUses: 100,
timesUsed: 23,
status: 'active'
},
{
id: 2,
code: 'SUMMER50',
name: 'Summer Special',
amountOff: 50,
validFrom: '2024-06-01',
validTo: '2024-08-31',
maxUses: 50,
timesUsed: 12,
status: 'active'
},
{
id: 3,
code: 'HOLIDAY2023',
name: 'Holiday Promotion',
percentageOff: 30,
validFrom: '2023-11-01',
validTo: '2023-12-31',
maxUses: 200,
timesUsed: 156,
status: 'expired'
},
{
id: 4,
code: 'VIP10',
name: 'VIP Discount',
percentageOff: 10,
validFrom: '2024-01-01',
validTo: '2024-12-31',
maxUses: 10,
timesUsed: 10,
status: 'maxed'
},
];
setCoupons(mockCoupons);
};
const handleAddCoupon = () => {
alert('Create new coupon - would open form');
};
const handleEditCoupon = (coupon) => {
alert(\`Edit coupon: \${coupon.code}\`);
};
const handleDeleteCoupon = (coupon) => {
if (confirm(\`Delete coupon "\${coupon.code}"?\`)) {
setCoupons(coupons.filter(c => c.id !== coupon.id));
}
};
const getDiscountText = (coupon) => {
if (coupon.percentageOff) return \`\${coupon.percentageOff}% off\`;
if (coupon.amountOff) return \`$\${coupon.amountOff} off\`;
return 'Discount';
};
const getStatusClass = (status) => {
if (status === 'active') return 'status-active';
if (status === 'expired') return 'status-expired';
if (status === 'maxed') return 'status-maxed';
return '';
};
const getStatusText = (status) => {
if (status === 'active') return 'Active';
if (status === 'expired') return 'Expired';
if (status === 'maxed') return 'Max Uses Reached';
return status;
};
return (
<div className="container">
<h1>Coupon Manager</h1>
<div className="toolbar">
<div style={{ color: '#666' }}>
{coupons.length} coupon{coupons.length !== 1 ? 's' : ''}
</div>
<button onClick={handleAddCoupon}>+ Create Coupon</button>
</div>
<div className="coupons-list">
{coupons.map(coupon => (
<div key={coupon.id} className="coupon-item">
<div className="coupon-main">
<div className="coupon-code">{coupon.code}</div>
<div className="coupon-details">
{coupon.name} • {getDiscountText(coupon)} •
Valid {new Date(coupon.validFrom).toLocaleDateString()} - {new Date(coupon.validTo).toLocaleDateString()}
</div>
<div className="coupon-stats">
<div className="stat">
<span className="stat-label">Used: </span>
<span className="stat-value">{coupon.timesUsed}/{coupon.maxUses}</span>
</div>
<div className="stat">
<span className="stat-label">Remaining: </span>
<span className="stat-value">{coupon.maxUses - coupon.timesUsed}</span>
</div>
</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '15px' }}>
<span className={\`coupon-status \${getStatusClass(coupon.status)}\`}>
{getStatusText(coupon.status)}
</span>
<div className="coupon-actions">
<button className="btn-small" onClick={() => handleEditCoupon(coupon)}>Edit</button>
<button className="btn-small btn-secondary" onClick={() => handleDeleteCoupon(coupon)}>Delete</button>
</div>
</div>
</div>
))}
</div>
</div>
);
}
ReactDOM.render(<CouponManager />, document.getElementById('root'));
</script>
</body>
</html>`;