From e52b56d51cf95294a858410685fc03d0fd074a7f Mon Sep 17 00:00:00 2001 From: poduck Date: Tue, 16 Dec 2025 23:16:30 -0500 Subject: [PATCH] Add comprehensive tenant API documentation and settings help pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create 6 new tenant API documentation pages: - HelpApiOverview: Authentication, scopes, rate limits, errors - HelpApiAppointments: CRUD operations for appointments - HelpApiServices: Read-only service catalog access - HelpApiResources: Staff, rooms, equipment endpoints - HelpApiCustomers: Customer management endpoints - HelpApiWebhooks: Real-time event subscriptions - Create 6 new settings help pages for granular documentation - Update HelpComprehensive with API section linking to new docs - Update platform HelpApiDocs with comprehensive endpoint coverage - Fix non-clickable /api/v1/docs/ links (now opens in new tab) - Add routes for all new help pages in App.tsx - Update FloatingHelpButton with new help page mappings 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- frontend/src/App.tsx | 24 + .../src/components/FloatingHelpButton.tsx | 8 + .../__tests__/FloatingHelpButton.test.tsx | 72 + frontend/src/i18n/locales/de.json | 33 +- frontend/src/i18n/locales/en.json | 33 +- frontend/src/i18n/locales/es.json | 31 +- frontend/src/i18n/locales/fr.json | 33 +- frontend/src/pages/HelpApiDocs.tsx | 1819 +++++++++++++++++ .../src/pages/help/HelpApiAppointments.tsx | 430 ++++ frontend/src/pages/help/HelpApiCustomers.tsx | 386 ++++ frontend/src/pages/help/HelpApiOverview.tsx | 551 +++++ frontend/src/pages/help/HelpApiResources.tsx | 337 +++ frontend/src/pages/help/HelpApiServices.tsx | 307 +++ frontend/src/pages/help/HelpApiWebhooks.tsx | 513 +++++ frontend/src/pages/help/HelpComprehensive.tsx | 175 +- frontend/src/pages/help/HelpLocations.tsx | 358 ++++ .../pages/help/HelpSettingsBusinessHours.tsx | 275 +++ .../pages/help/HelpSettingsCommunication.tsx | 425 ++++ .../pages/help/HelpSettingsEmailTemplates.tsx | 478 +++++ .../pages/help/HelpSettingsEmbedWidget.tsx | 299 +++ .../src/pages/help/HelpSettingsStaffRoles.tsx | 482 +++++ 21 files changed, 7061 insertions(+), 8 deletions(-) create mode 100644 frontend/src/pages/help/HelpApiAppointments.tsx create mode 100644 frontend/src/pages/help/HelpApiCustomers.tsx create mode 100644 frontend/src/pages/help/HelpApiOverview.tsx create mode 100644 frontend/src/pages/help/HelpApiResources.tsx create mode 100644 frontend/src/pages/help/HelpApiServices.tsx create mode 100644 frontend/src/pages/help/HelpApiWebhooks.tsx create mode 100644 frontend/src/pages/help/HelpLocations.tsx create mode 100644 frontend/src/pages/help/HelpSettingsBusinessHours.tsx create mode 100644 frontend/src/pages/help/HelpSettingsCommunication.tsx create mode 100644 frontend/src/pages/help/HelpSettingsEmailTemplates.tsx create mode 100644 frontend/src/pages/help/HelpSettingsEmbedWidget.tsx create mode 100644 frontend/src/pages/help/HelpSettingsStaffRoles.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index bfdd3679..4d151eed 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -92,6 +92,12 @@ const HelpPayments = React.lazy(() => import('./pages/help/HelpPayments')); const HelpContracts = React.lazy(() => import('./pages/help/HelpContracts')); const HelpAutomations = React.lazy(() => import('./pages/help/HelpAutomations')); const HelpSiteBuilder = React.lazy(() => import('./pages/help/HelpSiteBuilder')); +const HelpApiOverview = React.lazy(() => import('./pages/help/HelpApiOverview')); +const HelpApiAppointments = React.lazy(() => import('./pages/help/HelpApiAppointments')); +const HelpApiServices = React.lazy(() => import('./pages/help/HelpApiServices')); +const HelpApiResources = React.lazy(() => import('./pages/help/HelpApiResources')); +const HelpApiCustomers = React.lazy(() => import('./pages/help/HelpApiCustomers')); +const HelpApiWebhooks = React.lazy(() => import('./pages/help/HelpApiWebhooks')); const HelpSettingsGeneral = React.lazy(() => import('./pages/help/HelpSettingsGeneral')); const HelpSettingsResourceTypes = React.lazy(() => import('./pages/help/HelpSettingsResourceTypes')); const HelpSettingsBooking = React.lazy(() => import('./pages/help/HelpSettingsBooking')); @@ -102,6 +108,12 @@ const HelpSettingsApi = React.lazy(() => import('./pages/help/HelpSettingsApi')) const HelpSettingsAuth = React.lazy(() => import('./pages/help/HelpSettingsAuth')); const HelpSettingsBilling = React.lazy(() => import('./pages/help/HelpSettingsBilling')); const HelpSettingsQuota = React.lazy(() => import('./pages/help/HelpSettingsQuota')); +const HelpLocations = React.lazy(() => import('./pages/help/HelpLocations')); +const HelpSettingsBusinessHours = React.lazy(() => import('./pages/help/HelpSettingsBusinessHours')); +const HelpSettingsEmailTemplates = React.lazy(() => import('./pages/help/HelpSettingsEmailTemplates')); +const HelpSettingsEmbedWidget = React.lazy(() => import('./pages/help/HelpSettingsEmbedWidget')); +const HelpSettingsStaffRoles = React.lazy(() => import('./pages/help/HelpSettingsStaffRoles')); +const HelpSettingsCommunication = React.lazy(() => import('./pages/help/HelpSettingsCommunication')); const HelpComprehensive = React.lazy(() => import('./pages/help/HelpComprehensive')); const StaffHelp = React.lazy(() => import('./pages/help/StaffHelp')); const PlatformSupport = React.lazy(() => import('./pages/PlatformSupport')); // Import Platform Support page (for businesses to contact SmoothSchedule) @@ -762,6 +774,12 @@ const AppContent: React.FC = () => { } /> } /> } /> + } /> + } /> + } /> + } /> + } /> + } /> } /> } /> } /> @@ -772,6 +790,12 @@ const AppContent: React.FC = () => { } /> } /> } /> + } /> + } /> + } /> + } /> + } /> + } /> = { '/': 'dashboard', '/dashboard': 'dashboard', '/scheduler': 'scheduler', + '/my-schedule': 'scheduler', '/tasks': 'tasks', '/customers': 'customers', '/services': 'services', '/resources': 'resources', + '/locations': 'locations', '/staff': 'staff', '/time-blocks': 'time-blocks', '/my-availability': 'time-blocks', @@ -39,7 +41,13 @@ const routeToHelpSuffix: Record = { '/settings/resource-types': 'settings/resource-types', '/settings/booking': 'settings/booking', '/settings/appearance': 'settings/appearance', + '/settings/branding': 'settings/appearance', + '/settings/business-hours': 'settings/business-hours', '/settings/email': 'settings/email', + '/settings/email-templates': 'settings/email-templates', + '/settings/embed-widget': 'settings/embed-widget', + '/settings/staff-roles': 'settings/staff-roles', + '/settings/sms-calling': 'settings/communication', '/settings/domains': 'settings/domains', '/settings/api': 'settings/api', '/settings/auth': 'settings/auth', diff --git a/frontend/src/components/__tests__/FloatingHelpButton.test.tsx b/frontend/src/components/__tests__/FloatingHelpButton.test.tsx index 02d67ff6..958d0e8e 100644 --- a/frontend/src/components/__tests__/FloatingHelpButton.test.tsx +++ b/frontend/src/components/__tests__/FloatingHelpButton.test.tsx @@ -84,6 +84,42 @@ describe('FloatingHelpButton', () => { const link = screen.getByRole('link'); expect(link).toHaveAttribute('href', '/dashboard/help/site-builder'); }); + + it('links to /dashboard/help/locations for /dashboard/locations', () => { + renderWithRouter('/dashboard/locations'); + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('href', '/dashboard/help/locations'); + }); + + it('links to /dashboard/help/settings/business-hours for /dashboard/settings/business-hours', () => { + renderWithRouter('/dashboard/settings/business-hours'); + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('href', '/dashboard/help/settings/business-hours'); + }); + + it('links to /dashboard/help/settings/email-templates for /dashboard/settings/email-templates', () => { + renderWithRouter('/dashboard/settings/email-templates'); + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('href', '/dashboard/help/settings/email-templates'); + }); + + it('links to /dashboard/help/settings/embed-widget for /dashboard/settings/embed-widget', () => { + renderWithRouter('/dashboard/settings/embed-widget'); + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('href', '/dashboard/help/settings/embed-widget'); + }); + + it('links to /dashboard/help/settings/staff-roles for /dashboard/settings/staff-roles', () => { + renderWithRouter('/dashboard/settings/staff-roles'); + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('href', '/dashboard/help/settings/staff-roles'); + }); + + it('links to /dashboard/help/settings/communication for /dashboard/settings/sms-calling', () => { + renderWithRouter('/dashboard/settings/sms-calling'); + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('href', '/dashboard/help/settings/communication'); + }); }); describe('non-dashboard routes (public/platform)', () => { @@ -111,6 +147,42 @@ describe('FloatingHelpButton', () => { expect(link).toHaveAttribute('href', '/help/settings/general'); }); + it('links to /help/locations for /locations', () => { + renderWithRouter('/locations'); + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('href', '/help/locations'); + }); + + it('links to /help/settings/business-hours for /settings/business-hours', () => { + renderWithRouter('/settings/business-hours'); + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('href', '/help/settings/business-hours'); + }); + + it('links to /help/settings/email-templates for /settings/email-templates', () => { + renderWithRouter('/settings/email-templates'); + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('href', '/help/settings/email-templates'); + }); + + it('links to /help/settings/embed-widget for /settings/embed-widget', () => { + renderWithRouter('/settings/embed-widget'); + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('href', '/help/settings/embed-widget'); + }); + + it('links to /help/settings/staff-roles for /settings/staff-roles', () => { + renderWithRouter('/settings/staff-roles'); + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('href', '/help/settings/staff-roles'); + }); + + it('links to /help/settings/communication for /settings/sms-calling', () => { + renderWithRouter('/settings/sms-calling'); + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('href', '/help/settings/communication'); + }); + it('returns null on /help pages', () => { const { container } = renderWithRouter('/help/dashboard'); expect(container.firstChild).toBeNull(); diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index 79803032..17764ef9 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -1591,7 +1591,13 @@ "billing": "Abrechnung", "apiSettings": "API-Einstellungen", "authentication": "Authentifizierung", - "usageQuota": "Nutzung & Kontingent" + "usageQuota": "Nutzung & Kontingent", + "locations": "Standorte", + "businessHours": "Geschäftszeiten", + "emailTemplates": "E-Mail-Vorlagen", + "embedWidget": "Eingebettetes Widget", + "staffRoles": "Mitarbeiterrollen", + "smsCalling": "SMS und Anrufe" }, "introduction": { "title": "Einführung", @@ -1851,6 +1857,19 @@ "contractsDocumentation": "Vertragsdokumentation", "contractsDocumentationDesc": "Vollständige Anleitung zu Vorlagen, Unterzeichnung und Konformitätsfunktionen" }, + "locations": { + "title": "Standorte", + "description": "Verwalten Sie mehrere Geschäftsstandorte, jeweils mit eigener Adresse, Kontaktdaten und zugewiesenen Ressourcen und Dienstleistungen.", + "keyFeatures": "Hauptfunktionen", + "primaryLocation": "Hauptstandort", + "primaryLocationDesc": "Legen Sie einen Standort als Ihre Hauptgeschäftsadresse fest", + "activateDeactivate": "Aktivieren/Deaktivieren", + "activateDeactivateDesc": "Deaktivieren Sie Standorte vorübergehend, ohne sie zu löschen", + "addressManagement": "Adressverwaltung", + "addressManagementDesc": "Vollständige Adresse, Telefon, E-Mail und Zeitzone für jeden Standort", + "locationsDocumentation": "Standort-Dokumentation", + "locationsDocumentationDesc": "Vollständige Anleitung zur Multi-Standort-Verwaltung" + }, "settings": { "title": "Einstellungen", "description": "In den Einstellungen konfigurieren Geschäftsinhaber ihre Terminplanungsplattform. Die meisten Einstellungen sind nur für Inhaber und beeinflussen den Geschäftsbetrieb.", @@ -1898,7 +1917,17 @@ "apiSettingsLink": "API-Einstellungen", "apiSettingsLinkDesc": "API-Schlüssel und Webhooks", "usageQuotaLink": "Nutzung & Kontingent", - "usageQuotaLinkDesc": "Nutzung und Limits verfolgen" + "usageQuotaLinkDesc": "Nutzung und Limits verfolgen", + "businessHoursLink": "Geschäftszeiten", + "businessHoursLinkDesc": "Konfigurieren Sie die Öffnungszeiten für jeden Tag", + "emailTemplatesFullLink": "E-Mail-Vorlagen", + "emailTemplatesFullLinkDesc": "Passen Sie den Inhalt automatischer E-Mails an", + "embedWidgetLink": "Eingebettetes Widget", + "embedWidgetLinkDesc": "Fügen Sie Buchungen zu externen Websites hinzu", + "staffRolesLink": "Mitarbeiterrollen", + "staffRolesLinkDesc": "Konfigurieren Sie Berechtigungen für Mitarbeiter", + "smsCallingLink": "SMS und Anrufe", + "smsCallingLinkDesc": "Verwalten Sie Kommunikationsguthaben und Telefonnummern" }, "footer": { "title": "Benötigen Sie weitere Hilfe?", diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index cc23b58a..cecd93f7 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -3059,6 +3059,7 @@ "timeBlocks": "Time Blocks", "plugins": "Plugins", "contracts": "Contracts", + "locations": "Locations", "settings": "Settings", "servicesSetup": "Services Setup", "resourcesSetup": "Resources Setup", @@ -3070,7 +3071,12 @@ "billing": "Billing", "apiSettings": "API Settings", "authentication": "Authentication", - "usageQuota": "Usage & Quota" + "usageQuota": "Usage & Quota", + "businessHours": "Business Hours", + "emailTemplates": "Email Templates", + "embedWidget": "Embed Widget", + "staffRoles": "Staff Roles", + "smsCalling": "SMS & Calling" }, "introduction": { "title": "Introduction", @@ -3336,6 +3342,19 @@ "contractsDocumentation": "Contracts Documentation", "contractsDocumentationDesc": "Complete guide to templates, signing, and compliance features" }, + "locations": { + "title": "Locations", + "description": "Manage multiple business locations, each with their own address, contact info, and assigned resources and services.", + "keyFeatures": "Key Features", + "primaryLocation": "Primary Location", + "primaryLocationDesc": "Designate one location as your primary business address", + "activateDeactivate": "Activate/Deactivate", + "activateDeactivateDesc": "Temporarily disable locations without deleting them", + "addressManagement": "Address Management", + "addressManagementDesc": "Full address, phone, email, and timezone for each location", + "locationsDocumentation": "Locations Documentation", + "locationsDocumentationDesc": "Complete guide to multi-location management" + }, "settings": { "title": "Settings", "description": "Settings is where business owners configure their scheduling platform. Most settings are owner-only and affect how your business operates.", @@ -3383,7 +3402,17 @@ "apiSettingsLink": "API Settings", "apiSettingsLinkDesc": "API keys and webhooks", "usageQuotaLink": "Usage & Quota", - "usageQuotaLinkDesc": "Track usage and limits" + "usageQuotaLinkDesc": "Track usage and limits", + "businessHoursLink": "Business Hours", + "businessHoursLinkDesc": "Configure operating hours for each day", + "emailTemplatesFullLink": "Email Templates", + "emailTemplatesFullLinkDesc": "Customize automated email content", + "embedWidgetLink": "Embed Widget", + "embedWidgetLinkDesc": "Add booking to external websites", + "staffRolesLink": "Staff Roles", + "staffRolesLinkDesc": "Configure permissions for staff members", + "smsCallingLink": "SMS & Calling", + "smsCallingLinkDesc": "Manage communication credits and phone numbers" }, "footer": { "title": "Need More Help?", diff --git a/frontend/src/i18n/locales/es.json b/frontend/src/i18n/locales/es.json index d577bf7a..2bb05aff 100644 --- a/frontend/src/i18n/locales/es.json +++ b/frontend/src/i18n/locales/es.json @@ -1662,7 +1662,13 @@ "billing": "Facturación", "apiSettings": "Configuración de API", "authentication": "Autenticación", - "usageQuota": "Uso y Cuota" + "usageQuota": "Uso y Cuota", + "locations": "Ubicaciones", + "businessHours": "Horario Comercial", + "emailTemplates": "Plantillas de Email", + "embedWidget": "Widget Integrado", + "staffRoles": "Roles del Personal", + "smsCalling": "SMS y Llamadas" }, "introduction": { "title": "Introducción", @@ -1928,6 +1934,19 @@ "contractsDocumentation": "Documentación de Contratos", "contractsDocumentationDesc": "Guía completa de plantillas, firma y funciones de cumplimiento" }, + "locations": { + "title": "Ubicaciones", + "description": "Gestiona múltiples ubicaciones de negocio, cada una con su propia dirección, información de contacto y recursos y servicios asignados.", + "keyFeatures": "Características Principales", + "primaryLocation": "Ubicación Principal", + "primaryLocationDesc": "Designa una ubicación como tu dirección comercial principal", + "activateDeactivate": "Activar/Desactivar", + "activateDeactivateDesc": "Desactiva temporalmente ubicaciones sin eliminarlas", + "addressManagement": "Gestión de Direcciones", + "addressManagementDesc": "Dirección completa, teléfono, email y zona horaria para cada ubicación", + "locationsDocumentation": "Documentación de Ubicaciones", + "locationsDocumentationDesc": "Guía completa para la gestión de múltiples ubicaciones" + }, "settings": { "title": "Configuración", "description": "Configuración es donde los propietarios del negocio configuran su plataforma de programación. La mayoría de las configuraciones son solo para propietarios y afectan cómo opera tu negocio.", @@ -1966,8 +1985,18 @@ "otherSettings": "Otras Configuraciones", "resourceTypesLink": "Tipos de Recurso", "resourceTypesLinkDesc": "Configura tipos de personal, sala, equipo", + "businessHoursLink": "Horario Comercial", + "businessHoursLinkDesc": "Configura las horas de operación para cada día", "emailTemplatesLink": "Plantillas de Email", "emailTemplatesLinkDesc": "Personaliza notificaciones por email", + "emailTemplatesFullLink": "Plantillas de Email", + "emailTemplatesFullLinkDesc": "Personaliza el contenido de los emails automáticos", + "embedWidgetLink": "Widget Integrado", + "embedWidgetLinkDesc": "Añade reservas a sitios web externos", + "staffRolesLink": "Roles del Personal", + "staffRolesLinkDesc": "Configura permisos para los miembros del personal", + "smsCallingLink": "SMS y Llamadas", + "smsCallingLinkDesc": "Gestiona créditos de comunicación y números de teléfono", "customDomainsLink": "Dominios Personalizados", "customDomainsLinkDesc": "Usa tu propio dominio para reservas", "billingLink": "Facturación", diff --git a/frontend/src/i18n/locales/fr.json b/frontend/src/i18n/locales/fr.json index d7c52a62..0892fcf1 100644 --- a/frontend/src/i18n/locales/fr.json +++ b/frontend/src/i18n/locales/fr.json @@ -1591,6 +1591,7 @@ "timeBlocks": "Blocs de Temps", "plugins": "Plugins", "contracts": "Contrats", + "locations": "Emplacements", "settings": "Paramètres", "servicesSetup": "Configuration des Services", "resourcesSetup": "Configuration des Ressources", @@ -1602,7 +1603,12 @@ "billing": "Facturation", "apiSettings": "Paramètres API", "authentication": "Authentification", - "usageQuota": "Utilisation et Quota" + "usageQuota": "Utilisation et Quota", + "businessHours": "Heures d'Ouverture", + "emailTemplates": "Modèles d'Email", + "embedWidget": "Widget Intégré", + "staffRoles": "Rôles du Personnel", + "smsCalling": "SMS et Appels" }, "introduction": { "title": "Introduction", @@ -1868,6 +1874,19 @@ "contractsDocumentation": "Documentation des Contrats", "contractsDocumentationDesc": "Guide complet sur les modèles, la signature et les fonctions de conformité" }, + "locations": { + "title": "Emplacements", + "description": "Gérez plusieurs emplacements commerciaux, chacun avec sa propre adresse, coordonnées et ressources et services assignés.", + "keyFeatures": "Fonctionnalités Clés", + "primaryLocation": "Emplacement Principal", + "primaryLocationDesc": "Désignez un emplacement comme votre adresse commerciale principale", + "activateDeactivate": "Activer/Désactiver", + "activateDeactivateDesc": "Désactivez temporairement des emplacements sans les supprimer", + "addressManagement": "Gestion des Adresses", + "addressManagementDesc": "Adresse complète, téléphone, email et fuseau horaire pour chaque emplacement", + "locationsDocumentation": "Documentation des Emplacements", + "locationsDocumentationDesc": "Guide complet pour la gestion multi-emplacements" + }, "settings": { "title": "Paramètres", "description": "Paramètres est l'endroit où les propriétaires d'entreprise configurent leur plateforme de planification. La plupart des paramètres sont réservés aux propriétaires et affectent le fonctionnement de votre entreprise.", @@ -1915,7 +1934,17 @@ "apiSettingsLink": "Paramètres API", "apiSettingsLinkDesc": "Clés API et webhooks", "usageQuotaLink": "Utilisation et Quota", - "usageQuotaLinkDesc": "Suivez l'utilisation et les limites" + "usageQuotaLinkDesc": "Suivez l'utilisation et les limites", + "businessHoursLink": "Heures d'Ouverture", + "businessHoursLinkDesc": "Configurez les heures d'ouverture pour chaque jour", + "emailTemplatesFullLink": "Modèles d'Email", + "emailTemplatesFullLinkDesc": "Personnalisez le contenu des emails automatiques", + "embedWidgetLink": "Widget Intégré", + "embedWidgetLinkDesc": "Ajoutez la réservation sur des sites externes", + "staffRolesLink": "Rôles du Personnel", + "staffRolesLinkDesc": "Configurez les permissions pour le personnel", + "smsCallingLink": "SMS et Appels", + "smsCallingLinkDesc": "Gérez les crédits de communication et les numéros de téléphone" }, "footer": { "title": "Besoin de Plus d'Aide ?", diff --git a/frontend/src/pages/HelpApiDocs.tsx b/frontend/src/pages/HelpApiDocs.tsx index a731ac80..3a5ecf81 100644 --- a/frontend/src/pages/HelpApiDocs.tsx +++ b/frontend/src/pages/HelpApiDocs.tsx @@ -392,7 +392,10 @@ const navSections: NavSection[] = [ { titleKey: 'help.api.webhookEvents', id: 'webhook-events' }, { titleKey: 'help.api.createWebhook', id: 'create-webhook', method: 'POST' }, { titleKey: 'help.api.listWebhooks', id: 'list-webhooks', method: 'GET' }, + { titleKey: 'help.api.retrieveWebhook', id: 'retrieve-webhook', method: 'GET' }, + { titleKey: 'help.api.updateWebhook', id: 'update-webhook', method: 'PATCH' }, { titleKey: 'help.api.deleteWebhook', id: 'delete-webhook', method: 'DELETE' }, + { titleKey: 'help.api.testWebhook', id: 'test-webhook', method: 'POST' }, { titleKey: 'help.api.verifySignatures', id: 'verify-signatures' }, ], }, @@ -1033,6 +1036,856 @@ sub verify_webhook { }`, }; + const getResourcesCode: MultiLangCode = { + curl: `curl "${SANDBOX_URL}/resources/?type=STAFF" \\ + -H "Authorization: Bearer ${TEST_API_KEY}"`, + javascript: `const response = await fetch( + '${SANDBOX_URL}/resources/?type=STAFF', + { + headers: { + 'Authorization': 'Bearer ${TEST_API_KEY}' + } + } +); +const resources = await response.json();`, + python: `import requests + +response = requests.get( + '${SANDBOX_URL}/resources/', + params={'type': 'STAFF'}, + headers={'Authorization': 'Bearer ${TEST_API_KEY}'} +) +resources = response.json()`, + go: `req, _ := http.NewRequest("GET", + "${SANDBOX_URL}/resources/?type=STAFF", nil) +req.Header.Set("Authorization", "Bearer ${TEST_API_KEY}") +resp, _ := http.DefaultClient.Do(req)`, + java: `HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("${SANDBOX_URL}/resources/?type=STAFF")) + .header("Authorization", "Bearer ${TEST_API_KEY}") + .build();`, + csharp: `var response = await client.GetAsync( + "${SANDBOX_URL}/resources/?type=STAFF");`, + php: `$ch = curl_init('${SANDBOX_URL}/resources/?type=STAFF'); +curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Authorization: Bearer ${TEST_API_KEY}' +]);`, + ruby: `uri = URI('${SANDBOX_URL}/resources/') +uri.query = URI.encode_www_form(type: 'STAFF') +response = Net::HTTP.get_response(uri, { 'Authorization' => 'Bearer ${TEST_API_KEY}' })`, + perl: `my $response = $ua->get( + '${SANDBOX_URL}/resources/?type=STAFF', + 'Authorization' => 'Bearer ${TEST_API_KEY}' +);`, + }; + + const getResourceCode: MultiLangCode = { + curl: `curl ${SANDBOX_URL}/resources/res_123/ \\ + -H "Authorization: Bearer ${TEST_API_KEY}"`, + javascript: `const response = await fetch('${SANDBOX_URL}/resources/res_123/', { + headers: { + 'Authorization': 'Bearer ${TEST_API_KEY}' + } +}); +const resource = await response.json();`, + python: `import requests + +response = requests.get( + '${SANDBOX_URL}/resources/res_123/', + headers={'Authorization': 'Bearer ${TEST_API_KEY}'} +) +resource = response.json()`, + go: `req, _ := http.NewRequest("GET", "${SANDBOX_URL}/resources/res_123/", nil) +req.Header.Set("Authorization", "Bearer ${TEST_API_KEY}") +resp, _ := http.DefaultClient.Do(req)`, + java: `HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("${SANDBOX_URL}/resources/res_123/")) + .header("Authorization", "Bearer ${TEST_API_KEY}") + .build();`, + csharp: `var response = await client.GetAsync("${SANDBOX_URL}/resources/res_123/");`, + php: `$ch = curl_init('${SANDBOX_URL}/resources/res_123/'); +curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Authorization: Bearer ${TEST_API_KEY}' +]);`, + ruby: `response = Net::HTTP.get_response( + URI('${SANDBOX_URL}/resources/res_123/'), + { 'Authorization' => 'Bearer ${TEST_API_KEY}' } +)`, + perl: `my $response = $ua->get('${SANDBOX_URL}/resources/res_123/', + 'Authorization' => 'Bearer ${TEST_API_KEY}' +);`, + }; + + const getServiceCode: MultiLangCode = { + curl: `curl ${SANDBOX_URL}/services/svc_123/ \\ + -H "Authorization: Bearer ${TEST_API_KEY}"`, + javascript: `const response = await fetch('${SANDBOX_URL}/services/svc_123/', { + headers: { + 'Authorization': 'Bearer ${TEST_API_KEY}' + } +}); +const service = await response.json();`, + python: `import requests + +response = requests.get( + '${SANDBOX_URL}/services/svc_123/', + headers={'Authorization': 'Bearer ${TEST_API_KEY}'} +) +service = response.json()`, + go: `req, _ := http.NewRequest("GET", "${SANDBOX_URL}/services/svc_123/", nil) +req.Header.Set("Authorization", "Bearer ${TEST_API_KEY}") +resp, _ := http.DefaultClient.Do(req)`, + java: `HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("${SANDBOX_URL}/services/svc_123/")) + .header("Authorization", "Bearer ${TEST_API_KEY}") + .build();`, + csharp: `var response = await client.GetAsync("${SANDBOX_URL}/services/svc_123/");`, + php: `$ch = curl_init('${SANDBOX_URL}/services/svc_123/'); +curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Authorization: Bearer ${TEST_API_KEY}' +]);`, + ruby: `response = Net::HTTP.get_response( + URI('${SANDBOX_URL}/services/svc_123/'), + { 'Authorization' => 'Bearer ${TEST_API_KEY}' } +)`, + perl: `my $response = $ua->get('${SANDBOX_URL}/services/svc_123/', + 'Authorization' => 'Bearer ${TEST_API_KEY}' +);`, + }; + + const getAppointmentsCode: MultiLangCode = { + curl: `curl "${SANDBOX_URL}/appointments/?start_date=2025-12-01&end_date=2025-12-31" \\ + -H "Authorization: Bearer ${TEST_API_KEY}"`, + javascript: `const response = await fetch( + '${SANDBOX_URL}/appointments/?start_date=2025-12-01&end_date=2025-12-31', + { + headers: { + 'Authorization': 'Bearer ${TEST_API_KEY}' + } + } +); +const appointments = await response.json();`, + python: `import requests + +response = requests.get( + '${SANDBOX_URL}/appointments/', + params={'start_date': '2025-12-01', 'end_date': '2025-12-31'}, + headers={'Authorization': 'Bearer ${TEST_API_KEY}'} +) +appointments = response.json()`, + go: `req, _ := http.NewRequest("GET", + "${SANDBOX_URL}/appointments/?start_date=2025-12-01&end_date=2025-12-31", nil) +req.Header.Set("Authorization", "Bearer ${TEST_API_KEY}") +resp, _ := http.DefaultClient.Do(req)`, + java: `HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("${SANDBOX_URL}/appointments/?start_date=2025-12-01&end_date=2025-12-31")) + .header("Authorization", "Bearer ${TEST_API_KEY}") + .build();`, + csharp: `var response = await client.GetAsync( + "${SANDBOX_URL}/appointments/?start_date=2025-12-01&end_date=2025-12-31");`, + php: `$ch = curl_init('${SANDBOX_URL}/appointments/?start_date=2025-12-01&end_date=2025-12-31'); +curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Authorization: Bearer ${TEST_API_KEY}' +]);`, + ruby: `uri = URI('${SANDBOX_URL}/appointments/') +uri.query = URI.encode_www_form(start_date: '2025-12-01', end_date: '2025-12-31') +response = Net::HTTP.get_response(uri, { 'Authorization' => 'Bearer ${TEST_API_KEY}' })`, + perl: `my $response = $ua->get( + '${SANDBOX_URL}/appointments/?start_date=2025-12-01&end_date=2025-12-31', + 'Authorization' => 'Bearer ${TEST_API_KEY}' +);`, + }; + + const getAppointmentCode: MultiLangCode = { + curl: `curl ${SANDBOX_URL}/appointments/apt_123/ \\ + -H "Authorization: Bearer ${TEST_API_KEY}"`, + javascript: `const response = await fetch('${SANDBOX_URL}/appointments/apt_123/', { + headers: { + 'Authorization': 'Bearer ${TEST_API_KEY}' + } +}); +const appointment = await response.json();`, + python: `import requests + +response = requests.get( + '${SANDBOX_URL}/appointments/apt_123/', + headers={'Authorization': 'Bearer ${TEST_API_KEY}'} +) +appointment = response.json()`, + go: `req, _ := http.NewRequest("GET", "${SANDBOX_URL}/appointments/apt_123/", nil) +req.Header.Set("Authorization", "Bearer ${TEST_API_KEY}") +resp, _ := http.DefaultClient.Do(req)`, + java: `HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("${SANDBOX_URL}/appointments/apt_123/")) + .header("Authorization", "Bearer ${TEST_API_KEY}") + .build();`, + csharp: `var response = await client.GetAsync("${SANDBOX_URL}/appointments/apt_123/");`, + php: `$ch = curl_init('${SANDBOX_URL}/appointments/apt_123/'); +curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Authorization: Bearer ${TEST_API_KEY}' +]);`, + ruby: `response = Net::HTTP.get_response( + URI('${SANDBOX_URL}/appointments/apt_123/'), + { 'Authorization' => 'Bearer ${TEST_API_KEY}' } +)`, + perl: `my $response = $ua->get('${SANDBOX_URL}/appointments/apt_123/', + 'Authorization' => 'Bearer ${TEST_API_KEY}' +);`, + }; + + const updateAppointmentCode: MultiLangCode = { + curl: `curl ${SANDBOX_URL}/appointments/apt_123/ \\ + -X PATCH \\ + -H "Authorization: Bearer ${TEST_API_KEY}" \\ + -H "Content-Type: application/json" \\ + -d '{ + "notes": "Customer requested window seat", + "start_time": "2025-12-01T11:00:00Z" + }'`, + javascript: `const response = await fetch('${SANDBOX_URL}/appointments/apt_123/', { + method: 'PATCH', + headers: { + 'Authorization': 'Bearer ${TEST_API_KEY}', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + notes: 'Customer requested window seat', + start_time: '2025-12-01T11:00:00Z' + }) +});`, + python: `import requests + +response = requests.patch( + '${SANDBOX_URL}/appointments/apt_123/', + headers={'Authorization': 'Bearer ${TEST_API_KEY}'}, + json={ + 'notes': 'Customer requested window seat', + 'start_time': '2025-12-01T11:00:00Z' + } +)`, + go: `body := strings.NewReader(\`{ + "notes": "Customer requested window seat", + "start_time": "2025-12-01T11:00:00Z" +}\`) +req, _ := http.NewRequest("PATCH", "${SANDBOX_URL}/appointments/apt_123/", body) +req.Header.Set("Authorization", "Bearer ${TEST_API_KEY}") +req.Header.Set("Content-Type", "application/json")`, + java: `String json = """ + { + "notes": "Customer requested window seat", + "start_time": "2025-12-01T11:00:00Z" + } + """; +HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("${SANDBOX_URL}/appointments/apt_123/")) + .header("Authorization", "Bearer ${TEST_API_KEY}") + .method("PATCH", HttpRequest.BodyPublishers.ofString(json)) + .build();`, + csharp: `var content = new StringContent( + JsonSerializer.Serialize(new { + notes = "Customer requested window seat", + start_time = "2025-12-01T11:00:00Z" + }), + Encoding.UTF8, + "application/json" +); +var response = await client.PatchAsync("${SANDBOX_URL}/appointments/apt_123/", content);`, + php: `$data = [ + 'notes' => 'Customer requested window seat', + 'start_time' => '2025-12-01T11:00:00Z' +]; +$ch = curl_init('${SANDBOX_URL}/appointments/apt_123/'); +curl_setopt_array($ch, [ + CURLOPT_CUSTOMREQUEST => 'PATCH', + CURLOPT_POSTFIELDS => json_encode($data), + CURLOPT_HTTPHEADER => [ + 'Authorization: Bearer ${TEST_API_KEY}', + 'Content-Type: application/json' + ] +]);`, + ruby: `uri = URI('${SANDBOX_URL}/appointments/apt_123/') +http = Net::HTTP.new(uri.host, uri.port) +http.use_ssl = true +request = Net::HTTP::Patch.new(uri) +request['Authorization'] = 'Bearer ${TEST_API_KEY}' +request['Content-Type'] = 'application/json' +request.body = { + notes: 'Customer requested window seat', + start_time: '2025-12-01T11:00:00Z' +}.to_json`, + perl: `my $ua = LWP::UserAgent->new; +my $req = HTTP::Request->new(PATCH => '${SANDBOX_URL}/appointments/apt_123/'); +$req->header('Authorization' => 'Bearer ${TEST_API_KEY}'); +$req->header('Content-Type' => 'application/json'); +$req->content(encode_json({ + notes => 'Customer requested window seat', + start_time => '2025-12-01T11:00:00Z' +}));`, + }; + + const cancelAppointmentCode: MultiLangCode = { + curl: `curl ${SANDBOX_URL}/appointments/apt_123/ \\ + -X DELETE \\ + -H "Authorization: Bearer ${TEST_API_KEY}"`, + javascript: `const response = await fetch('${SANDBOX_URL}/appointments/apt_123/', { + method: 'DELETE', + headers: { + 'Authorization': 'Bearer ${TEST_API_KEY}' + } +});`, + python: `import requests + +response = requests.delete( + '${SANDBOX_URL}/appointments/apt_123/', + headers={'Authorization': 'Bearer ${TEST_API_KEY}'} +)`, + go: `req, _ := http.NewRequest("DELETE", "${SANDBOX_URL}/appointments/apt_123/", nil) +req.Header.Set("Authorization", "Bearer ${TEST_API_KEY}") +resp, _ := http.DefaultClient.Do(req)`, + java: `HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("${SANDBOX_URL}/appointments/apt_123/")) + .header("Authorization", "Bearer ${TEST_API_KEY}") + .DELETE() + .build();`, + csharp: `var response = await client.DeleteAsync("${SANDBOX_URL}/appointments/apt_123/");`, + php: `$ch = curl_init('${SANDBOX_URL}/appointments/apt_123/'); +curl_setopt_array($ch, [ + CURLOPT_CUSTOMREQUEST => 'DELETE', + CURLOPT_HTTPHEADER => [ + 'Authorization: Bearer ${TEST_API_KEY}' + ] +]);`, + ruby: `uri = URI('${SANDBOX_URL}/appointments/apt_123/') +http = Net::HTTP.new(uri.host, uri.port) +http.use_ssl = true +request = Net::HTTP::Delete.new(uri) +request['Authorization'] = 'Bearer ${TEST_API_KEY}' +response = http.request(request)`, + perl: `my $req = HTTP::Request->new(DELETE => '${SANDBOX_URL}/appointments/apt_123/'); +$req->header('Authorization' => 'Bearer ${TEST_API_KEY}'); +my $response = $ua->request($req);`, + }; + + const getCustomersCode: MultiLangCode = { + curl: `curl "${SANDBOX_URL}/customers/?search=john" \\ + -H "Authorization: Bearer ${TEST_API_KEY}"`, + javascript: `const response = await fetch( + '${SANDBOX_URL}/customers/?search=john', + { + headers: { + 'Authorization': 'Bearer ${TEST_API_KEY}' + } + } +); +const customers = await response.json();`, + python: `import requests + +response = requests.get( + '${SANDBOX_URL}/customers/', + params={'search': 'john'}, + headers={'Authorization': 'Bearer ${TEST_API_KEY}'} +) +customers = response.json()`, + go: `req, _ := http.NewRequest("GET", + "${SANDBOX_URL}/customers/?search=john", nil) +req.Header.Set("Authorization", "Bearer ${TEST_API_KEY}") +resp, _ := http.DefaultClient.Do(req)`, + java: `HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("${SANDBOX_URL}/customers/?search=john")) + .header("Authorization", "Bearer ${TEST_API_KEY}") + .build();`, + csharp: `var response = await client.GetAsync( + "${SANDBOX_URL}/customers/?search=john");`, + php: `$ch = curl_init('${SANDBOX_URL}/customers/?search=john'); +curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Authorization: Bearer ${TEST_API_KEY}' +]);`, + ruby: `uri = URI('${SANDBOX_URL}/customers/') +uri.query = URI.encode_www_form(search: 'john') +response = Net::HTTP.get_response(uri, { 'Authorization' => 'Bearer ${TEST_API_KEY}' })`, + perl: `my $response = $ua->get( + '${SANDBOX_URL}/customers/?search=john', + 'Authorization' => 'Bearer ${TEST_API_KEY}' +);`, + }; + + const getCustomerCode: MultiLangCode = { + curl: `curl ${SANDBOX_URL}/customers/cust_123/ \\ + -H "Authorization: Bearer ${TEST_API_KEY}"`, + javascript: `const response = await fetch('${SANDBOX_URL}/customers/cust_123/', { + headers: { + 'Authorization': 'Bearer ${TEST_API_KEY}' + } +}); +const customer = await response.json();`, + python: `import requests + +response = requests.get( + '${SANDBOX_URL}/customers/cust_123/', + headers={'Authorization': 'Bearer ${TEST_API_KEY}'} +) +customer = response.json()`, + go: `req, _ := http.NewRequest("GET", "${SANDBOX_URL}/customers/cust_123/", nil) +req.Header.Set("Authorization", "Bearer ${TEST_API_KEY}") +resp, _ := http.DefaultClient.Do(req)`, + java: `HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("${SANDBOX_URL}/customers/cust_123/")) + .header("Authorization", "Bearer ${TEST_API_KEY}") + .build();`, + csharp: `var response = await client.GetAsync("${SANDBOX_URL}/customers/cust_123/");`, + php: `$ch = curl_init('${SANDBOX_URL}/customers/cust_123/'); +curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Authorization: Bearer ${TEST_API_KEY}' +]);`, + ruby: `response = Net::HTTP.get_response( + URI('${SANDBOX_URL}/customers/cust_123/'), + { 'Authorization' => 'Bearer ${TEST_API_KEY}' } +)`, + perl: `my $response = $ua->get('${SANDBOX_URL}/customers/cust_123/', + 'Authorization' => 'Bearer ${TEST_API_KEY}' +);`, + }; + + const createCustomerCode: MultiLangCode = { + curl: `curl ${SANDBOX_URL}/customers/ \\ + -H "Authorization: Bearer ${TEST_API_KEY}" \\ + -H "Content-Type: application/json" \\ + -d '{ + "email": "jane@example.com", + "name": "Jane Smith", + "phone": "+1234567890" + }'`, + javascript: `const response = await fetch('${SANDBOX_URL}/customers/', { + method: 'POST', + headers: { + 'Authorization': 'Bearer ${TEST_API_KEY}', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + email: 'jane@example.com', + name: 'Jane Smith', + phone: '+1234567890' + }) +});`, + python: `import requests + +response = requests.post( + '${SANDBOX_URL}/customers/', + headers={'Authorization': 'Bearer ${TEST_API_KEY}'}, + json={ + 'email': 'jane@example.com', + 'name': 'Jane Smith', + 'phone': '+1234567890' + } +)`, + go: `body := strings.NewReader(\`{ + "email": "jane@example.com", + "name": "Jane Smith", + "phone": "+1234567890" +}\`) +req, _ := http.NewRequest("POST", "${SANDBOX_URL}/customers/", body) +req.Header.Set("Authorization", "Bearer ${TEST_API_KEY}") +req.Header.Set("Content-Type", "application/json")`, + java: `String json = """ + { + "email": "jane@example.com", + "name": "Jane Smith", + "phone": "+1234567890" + } + """; +HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("${SANDBOX_URL}/customers/")) + .header("Authorization", "Bearer ${TEST_API_KEY}") + .POST(HttpRequest.BodyPublishers.ofString(json)) + .build();`, + csharp: `var content = new StringContent( + JsonSerializer.Serialize(new { + email = "jane@example.com", + name = "Jane Smith", + phone = "+1234567890" + }), + Encoding.UTF8, + "application/json" +); +var response = await client.PostAsync("${SANDBOX_URL}/customers/", content);`, + php: `$data = [ + 'email' => 'jane@example.com', + 'name' => 'Jane Smith', + 'phone' => '+1234567890' +]; +$ch = curl_init('${SANDBOX_URL}/customers/'); +curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => json_encode($data), + CURLOPT_HTTPHEADER => [ + 'Authorization: Bearer ${TEST_API_KEY}', + 'Content-Type: application/json' + ] +]);`, + ruby: `uri = URI('${SANDBOX_URL}/customers/') +http = Net::HTTP.new(uri.host, uri.port) +http.use_ssl = true +request = Net::HTTP::Post.new(uri) +request['Authorization'] = 'Bearer ${TEST_API_KEY}' +request['Content-Type'] = 'application/json' +request.body = { + email: 'jane@example.com', + name: 'Jane Smith', + phone: '+1234567890' +}.to_json`, + perl: `my $ua = LWP::UserAgent->new; +my $req = HTTP::Request->new(POST => '${SANDBOX_URL}/customers/'); +$req->header('Authorization' => 'Bearer ${TEST_API_KEY}'); +$req->header('Content-Type' => 'application/json'); +$req->content(encode_json({ + email => 'jane@example.com', + name => 'Jane Smith', + phone => '+1234567890' +}));`, + }; + + const updateCustomerCode: MultiLangCode = { + curl: `curl ${SANDBOX_URL}/customers/cust_123/ \\ + -X PATCH \\ + -H "Authorization: Bearer ${TEST_API_KEY}" \\ + -H "Content-Type: application/json" \\ + -d '{ + "phone": "+1987654321", + "name": "Jane Doe" + }'`, + javascript: `const response = await fetch('${SANDBOX_URL}/customers/cust_123/', { + method: 'PATCH', + headers: { + 'Authorization': 'Bearer ${TEST_API_KEY}', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + phone: '+1987654321', + name: 'Jane Doe' + }) +});`, + python: `import requests + +response = requests.patch( + '${SANDBOX_URL}/customers/cust_123/', + headers={'Authorization': 'Bearer ${TEST_API_KEY}'}, + json={ + 'phone': '+1987654321', + 'name': 'Jane Doe' + } +)`, + go: `body := strings.NewReader(\`{ + "phone": "+1987654321", + "name": "Jane Doe" +}\`) +req, _ := http.NewRequest("PATCH", "${SANDBOX_URL}/customers/cust_123/", body) +req.Header.Set("Authorization", "Bearer ${TEST_API_KEY}") +req.Header.Set("Content-Type", "application/json")`, + java: `String json = """ + { + "phone": "+1987654321", + "name": "Jane Doe" + } + """; +HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("${SANDBOX_URL}/customers/cust_123/")) + .header("Authorization", "Bearer ${TEST_API_KEY}") + .method("PATCH", HttpRequest.BodyPublishers.ofString(json)) + .build();`, + csharp: `var content = new StringContent( + JsonSerializer.Serialize(new { + phone = "+1987654321", + name = "Jane Doe" + }), + Encoding.UTF8, + "application/json" +); +var response = await client.PatchAsync("${SANDBOX_URL}/customers/cust_123/", content);`, + php: `$data = [ + 'phone' => '+1987654321', + 'name' => 'Jane Doe' +]; +$ch = curl_init('${SANDBOX_URL}/customers/cust_123/'); +curl_setopt_array($ch, [ + CURLOPT_CUSTOMREQUEST => 'PATCH', + CURLOPT_POSTFIELDS => json_encode($data), + CURLOPT_HTTPHEADER => [ + 'Authorization: Bearer ${TEST_API_KEY}', + 'Content-Type: application/json' + ] +]);`, + ruby: `uri = URI('${SANDBOX_URL}/customers/cust_123/') +http = Net::HTTP.new(uri.host, uri.port) +http.use_ssl = true +request = Net::HTTP::Patch.new(uri) +request['Authorization'] = 'Bearer ${TEST_API_KEY}' +request['Content-Type'] = 'application/json' +request.body = { + phone: '+1987654321', + name: 'Jane Doe' +}.to_json`, + perl: `my $ua = LWP::UserAgent->new; +my $req = HTTP::Request->new(PATCH => '${SANDBOX_URL}/customers/cust_123/'); +$req->header('Authorization' => 'Bearer ${TEST_API_KEY}'); +$req->header('Content-Type' => 'application/json'); +$req->content(encode_json({ + phone => '+1987654321', + name => 'Jane Doe' +}));`, + }; + + const getWebhooksCode: MultiLangCode = { + curl: `curl ${SANDBOX_URL}/webhooks/ \\ + -H "Authorization: Bearer ${TEST_API_KEY}"`, + javascript: `const response = await fetch('${SANDBOX_URL}/webhooks/', { + headers: { + 'Authorization': 'Bearer ${TEST_API_KEY}' + } +}); +const webhooks = await response.json();`, + python: `import requests + +response = requests.get( + '${SANDBOX_URL}/webhooks/', + headers={'Authorization': 'Bearer ${TEST_API_KEY}'} +) +webhooks = response.json()`, + go: `req, _ := http.NewRequest("GET", "${SANDBOX_URL}/webhooks/", nil) +req.Header.Set("Authorization", "Bearer ${TEST_API_KEY}") +resp, _ := http.DefaultClient.Do(req)`, + java: `HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("${SANDBOX_URL}/webhooks/")) + .header("Authorization", "Bearer ${TEST_API_KEY}") + .build();`, + csharp: `var response = await client.GetAsync("${SANDBOX_URL}/webhooks/");`, + php: `$ch = curl_init('${SANDBOX_URL}/webhooks/'); +curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Authorization: Bearer ${TEST_API_KEY}' +]);`, + ruby: `response = Net::HTTP.get_response( + URI('${SANDBOX_URL}/webhooks/'), + { 'Authorization' => 'Bearer ${TEST_API_KEY}' } +)`, + perl: `my $response = $ua->get('${SANDBOX_URL}/webhooks/', + 'Authorization' => 'Bearer ${TEST_API_KEY}' +);`, + }; + + const getWebhookCode: MultiLangCode = { + curl: `curl ${SANDBOX_URL}/webhooks/whk_123/ \\ + -H "Authorization: Bearer ${TEST_API_KEY}"`, + javascript: `const response = await fetch('${SANDBOX_URL}/webhooks/whk_123/', { + headers: { + 'Authorization': 'Bearer ${TEST_API_KEY}' + } +}); +const webhook = await response.json();`, + python: `import requests + +response = requests.get( + '${SANDBOX_URL}/webhooks/whk_123/', + headers={'Authorization': 'Bearer ${TEST_API_KEY}'} +) +webhook = response.json()`, + go: `req, _ := http.NewRequest("GET", "${SANDBOX_URL}/webhooks/whk_123/", nil) +req.Header.Set("Authorization", "Bearer ${TEST_API_KEY}") +resp, _ := http.DefaultClient.Do(req)`, + java: `HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("${SANDBOX_URL}/webhooks/whk_123/")) + .header("Authorization", "Bearer ${TEST_API_KEY}") + .build();`, + csharp: `var response = await client.GetAsync("${SANDBOX_URL}/webhooks/whk_123/");`, + php: `$ch = curl_init('${SANDBOX_URL}/webhooks/whk_123/'); +curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Authorization: Bearer ${TEST_API_KEY}' +]);`, + ruby: `response = Net::HTTP.get_response( + URI('${SANDBOX_URL}/webhooks/whk_123/'), + { 'Authorization' => 'Bearer ${TEST_API_KEY}' } +)`, + perl: `my $response = $ua->get('${SANDBOX_URL}/webhooks/whk_123/', + 'Authorization' => 'Bearer ${TEST_API_KEY}' +);`, + }; + + const updateWebhookCode: MultiLangCode = { + curl: `curl ${SANDBOX_URL}/webhooks/whk_123/ \\ + -X PATCH \\ + -H "Authorization: Bearer ${TEST_API_KEY}" \\ + -H "Content-Type: application/json" \\ + -d '{ + "events": ["appointment.created", "appointment.updated", "appointment.cancelled"], + "is_active": true + }'`, + javascript: `const response = await fetch('${SANDBOX_URL}/webhooks/whk_123/', { + method: 'PATCH', + headers: { + 'Authorization': 'Bearer ${TEST_API_KEY}', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + events: ['appointment.created', 'appointment.updated', 'appointment.cancelled'], + is_active: true + }) +});`, + python: `import requests + +response = requests.patch( + '${SANDBOX_URL}/webhooks/whk_123/', + headers={'Authorization': 'Bearer ${TEST_API_KEY}'}, + json={ + 'events': ['appointment.created', 'appointment.updated', 'appointment.cancelled'], + 'is_active': True + } +)`, + go: `body := strings.NewReader(\`{ + "events": ["appointment.created", "appointment.updated", "appointment.cancelled"], + "is_active": true +}\`) +req, _ := http.NewRequest("PATCH", "${SANDBOX_URL}/webhooks/whk_123/", body) +req.Header.Set("Authorization", "Bearer ${TEST_API_KEY}") +req.Header.Set("Content-Type", "application/json")`, + java: `String json = """ + { + "events": ["appointment.created", "appointment.updated", "appointment.cancelled"], + "is_active": true + } + """; +HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("${SANDBOX_URL}/webhooks/whk_123/")) + .header("Authorization", "Bearer ${TEST_API_KEY}") + .method("PATCH", HttpRequest.BodyPublishers.ofString(json)) + .build();`, + csharp: `var content = new StringContent( + JsonSerializer.Serialize(new { + events = new[] { "appointment.created", "appointment.updated", "appointment.cancelled" }, + is_active = true + }), + Encoding.UTF8, + "application/json" +); +var response = await client.PatchAsync("${SANDBOX_URL}/webhooks/whk_123/", content);`, + php: `$data = [ + 'events' => ['appointment.created', 'appointment.updated', 'appointment.cancelled'], + 'is_active' => true +]; +$ch = curl_init('${SANDBOX_URL}/webhooks/whk_123/'); +curl_setopt_array($ch, [ + CURLOPT_CUSTOMREQUEST => 'PATCH', + CURLOPT_POSTFIELDS => json_encode($data), + CURLOPT_HTTPHEADER => [ + 'Authorization: Bearer ${TEST_API_KEY}', + 'Content-Type: application/json' + ] +]);`, + ruby: `uri = URI('${SANDBOX_URL}/webhooks/whk_123/') +http = Net::HTTP.new(uri.host, uri.port) +http.use_ssl = true +request = Net::HTTP::Patch.new(uri) +request['Authorization'] = 'Bearer ${TEST_API_KEY}' +request['Content-Type'] = 'application/json' +request.body = { + events: ['appointment.created', 'appointment.updated', 'appointment.cancelled'], + is_active: true +}.to_json`, + perl: `my $ua = LWP::UserAgent->new; +my $req = HTTP::Request->new(PATCH => '${SANDBOX_URL}/webhooks/whk_123/'); +$req->header('Authorization' => 'Bearer ${TEST_API_KEY}'); +$req->header('Content-Type' => 'application/json'); +$req->content(encode_json({ + events => ['appointment.created', 'appointment.updated', 'appointment.cancelled'], + is_active => 1 +}));`, + }; + + const deleteWebhookCode: MultiLangCode = { + curl: `curl ${SANDBOX_URL}/webhooks/whk_123/ \\ + -X DELETE \\ + -H "Authorization: Bearer ${TEST_API_KEY}"`, + javascript: `const response = await fetch('${SANDBOX_URL}/webhooks/whk_123/', { + method: 'DELETE', + headers: { + 'Authorization': 'Bearer ${TEST_API_KEY}' + } +});`, + python: `import requests + +response = requests.delete( + '${SANDBOX_URL}/webhooks/whk_123/', + headers={'Authorization': 'Bearer ${TEST_API_KEY}'} +)`, + go: `req, _ := http.NewRequest("DELETE", "${SANDBOX_URL}/webhooks/whk_123/", nil) +req.Header.Set("Authorization", "Bearer ${TEST_API_KEY}") +resp, _ := http.DefaultClient.Do(req)`, + java: `HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("${SANDBOX_URL}/webhooks/whk_123/")) + .header("Authorization", "Bearer ${TEST_API_KEY}") + .DELETE() + .build();`, + csharp: `var response = await client.DeleteAsync("${SANDBOX_URL}/webhooks/whk_123/");`, + php: `$ch = curl_init('${SANDBOX_URL}/webhooks/whk_123/'); +curl_setopt_array($ch, [ + CURLOPT_CUSTOMREQUEST => 'DELETE', + CURLOPT_HTTPHEADER => [ + 'Authorization: Bearer ${TEST_API_KEY}' + ] +]);`, + ruby: `uri = URI('${SANDBOX_URL}/webhooks/whk_123/') +http = Net::HTTP.new(uri.host, uri.port) +http.use_ssl = true +request = Net::HTTP::Delete.new(uri) +request['Authorization'] = 'Bearer ${TEST_API_KEY}' +response = http.request(request)`, + perl: `my $req = HTTP::Request->new(DELETE => '${SANDBOX_URL}/webhooks/whk_123/'); +$req->header('Authorization' => 'Bearer ${TEST_API_KEY}'); +my $response = $ua->request($req);`, + }; + + const testWebhookCode: MultiLangCode = { + curl: `curl ${SANDBOX_URL}/webhooks/whk_123/test/ \\ + -X POST \\ + -H "Authorization: Bearer ${TEST_API_KEY}"`, + javascript: `const response = await fetch('${SANDBOX_URL}/webhooks/whk_123/test/', { + method: 'POST', + headers: { + 'Authorization': 'Bearer ${TEST_API_KEY}' + } +}); +const result = await response.json();`, + python: `import requests + +response = requests.post( + '${SANDBOX_URL}/webhooks/whk_123/test/', + headers={'Authorization': 'Bearer ${TEST_API_KEY}'} +) +result = response.json()`, + go: `req, _ := http.NewRequest("POST", "${SANDBOX_URL}/webhooks/whk_123/test/", nil) +req.Header.Set("Authorization", "Bearer ${TEST_API_KEY}") +resp, _ := http.DefaultClient.Do(req)`, + java: `HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("${SANDBOX_URL}/webhooks/whk_123/test/")) + .header("Authorization", "Bearer ${TEST_API_KEY}") + .POST(HttpRequest.BodyPublishers.noBody()) + .build();`, + csharp: `var response = await client.PostAsync("${SANDBOX_URL}/webhooks/whk_123/test/", null);`, + php: `$ch = curl_init('${SANDBOX_URL}/webhooks/whk_123/test/'); +curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_HTTPHEADER => [ + 'Authorization: Bearer ${TEST_API_KEY}' + ] +]);`, + ruby: `uri = URI('${SANDBOX_URL}/webhooks/whk_123/test/') +http = Net::HTTP.new(uri.host, uri.port) +http.use_ssl = true +request = Net::HTTP::Post.new(uri) +request['Authorization'] = 'Bearer ${TEST_API_KEY}' +response = http.request(request)`, + perl: `my $req = HTTP::Request->new(POST => '${SANDBOX_URL}/webhooks/whk_123/test/'); +$req->header('Authorization' => 'Bearer ${TEST_API_KEY}'); +my $response = $ua->request($req);`, + }; + const authExampleCode: MultiLangCode = { curl: `curl ${SANDBOX_URL}/services/ \\ -H "Authorization: Bearer ${TEST_API_KEY}"`, @@ -1490,6 +2343,203 @@ X-RateLimit-Burst-Remaining: 95`} + {/* Retrieve Service */} + + +

