Dentolize · Plugin Platform Walkthrough
On this pageBusiness viewTechnical view

Plugin Hub (clinic side)

Business view

The Plugin Hub is where a clinic owner (or anyone with the right permission) browses, installs, and manages third-party integrations, without ever leaving Dentolize or needing help from support. It's reached from Settings → Plugins in the sidebar.

Two tabs cover the whole lifecycle:

  • Browse — search and filter every published plugin, read what it does and what it wants access to, and install it.
  • Installed — see everything currently connected to your clinic, with quick access to each one's configuration.

Once installed, a plugin gets its own four-tab settings page:

  • Settings — whatever configuration the plugin itself defined (e.g. "how many hours before an appointment to send a reminder"). This form is generated automatically from the plugin's own schema — Dentolize doesn't know or care what's in it beyond rendering it correctly in Arabic and English.
  • Connection — the technical plumbing: the API credential the plugin uses to call Dentolize, and the webhook endpoints Dentolize calls to notify the plugin. This is the one screen where secret values ever appear, and only once, at the moment they're created or rotated.
  • Activity — a log of every webhook delivery attempt, so a clinic (or its IT contact) can see at a glance whether a plugin's integration is actually working, and manually retry a failed one.
  • Danger — the single "eject" button: uninstall, which immediately cuts off API access and stops all webhook deliveries.

Two permissions govern who can do what: viewPlugins lets someone browse and see what's installed; managePlugins is required to actually install, configure, rotate secrets, or uninstall anything.

Technical view

Where it lives and how it's gated

The UI itself is a set of React/TypeScript components in packages/clinic-web-canary/src/features/PluginHub/, exported as a pure component library and mounted into the running app (packages/clinic-web) via DashboardRouter.js:858-869. Every route is gated on both a permission and a feature flag:

RouteScreenRequired permission
/plugins, /plugins/tab/:tabBrowse / Installed`doAll \\viewPlugins`
/plugins/:slugPlugin detail`doAll \\viewPlugins`
/settings/plugins/:slugConfigure (4 tabs)`doAll \\managePlugins`

All four routes additionally require the FEATURE_PLUGIN_HUB flag (useSidebarFeatureFlags.js:13, checked per-route in DashboardRouter.js:716). The sidebar entry itself (Sidebar.js:529-536) uses the Blocks icon from lucide-react, is nested under the Settings group, and is translated as "Plugin Hub" / "متجر الإضافات".

PluginDetail.tsx and InstallPluginModal.tsx both render the same ScopeList/WebhookEventList components from ConsentLists.tsx, so the exact same plain-language breakdown a browsing owner sees on the detail page is what they see again — at the point of actually committing — inside the install wizard. That repetition is deliberate, not a code-reuse accident: see Scopes & consent.

Connection tab (settingsTabs/ConnectionTab.tsx, 420 lines)

API tokens card: tokens are always shown masked (tokenPrefix…) with a mode tag (Live/Test); there is no "reveal" action anywhere in the UI — the plaintext only ever appears once, in a dismissable one-time alert, immediately after install or a rotate. Two actions per token: Rotate (mints a replacement, invalidates the old one immediately — no grace period exposed in this UI, though the docs describe a 24h server-side grace window) and Revoke. There is no "create a new token" button — tokens only come from install or rotate.

Webhook endpoints card: each endpoint shows its URL, subscribed event tags, an Active toggle (editWebhookEndpoint with { id, active }), Rotate secret, Delete, and a per-endpoint Send test control (event dropdown limited to that endpoint's own subscribed types → SEND_TEST_PLUGIN_EVENT mutation). The Add endpoint sub-form validates the URL is https:// client-side (isValidHttpsUrl) and only offers events from installation.grantedEvents — i.e. a clinic can't subscribe an endpoint to an event the installed plugin never actually requested.

Activity tab (settingsTabs/ActivityTab.tsx)

Despite covering "activity" broadly in its name, this component queries PLUGIN_WEBHOOK_DELIVERIES only — webhook deliveries, not the separate API request log (pluginApiRequestLogs, which the schema exposes but this screen doesn't surface). Columns: Event, Status (color-coded — Pending/Delivering/Succeeded/Failed/Exhausted/Disabled), Attempts, Response code, Date, and a Redeliver action (REDELIVER_PLUGIN_WEBHOOK). Rows with an error message or response snippet are expandable. Cursor-paginated, 20 rows at a time (ACTIVITY_PAGE_SIZE, PluginHub.constants.tsx:63).

Danger tab (settingsTabs/DangerTab.tsx)

A single action, by design: a code comment in the source explicitly notes the registry schema exposes no enable/disable mutation for installations, so there is no "pause" toggle here — only uninstall (UninstallPluginModal.tsx), which requires typing the plugin's exact slug to confirm and offers a non-binding "request data deletion" checkbox forwarded to the developer.

Settings tab and the settings-form renderer

Covered in depth in Settings schema — the short version is that schemaForm/PluginSettingsForm.tsx turns a plugin's JSON schema into a live react-hook-form form with client-side validation mirroring the server's, including a special secret-field UX (masked with a "Replace" / "Keep existing" toggle, since the server never returns a real secret value back to a client — only the sentinel "__SET__").