import React, { useMemo, useState } from 'react'; import { createRoot } from 'react-dom/client'; import { IframeBridge, callHandlerJson, type HandlerMethod } from '@alga-psa/extension-iframe-sdk'; import { Button, Card, Stack, Text } from '@alga-psa/ui-kit'; type ScheduleRecord = { id: string; name?: string | null; endpointMethod: string; endpointPath: string; cron: string; timezone: string; enabled: boolean; lastRunAt?: string | null; }; type ApiResult = { ok: boolean; status?: number; data?: unknown; error?: string; }; const bridge = new IframeBridge({ devAllowWildcard: true }); bridge.ready(); const themeFallback = { '--alga-bg': '#ffffff', '--alga-fg': '#111827', '--alga-border': '#e5e7eb', '--alga-muted': '#f3f4f6', '--alga-muted-fg': '#6b7280', '--alga-primary': '#2563eb', '--alga-primary-foreground': '#ffffff', '--alga-danger': '#dc2626', '--alga-radius': '8px', } as React.CSSProperties; function asErrorMessage(err: unknown): string { return err instanceof Error ? err.message : String(err); } function addLog( prev: string[], message: string, type: 'info' | 'error' | 'success' = 'info', ): string[] { const timestamp = new Date().toISOString().split('T')[1].split('.')[0]; const prefix = type === 'error' ? '✗' : type === 'success' ? '✓' : '→'; return [`[${timestamp}] ${prefix} ${message}`, ...prev]; } async function apiCall(method: HandlerMethod, path: string): Promise { try { const data = await callHandlerJson(bridge, path, { method }); return { ok: true, status: 200, data: data ?? {} }; } catch (err) { const error = asErrorMessage(err); const statusMatch = error.match(/^Proxy error (\d+):\s*/); if (statusMatch) { return { ok: false, status: Number(statusMatch[1]), data: { error } }; } return { ok: false, error }; } } function SchedulerDemoApp() { const [logs, setLogs] = useState(['Ready.']); const [schedules, setSchedules] = useState([]); const [loadingSchedules, setLoadingSchedules] = useState(false); const [setupBusy, setSetupBusy] = useState(false); const outputText = useMemo(() => logs.join('\n'), [logs]); const runSetup = async () => { setSetupBusy(true); setLogs((prev) => addLog(prev, 'Running schedule setup...')); try { const result = await apiCall('POST', '/api/setup'); if (result.ok) { const payload = result.data as { results?: unknown }; setLogs((prev) => addLog(prev, `Setup completed: ${JSON.stringify(payload?.results, null, 2)}`, 'success'), ); await loadSchedules(); return; } setLogs((prev) => addLog(prev, `Setup failed: ${JSON.stringify(result.data ?? result.error)}`, 'error')); } finally { setSetupBusy(false); } }; const loadSchedules = async () => { setLoadingSchedules(true); setLogs((prev) => addLog(prev, 'Loading schedules...')); try { const result = await apiCall('GET', '/api/schedules'); const payload = result.data as { schedules?: ScheduleRecord[] } | undefined; const nextSchedules = payload?.schedules ?? []; if (result.ok && Array.isArray(payload?.schedules)) { setSchedules(nextSchedules); if (nextSchedules.length === 0) { setLogs((prev) => addLog(prev, 'No schedules found')); } else { setLogs((prev) => addLog(prev, `Loaded ${nextSchedules.length} schedule(s)`, 'success')); } return; } setLogs((prev) => addLog(prev, `Failed to load schedules: ${JSON.stringify(result.data ?? result.error)}`, 'error')); } finally { setLoadingSchedules(false); } }; const deleteSchedule = async (scheduleId: string) => { if (!window.confirm('Delete this schedule?')) return; setLogs((prev) => addLog(prev, `Deleting schedule ${scheduleId}...`)); const result = await apiCall('DELETE', `/api/schedules/${scheduleId}`); if (result.ok) { setLogs((prev) => addLog(prev, 'Schedule deleted', 'success')); await loadSchedules(); return; } setLogs((prev) => addLog(prev, `Delete failed: ${JSON.stringify(result.data ?? result.error)}`, 'error')); }; const checkStatus = async () => { setLogs((prev) => addLog(prev, 'Checking status...')); const result = await apiCall('GET', '/api/status'); if (result.ok) { setLogs((prev) => addLog(prev, `Status: ${JSON.stringify(result.data, null, 2)}`, 'success')); return; } setLogs((prev) => addLog(prev, `Status check failed: ${JSON.stringify(result.data ?? result.error)}`, 'error')); }; return (
Scheduler Demo Demonstrates the cap:scheduler.manage capability for extensions Current Schedules {loadingSchedules && Loading...} {!loadingSchedules && schedules.length === 0 && ( No schedules found. Click "Setup Schedules" to create some. )} {!loadingSchedules && schedules.length > 0 && ( {schedules.map((schedule) => ( {schedule.name || 'Unnamed Schedule'} {schedule.endpointMethod} {schedule.endpointPath} • {schedule.cron} ({schedule.timezone}) {schedule.enabled ? 'Enabled' : 'Disabled'} {schedule.lastRunAt ? ` • Last run: ${schedule.lastRunAt}` : ''} ))} )} Output
              {outputText}
            
); } const mountEl = document.getElementById('app'); if (!mountEl) { throw new Error('Missing #app mount element'); } createRoot(mountEl).render();