+ + GET + + {t('help.api.retrieveService')} +

+

+ {t('help.api.retrieveServiceDescription', 'Retrieve details of a specific service.')} +

+

+ {t('help.api.requiredScope')} +

+

+ services:read +

+
+ + + + + +
+ + {/* Resource Object */} + + +

+ {t('help.api.resourceObject')} +

+

+ {t('help.api.resourceObjectDescription', 'Resources represent staff members, rooms, equipment, or any bookable entity in your business.')} +

+

+ {t('help.api.attributes')} +

+ +
+ + + +
+ + {/* List Resources */} + + +

+ + GET + + {t('help.api.listResources')} +

+

+ {t('help.api.listResourcesDescription', 'Retrieve a list of all resources in your business.')} +

+

+ {t('help.api.parameters')} +

+ +

+ {t('help.api.requiredScope')} +

+

+ resources:read +

+
+ + + + + +
+ + {/* Retrieve Resource */} + + +

+ + GET + + {t('help.api.retrieveResource')} +

+

+ {t('help.api.retrieveResourceDescription', 'Retrieve details of a specific resource.')} +

+

+ {t('help.api.requiredScope')} +

+

+ resources:read +

+
+ + + + + +
+ {/* Check Availability */} @@ -1559,6 +2609,63 @@ X-RateLimit-Burst-Remaining: 95`} + {/* Appointment Object */} + + +

+ {t('help.api.appointmentObject')} +

+

+ {t('help.api.appointmentObjectDescription', 'Appointments represent scheduled bookings for services with resources.')} +

+

+ {t('help.api.attributes')} +

+ +
+ + + +
+ {/* Create Appointment */} @@ -1626,6 +2733,493 @@ X-RateLimit-Burst-Remaining: 95`} + {/* Retrieve Appointment */} + + +

