Communication
Notification Modal
Live notification hub for bookings, conversations, and orders - polls the backend every 15 seconds, persists read and dismissed state per app, and adapts notification copy for customer or provider roles automatically.
All three are fetched on mount and refreshed every 15 s. Role-based copy is applied per source automatically.
Read and dismissed IDs are stored locally, capped at 500 per app. Users keep their read state between sessions.
Available on free and paid plans.
Four things worth knowing
Three data sources, one merged feed
On mount the component fetches bookings, conversations, and orders in parallel via secureApiCall. Each source is mapped to a BuilderNotificationItem with role-specific copy - a provider sees the customer name and service status; a customer sees the provider name. All three are merged, sorted newest first, and refreshed every 15 seconds.
Read and dismissed state survives sessions
Read and dismissed notification IDs are written to AsyncStorage under a key scoped to appId. The list is capped at 500 IDs. On next mount the component loads these IDs before rendering, so users never see previously dismissed notifications come back. Do not change STORAGE_PREFIX without a migration plan - all persisted state will be lost.
The button is silent without appId and secureApiCall
If either appId or secureApiCall is missing, or if isPreview is false, the component renders a non-interactive button. Polling never starts. This is intentional - the component will not make unauthenticated API calls. Always verify both props are injected by the runtime before testing.
Trigger mode controls who opens the modal
With triggerMode=“internal” (default), the user taps the 46×46 circle button to open the modal. With triggerMode=“external”, the parent controls visibility via externalVisible and onRequestClose - the built-in button is bypassed. Use external mode when you want to open the notification panel from a custom nav bar element.
Find it, drop it, set the view mode
Where to find itAdd it to any screen
- Component picker → Communication → Notification Modal.
- Drop it anywhere - it renders as a fixed-size button that opens an overlay modal.
- Set viewMode to
customerorproviderto get the correct notification copy.
Before you publishThree things to check
- Confirm isPreview is
true- without it the button is non-interactive in the builder. - Verify appId and secureApiCall are injected by the runtime; the component will not poll without them.
- After changing any registry defaults or component logic, run
node collectSourceFiles.jsinapp/to sync the deploy bundle.
Identity, text, and trigger mode
| Prop | Type | Default | Description |
|---|---|---|---|
viewMode | select | ’customer’ | customer or provider - determines notification copy for each source. |
title | string | ’Notifications’ | Modal header title and empty-state heading. |
subtitle | string | ’New bookings, chats…’ | Modal header subtitle and empty-state subtext. |
buttonIconName | string | ’notifications’ | Ionicons icon rendered inside the trigger button. |
triggerMode | select | ’internal’ | internal - button opens modal. external - parent controls via externalVisible. |
borderRadius | number | 20 | Modal corner radius in px. |
padding | number | 18 | Modal inner padding in px. |
fontFamily | string | - | Font family applied to all text in the modal. |
Make it yours
Every colour and visibility toggle below is yours to change. The defaults are a neutral starting point - adjust them to match the brand without touching anything else.
| Prop | Default | What it controls |
|---|---|---|
showSubtitle | true | Subtitle visibility in both the modal header and empty state. |
showBadge | true | Unread count badge on the trigger button. |
showTimestamp | true | ”5 min ago” / “Yesterday” timestamp on each notification card. |
showAudienceTag | true | ”Provider” / “Consumer” audience tag on each card. |
showTypeTag | true | ”Booking” / “Chat” / “Order” type tag on each card. |
showMarkAllRead | true | ”Mark all read” action button in the modal header. |
buttonBackgroundColor | #F5F7FB | Trigger button background. |
buttonIconColor | #1F2937 | Icon colour inside the trigger button. |
badgeColor | #EF4444 | Unread badge background. |
badgeTextColor | #FFFFFF | Unread badge count text. |
overlayColor | rgba(15,23,42,0.45) | Dark overlay behind the modal. |
modalBackgroundColor | #FFFFFF | Modal card background. |
cardBackgroundColor | #F8FAFC | Individual notification card background. |
primaryColor | #2563EB | Icon tint and action link colour. |
borderColor | #E5E7EB | Card borders. |
unreadDotColor | #2563EB | Unread indicator dot on each card. |
titleColor | #111827 | Modal header title and notification card title text. |
subtitleColor | #6B7280 | Modal subtitle and timestamp text. |
textColor | #111827 | Notification body title text. |
mutedTextColor | #6B7280 | Notification body and timestamp muted text. |
Common mistakes to avoid
Button is non-interactive in the builder
The modal will not open unless isPreview= is set. In the builder canvas this prop must be enabled explicitly. Without it the component renders a static button - no tap, no modal, no polling. This is by design to prevent unexpected API calls during layout editing.
Unread badge always shows 0
The badge count comes from unread logic applied per source: bookings unread if timestamp is within 36 hours and not cancelled; conversations unread if unreadCount > 0; orders unread if recent and not cancelled. If all sources return no unread items the badge will be empty - verify the backend data shape matches these rules.
Do not change STORAGE_PREFIX without migration
Read and dismissed IDs are stored in AsyncStorage under a key that includes STORAGE_PREFIX. If you rename it, all existing persisted state is orphaned - users will see every previously dismissed notification again. Always write a migration or clear AsyncStorage intentionally if you need to change the prefix.
Keep polling interval at 10 s or above
The default 15-second interval is a deliberate balance between freshness and backend load. Intervals below 10 s on apps with many concurrent users will cause disproportionate API pressure. Change the setInterval value in BuilderNotificationModal.tsx only if you have confirmed your backend can handle the increased rate.