diff --git a/content/docs/platform/inbox/configuration/meta.json b/content/docs/platform/inbox/configuration/meta.json index 86749b07d..c17290c27 100644 --- a/content/docs/platform/inbox/configuration/meta.json +++ b/content/docs/platform/inbox/configuration/meta.json @@ -1,6 +1,6 @@ { "title": "Customize and configure", "icon": "SlidersHorizontal", - "pages": ["styling", "icons", "tabs", "preferences", "snooze", "data-object"], + "pages": ["styling", "icons", "tabs", "preferences", "data-object"], "description": "Learn how to configure your inbox with styling, tabs, preferences, data objects, and snooze functionality" } diff --git a/content/docs/platform/inbox/configuration/styling.mdx b/content/docs/platform/inbox/configuration/styling.mdx index a05215d93..d6c052775 100644 --- a/content/docs/platform/inbox/configuration/styling.mdx +++ b/content/docs/platform/inbox/configuration/styling.mdx @@ -160,6 +160,14 @@ Here's a list of some available elements that can be styled using the elements o | Snooze button | `notificationSnooze__button` | | Unread/read indicator button | `notificationUnread__button` | | Notification list container | `notificationList` | +| Schedule container | `scheduleContainer` | +| Schedule header | `scheduleHeader` | +| Schedule body | `scheduleBody` | +| Schedule table | `scheduleTable` | +| Day schedule copy title | `dayScheduleCopyTitle` | +| Day schedule copy menu | `dayScheduleCopy__dropdownContent` | +| Time select drop-down list | `timeSelect__dropdownTrigger` | +| Time select list | `timeSelect__dropdownContent` | Any selector that appears before the 🔔 emoji in the Devtools, can be targeted via the elements property in the appearance prop (stripping the `nv-` prefix). You can also use TS autocomplete to find the available elements. @@ -352,6 +360,31 @@ You can customize specific parts of the Inbox UI by providing callback functions | `notificationDeliveredAt__icon` | `(context: { notification: Notification }) => string` | | `notificationSnoozedUntil__icon` | `(context: { notification: Notification }) => string` | | `notificationDot` | `(context: { notification: Notification }) => string` | +| **Schedule** | | +| `scheduleContainer` | `(context: { schedule?: Schedule }) => string` | +| `scheduleHeader` | `(context: { schedule?: Schedule }) => string` | +| `scheduleLabelContainer` | `(context: { schedule?: Schedule }) => string` | +| `scheduleLabelIcon` | `(context: { schedule?: Schedule }) => string` | +| `scheduleLabel` | `(context: { schedule?: Schedule }) => string` | +| `scheduleActionsContainer` | `(context: { schedule?: Schedule }) => string` | +| `scheduleActionsContainerRight` | `(context: { schedule?: Schedule }) => string` | +| `scheduleBody` | `(context: { schedule?: Schedule }) => string` | +| `scheduleDescription` | `(context: { schedule?: Schedule }) => string` | +| `scheduleTable` | `(context: { schedule?: Schedule }) => string` | +| `scheduleTableHeader` | `(context: { schedule?: Schedule }) => string` | +| `scheduleHeaderColumn` | `(context: { schedule?: Schedule }) => string` | +| `scheduleTableBody` | `(context: { schedule?: Schedule }) => string` | +| `scheduleBodyRow` | `(context: { schedule?: Schedule }) => string` | +| `scheduleBodyColumn` | `(context: { schedule?: Schedule }) => string` | +| `scheduleInfoContainer` | `(context: { schedule?: Schedule }) => string` | +| `scheduleInfoIcon` | `(context: { schedule?: Schedule }) => string` | +| `scheduleInfo` | `(context: { schedule?: Schedule }) => string` | +| **Day Schedule Copy** | | +| `dayScheduleCopyTitle` | `(context: { schedule?: Schedule }) => string` | +| `dayScheduleCopyIcon` | `(context: { schedule?: Schedule }) => string` | +| `dayScheduleCopySelectAll` | `(context: { schedule?: Schedule }) => string` | +| `dayScheduleCopyDay` | `(context: { schedule?: Schedule }) => string` | +| `dayScheduleCopyFooterContainer` | `(context: { schedule?: Schedule }) => string` | diff --git a/content/docs/platform/inbox/features/meta.json b/content/docs/platform/inbox/features/meta.json new file mode 100644 index 000000000..8d6385fc8 --- /dev/null +++ b/content/docs/platform/inbox/features/meta.json @@ -0,0 +1,6 @@ +{ + "title": " Features", + "icon": "Blocks", + "pages": ["snooze", "schedule"], + "description": "Learn how to customize the appearance and behavior of the inbox component to match your application’s design system." +} diff --git a/content/docs/platform/inbox/features/schedule.mdx b/content/docs/platform/inbox/features/schedule.mdx new file mode 100644 index 000000000..852232544 --- /dev/null +++ b/content/docs/platform/inbox/features/schedule.mdx @@ -0,0 +1,101 @@ +--- +title: 'Schedule' +description: "Learn how subscribers can use the Schedule feature in the Inbox component to control when they receive notifications from email, SMS and push channels." +icon: 'CalendarCheck' +--- + +The **Schedule** feature in the {``} lets subscribers control when they want to receive notifications from email, SMS, and push channels. + +By defining their hours of availability from the {``} UI, subscribers can automatically skip notifications outside of their chosen time range. + +![](/images/inbox/schedule.png) + +In-app notifications and notifications from critical workflows are never paused and will always be delivered, regardless of schedule settings. + +## How scheduling works + +The schedule functionality is based on a clear set of rules controlled by the subscriber within the {``}. When a subscriber enables their schedule, they can then manage their availability for each day of the week. + +### Daily availability +For any day that the main schedule is active, there will be two possible states: + +- **Day is enabled**: If a day is toggled on, then the subscriber must set a time range. Notifications will only be delivered within these hours. + +- **Day is disabled**: If a day is toggled off, then the subscriber will not receive any non-critical notifications for that entire day. + +### Automatic timezone handling + +The schedule operates in the subscriber's local timezone, detected automatically from their system settings. There is no need for manual configuration. + +For example, if a subscriber in Warsaw sets their schedule from 9:00 AM to 5:00 PM, they will receive notifications during those hours in Central European Time (CET). + +## Manage Schedule + +The schedule UI in the preferences section is designed to be intuitive, giving subscribers several tools to quickly configure their availability. + +You can access **Schedule** from the **Preferences** section of the {``} UI. By default, scheduling is turned off, and all notifications will be delivered normally. + +![Manage schedule](/images/inbox/inbox-schedule.png) + +### Enable the main schedule + +The entire feature is controlled by a main schedule toggle. When a subscriber turns this on, they can begin configuring individual days. When it's off, all other settings will be inactive, and notifications will be delivered normally. + +### Setting daily availability + +For each day of the week, the subscriber can: + +- Use the toggle next to the day's name to activate or deactivate it. +- If activated, use the time-selector menus to set a "from" and "to" time. The time pickers are set to 30-minute increments for ease of use. + +### Edit or remove schedules + +Existing availability can be adjusted at any time. Subscribers can: +- Change the start and end times for a day. +- Re-enable a previously disabled day. +- Clear a schedule entirely to return to unrestricted delivery. + +### Copying times to multiple days + +To make setup faster, subscribers can configure the time for one day and apply it to others in a few clicks: + +1. Set the desired time range for a single day. +2. Click **Copy times to...** associated with that day. +3. In the menu that appears, select the other days of the week to apply this schedule to. +4. Click "Apply." The selected days will automatically be enabled and updated with the new time range. + +![Copying times to multiple days](/images/inbox/schedule.gif) + +### Automatic saving + +All changes made in the schedule interface will be saved automatically in the background. The UI updates optimistically, providing a seamless experience without requiring the subscriber to click save. + +## Set default schedule for Subscribers + +You can provide a default schedule for your subscribers by passing the `defaultSchedule` prop to the {``}. This is useful for pre-configuring a recommended schedule that your subscribers can then customize. + +This default schedule only applies if a subscriber has not yet configured their own schedule. Once they make any changes, their custom schedule takes precedence. + +```tsx +import { Inbox } from '@novu/react'; + +function InboxWithDefaultSchedule() { + return ( + + ); +} + +export default InboxWithDefaultSchedule; +``` diff --git a/content/docs/platform/inbox/configuration/snooze.mdx b/content/docs/platform/inbox/features/snooze.mdx similarity index 100% rename from content/docs/platform/inbox/configuration/snooze.mdx rename to content/docs/platform/inbox/features/snooze.mdx diff --git a/content/docs/platform/inbox/meta.json b/content/docs/platform/inbox/meta.json index 52afdad0e..6ec7bce74 100644 --- a/content/docs/platform/inbox/meta.json +++ b/content/docs/platform/inbox/meta.json @@ -2,7 +2,7 @@ "pages": [ "overview", "setup-inbox", - "navigation-and-events", + "features", "configuration", "advanced-customization", "advanced-concepts", diff --git a/content/docs/platform/sdks/javascript/index.mdx b/content/docs/platform/sdks/javascript/index.mdx index fea9d9c4f..0615e551c 100644 --- a/content/docs/platform/sdks/javascript/index.mdx +++ b/content/docs/platform/sdks/javascript/index.mdx @@ -460,6 +460,64 @@ The response will be of type: } }} /> + +### Schedule + +The `preferences.schedule` submodule lets you fetch and update a subscriber’s delivery schedule. + +#### get + +Fetches the subscriber’s schedule. + +```tsx +const novu = new Novu(...); + +const { data: { schedule } } = await novu.preferences.schedule.get(); +``` + +#### update + +Updates the subscriber’s schedule. You can update the entire weekly schedule or only specific days. + +```tsx +const novu = new Novu(...); + +// Update schedule via preferences +const { data: { schedule } } = await novu.preferences.schedule.update({ + weeklySchedule: { + monday: { + isEnabled: true, + hours: [{ start: '09:00 AM', end: '05:00 PM' }], + }, + }, +}); + +// Or update directly from a Schedule instance +const { data: { schedule: updatedSchedule } } = + await schedule.update({ isEnabled: false }); +``` + +### Schedule Class + +A `Schedule` instance is returned when fetching or updating a schedule. + +) => Result" + } + }} +/> + + ## Events The Novu client provides real-time event handling through WebSocket connections. @@ -469,6 +527,10 @@ The Novu client provides real-time event handling through WebSocket connections. - `notifications.notification_received`: Triggered when a new notification is received - `notifications.unread_count_changed`: Triggered when the unread count changes - `notifications.unseen_count_changed`: Triggered when the unseen count changes +- `preferences.schedule.get.pending`: Triggered when fetching a schedule starts +- `preferences.schedule.get.resolved`: Triggered when fetching a schedule succeeds +- `preferences.schedule.update.pending`: Triggered when updating a schedule starts +- `preferences.schedule.update.resolved`: Triggered when updating a schedule succeeds ### Usage @@ -489,6 +551,10 @@ novu.on('notifications.unread_count_changed', (data) => { novu.on('notifications.unseen_count_changed', (data) => { console.log('Unseen count:', data); }); + +novu.on('preferences.schedule.update.resolved', ({ data }) => { + console.log('Schedule updated:', data.schedule); +}); ``` ## Types diff --git a/content/docs/platform/sdks/react-native/hooks/meta.json b/content/docs/platform/sdks/react-native/hooks/meta.json index 6f7753cc0..3c8cfe938 100644 --- a/content/docs/platform/sdks/react-native/hooks/meta.json +++ b/content/docs/platform/sdks/react-native/hooks/meta.json @@ -1,3 +1,10 @@ { - "pages": ["novu-provider", "use-novu", "use-notifications", "use-counts", "use-preferences"] + "pages": [ + "novu-provider", + "use-novu", + "use-notifications", + "use-counts", + "use-preferences", + "use-schedule" + ] } diff --git a/content/docs/platform/sdks/react-native/hooks/use-schedule.mdx b/content/docs/platform/sdks/react-native/hooks/use-schedule.mdx new file mode 100644 index 000000000..3a0798483 --- /dev/null +++ b/content/docs/platform/sdks/react-native/hooks/use-schedule.mdx @@ -0,0 +1,150 @@ +--- +title: 'useSchedule' +description: 'Learn how to use the useSchedule hook to manage notification delivery schedules in your React Native application' +--- + +The `useSchedule` hook provides a way to fetch and manage the notification schedule for the current subscriber. It allows you to retrieve the existing schedule and update it with new availability settings directly from your React Native app. + +## Return Value + +```tsx +type ScheduleReturn = { + schedule?: { + isEnabled: boolean; + weeklySchedule: { + monday?: DaySchedule; + tuesday?: DaySchedule; + wednesday?: DaySchedule; + thursday?: DaySchedule; + friday?: DaySchedule; + saturday?: DaySchedule; + sunday?: DaySchedule; + }; + update: (params: Partial) => Promise; + }; + isLoading: boolean; + error: Error | null; + refetch: () => Promise; +}; + +type DaySchedule = { + isEnabled: boolean; + hours: Array<{ start: string; end:string; }>; +}; +``` + +## Example usage + +Here's how to use the `useSchedule` hook in a React Native component to display and update a subscriber's notification schedule. The `schedule.update()` method will create a schedule if one doesn't exist or update the existing one. + +```tsx +import { useSchedule } from '@novu/react-native'; +import { View, Text, TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native'; + +function ScheduleManager() { + const { schedule, isLoading, error, refetch } = useSchedule(); + + if (isLoading) { + return ; + } + + if (error) { + return Error: {error.message}; + } + + const handleUpdateSchedule = async () => { + await schedule?.update({ + isEnabled: true, + weeklySchedule: { + monday: { + isEnabled: true, + hours: [{ start: '09:00 AM', end: '05:00 PM' }], + }, + // Disable Wednesday + wednesday: { + isEnabled: false, + hours: [], + }, + }, + }); + }; + + return ( + + My Notification Schedule + + Schedule Enabled: + {schedule?.isEnabled ? 'Yes' : 'No'} + + + + Monday Hours: + {schedule?.weeklySchedule?.monday?.isEnabled ? ( + schedule.weeklySchedule.monday.hours.map((range, index) => ( + {`• ${range.start} - ${range.end}`} + )) + ) : ( + Not scheduled + )} + + + + Set Work Hours + + + ); +} + +const styles = StyleSheet.create({ + container: { + padding: 16, + backgroundColor: '#FFFFFF', + }, + loader: { + marginTop: 20, + }, + errorText: { + color: 'red', + textAlign: 'center', + }, + heading: { + fontSize: 18, + fontWeight: 'bold', + marginBottom: 12, + }, + subHeading: { + fontSize: 16, + fontWeight: 'bold', + marginBottom: 4, + }, + text: { + fontSize: 16, + marginBottom: 4, + }, + boldText: { + fontWeight: 'bold', + }, + dayContainer: { + marginTop: 12, + }, + button: { + marginTop: 16, + backgroundColor: '#007BFF', + padding: 12, + borderRadius: 8, + alignItems: 'center', + }, + buttonText: { + color: '#FFFFFF', + fontSize: 16, + fontWeight: 'bold', + }, +}); + +export default ScheduleManager; +``` + + Changes to schedules are automatically synchronized with the server and will affect notification delivery times immediately. \ No newline at end of file diff --git a/content/docs/platform/sdks/react/hooks/use-schedule.mdx b/content/docs/platform/sdks/react/hooks/use-schedule.mdx new file mode 100644 index 000000000..ef84f2cf1 --- /dev/null +++ b/content/docs/platform/sdks/react/hooks/use-schedule.mdx @@ -0,0 +1,176 @@ +--- +title: "useSchedule" +description: "Learn how to use the useSchedule hook to manage subscriber notification schedules in your React application" +--- + +import { TypeTable } from 'fumadocs-ui/components/type-table'; +import { Tab, Tabs } from "fumadocs-ui/components/tabs"; + +Schedules define when notifications should be delivered by setting availability hours across days of the week. The `useSchedule` hook provides a way to fetch, display, and update the notification schedule for the current subscriber. + +## Hook parameters + + void", + description: + "Callback function called when the schedule is successfully fetched", + }, + onError: { + type: "(error: NovuError) => void", + description: "Callback function called when an error occurs", + }, + }} +/> + +## Return value + + Promise", + description: "Function to manually trigger a refetch of the schedule", + }, + }} +/> + +## Schedule type + +The `Schedule` type from `@novu/react` includes these properties: + + + +### Schedule object structure. + +Each `DaySchedule` object within `weeklySchedule` has the following structure: + +", + description: "An array of time ranges for when notifications are allowed. Multiple ranges per day are supported (for example, 9-12 AM and 2-5 PM).", + }, + }} +/> + +## Updating the schedule + +The hook also allows updating the subscriber’s schedule via `schedule.update`: + +```tsx +const handleClick = async () => { + await schedule?.update({ + isEnabled: true, + weeklySchedule: { + monday: { + isEnabled: true, + hours: [{ start: '08:00 AM', end: '18:00 PM' }], + }, + } + }) +}; +``` + +## Example usage + +Here's how to use the `useSchedule` hook to display and update a subscriber's notification schedule. The `schedule.update()` method creates a schedule if one doesn't exist or updates the existing one. + +```tsx +import { useSchedule } from "@novu/react"; + +function ScheduleManager() { + const { schedule, isLoading, error, refetch } = useSchedule(); + + if (isLoading) return
Loading schedule...
; + if (error) return
Error: {error.message}
; + + const handleUpdateSchedule = async () => { + await schedule?.update({ + isEnabled: true, + weeklySchedule: { + monday: { + isEnabled: true, + hours: [ + { start: '09:00 AM', end: '12:00 PM' }, + { start: '02:00 PM', end: '05:00 PM' }, + ], + }, + tuesday: { + isEnabled: true, + hours: [{ start: '09:00 AM', end: '05:00 PM' }], + }, + // Other days can be left undefined or explicitly disabled + wednesday: { + isEnabled: false, + hours: [], + }, + }, + }); + }; + + return ( +
+

My Notification Schedule

+

+ Schedule Enabled: {schedule?.isEnabled ? 'Yes' : 'No'} +

+ +
+

Monday Hours:

+ {schedule?.weeklySchedule?.monday?.isEnabled ? ( +
    + {schedule.weeklySchedule.monday.hours.map((range, index) => ( +
  • {`${range.start} - ${range.end}`}
  • + ))} +
+ ) : ( +

Not scheduled

+ )} +
+ + +
+ ); +} +``` \ No newline at end of file diff --git a/public/images/inbox/inbox-schedule.png b/public/images/inbox/inbox-schedule.png new file mode 100644 index 000000000..ac1a82feb Binary files /dev/null and b/public/images/inbox/inbox-schedule.png differ diff --git a/public/images/inbox/schedule.gif b/public/images/inbox/schedule.gif new file mode 100644 index 000000000..7def34ad1 Binary files /dev/null and b/public/images/inbox/schedule.gif differ diff --git a/public/images/inbox/schedule.png b/public/images/inbox/schedule.png new file mode 100644 index 000000000..2b6cac18d Binary files /dev/null and b/public/images/inbox/schedule.png differ