+ + GET + + {t('help.api.retrieveAppointment')} +

+

+ {t('help.api.retrieveAppointmentDescription', 'Retrieve details of a specific appointment.')} +

+

+ {t('help.api.requiredScope')} +

+

+ bookings:read +

+
+ + + + + +
+ + {/* Update Appointment */} + + +

+ + PATCH + + {t('help.api.updateAppointment')} +

+

+ {t('help.api.updateAppointmentDescription', 'Update an existing appointment.')} +

+

+ {t('help.api.parameters')} +

+ +

+ {t('help.api.requiredScope')} +

+

+ bookings:write +

+
+ + + + + +
+ + {/* Cancel Appointment */} + + +

+ + DELETE + + {t('help.api.cancelAppointment')} +

+

+ {t('help.api.cancelAppointmentDescription', 'Cancel an appointment. This action cannot be undone.')} +

+

+ {t('help.api.requiredScope')} +

+

+ bookings:write +

+
+ + + + + +
+ + {/* List Appointments */} + + +

+ + GET + + {t('help.api.listAppointments')} +

+

+ {t('help.api.listAppointmentsDescription', 'Retrieve a list of appointments filtered by date range and status.')} +

+

+ {t('help.api.parameters')} +

+ +

+ {t('help.api.requiredScope')} +

+

+ bookings:read +

+
+ + + + + +
+ + {/* Customer Object */} + + +

+ {t('help.api.customerObject')} +

+

+ {t('help.api.customerObjectDescription', 'Customers are individuals who book appointments at your business.')} +

+

+ {t('help.api.attributes')} +

+ +
+ + + +
+ + {/* Create Customer */} + + +

+ + POST + + {t('help.api.createCustomer')} +

+

+ {t('help.api.createCustomerDescription', 'Create a new customer record.')} +

+

+ {t('help.api.parameters')} +

+ +

+ {t('help.api.requiredScope')} +

+

+ customers:write +

+
+ + + + + +
+ + {/* Retrieve Customer */} + + +

+ + GET + + {t('help.api.retrieveCustomer')} +

+

+ {t('help.api.retrieveCustomerDescription', 'Retrieve details of a specific customer.')} +

+

+ {t('help.api.requiredScope')} +

+

+ customers:read +

+
+ + + + + +
+ + {/* Update Customer */} + + +

+ + PATCH + + {t('help.api.updateCustomer')} +

+

+ {t('help.api.updateCustomerDescription', 'Update customer information.')} +

+

+ {t('help.api.parameters')} +

+ +

+ {t('help.api.requiredScope')} +

+

+ customers:write +

+
+ + + + + +
+ + {/* List Customers */} + + +

+ + GET + + {t('help.api.listCustomers')} +

+

+ {t('help.api.listCustomersDescription', 'Retrieve a list of customers with optional filtering.')} +

+

+ {t('help.api.parameters')} +

+ +

+ {t('help.api.requiredScope')} +

+

+ customers:read +

+
+ + + + + +
+ {/* Webhook Events */} @@ -1736,6 +3330,231 @@ X-RateLimit-Burst-Remaining: 95`} + {/* List Webhooks */} + + +

+ + GET + + {t('help.api.listWebhooks')} +

+

+ {t('help.api.listWebhooksDescription', 'Retrieve all webhook endpoints configured for your business.')} +

+

+ {t('help.api.requiredScope')} +

+

+ webhooks:manage +

+
+ + + + + +
+ + {/* Retrieve Webhook */} + + +

+ + GET + + {t('help.api.retrieveWebhook')} +

+

+ {t('help.api.retrieveWebhookDescription', 'Retrieve details of a specific webhook endpoint.')} +

+

+ {t('help.api.requiredScope')} +

+

+ webhooks:manage +

+
+ + + + + +
+ + {/* Update Webhook */} + + +

+ + PATCH + + {t('help.api.updateWebhook')} +

+

+ {t('help.api.updateWebhookDescription', 'Update webhook configuration including event subscriptions and active status.')} +

+

+ {t('help.api.parameters')} +

+ +

+ {t('help.api.requiredScope')} +

+

+ webhooks:manage +

+
+ + + + + +
+ + {/* Delete Webhook */} + + +

+ + DELETE + + {t('help.api.deleteWebhook')} +

+

+ {t('help.api.deleteWebhookDescription', 'Delete a webhook endpoint. This action cannot be undone.')} +

+

+ {t('help.api.requiredScope')} +

+

+ webhooks:manage +

+
+ + + + + +
+ + {/* Test Webhook */} + + +

+ + POST + + {t('help.api.testWebhook')} +

+

+ {t('help.api.testWebhookDescription', 'Send a test event to your webhook endpoint to verify it is working correctly.')} +

+

+ {t('help.api.requiredScope')} +

+

+ webhooks:manage +

+
+ + + + + +
+ {/* Verify Signatures */} diff --git a/frontend/src/pages/help/HelpApiAppointments.tsx b/frontend/src/pages/help/HelpApiAppointments.tsx new file mode 100644 index 00000000..fbf99689 --- /dev/null +++ b/frontend/src/pages/help/HelpApiAppointments.tsx @@ -0,0 +1,430 @@ +/** + * Help API Appointments Page + * + * User-friendly documentation for the Appointments API. + */ + +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate, Link } from 'react-router-dom'; +import { + ArrowLeft, + Calendar, + Clock, + CheckCircle, + XCircle, + AlertCircle, + Code, + BookOpen, + HelpCircle, +} from 'lucide-react'; + +const HelpApiAppointments: React.FC = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + return ( +
+ {/* Back Button */} + + + {/* Header */} +
+
+
+ +
+
+

+ Appointments API +

+

+ Manage appointments and bookings programmatically +

+
+
+
+ + {/* Overview Section */} +
+

+ + Overview +

+
+

+ The Appointments API allows you to programmatically manage appointments, bookings, and scheduling + events in your SmoothSchedule account. Create, retrieve, update, and cancel appointments with + full control over resources, customers, and scheduling. +

+
+

+ Required OAuth Scopes: bookings:read, bookings:write +

+
+
+
+ + {/* Endpoints Section */} +
+

+ + API Endpoints +

+ + {/* List Appointments */} +
+
+ GET + /api/v1/appointments/ +
+

Retrieve a list of appointments with optional filtering.

+ +

Query Parameters:

+
    +
  • start_date - Filter by start date (YYYY-MM-DD)
  • +
  • end_date - Filter by end date (YYYY-MM-DD)
  • +
  • status - Filter by status (scheduled, confirmed, completed, etc.)
  • +
  • customer_id - Filter by customer UUID
  • +
+ +
+
+{`curl -X GET "https://api.smoothschedule.com/api/v1/appointments/?start_date=2025-12-01" \\
+  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"`}
+            
+
+
+ + {/* Get Appointment */} +
+
+ GET + /api/v1/appointments/{id}/ +
+

Retrieve a single appointment by ID.

+ +
+
+{`curl -X GET "https://api.smoothschedule.com/api/v1/appointments/a1b2c3d4-5678-90ab-cdef-1234567890ab/" \\
+  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"`}
+            
+
+
+ + {/* Create Appointment */} +
+
+ POST + /api/v1/appointments/ +
+

Create a new appointment.

+ +

Request Body:

+
+
+{`{
+  "service_id": "uuid",
+  "resource_id": "uuid",  // optional
+  "customer_id": "uuid",  // or use customer_email/name/phone
+  "customer_email": "john@example.com",  // if customer_id not provided
+  "customer_name": "John Doe",
+  "customer_phone": "+1234567890",
+  "start_time": "2025-12-16T14:00:00Z",
+  "notes": "Customer requested window seat"
+}`}
+            
+
+ +
+
+{`curl -X POST "https://api.smoothschedule.com/api/v1/appointments/" \\
+  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \\
+  -H "Content-Type: application/json" \\
+  -d '{
+    "service_id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
+    "customer_email": "john@example.com",
+    "customer_name": "John Doe",
+    "start_time": "2025-12-16T14:00:00Z",
+    "notes": "Customer requested window seat"
+  }'`}
+            
+
+
+ + {/* Update Appointment */} +
+
+ PATCH + /api/v1/appointments/{id}/ +
+

Update an existing appointment.

+ +

Request Body (partial update):

+
+
+{`{
+  "start_time": "2025-12-16T15:00:00Z",
+  "resource_id": "new-resource-uuid",
+  "notes": "Updated notes"
+}`}
+            
+
+ +
+
+{`curl -X PATCH "https://api.smoothschedule.com/api/v1/appointments/a1b2c3d4-5678-90ab-cdef-1234567890ab/" \\
+  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \\
+  -H "Content-Type: application/json" \\
+  -d '{
+    "start_time": "2025-12-16T15:00:00Z",
+    "notes": "Rescheduled to 3 PM"
+  }'`}
+            
+
+
+ + {/* Cancel Appointment */} +
+
+ DELETE + /api/v1/appointments/{id}/ +
+

Cancel an appointment. Optionally provide a cancellation reason.

+ +

Request Body (optional):

+
+
+{`{
+  "reason": "Customer requested cancellation"
+}`}
+            
+
+ +
+
+{`curl -X DELETE "https://api.smoothschedule.com/api/v1/appointments/a1b2c3d4-5678-90ab-cdef-1234567890ab/" \\
+  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \\
+  -H "Content-Type: application/json" \\
+  -d '{"reason": "Customer requested cancellation"}'`}
+            
+
+
+
+ + {/* Appointment Object Schema */} +
+

+ + Appointment Object +

+
+

+ Each appointment object contains the following fields: +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
idUUIDUnique appointment identifier
serviceObjectService details (id, name, duration, price)
resourceObjectAssigned resource (id, name, type)
customerObjectCustomer details (id, email, name, phone)
start_timeISO 8601Appointment start time (UTC)
end_timeISO 8601Appointment end time (UTC)
statusStringCurrent status (see Status Values below)
notesStringAdditional notes or instructions
created_atISO 8601When the appointment was created
+
+
+
+ + {/* Status Values */} +
+

+ + Status Values +

+
+

+ Appointments can have one of the following status values: +

+
+
+ + SCHEDULED + + Appointment has been scheduled but not yet confirmed +
+
+ + CONFIRMED + + Customer has confirmed the appointment +
+
+ + IN_PROGRESS + + Appointment is currently in progress +
+
+ + COMPLETED + + Appointment has been completed successfully +
+
+ + CANCELLED + + Appointment was cancelled +
+
+ + NO_SHOW + + Customer did not show up for the appointment +
+
+
+
+ + {/* Response Example */} +
+

+ + Example Response +

+
+

+ A typical appointment object in the API response: +

+
+
+{`{
+  "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
+  "service": {
+    "id": "srv-uuid",
+    "name": "Haircut",
+    "duration": 60,
+    "price": 5000
+  },
+  "resource": {
+    "id": "res-uuid",
+    "name": "Sarah Johnson",
+    "type": "STAFF"
+  },
+  "customer": {
+    "id": "cust-uuid",
+    "email": "john@example.com",
+    "name": "John Doe",
+    "phone": "+1234567890"
+  },
+  "start_time": "2025-12-16T14:00:00Z",
+  "end_time": "2025-12-16T15:00:00Z",
+  "status": "confirmed",
+  "notes": "Customer requested window seat",
+  "created_at": "2025-12-01T10:30:00Z"
+}`}
+            
+
+
+
+ + {/* Rate Limiting */} +
+
+
+ +
+

+ Rate Limiting +

+

+ API requests are limited to 1000 requests per hour per API key. + Rate limit information is included in response headers: +

+
+
+{`X-RateLimit-Limit: 1000
+X-RateLimit-Remaining: 987
+X-RateLimit-Reset: 1702742400`}
+                
+
+
+
+
+
+ + {/* Need More Help */} +
+ +

+ Need More Help? +

+

+ Our support team is ready to help with API integration and technical questions. +

+ +
+
+ ); +}; + +export default HelpApiAppointments; diff --git a/frontend/src/pages/help/HelpApiCustomers.tsx b/frontend/src/pages/help/HelpApiCustomers.tsx new file mode 100644 index 00000000..56067746 --- /dev/null +++ b/frontend/src/pages/help/HelpApiCustomers.tsx @@ -0,0 +1,386 @@ +/** + * Help API Customers Page + * + * User-friendly help documentation for the Customers API. + */ + +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { + ArrowLeft, + Users, + Mail, + Phone, + Calendar, + Code, + HelpCircle, +} from 'lucide-react'; + +const HelpApiCustomers: React.FC = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + return ( +
+ {/* Back Button */} + + + {/* Header */} +
+
+
+ +
+
+

+ Customers API +

+

+ Manage customer records programmatically +

+
+
+
+ + {/* Overview Section */} +
+

+ + Overview +

+
+

+ The Customers API allows you to manage customer records in your SmoothSchedule account. + Create, retrieve, update, and list customer information programmatically. +

+
+

+ Required Scopes: customers:read for reading, + customers:write for creating/updating +

+
+
+
+ + {/* Endpoints Section */} +
+

+ + Endpoints +

+ + {/* List Customers */} +
+

+ List Customers +

+
+ GET /api/v1/customers/ +
+

+ Retrieve a paginated list of customers. Results are limited to 100 customers per request. +

+
+

Query Parameters:

+
    +
  • email - Filter by exact email address
  • +
  • search - Search by name or email (partial match)
  • +
+
+
+ + {/* Get Customer */} +
+

+ Get Customer +

+
+ GET /api/v1/customers/{id}/ +
+

+ Retrieve a specific customer by their UUID. +

+
+ + {/* Create Customer */} +
+

+ Create Customer +

+
+ POST /api/v1/customers/ +
+

+ Create a new customer record. +

+
+

Request Body:

+
    +
  • email - *required Email address (must be unique)
  • +
  • name - Customer's full name
  • +
  • phone - Phone number
  • +
+
+
+

+ Note: Returns 409 Conflict if a customer with the email already exists. +

+
+
+ + {/* Update Customer */} +
+

+ Update Customer +

+
+ PATCH /api/v1/customers/{id}/ +
+

+ Update an existing customer's information. +

+
+

Request Body:

+
    +
  • name - Customer's full name
  • +
  • phone - Phone number
  • +
+
+
+

+ Important: Email addresses cannot be changed after creation. +

+
+
+
+ + {/* Customer Object Section */} +
+

+ + Customer Object +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Field + + Type + + Description +
+ id + + UUID + + Unique customer identifier +
+ email + + string + + Customer's email address (unique) +
+ name + + string + + Customer's full name +
+ phone + + string | null + + Customer's phone number +
+ created_at + + ISO 8601 + + Timestamp when customer was created +
+ total_appointments + + integer + + Total number of appointments for this customer +
+ last_appointment_at + + ISO 8601 | null + + Timestamp of customer's most recent appointment +
+
+
+
+ + {/* Example Response Section */} +
+

+ + Example Response +

+
+
+            
+{`{
+  "id": "123e4567-e89b-12d3-a456-426614174000",
+  "email": "customer@example.com",
+  "name": "Jane Doe",
+  "phone": "+1234567890",
+  "created_at": "2024-01-01T10:00:00Z",
+  "total_appointments": 5,
+  "last_appointment_at": "2024-12-01T14:00:00Z"
+}`}
+            
+          
+
+
+ + {/* Code Examples Section */} +
+

+ + Code Examples +

+ + {/* List Customers Example */} +
+

+ List All Customers +

+
+            
+{`curl -X GET "https://yourbusiness.smoothschedule.com/api/v1/customers/" \\
+  -H "Authorization: Bearer YOUR_API_KEY" \\
+  -H "Content-Type: application/json"`}
+            
+          
+
+ + {/* Search Customers Example */} +
+

+ Search Customers +

+
+            
+{`curl -X GET "https://yourbusiness.smoothschedule.com/api/v1/customers/?search=jane" \\
+  -H "Authorization: Bearer YOUR_API_KEY" \\
+  -H "Content-Type: application/json"`}
+            
+          
+
+ + {/* Create Customer Example */} +
+

+ Create Customer +

+
+            
+{`curl -X POST "https://yourbusiness.smoothschedule.com/api/v1/customers/" \\
+  -H "Authorization: Bearer YOUR_API_KEY" \\
+  -H "Content-Type: application/json" \\
+  -d '{
+    "email": "customer@example.com",
+    "name": "Jane Doe",
+    "phone": "+1234567890"
+  }'`}
+            
+          
+
+ + {/* Update Customer Example */} +
+

+ Update Customer +

+
+            
+{`curl -X PATCH "https://yourbusiness.smoothschedule.com/api/v1/customers/123e4567-e89b-12d3-a456-426614174000/" \\
+  -H "Authorization: Bearer YOUR_API_KEY" \\
+  -H "Content-Type: application/json" \\
+  -d '{
+    "name": "Jane Smith",
+    "phone": "+1987654321"
+  }'`}
+            
+          
+
+
+ + {/* Need More Help */} +
+ +

+ Need More Help? +

+

+ Our support team is ready to help with any questions about the Customers API. +

+ +
+
+ ); +}; + +export default HelpApiCustomers; diff --git a/frontend/src/pages/help/HelpApiOverview.tsx b/frontend/src/pages/help/HelpApiOverview.tsx new file mode 100644 index 00000000..2af9a1cc --- /dev/null +++ b/frontend/src/pages/help/HelpApiOverview.tsx @@ -0,0 +1,551 @@ +/** + * Help API Overview Page + * + * Tenant-facing API documentation overview. + */ + +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate, Link } from 'react-router-dom'; +import { + ArrowLeft, + Code, + Key, + Shield, + AlertCircle, + Zap, + Book, + HelpCircle, + ChevronRight, + Calendar, + Users, + Settings, + Webhook, + CheckCircle, + XCircle, +} from 'lucide-react'; + +const HelpApiOverview: React.FC = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + return ( +
+ {/* Back Button */} + + + {/* Header */} +
+
+
+ +
+
+

+ API Overview +

+

+ REST API for third-party integrations +

+
+
+
+ + {/* Introduction Section */} +
+

+ + Introduction +

+
+

+ SmoothSchedule provides a comprehensive REST API that allows you to integrate your scheduling + platform with third-party applications, build custom tools, and automate workflows. +

+
+

Base URL

+ + https://your-subdomain.smoothschedule.com/api/v1/ + +
+
+
+ +
+

+ Interactive Documentation: Explore and test API endpoints at{' '} + + /api/v1/docs/ + +

+
+
+
+
+
+ + {/* Authentication Section */} +
+

+ + Authentication +

+
+

+ All API requests require authentication using Bearer tokens in the Authorization header. +

+ +
+

Token Format

+
+
+ +
+ ss_live_xxxxxxxxx +

Production environment

+
+
+
+ +
+ ss_test_xxxxxxxxx +

Sandbox environment (safe for testing)

+
+
+
+
+ +
+

Example Request

+
+
+{`curl -X GET "https://demo.smoothschedule.com/api/v1/services/" \\
+  -H "Authorization: Bearer ss_live_xxxxxxxxx" \\
+  -H "Content-Type: application/json"`}
+              
+
+
+ +
+
+ +
+

+ Security: API tokens are created in Business Settings → API. + Each token has configurable scopes that control access to specific endpoints and operations. +

+
+
+
+
+
+ + {/* Available Scopes Section */} +
+

+ + Available Scopes +

+
+

+ Control which operations your API token can perform by selecting scopes: +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ScopeDescription
+ services:read + View services and pricing
+ resources:read + View resources and staff
+ availability:read + Check time slot availability
+ bookings:read + View appointments
+ bookings:write + Create, update, cancel appointments
+ customers:read + View customer information
+ customers:write + Create and update customers
+ business:read + View business information
+ webhooks:manage + Manage webhook subscriptions
+
+
+
+ + {/* Rate Limiting Section */} +
+

+ + Rate Limiting +

+
+

+ API requests are rate-limited to ensure fair usage and platform stability: +

+
+
+ +
+

Global Limit

+

1,000 requests per hour

+
+
+
+ +
+

Burst Limit

+

100 requests per minute

+
+
+
+
+

Response Headers

+
+
X-RateLimit-Limit - Maximum requests allowed
+
X-RateLimit-Remaining - Requests remaining in window
+
X-RateLimit-Reset - Unix timestamp when limit resets
+
+
+
+
+ + {/* Error Responses Section */} +
+

+ + Error Responses +

+
+

+ All errors follow a consistent JSON format: +

+
+
+{`{
+  "error": "validation_error",
+  "message": "Invalid request data",
+  "details": {
+    "start_time": ["This field is required."],
+    "service_id": ["Invalid service ID."]
+  }
+}`}
+            
+
+ +

HTTP Status Codes

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CodeStatusDescription
200OKRequest succeeded
201CreatedResource created successfully
400Bad RequestInvalid request data
401UnauthorizedMissing or invalid token
403ForbiddenInsufficient scope permissions
404Not FoundResource does not exist
409ConflictResource conflict (e.g., double booking)
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer error (contact support)
+
+
+
+ + {/* Sandbox Mode Section */} +
+

+ + Sandbox Mode +

+
+

+ Test your integration safely without affecting production data: +

+
    +
  • + +
    +

    Test Tokens

    +

    + Tokens prefixed with ss_test_ work with sandbox data only +

    +
    +
  • +
  • + +
    +

    Safe Testing

    +

    + Create, update, and delete test appointments without affecting real bookings +

    +
    +
  • +
  • + +
    +

    Easy Toggle

    +

    + Switch between test and live modes in Business Settings → API +

    +
    +
  • +
+
+
+ +
+

+ Tip: Always test with sandbox tokens before using production tokens in your application. +

+
+
+
+
+
+ + {/* Quick Links Section */} +
+

+ + API Endpoints +

+
+ +
+
+ +
+
+

+ Appointments API +

+

+ Create, manage, and query appointments +

+
+
+
+ View Docs + +
+ + + +
+
+ +
+
+

+ Services API +

+

+ Access service catalog and pricing +

+
+
+
+ View Docs + +
+ + + +
+
+ +
+
+

+ Resources API +

+

+ Staff, rooms, and equipment data +

+
+
+
+ View Docs + +
+ + + +
+
+ +
+
+

+ Customers API +

+

+ Customer profiles and contact info +

+
+
+
+ View Docs + +
+ + + +
+
+ +
+
+

+ Webhooks +

+

+ Real-time event notifications +

+
+
+
+ View Docs + +
+ +
+
+ + {/* Need More Help */} +
+ +

+ Need More Help? +

+

+ Our support team is ready to help with API integration questions. +

+ +
+
+ ); +}; + +export default HelpApiOverview; diff --git a/frontend/src/pages/help/HelpApiResources.tsx b/frontend/src/pages/help/HelpApiResources.tsx new file mode 100644 index 00000000..0a6a9ee8 --- /dev/null +++ b/frontend/src/pages/help/HelpApiResources.tsx @@ -0,0 +1,337 @@ +/** + * Help API Resources Page + * + * User-friendly help documentation for the Resources API. + */ + +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate, Link } from 'react-router-dom'; +import { + ArrowLeft, + Users, + User, + Building, + Wrench, + Code, + BookOpen, + HelpCircle, + CheckCircle, + ChevronRight, +} from 'lucide-react'; + +const HelpApiResources: React.FC = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + return ( +
+ {/* Back Button */} + + + {/* Header */} +
+
+
+ +
+
+

+ Resources API +

+

+ Access bookable resources via the public API +

+
+
+
+ + {/* Overview Section */} +
+

+ + Overview +

+
+

+ The Resources API provides read-only access to your bookable resources including staff members, + rooms, and equipment. Use this API to list available resources and retrieve their details. +

+
+ +
+

+ Required OAuth Scope: resources:read +

+

+ Resources are read-only via the public API. To create or modify resources, use the main dashboard. +

+
+
+
+
+ + {/* Endpoints Section */} +
+

+ + Endpoints +

+ + {/* List Resources */} +
+

+ + List Resources +

+
+ GET{' '} + /api/v1/resources/ +
+

+ Returns a list of all active resources in your account. +

+
+

Query Parameters

+
+ + + + + + + + + + + + + + + +
ParameterTypeDescription
typestringFilter by resource type (STAFF, ROOM, EQUIPMENT)
+
+
+
+ + {/* Get Resource */} +
+

+ + Get Resource +

+
+ GET{' '} + /api/v1/resources/ + {'{id}'} + / +
+

+ Retrieve details for a specific resource by its ID. +

+
+
+ + {/* Resource Object Section */} +
+

+ + Resource Object +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
idUUIDUnique identifier
namestringResource name
descriptionstring (nullable)Resource description
resource_typeobjectResource type object with id, name, category
photo_urlstring (nullable)URL to resource photo
is_activebooleanWhether resource is active
+
+
+
+ + {/* Resource Types Section */} +
+

+ + Resource Types +

+
+
+
+ +
+

STAFF

+

Team members who provide services

+
+
+
+ +
+

ROOM

+

Physical spaces for appointments

+
+
+
+ +
+

EQUIPMENT

+

Tools or equipment needed for services

+
+
+
+
+
+ + {/* Example Response Section */} +
+

+ + Example Response +

+
+
+            {`{
+  "id": "550e8400-e29b-41d4-a716-446655440000",
+  "name": "John Smith",
+  "description": "Senior Stylist with 10 years experience",
+  "resource_type": {
+    "id": "660e8400-e29b-41d4-a716-446655440000",
+    "name": "Stylist",
+    "category": "STAFF"
+  },
+  "photo_url": "https://example.com/photos/john-smith.jpg",
+  "is_active": true
+}`}
+          
+
+
+ + {/* Code Examples Section */} +
+

+ + Code Examples +

+ + {/* List All Resources */} +
+

+ List All Resources +

+
+            {`curl -X GET "https://yourbusiness.smoothschedule.com/api/v1/resources/" \\
+  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"`}
+          
+
+ + {/* Filter by Type */} +
+

+ Filter by Type +

+
+            {`curl -X GET "https://yourbusiness.smoothschedule.com/api/v1/resources/?type=STAFF" \\
+  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"`}
+          
+
+ + {/* Get Specific Resource */} +
+

+ Get Specific Resource +

+
+            {`curl -X GET "https://yourbusiness.smoothschedule.com/api/v1/resources/550e8400-e29b-41d4-a716-446655440000/" \\
+  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"`}
+          
+
+
+ + {/* Back to API Docs */} +
+
+
+ +
+

+ Explore More API Endpoints +

+

+ The Resources API is just one part of our comprehensive public API. + View the full documentation to learn about appointments, customers, services, and more. +

+ + View Full API Docs + + +
+
+
+
+ + {/* Need More Help */} +
+ +

+ Need More Help? +

+

+ Our support team is ready to help with any questions about the API. +

+ +
+
+ ); +}; + +export default HelpApiResources; diff --git a/frontend/src/pages/help/HelpApiServices.tsx b/frontend/src/pages/help/HelpApiServices.tsx new file mode 100644 index 00000000..d62c68d8 --- /dev/null +++ b/frontend/src/pages/help/HelpApiServices.tsx @@ -0,0 +1,307 @@ +/** + * Help API Services Page + * + * User-friendly help documentation for the Services API. + */ + +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { + ArrowLeft, + Briefcase, + DollarSign, + Clock, + Image, + Code, + HelpCircle, +} from 'lucide-react'; + +const HelpApiServices: React.FC = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + return ( +
+ {/* Back Button */} + + + {/* Header */} +
+
+
+ +
+
+

+ Services API +

+

+ Access your service catalog via API +

+
+
+
+ + {/* Overview Section */} +
+

+ + Overview +

+
+
+

+ Access your service catalog via the public API to integrate scheduling capabilities into your own applications, websites, or mobile apps. +

+
+

+ Required scope: services:read +

+
+
+

+ Note: Services are read-only via the public API. Use the dashboard to create, update, or delete services. +

+
+
+
+
+ + {/* Endpoints Section */} +
+

+ + Endpoints +

+
+ {/* List Services */} +
+
+ + GET + + + /api/v1/services/ + +
+

+ Returns all active services ordered by display_order. +

+
+ + {/* Get Service */} +
+
+ + GET + + + /api/v1/services/{'{id}'}/ + +
+

+ Returns detailed information for a specific service by UUID. +

+
+
+
+ + {/* Service Object Section */} +
+

+ + Service Object +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Field + + Type + + Description +
+ id + + UUID + + Unique identifier for the service +
+ name + + string + + Service name (e.g., "Haircut", "Oil Change") +
+ description + + string | null + + Optional detailed description of the service +
+
+ + duration +
+
+ integer + + Duration in minutes (e.g., 30, 60, 90) +
+
+ + price +
+
+ decimal | null + + Price in dollars (null for variable pricing) +
+
+ + photos +
+
+ array + + Array of photo URLs for the service +
+ is_active + + boolean + + Whether the service is currently active +
+
+
+
+ + {/* Example Response Section */} +
+

+ + Example Response +

+
+
+{`{
+  "id": "550e8400-e29b-41d4-a716-446655440000",
+  "name": "Haircut",
+  "description": "Professional haircut service",
+  "duration": 30,
+  "price": "45.00",
+  "photos": [
+    "https://smoothschedule.nyc3.digitaloceanspaces.com/..."
+  ],
+  "is_active": true
+}`}
+          
+
+
+ + {/* Code Examples Section */} +
+

+ + Code Examples +

+
+ {/* List Services */} +
+

+ List All Services +

+
+{`curl -X GET "https://yourbusiness.smoothschedule.com/api/v1/services/" \\
+  -H "Authorization: Bearer YOUR_API_KEY" \\
+  -H "Content-Type: application/json"`}
+            
+
+ + {/* Get Service */} +
+

+ Get Specific Service +

+
+{`curl -X GET "https://yourbusiness.smoothschedule.com/api/v1/services/550e8400-e29b-41d4-a716-446655440000/" \\
+  -H "Authorization: Bearer YOUR_API_KEY" \\
+  -H "Content-Type: application/json"`}
+            
+
+
+
+ + {/* Need More Help */} +
+ +

+ Need More Help? +

+

+ Our support team is ready to help with any questions about the Services API. +

+ +
+
+ ); +}; + +export default HelpApiServices; diff --git a/frontend/src/pages/help/HelpApiWebhooks.tsx b/frontend/src/pages/help/HelpApiWebhooks.tsx new file mode 100644 index 00000000..e21d9dfa --- /dev/null +++ b/frontend/src/pages/help/HelpApiWebhooks.tsx @@ -0,0 +1,513 @@ +/** + * Help API Webhooks Page + * + * User-friendly help documentation for Webhooks API. + */ + +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { + ArrowLeft, + Link as LinkIcon, + Bell, + Shield, + RefreshCw, + CheckCircle, + HelpCircle, + Code, + List, + Plus, + Edit, + Trash2, + Send, + Clock, +} from 'lucide-react'; + +const HelpApiWebhooks: React.FC = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + return ( +
+ {/* Back Button */} + + + {/* Header */} +
+
+
+ +
+
+

+ Webhooks API +

+

+ Receive real-time notifications when events occur in your account +

+
+
+
+ + {/* Overview Section */} +
+

+ + Overview +

+
+

+ Webhooks allow your application to receive real-time notifications when events occur in your SmoothSchedule account. + Instead of polling the API, webhooks POST JSON payloads to your specified endpoint whenever subscribed events happen. +

+
+
+ + Required scope: webhooks:manage +
+
+ + Format: JSON payloads POSTed to your endpoint +
+
+ + Security: HMAC-SHA256 signature verification +
+
+
+
+ + {/* API Endpoints */} +
+

+ + API Endpoints +

+
+ + {/* List Subscriptions */} +
+
+ +
+

List Subscriptions

+ GET /api/v1/webhooks/ +

+ Returns a list of all webhook subscriptions for your account. +

+
+
+
+ + {/* Create Subscription */} +
+
+ +
+

Create Subscription

+ POST /api/v1/webhooks/ +

+ Create a new webhook subscription. Returns the subscription including a secret for signature verification. +

+
+

Request Body:

+
+{`{
+  "url": "https://example.com/webhooks",
+  "events": ["appointment.created", "appointment.cancelled"],
+  "description": "Production webhook endpoint"
+}`}
+                  
+
+
+

+ Important: The secret is only shown once in the response. Store it securely for signature verification. +

+
+
+
+
+ + {/* Get Subscription */} +
+
+ +
+

Get Subscription

+ GET /api/v1/webhooks/{`{id}`}/ +

+ Retrieve details of a specific webhook subscription. +

+
+
+
+ + {/* Update Subscription */} +
+
+ +
+

Update Subscription

+ PATCH /api/v1/webhooks/{`{id}`}/ +

+ Update an existing webhook subscription (URL, events, or description). +

+
+
+
+ + {/* Delete Subscription */} +
+
+ +
+

Delete Subscription

+ DELETE /api/v1/webhooks/{`{id}`}/ +

+ Delete a webhook subscription permanently. +

+
+
+
+ + {/* List Event Types */} +
+
+ +
+

List Event Types

+ GET /api/v1/webhooks/events/ +

+ Get a list of all available webhook event types. +

+
+
+
+ + {/* Send Test Webhook */} +
+
+ +
+

Send Test Webhook

+ POST /api/v1/webhooks/{`{id}`}/test/ +

+ Send a test webhook to verify your endpoint is working correctly. +

+
+
+
+ + {/* View Delivery History */} +
+
+ +
+

View Delivery History

+ GET /api/v1/webhooks/{`{id}`}/deliveries/ +

+ View delivery history and status for a webhook subscription. +

+
+
+
+ +
+
+ + {/* Available Events */} +
+

+ + Available Events +

+
+

+ Subscribe to one or more of these events to receive notifications: +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EventDescription
appointment.createdTriggered when a new appointment is created
appointment.updatedTriggered when an appointment is updated
appointment.cancelledTriggered when an appointment is cancelled
customer.createdTriggered when a new customer is created
customer.updatedTriggered when customer information is updated
service.createdTriggered when a new service is created
service.updatedTriggered when a service is updated
+
+
+
+ + {/* Webhook Payload Format */} +
+

+ + Webhook Payload Format +

+
+

+ All webhook payloads follow this standard format: +

+
+
+{`{
+  "event": "appointment.created",
+  "timestamp": "2024-12-16T10:00:00Z",
+  "data": {
+    "id": "123",
+    "customer": {
+      "id": "456",
+      "email": "customer@example.com",
+      "name": "John Doe"
+    },
+    "service": {
+      "id": "789",
+      "name": "Haircut",
+      "duration": 30
+    },
+    "start_time": "2024-12-20T14:00:00Z",
+    "end_time": "2024-12-20T14:30:00Z",
+    "status": "confirmed"
+    // ... additional event-specific fields
+  }
+}`}
+            
+
+
+
+ + {/* Signature Verification */} +
+

+ + Signature Verification +

+
+

+ All webhooks include an X-Webhook-Signature header + containing an HMAC-SHA256 signature. Verify this signature to ensure the webhook came from SmoothSchedule. +

+ +
+
+

Header

+ X-Webhook-Signature: sha256=a1b2c3d4... +
+ +
+

Algorithm

+

HMAC-SHA256 of the raw request body using your webhook secret

+
+ +
+

Python Example

+
+
+{`import hmac
+import hashlib
+
+def verify_webhook(request, secret):
+    signature = request.headers.get('X-Webhook-Signature', '')
+    expected = 'sha256=' + hmac.new(
+        secret.encode(),
+        request.body,
+        hashlib.sha256
+    ).hexdigest()
+    return hmac.compare_digest(signature, expected)`}
+                
+
+
+ +
+

Node.js Example

+
+
+{`const crypto = require('crypto');
+
+function verifyWebhook(request, secret) {
+  const signature = request.headers['x-webhook-signature'];
+  const expected = 'sha256=' + crypto
+    .createHmac('sha256', secret)
+    .update(request.rawBody)
+    .digest('hex');
+  return crypto.timingSafeEqual(
+    Buffer.from(signature),
+    Buffer.from(expected)
+  );
+}`}
+                
+
+
+
+
+
+ + {/* Retry Policy */} +
+

+ + Retry Policy +

+
+

+ If your endpoint fails to respond with a 2xx status code, SmoothSchedule will automatically retry delivery: +

+
+
+ + Retry attempts: 3 automatic retries +
+
+ + Backoff schedule: 1 minute, 5 minutes, 30 minutes +
+
+ + Auto-disable: Subscription disabled after 10 consecutive failures +
+
+
+

+ Important: Your endpoint should respond within 5 seconds and return a 2xx status code to acknowledge receipt. + Process the webhook asynchronously if needed. +

+
+
+
+ + {/* Code Examples */} +
+

+ + Code Examples +

+
+ +
+

Creating a Webhook Subscription

+
+
+{`# Using curl
+curl -X POST https://api.smoothschedule.com/api/v1/webhooks/ \\
+  -H "Authorization: Bearer YOUR_API_KEY" \\
+  -H "Content-Type: application/json" \\
+  -d '{
+    "url": "https://example.com/webhooks",
+    "events": ["appointment.created", "appointment.cancelled"],
+    "description": "Production webhook endpoint"
+  }'
+
+# Response (save the secret!)
+{
+  "id": "wh_abc123",
+  "url": "https://example.com/webhooks",
+  "events": ["appointment.created", "appointment.cancelled"],
+  "description": "Production webhook endpoint",
+  "secret": "whsec_xyz789...",  // Only shown once!
+  "is_active": true,
+  "created_at": "2024-12-16T10:00:00Z"
+}`}
+              
+
+
+ +
+

Handling Webhook in Express.js

+
+
+{`const express = require('express');
+const crypto = require('crypto');
+
+const app = express();
+
+// Important: Use raw body for signature verification
+app.post('/webhooks',
+  express.raw({ type: 'application/json' }),
+  (req, res) => {
+    // Verify signature
+    const signature = req.headers['x-webhook-signature'];
+    const expected = 'sha256=' + crypto
+      .createHmac('sha256', process.env.WEBHOOK_SECRET)
+      .update(req.body)
+      .digest('hex');
+
+    if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
+      return res.status(401).send('Invalid signature');
+    }
+
+    // Parse and process webhook
+    const payload = JSON.parse(req.body);
+    console.log('Received event:', payload.event);
+
+    // Process asynchronously
+    processWebhook(payload).catch(console.error);
+
+    // Respond immediately
+    res.status(200).send('OK');
+  }
+);`}
+              
+
+
+ +
+
+ + {/* Need More Help */} +
+ +

+ Need More Help? +

+

+ Our support team is ready to help with any questions about webhooks. +

+ +
+
+ ); +}; + +export default HelpApiWebhooks; diff --git a/frontend/src/pages/help/HelpComprehensive.tsx b/frontend/src/pages/help/HelpComprehensive.tsx index ba42eff2..21ad6ebe 100644 --- a/frontend/src/pages/help/HelpComprehensive.tsx +++ b/frontend/src/pages/help/HelpComprehensive.tsx @@ -14,7 +14,7 @@ import { Clock, Eye, Palette, Link2, Mail, Globe, CreditCard, Zap, Search, Filter, Plus, Edit, Trash2, ArrowUpDown, GripVertical, Image, Save, ExternalLink, MessageSquare, Tag, UserPlus, Shield, Copy, Layers, Play, Pause, Puzzle, - FileSignature, Send, Download, Link as LinkIcon, CalendarOff, + FileSignature, Send, Download, Link as LinkIcon, CalendarOff, MapPin, Code, } from 'lucide-react'; interface TocSubItem { @@ -55,9 +55,11 @@ const HelpComprehensive: React.FC = () => { { id: 'customers', label: t('helpComprehensive.toc.customers'), icon: }, { id: 'staff', label: t('helpComprehensive.toc.staff'), icon: }, { id: 'time-blocks', label: t('helpComprehensive.toc.timeBlocks'), icon: }, + { id: 'locations', label: 'Locations', icon: }, { id: 'site-builder', label: 'Site Builder', icon: }, { id: 'automations', label: 'Automations', icon: }, { id: 'contracts', label: t('helpComprehensive.toc.contracts'), icon: }, + { id: 'api', label: 'API', icon: }, { id: 'settings', label: t('helpComprehensive.toc.settings'), @@ -70,6 +72,11 @@ const HelpComprehensive: React.FC = () => { { label: t('helpComprehensive.toc.apiSettings'), href: '/help/settings/api' }, { label: t('helpComprehensive.toc.authentication'), href: '/help/settings/auth' }, { label: t('helpComprehensive.toc.usageQuota'), href: '/help/settings/quota' }, + { label: 'Business Hours', href: '/help/settings/business-hours' }, + { label: 'Email Templates', href: '/help/settings/email-templates' }, + { label: 'Embed Widget', href: '/help/settings/embed-widget' }, + { label: 'Staff Roles', href: '/help/settings/staff-roles' }, + { label: 'SMS & Calling', href: '/help/settings/communication' }, ], }, ]; @@ -716,6 +723,58 @@ const HelpComprehensive: React.FC = () => { + {/* ============================================== */} + {/* LOCATIONS */} + {/* ============================================== */} +
+
+
+ +
+

Locations

+
+ +
+

+ Manage multiple business locations with ease. Each location can have its own resources, services, and schedule settings while maintaining centralized control. +

+ +

Key Features

+
    +
  • + + Primary Location: Set a default location for your business operations +
  • +
  • + + Activate/Deactivate: Temporarily disable locations without deleting them +
  • +
  • + + Address Management: Store complete address information for each location +
  • +
  • + + Resource Assignment: Assign staff, rooms, and equipment to specific locations +
  • +
  • + + Service Assignment: Configure which services are available at each location +
  • +
+ +

Learn More

+ + +
+

Locations Documentation

+

Complete guide to managing multiple business locations

+
+ + +
+
+ {/* ============================================== */} {/* SITE BUILDER */} {/* ============================================== */} @@ -1000,6 +1059,100 @@ const HelpComprehensive: React.FC = () => { + {/* ============================================== */} + {/* API DOCUMENTATION */} + {/* ============================================== */} +
+
+
+ +
+

API Documentation

+
+ +
+

+ SmoothSchedule provides a comprehensive REST API for third-party integrations. Build custom applications, mobile apps, or connect with external services using our secure and well-documented API. +

+ +

Key Features

+
    +
  • + + REST API: Simple, intuitive REST API with JSON responses +
  • +
  • + + Token-based Authentication: Secure API tokens with configurable expiration +
  • +
  • + + Scope-based Permissions: Fine-grained control over API access +
  • +
  • + + Webhook Support: Receive real-time events for appointments, bookings, and more +
  • +
  • + + Rate Limiting: 1000 requests per hour (configurable per token) +
  • +
  • + + Sandbox Mode: Test your integration without affecting live data +
  • +
+ +

API Documentation

+
+ +

API Overview

+

Authentication, rate limits, and getting started

+ + +

Appointments API

+

Create, update, and manage appointments

+ + +

Services API

+

Manage services and availability

+ + +

Resources API

+

Access and update staff, rooms, and equipment

+ + +

Customers API

+

Manage customer data and preferences

+ + +

Webhooks

+

Configure real-time event notifications

+ +
+ +
+
+ +
+

Interactive API Documentation

+

+ Access interactive API documentation with example requests and responses at{' '} + + /api/v1/docs/ + +

+
+
+
+
+
+ {/* ============================================== */} {/* SETTINGS */} {/* ============================================== */} @@ -1096,6 +1249,26 @@ const HelpComprehensive: React.FC = () => {

{t('helpComprehensive.settings.usageQuotaLink')}

{t('helpComprehensive.settings.usageQuotaLinkDesc')}

+ +

Business Hours

+

Configure operating hours and availability

+ + +

Email Templates

+

Customize automated email templates

+ + +

Embed Widget

+

Embed booking widget on your website

+ + +

Staff Roles

+

Manage team permissions and access levels

+ + +

SMS & Calling

+

Configure SMS and calling features

+ diff --git a/frontend/src/pages/help/HelpLocations.tsx b/frontend/src/pages/help/HelpLocations.tsx new file mode 100644 index 00000000..6df3d822 --- /dev/null +++ b/frontend/src/pages/help/HelpLocations.tsx @@ -0,0 +1,358 @@ +/** + * Help Locations Page + * + * User-friendly help documentation for Locations. + */ + +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { + ArrowLeft, + MapPin, + Building2, + Star, + Power, + PowerOff, + Edit, + Trash2, + Phone, + Mail, + Globe, + Clock, + CheckCircle, + HelpCircle, + Users, + Briefcase, +} from 'lucide-react'; + +const HelpLocations: React.FC = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + return ( +
+ {/* Back Button */} + + + {/* Header */} +
+
+
+ +
+
+

+ Locations Guide +

+

+ Manage multiple business locations with ease +

+
+
+
+ + {/* Overview Section */} +
+

+ + Overview +

+
+

+ Locations allow you to manage multiple business sites from a single account. Whether you have + multiple offices, retail stores, or service areas, each location can have its own address, + contact information, timezone settings, and dedicated resources and services. +

+

+ Assign resources (staff, rooms, equipment) and services to specific locations to keep your + scheduling organized and ensure customers book appointments at the right place. +

+
+
+ + {/* Location Information Section */} +
+

+ + Location Information +

+
+

+ Each location stores comprehensive information to help customers find and contact you: +

+
+
+ +
+

Name & Address

+

Location name, street address, city, state, postal code, and country

+
+
+
+ +
+

Contact Details

+

Phone number and email address for customer inquiries

+
+
+
+ +
+

Timezone

+

Set the local timezone for accurate appointment scheduling

+
+
+
+ +
+

Primary Status

+

Designate one location as your primary business location

+
+
+
+
+
+ + {/* Primary Location Section */} +
+

+ + Primary Location +

+
+

+ You can designate one location as your primary location. This is your main + business address and will be used as the default location for new resources and services. +

+
+
+ +
+

Setting a Primary Location

+

Click the star icon next to any location to make it primary. The previous primary location will automatically be unmarked.

+
+
+
+ +
+

Benefits

+

Primary location appears first in lists and is pre-selected when creating new resources or services.

+
+
+
+
+
+ + {/* Managing Locations Section */} +
+

+ + Managing Locations +

+
+
+
+

+ + Creating a Location +

+
    +
  1. + 1. Navigate to Settings → Locations +
  2. +
  3. + 2. Click Add Location +
  4. +
  5. + 3. Fill in the location name, address, and contact details +
  6. +
  7. + 4. Select the appropriate timezone for the location +
  8. +
  9. + 5. Optionally mark it as the primary location +
  10. +
  11. + 6. Click Save +
  12. +
+
+ +
+

+ + Editing a Location +

+

+ Click the Edit button next to any location to update its information. + All changes are saved immediately and will apply to future bookings. +

+
+ +
+

+ + Activating/Deactivating Locations +

+
+

+ Instead of deleting locations, you can temporarily deactivate them: +

+
    +
  • + + Deactivate: Location is hidden from scheduling but data is preserved +
  • +
  • + + Activate: Location becomes available for scheduling again +
  • +
+
+
+ +
+

+ + Deleting a Location +

+
+

+ Click the Delete button to permanently remove a location. +

+
+

+ Warning: You cannot delete a location that has associated resources or services. + Reassign or delete them first, or use the deactivate option instead. +

+
+
+
+
+
+
+ + {/* Resource & Service Counts Section */} +
+

+ + Resources & Services +

+
+

+ Each location displays counts of associated resources and services to help you understand + how your business is organized: +

+
+
+ +
+

Resources

+

+ See how many staff members, rooms, or equipment items are assigned to each location +

+
+
+
+ +
+

Services

+

+ Track which services are available at each location for accurate booking +

+
+
+
+
+
+ + {/* Tips Section */} +
+

+ + Tips & Best Practices +

+
+
+
+ +
+

Set Accurate Timezones

+

+ Always configure the correct timezone for each location to ensure appointment times are displayed + correctly for both staff and customers in different time zones. +

+
+
+
+ +
+

Use Descriptive Names

+

+ Give locations clear, recognizable names like "Downtown Office" or "West Side Clinic" to help + staff and customers easily identify the right location. +

+
+
+
+ +
+

Choose Your Primary Wisely

+

+ Select your main or headquarters location as primary. This becomes the default for new resources + and services and appears first in location lists. +

+
+
+
+ +
+

Deactivate Instead of Delete

+

+ If a location is temporarily closed or inactive, deactivate it instead of deleting. This preserves + historical data and allows you to reactivate it later if needed. +

+
+
+
+ +
+

Keep Contact Info Current

+

+ Regularly update phone numbers and email addresses to ensure customers can reach the right location + if they need to contact you about their appointment. +

+
+
+
+
+
+ + {/* Need More Help */} +
+ +

+ Need More Help? +

+

+ Our support team is ready to help with any questions about locations. +

+ +
+
+ ); +}; + +export default HelpLocations; diff --git a/frontend/src/pages/help/HelpSettingsBusinessHours.tsx b/frontend/src/pages/help/HelpSettingsBusinessHours.tsx new file mode 100644 index 00000000..15f3a9e4 --- /dev/null +++ b/frontend/src/pages/help/HelpSettingsBusinessHours.tsx @@ -0,0 +1,275 @@ +/** + * Help Business Hours Settings Page + * + * User-friendly help documentation for Business Hours settings. + */ + +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { + ArrowLeft, + Clock, + Calendar, + Settings, + CheckCircle, + HelpCircle, + Sun, + Moon, + Users, + AlertCircle, +} from 'lucide-react'; + +const HelpSettingsBusinessHours: React.FC = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + return ( +
+ {/* Back Button */} + + + {/* Header */} +
+
+
+ +
+
+

+ Business Hours Guide +

+

+ Configure your operating hours and availability +

+
+
+
+ + {/* Overview Section */} +
+

+ + Overview +

+
+

+ Business Hours define when your business is open and accepting appointments. Setting accurate + hours is essential for providing a smooth booking experience for your customers. +

+

+ Customers can only book appointments during your configured business hours. Any time slots + outside these hours will be automatically hidden from the booking interface. +

+
+
+ + {/* Setting Your Hours Section */} +
+

+ + Setting Your Hours +

+
+
    +
  1. + 1 +
    +

    Navigate to Settings

    +

    Go to Settings → Business Hours from your dashboard.

    +
    +
  2. +
  3. + 2 +
    +

    Select Each Day

    +

    Each day of the week can have different hours. Toggle each day on or off.

    +
    +
  4. +
  5. + 3 +
    +

    Set Open and Close Times

    +

    For each enabled day, choose when you open and close using the time pickers.

    +
    +
  6. +
  7. + 4 +
    +

    Save Changes

    +

    Click "Save Business Hours" to apply your changes. They take effect immediately.

    +
    +
  8. +
+
+
+ + {/* Closed Days Section */} +
+

+ + Closed Days +

+
+

+ To mark a day as closed, simply toggle it off in the Business Hours settings. Common scenarios: +

+
+
+ +
+

Weekends

+

Disable Saturday and Sunday if you're closed on weekends

+
+
+
+ +
+

Specific Days

+

Turn off any day you're regularly closed (e.g., Mondays)

+
+
+
+ +
+

Different Hours

+

Set different hours for different days (e.g., shorter hours on Friday)

+
+
+
+ +
+

Holidays

+

Use Time Blocks for one-time closures like holidays

+
+
+
+
+
+ + {/* How It Affects Booking Section */} +
+

+ + How It Affects Booking +

+
+

+ Your business hours directly control when customers can book appointments: +

+
+
+ +
+

Available Time Slots

+

+ Only time slots within your business hours will be shown to customers in the booking calendar. +

+
+
+
+ +
+

Closed Days Hidden

+

+ Days that are disabled won't appear as available booking options for customers. +

+
+
+
+ +
+

Service Duration Respected

+

+ Appointments must end before closing time. A 2-hour service can't start 1 hour before close. +

+
+
+
+ +
+

Immediate Effect

+

+ Changes to business hours take effect immediately and apply to all future bookings. +

+
+
+
+
+
+ + {/* Tips Section */} +
+

+ + Tips +

+
+
+
+ +
+

Account for Buffer Time

+

+ Set closing time 15-30 minutes after your last desired appointment to account for cleanup and staff departure. +

+
+
+
+ +
+

Use Time Blocks for Exceptions

+

+ For one-time closures (holidays, maintenance), use Time Blocks instead of changing business hours. +

+
+
+
+ +
+

Coordinate with Resources

+

+ If specific staff have different schedules, use Resource availability settings in addition to business hours. +

+
+
+
+ +
+

Review Regularly

+

+ Update your hours seasonally or as your business evolves to ensure accurate availability. +

+
+
+
+
+
+ + {/* Need More Help */} +
+ +

+ Need More Help? +

+

+ Our support team is ready to help with any questions about business hours settings. +

+ +
+
+ ); +}; + +export default HelpSettingsBusinessHours; diff --git a/frontend/src/pages/help/HelpSettingsCommunication.tsx b/frontend/src/pages/help/HelpSettingsCommunication.tsx new file mode 100644 index 00000000..3198cc62 --- /dev/null +++ b/frontend/src/pages/help/HelpSettingsCommunication.tsx @@ -0,0 +1,425 @@ +/** + * Help Settings Communication Page + * + * User-friendly help documentation for Communication (SMS/Calling) Settings. + */ + +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { + ArrowLeft, + Phone, + MessageSquare, + CreditCard, + RefreshCw, + DollarSign, + Settings, + History, + HelpCircle, + ChevronRight, + Bell, + ShieldCheck, + TrendingUp, + Clock, + CheckCircle, +} from 'lucide-react'; + +const HelpSettingsCommunication: React.FC = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + return ( +
+ {/* Back Button */} + + + {/* Header */} +
+
+
+ +
+
+

+ Communication Settings Guide +

+

+ Manage SMS and calling features for your business +

+
+
+
+ + {/* Overview Section */} +
+

+ + Overview +

+
+

+ Communication settings enable you to send SMS reminders to customers and make masked calls + through your business phone number. The system uses a credit-based model powered by Twilio, + ensuring reliable delivery and professional communication. +

+

+ Set up automatic appointment reminders, make calls that display your business number, + and manage your communication budget with auto-reload features. +

+
+
+ + {/* Getting Started */} +
+

+ + Getting Started +

+
+
    +
  1. + 1 +
    +

    Complete Setup Wizard

    +

    Answer a few questions about your business size and communication needs to estimate your monthly usage.

    +
    +
  2. +
  3. + 2 +
    +

    Purchase Initial Credits

    +

    Add credits to your account based on the wizard's recommendation or choose your own amount.

    +
    +
  4. +
  5. + 3 +
    +

    Provision Phone Number

    +

    Search for and claim a local phone number in your area code for SMS and calls.

    +
    +
  6. +
  7. + 4 +
    +

    Configure Auto-Reload

    +

    Set up automatic credit top-ups to ensure uninterrupted service.

    +
    +
  8. +
+
+
+ + {/* Credit System */} +
+

+ + Credit System +

+
+

+ Credits are used for both SMS and calling features. Your balance is displayed prominently + in the Communication Settings page. +

+
+
+ +
+

SMS Pricing

+

Approximately $0.01 - $0.02 per SMS depending on destination country. Long messages may use multiple credits.

+
+
+
+ +
+

Call Pricing

+

Charged per minute of talk time. Pricing varies by country but typically ranges from $0.01 - $0.05 per minute.

+
+
+
+ +
+

Purchasing Credits

+

Buy credits in bulk with minimum purchase of $10. Larger purchases may qualify for volume discounts.

+
+
+
+
+
+ + {/* Auto-Reload Configuration */} +
+

+ + Auto-Reload Configuration +

+
+

+ Auto-reload ensures you never run out of credits during important communications. + Configure threshold and reload amounts to match your usage patterns. +

+
+
+ +
+

How It Works

+

+ When your balance falls below the threshold amount (e.g., $10), we automatically + charge your payment method and add the reload amount (e.g., $50) to your account. +

+
+
+
+
    +
  • + + Threshold: Set the balance level that triggers auto-reload (minimum $5) +
  • +
  • + + Reload Amount: Choose how much to add when triggered (minimum $10) +
  • +
  • + + Email Notifications: Receive confirmation emails when auto-reload occurs +
  • +
+
+
+ + {/* Phone Number Provisioning */} +
+

+ + Phone Number Provisioning +

+
+

+ Your business needs a dedicated phone number for sending SMS and making masked calls. + Search and provision numbers directly through the settings page. +

+
+
+ 🔍 +
+

Search by Area Code

+

Enter your desired area code (e.g., 303 for Denver) to see available local numbers.

+
+
+
+ 📱 +
+

Choose Your Number

+

Select from available numbers. Local numbers build trust and improve answer rates.

+
+
+
+ 💰 +
+

Monthly Fee

+

Phone numbers have a small monthly fee (typically $1-2) automatically deducted from your credits.

+
+
+
+
+
+ + {/* SMS Reminders */} +
+

+ + SMS Reminders +

+
+

+ Reduce no-shows by automatically sending SMS reminders to customers before their appointments. +

+
+
+ +
+

24-Hour Reminders

+

Sent one day before appointments

+
+
+
+ +
+

2-Hour Reminders

+

Sent shortly before appointments

+
+
+
+
+
+ +
+

Customer Consent

+

+ Only send SMS to customers who have provided their phone number and consented to + receive communications. Respect opt-out requests immediately. +

+
+
+
+
+
+ + {/* Masked Calling */} +
+

+ + Masked Calling +

+
+

+ Make calls to customers that display your business phone number instead of personal cell phones. + Protects staff privacy while maintaining professional communication. +

+
+
+ +
+

Privacy Protection

+

Your personal phone number remains hidden. Customers see only your business number.

+
+
+
+ +
+

Higher Answer Rates

+

Customers are more likely to answer calls from recognized local numbers.

+
+
+
+ +
+

Call Recording

+

Optional call recording for quality assurance and training purposes.

+
+
+
+
+
+ + {/* Transaction History */} +
+

+ + Transaction History +

+
+

+ View detailed logs of all communication activity including credit purchases, SMS sends, + and call minutes used. +

+
    +
  • + + Credit Purchases: Track all top-ups and auto-reload transactions +
  • +
  • + + SMS Usage: See each message sent, recipient, and cost +
  • +
  • + + Call Logs: Review call duration, destination, and charges +
  • +
  • + + Monthly Fees: Monitor phone number rental and other recurring charges +
  • +
+
+
+ + {/* Tips and Best Practices */} +
+

+ + Tips and Best Practices +

+
+
+
+ 💡 +
+

Start with the Setup Wizard

+

+ The wizard provides accurate usage estimates based on your business size and helps + you avoid over or under-purchasing credits. +

+
+
+
+ 🔄 +
+

Enable Auto-Reload

+

+ Prevent service interruptions by setting up auto-reload. Set the threshold to 20-30% + of your typical monthly usage for best results. +

+
+
+
+ 📊 +
+

Monitor Your Usage

+

+ Check the transaction history monthly to understand patterns and optimize your + auto-reload settings. Look for opportunities to reduce no-shows. +

+
+
+
+ 📱 +
+

Choose Local Numbers

+

+ Local area codes increase trust and answer rates. Customers are more likely to + respond to numbers they recognize as being from their area. +

+
+
+
+ ⚖️ +
+

Comply with Regulations

+

+ Always obtain customer consent before sending SMS or making calls. Honor opt-out + requests immediately and maintain do-not-contact lists. +

+
+
+
+
+
+ + {/* Need More Help */} +
+ +

+ Need More Help? +

+

+ Our support team is ready to help with communication settings and troubleshooting. +

+ +
+
+ ); +}; + +export default HelpSettingsCommunication; diff --git a/frontend/src/pages/help/HelpSettingsEmailTemplates.tsx b/frontend/src/pages/help/HelpSettingsEmailTemplates.tsx new file mode 100644 index 00000000..a5286b7f --- /dev/null +++ b/frontend/src/pages/help/HelpSettingsEmailTemplates.tsx @@ -0,0 +1,478 @@ +/** + * Help Settings Email Templates Page + * + * User-friendly help documentation for Email Templates settings. + */ + +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { + ArrowLeft, + Mail, + FileText, + Eye, + RefreshCw, + Palette, + Code, + CheckCircle, + HelpCircle, + Settings, + User, + Calendar, + DollarSign, + MessageSquare, +} from 'lucide-react'; + +const HelpSettingsEmailTemplates: React.FC = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + return ( +
+ {/* Back Button */} + + + {/* Header */} +
+
+
+ +
+
+

+ Email Templates Guide +

+

+ Customize your automated email communications +

+
+
+
+ + {/* Overview Section */} +
+

+ + Overview +

+
+

+ Email Templates allow you to customize all automated emails sent from your scheduling platform. + Using our visual drag-and-drop editor powered by Puck, you can create professional, branded + emails without any coding knowledge. +

+

+ Each template supports dynamic content through template tags, allowing you to personalize + messages with customer names, appointment details, and other relevant information. +

+
+
+ + {/* Template Categories Section */} +
+

+ + Template Categories +

+
+
+
+ +
+

Welcome Email

+

+ Sent when a new customer account is created. Sets the tone for their relationship with your business. +

+
+
+ +
+ +
+

Appointment Templates

+

+ Four templates for the complete appointment lifecycle: +

+
    +
  • Confirmation: Sent immediately when an appointment is booked
  • +
  • Reminder: Sent before the appointment (configurable timing)
  • +
  • Rescheduled: Sent when an appointment time is changed
  • +
  • Cancelled: Sent when an appointment is cancelled
  • +
+
+
+ +
+ +
+

Contract Email

+

+ Sent when a contract or agreement needs to be signed by the customer. +

+
+
+ +
+ +
+

Payment Email

+

+ Sent for payment confirmations, receipts, or payment reminders. +

+
+
+ +
+ +
+

Ticket Email

+

+ Sent when support tickets are created, updated, or resolved. +

+
+
+
+
+
+ + {/* Using the Editor Section */} +
+

+ + Using the Editor +

+
+

+ Our email editor uses Puck, a powerful visual drag-and-drop interface that makes email + design simple and intuitive. +

+
    +
  1. + 1 +
    +

    Select a Template

    +

    + Navigate to Settings → Email Templates and choose the template you want to customize. +

    +
    +
  2. +
  3. + 2 +
    +

    Drag & Drop Components

    +

    + Add text blocks, images, buttons, dividers, and other elements by dragging them from + the component panel into your email layout. +

    +
    +
  4. +
  5. + 3 +
    +

    Customize Content

    +

    + Click on any element to edit its properties - text, colors, fonts, spacing, alignment, and more. +

    +
    +
  6. +
  7. + 4 +
    +

    Insert Template Tags

    +

    + Use template tags (like {{customer_name}}) to add dynamic content that changes + for each email sent. +

    +
    +
  8. +
  9. + 5 +
    +

    Save Changes

    +

    + Click "Save" to apply your changes. The new template will be used for all future emails + of that type. +

    +
    +
  10. +
+
+
+ + {/* Template Tags Section */} +
+

+ + Template Tags +

+
+

+ Template tags are placeholders that get replaced with real data when the email is sent. + Wrap tag names in double curly braces: {{tag_name}} +

+
+
+

Customer Tags

+
+
+ {{customer_name}} +
+
+ {{customer_email}} +
+
+ {{customer_phone}} +
+
+ {{customer_id}} +
+
+
+ +
+

Appointment Tags

+
+
+ {{appointment_date}} +
+
+ {{appointment_time}} +
+
+ {{appointment_duration}} +
+
+ {{service_name}} +
+
+ {{staff_name}} +
+
+ {{location}} +
+
+
+ +
+

Business Tags

+
+
+ {{business_name}} +
+
+ {{business_phone}} +
+
+ {{business_email}} +
+
+ {{business_address}} +
+
+ {{business_website}} +
+
+ {{business_logo}} +
+
+
+ +
+

Other Tags

+
+
+ {{confirmation_link}} +
+
+ {{cancellation_link}} +
+
+ {{reschedule_link}} +
+
+ {{current_year}} +
+
+
+
+
+
+ + {/* Preview & Testing Section */} +
+

+ + Preview & Testing +

+
+
+
+ +
+

Live Preview

+

+ The editor shows a real-time preview of your email as you make changes. What you see + is what your customers will receive. +

+
+
+
+ +
+

HTML & Plain Text

+

+ Preview both the HTML version (for modern email clients) and plain text version + (for simple email clients or accessibility). +

+
+
+
+ +
+

Send Test Email

+

+ Send a test email to yourself to see how it appears in your actual email client. + Template tags will be filled with sample data. +

+
+
+
+ +
+

Mobile Preview

+

+ Toggle between desktop and mobile views to ensure your emails look great on all devices. +

+
+
+
+
+
+ + {/* Branding Section */} +
+

+ + Brand Colors & Logo +

+
+

+ Email templates automatically integrate with your business branding settings: +

+
    +
  • + + Brand Colors: Your primary brand color is available for buttons, headers, and accents +
  • +
  • + + Logo: Use the {{business_logo}} tag to insert your company logo +
  • +
  • + + Consistent Styling: Templates maintain consistent fonts and spacing automatically +
  • +
+
+
+ + {/* Reset to Default Section */} +
+

+ + Reset to Default +

+
+

+ If you want to start over or undo your customizations, you can reset any template to its + default state. +

+
+
+ +
+

+ Warning: This action cannot be undone +

+

+ Resetting a template will permanently delete all your customizations and restore + the original default template. Make sure to save a copy if you want to keep your work. +

+
+
+
+
+
+ + {/* Tips Section */} +
+

+ + Tips & Best Practices +

+
+
    +
  • + + Keep it simple: Avoid cluttered designs. Focus on clear, scannable content with plenty of white space. +
  • +
  • + + Mobile-first: Over 50% of emails are opened on mobile devices. Always check the mobile preview. +
  • +
  • + + Clear CTAs: Make call-to-action buttons prominent with contrasting colors and clear text. +
  • +
  • + + Test thoroughly: Send test emails to different email clients (Gmail, Outlook, Apple Mail) to verify rendering. +
  • +
  • + + Personalize: Use template tags to make emails feel personal and relevant to each recipient. +
  • +
  • + + Be concise: Keep email content brief and to the point. Include only essential information. +
  • +
  • + + Brand consistency: Maintain consistent colors, fonts, and tone across all email templates. +
  • +
  • + + Include footer: Always add contact info and unsubscribe options in the email footer. +
  • +
+
+
+ + {/* Need More Help */} +
+ +

+ Need More Help? +

+

+ Our support team is ready to help with any questions about email templates. +

+ +
+
+ ); +}; + +export default HelpSettingsEmailTemplates; diff --git a/frontend/src/pages/help/HelpSettingsEmbedWidget.tsx b/frontend/src/pages/help/HelpSettingsEmbedWidget.tsx new file mode 100644 index 00000000..825c2c86 --- /dev/null +++ b/frontend/src/pages/help/HelpSettingsEmbedWidget.tsx @@ -0,0 +1,299 @@ +/** + * Help Embed Widget Settings Page + * + * User-friendly help documentation for the Embed Widget feature. + */ + +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { + ArrowLeft, + Code, + ExternalLink, + Copy, + Layout, + Palette, + Globe, + ChevronRight, + HelpCircle, + Monitor, + Smartphone, + Settings, + CheckCircle, +} from 'lucide-react'; + +const HelpSettingsEmbedWidget: React.FC = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + return ( +
+ {/* Back Button */} + + + {/* Header */} +
+
+
+ +
+
+

+ Embed Widget Guide +

+

+ Add a booking widget to your website in minutes +

+
+
+
+ + {/* Overview Section */} +
+

+ + Overview +

+
+

+ The Embed Widget lets you add SmoothSchedule's booking functionality directly to your existing website. + Your customers can book appointments without leaving your site, creating a seamless experience that + matches your brand. +

+

+ Works on any website platform including WordPress, Wix, Squarespace, Shopify, or custom HTML sites. + No technical expertise required - just copy and paste the code snippet. +

+
+
+ + {/* Embed Options Section */} +
+

+ + Embed Options +

+
+
+
+ +
+

Iframe Embed

+

Simple HTML iframe - works everywhere, fixed size, easy to implement

+
+
+
+ +
+

JavaScript Snippet

+

Advanced option - responsive sizing, better integration, modal support

+
+
+
+
+

+ Recommendation: Use the iframe for quick setup. Use JavaScript snippet for better responsive design + and if you want the widget to appear as a modal/popup. +

+
+
+
+ + {/* Customization Section */} +
+

+ + Customization Options +

+
+
+
+ +
+

Brand Colors

+

Customize primary color, button styles, and backgrounds to match your website

+
+
+
+ +
+

Widget Size

+

Set width and height (iframe) or let it adapt to your page layout (JavaScript)

+
+
+
+ +
+

Responsive Design

+

Widget automatically adapts to mobile, tablet, and desktop screen sizes

+
+
+
+ +
+

Display Preferences

+

Choose default service, hide certain options, or pre-select staff members

+
+
+
+
+
+ + {/* Installation Section */} +
+

+ + Installation by Platform +

+
+
    +
  1. + 1 +
    +

    WordPress

    +

    + Add a Custom HTML block to your page, paste the embed code, and publish. Works with any page builder. +

    +
    +
  2. +
  3. + 2 +
    +

    Wix

    +

    + Click "Add Elements" → "Embed" → "Embed a Widget" → "HTML iframe" or "Custom Code", then paste your code. +

    +
    +
  4. +
  5. + 3 +
    +

    Squarespace

    +

    + Edit your page, add a "Code" block, paste the embed code, and save. Set to HTML mode if prompted. +

    +
    +
  6. +
  7. + 4 +
    +

    Shopify

    +

    + Go to Online Store → Pages → Create/Edit page → Show HTML editor → Paste code in desired location. +

    +
    +
  8. +
  9. + 5 +
    +

    Custom HTML Website

    +

    + Paste the embed code directly into your HTML file where you want the widget to appear. For JavaScript snippet, add before closing </body> tag. +

    +
    +
  10. +
+
+
+ + {/* Tips & Best Practices */} +
+

+ + Tips & Best Practices +

+
+
    +
  • + + + Copy-to-clipboard: Use the built-in copy button in Settings → Embed Widget to easily copy your personalized code. + +
  • +
  • + + + Test responsiveness: Preview the widget on mobile, tablet, and desktop before publishing to ensure it looks good everywhere. + +
  • +
  • + + + Match your brand: Customize colors to align with your website's design. The widget should feel like a natural part of your site. + +
  • +
  • + + + Placement matters: Put the widget on high-traffic pages like your homepage or services page for maximum bookings. + +
  • +
  • + + + Mobile optimization: Most bookings happen on mobile. Ensure the widget is easy to use on small screens. + +
  • +
  • + + + Update anytime: Changes to your services, availability, or branding automatically appear in the widget - no need to re-embed. + +
  • +
+
+
+ + {/* Quick Setup Guide */} +
+
+
+ +
+

+ Ready to Get Started? +

+

+ Head to your Embed Widget settings to customize your widget, preview it, and copy the code snippet. + You'll have it live on your website in just a few minutes. +

+ +
+
+
+
+ + {/* Need More Help */} +
+ +

+ Need More Help? +

+

+ Having trouble embedding the widget? Our support team can help with installation and customization. +

+ +
+
+ ); +}; + +export default HelpSettingsEmbedWidget; diff --git a/frontend/src/pages/help/HelpSettingsStaffRoles.tsx b/frontend/src/pages/help/HelpSettingsStaffRoles.tsx new file mode 100644 index 00000000..0f5f7a8c --- /dev/null +++ b/frontend/src/pages/help/HelpSettingsStaffRoles.tsx @@ -0,0 +1,482 @@ +/** + * Help Settings Staff Roles Page + * + * User-friendly help documentation for Staff Roles Settings. + */ + +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { + ArrowLeft, + Shield, + Users, + Lock, + AlertTriangle, + Settings, + CheckCircle, + ChevronRight, + HelpCircle, +} from 'lucide-react'; + +const HelpSettingsStaffRoles: React.FC = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + return ( +
+ {/* Back Button */} + + + {/* Header */} +
+
+
+ +
+
+

+ Staff Roles Guide +

+

+ Control staff access and permissions with custom roles +

+
+
+
+ + {/* Overview Section */} +
+

+ + Overview +

+
+

+ Staff Roles allow you to control what your staff members can see and do in your scheduling platform. + Create custom roles with specific permissions to match your business needs. +

+

+ Each role defines which menu items staff can access and which dangerous operations (like deleting + customers or appointments) they can perform. Assign roles to staff members to apply these permissions + automatically. +

+
+
+ + {/* Default Roles Section */} +
+

+ + Default Roles +

+
+

+ SmoothSchedule comes with three built-in roles to get you started: +

+
+
+
+ +

Full Access Staff

+
+

+ Access to all menu items and all dangerous permissions. Best for managers and supervisors. +

+
+
+
+ +

Limited Staff

+
+

+ Access to Scheduler, Customers, and Messages only. No dangerous permissions. Best for general staff. +

+
+
+
+ +

Front Desk

+
+

+ Access to Scheduler, Services, Customers, and Messages. No delete permissions. Best for reception. +

+
+
+
+
+ + {/* Creating Custom Roles */} +
+

+ + Creating Custom Roles +

+
+
    +
  1. + 1 +
    +

    Navigate to Settings

    +

    Go to Settings → Staff Roles.

    +
    +
  2. +
  3. + 2 +
    +

    Click "Create Role"

    +

    Click the "Create Role" button to open the role creation form.

    +
    +
  4. +
  5. + 3 +
    +

    Enter Role Details

    +

    Provide a name (e.g., "Technician") and optional description.

    +
    +
  6. +
  7. + 4 +
    +

    Select Menu Permissions

    +

    Choose which sidebar menu items this role can access.

    +
    +
  8. +
  9. + 5 +
    +

    Configure Dangerous Permissions

    +

    Carefully choose delete permissions for critical operations.

    +
    +
  10. +
  11. + 6 +
    +

    Save and Assign

    +

    Save the role and assign it to staff members.

    +
    +
  12. +
+
+
+ + {/* Menu Permissions */} +
+

+ + Menu Permissions +

+
+

+ Menu permissions control which sidebar items are visible to staff members with this role. + If a permission is not granted, the menu item will be hidden from the sidebar. +

+
+
+ +
+
Scheduler
+

View and manage the calendar

+
+
+
+ +
+
Services
+

Manage offered services

+
+
+
+ +
+
Resources
+

Manage staff and equipment

+
+
+
+ +
+
Staff
+

Manage staff members

+
+
+
+ +
+
Customers
+

View customer information

+
+
+
+ +
+
Time Blocks
+

Manage availability blocks

+
+
+
+ +
+
Messages
+

Access messaging features

+
+
+
+ +
+
Tickets
+

Manage support tickets

+
+
+
+ +
+
Payments
+

View payment information

+
+
+
+ +
+
Contracts
+

Manage contracts and signatures

+
+
+
+ +
+
Tasks
+

View automation tasks

+
+
+
+ +
+
Site Builder
+

Edit marketing site pages

+
+
+
+ +
+
Gallery
+

Manage media gallery

+
+
+
+ +
+
Settings
+

Access business settings

+
+
+
+
+
+ + {/* Dangerous Permissions */} +
+

+ + Dangerous Permissions +

+
+
+ +
+

+ Exercise Caution with These Permissions +

+

+ Dangerous permissions allow staff to perform irreversible delete operations. + Only grant these permissions to trusted staff members. +

+
+
+
+
+ +
+

Delete Customers

+

+ Allows staff to permanently delete customer records and all associated data +

+
+
+
+ +
+

Delete Appointments

+

+ Allows staff to permanently delete scheduled appointments +

+
+
+
+ +
+

Delete Services

+

+ Allows staff to permanently delete services from your offerings +

+
+
+
+ +
+

Delete Resources

+

+ Allows staff to permanently delete resources (staff members, rooms, equipment) +

+
+
+
+
+
+ + {/* Assigning Roles */} +
+

+ + Assigning Roles to Staff +

+
+

+ Once you've created a role, you can assign it to staff members to apply the permissions: +

+
    +
  1. + 1 +
    +

    + Navigate to Staff in the sidebar +

    +
    +
  2. +
  3. + 2 +
    +

    + Click on a staff member to edit their details +

    +
    +
  4. +
  5. + 3 +
    +

    + Select the desired role from the Staff Role dropdown +

    +
    +
  6. +
  7. + 4 +
    +

    + Save changes - permissions apply immediately +

    +
    +
  8. +
+
+

+ Note: The Staff Roles page shows how many staff members are assigned to each role. + This helps you track role usage across your team. +

+
+
+
+ + {/* Tips Section */} +
+

+ + Best Practices & Tips +

+
+
    +
  • + +
    +

    Start with Default Roles

    +

    + Use the built-in roles (Full Access, Limited, Front Desk) as templates when creating custom roles +

    +
    +
  • +
  • + +
    +

    Principle of Least Privilege

    +

    + Only grant permissions that staff members need to do their job - nothing more +

    +
    +
  • +
  • + +
    +

    Use Descriptive Names

    +

    + Name roles clearly (e.g., "Massage Therapist", "Receptionist") so their purpose is obvious +

    +
    +
  • +
  • + +
    +

    Limit Dangerous Permissions

    +

    + Be very selective about granting delete permissions - these operations cannot be undone +

    +
    +
  • +
  • + +
    +

    Review Regularly

    +

    + Periodically review staff roles and permissions to ensure they're still appropriate +

    +
    +
  • +
  • + +
    +

    Add Descriptions

    +

    + Include a description when creating roles to document their intended purpose and use cases +

    +
    +
  • +
+
+
+ + {/* Need More Help */} +
+ +

+ Need More Help? +

+

+ Our support team is ready to help with any questions about staff roles and permissions. +

+ +
+
+ ); +}; + +export default HelpSettingsStaffRoles;