From 29b97ef6d85bbea31dd59b7ad857b0d8ab30bff0 Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Wed, 17 Feb 2021 21:26:22 -0500 Subject: [PATCH] feat(notif): Add Pushbullet notification agent (#950) --- docs/using-overseerr/notifications/README.md | 5 +- overseerr-api.yml | 64 +++++- server/index.ts | 4 +- server/lib/notifications/agents/pushbullet.ts | 146 +++++++++++++ server/lib/settings.ts | 20 +- server/routes/settings/notifications.ts | 35 ++++ .../{discord_white.svg => discord.svg} | 0 src/assets/extlogos/pushbullet.svg | 1 + .../NotificationsPushbullet/index.tsx | 198 ++++++++++++++++++ .../NotificationsPushover/index.tsx | 6 +- .../NotificationsSlack/index.tsx | 2 +- .../Notifications/NotificationsTelegram.tsx | 6 +- .../Settings/SettingsNotifications.tsx | 14 +- src/i18n/locale/en.json | 18 +- .../settings/notifications/pushbullet.tsx | 17 ++ 15 files changed, 515 insertions(+), 21 deletions(-) create mode 100644 server/lib/notifications/agents/pushbullet.ts rename src/assets/extlogos/{discord_white.svg => discord.svg} (100%) create mode 100644 src/assets/extlogos/pushbullet.svg create mode 100644 src/components/Settings/Notifications/NotificationsPushbullet/index.tsx create mode 100644 src/pages/settings/notifications/pushbullet.tsx diff --git a/docs/using-overseerr/notifications/README.md b/docs/using-overseerr/notifications/README.md index a4afbc00e..8cb53c427 100644 --- a/docs/using-overseerr/notifications/README.md +++ b/docs/using-overseerr/notifications/README.md @@ -4,11 +4,12 @@ Overseerr already supports a good number of notification agents, such as **Disco ## Currently Supported Notification Agents -- Email - Discord +- Email +- Pushbullet +- Pushover - Slack - Telegram -- Pushover - [Webhooks](./webhooks.md) ## Setting Up Notifications diff --git a/overseerr-api.yml b/overseerr-api.yml index f9acf65b5..e3b2d1ce6 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -1124,6 +1124,20 @@ components: type: string chatId: type: string + PushbulletSettings: + type: object + properties: + enabled: + type: boolean + example: false + types: + type: number + example: 2 + options: + type: object + properties: + accessToken: + type: string PushoverSettings: type: object properties: @@ -1142,8 +1156,6 @@ components: type: string priority: type: number - sound: - type: string NotificationSettings: type: object properties: @@ -2356,6 +2368,52 @@ paths: responses: '204': description: Test notification attempted + /settings/notifications/pushbullet: + get: + summary: Get Pushbullet notification settings + description: Returns current Pushbullet notification settings in a JSON object. + tags: + - settings + responses: + '200': + description: Returned Pushbullet settings + content: + application/json: + schema: + $ref: '#/components/schemas/PushbulletSettings' + post: + summary: Update Pushbullet notification settings + description: Update Pushbullet notification settings with the provided values. + tags: + - settings + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PushbulletSettings' + responses: + '200': + description: 'Values were sucessfully updated' + content: + application/json: + schema: + $ref: '#/components/schemas/PushbulletSettings' + /settings/notifications/pushbullet/test: + post: + summary: Test Pushover settings + description: Sends a test notification to the Pushover agent. + tags: + - settings + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PushoverSettings' + responses: + '204': + description: Test notification attempted /settings/notifications/pushover: get: summary: Get Pushover notification settings @@ -2370,7 +2428,7 @@ paths: schema: $ref: '#/components/schemas/PushoverSettings' post: - summary: Update pushover notification settings + summary: Update Pushover notification settings description: Update Pushover notification settings with the provided values. tags: - settings diff --git a/server/index.ts b/server/index.ts index 564816826..3cfd0dba3 100644 --- a/server/index.ts +++ b/server/index.ts @@ -24,6 +24,7 @@ import SlackAgent from './lib/notifications/agents/slack'; import PushoverAgent from './lib/notifications/agents/pushover'; import WebhookAgent from './lib/notifications/agents/webhook'; import { getClientIp } from '@supercharge/request-ip'; +import PushbulletAgent from './lib/notifications/agents/pushbullet'; const API_SPEC_PATH = path.join(__dirname, '../overseerr-api.yml'); @@ -51,9 +52,10 @@ app notificationManager.registerAgents([ new DiscordAgent(), new EmailAgent(), + new PushbulletAgent(), + new PushoverAgent(), new SlackAgent(), new TelegramAgent(), - new PushoverAgent(), new WebhookAgent(), ]); diff --git a/server/lib/notifications/agents/pushbullet.ts b/server/lib/notifications/agents/pushbullet.ts new file mode 100644 index 000000000..c7becfab0 --- /dev/null +++ b/server/lib/notifications/agents/pushbullet.ts @@ -0,0 +1,146 @@ +import axios from 'axios'; +import { hasNotificationType, Notification } from '..'; +import logger from '../../../logger'; +import { getSettings, NotificationAgentPushbullet } from '../../settings'; +import { BaseAgent, NotificationAgent, NotificationPayload } from './agent'; + +interface PushbulletPayload { + title: string; + body: string; +} + +class PushbulletAgent + extends BaseAgent + implements NotificationAgent { + protected getSettings(): NotificationAgentPushbullet { + if (this.settings) { + return this.settings; + } + + const settings = getSettings(); + + return settings.notifications.agents.pushbullet; + } + + public shouldSend(type: Notification): boolean { + if ( + this.getSettings().enabled && + this.getSettings().options.accessToken && + hasNotificationType(type, this.getSettings().types) + ) { + return true; + } + + return false; + } + + private constructMessageDetails( + type: Notification, + payload: NotificationPayload + ): { + title: string; + body: string; + } { + let messageTitle = ''; + let message = ''; + + const title = payload.subject; + const plot = payload.message; + const username = payload.notifyUser.displayName; + + switch (type) { + case Notification.MEDIA_PENDING: + messageTitle = 'New Request'; + message += `${title}`; + if (plot) { + message += `\n\n${plot}`; + } + message += `\n\nRequested By: ${username}`; + message += `\nStatus: Pending Approval`; + break; + case Notification.MEDIA_APPROVED: + messageTitle = 'Request Approved'; + message += `${title}`; + if (plot) { + message += `\n\n${plot}`; + } + message += `\n\nRequested By: ${username}`; + message += `\nStatus: Processing`; + break; + case Notification.MEDIA_AVAILABLE: + messageTitle = 'Now Available'; + message += `${title}`; + if (plot) { + message += `\n\n${plot}`; + } + message += `\n\nRequested By: ${username}`; + message += `\nStatus: Available`; + break; + case Notification.MEDIA_DECLINED: + messageTitle = 'Request Declined'; + message += `${title}`; + if (plot) { + message += `\n\n${plot}`; + } + message += `\n\nRequested By: ${username}`; + message += `\nStatus: Declined`; + break; + case Notification.MEDIA_FAILED: + messageTitle = 'Failed Request'; + message += `${title}`; + if (plot) { + message += `\n\n${plot}`; + } + message += `\n\nRequested By: ${username}`; + message += `\nStatus: Failed`; + break; + case Notification.TEST_NOTIFICATION: + messageTitle = 'Test Notification'; + message += `${plot}`; + break; + } + + return { + title: messageTitle, + body: message, + }; + } + + public async send( + type: Notification, + payload: NotificationPayload + ): Promise { + logger.debug('Sending Pushbullet notification', { label: 'Notifications' }); + try { + const endpoint = 'https://api.pushbullet.com/v2/pushes'; + + const { accessToken } = this.getSettings().options; + + const { title, body } = this.constructMessageDetails(type, payload); + + await axios.post( + endpoint, + { + type: 'note', + title: title, + body: body, + } as PushbulletPayload, + { + headers: { + 'Access-Token': accessToken, + }, + } + ); + + return true; + } catch (e) { + logger.error('Error sending Pushbullet notification', { + label: 'Notifications', + message: e.message, + }); + return false; + } + } +} + +export default PushbulletAgent; diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 3d168497f..2d0e9c87d 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -111,12 +111,17 @@ export interface NotificationAgentTelegram extends NotificationAgentConfig { }; } +export interface NotificationAgentPushbullet extends NotificationAgentConfig { + options: { + accessToken: string; + }; +} + export interface NotificationAgentPushover extends NotificationAgentConfig { options: { accessToken: string; userToken: string; priority: number; - sound: string; }; } @@ -129,11 +134,12 @@ export interface NotificationAgentWebhook extends NotificationAgentConfig { } interface NotificationAgents { - email: NotificationAgentEmail; discord: NotificationAgentDiscord; + email: NotificationAgentEmail; + pushbullet: NotificationAgentPushbullet; + pushover: NotificationAgentPushover; slack: NotificationAgentSlack; telegram: NotificationAgentTelegram; - pushover: NotificationAgentPushover; webhook: NotificationAgentWebhook; } @@ -224,6 +230,13 @@ class Settings { sendSilently: false, }, }, + pushbullet: { + enabled: false, + types: 0, + options: { + accessToken: '', + }, + }, pushover: { enabled: false, types: 0, @@ -231,7 +244,6 @@ class Settings { accessToken: '', userToken: '', priority: 0, - sound: '', }, }, webhook: { diff --git a/server/routes/settings/notifications.ts b/server/routes/settings/notifications.ts index 7f52e7dbd..58be3a4f2 100644 --- a/server/routes/settings/notifications.ts +++ b/server/routes/settings/notifications.ts @@ -7,6 +7,7 @@ import SlackAgent from '../../lib/notifications/agents/slack'; import TelegramAgent from '../../lib/notifications/agents/telegram'; import PushoverAgent from '../../lib/notifications/agents/pushover'; import WebhookAgent from '../../lib/notifications/agents/webhook'; +import PushbulletAgent from '../../lib/notifications/agents/pushbullet'; const notificationRoutes = Router(); @@ -135,6 +136,40 @@ notificationRoutes.post('/telegram/test', (req, res, next) => { return res.status(204).send(); }); +notificationRoutes.get('/pushbullet', (_req, res) => { + const settings = getSettings(); + + res.status(200).json(settings.notifications.agents.pushbullet); +}); + +notificationRoutes.post('/pushbullet', (req, res) => { + const settings = getSettings(); + + settings.notifications.agents.pushbullet = req.body; + settings.save(); + + res.status(200).json(settings.notifications.agents.pushbullet); +}); + +notificationRoutes.post('/pushbullet/test', (req, res, next) => { + if (!req.user) { + return next({ + status: 500, + message: 'User information missing from request', + }); + } + + const pushbulletAgent = new PushbulletAgent(req.body); + pushbulletAgent.send(Notification.TEST_NOTIFICATION, { + notifyUser: req.user, + subject: 'Test Notification', + message: + 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', + }); + + return res.status(204).send(); +}); + notificationRoutes.get('/pushover', (_req, res) => { const settings = getSettings(); diff --git a/src/assets/extlogos/discord_white.svg b/src/assets/extlogos/discord.svg similarity index 100% rename from src/assets/extlogos/discord_white.svg rename to src/assets/extlogos/discord.svg diff --git a/src/assets/extlogos/pushbullet.svg b/src/assets/extlogos/pushbullet.svg new file mode 100644 index 000000000..c241c5d43 --- /dev/null +++ b/src/assets/extlogos/pushbullet.svg @@ -0,0 +1 @@ +image/svg+xml diff --git a/src/components/Settings/Notifications/NotificationsPushbullet/index.tsx b/src/components/Settings/Notifications/NotificationsPushbullet/index.tsx new file mode 100644 index 000000000..9a44e0eb0 --- /dev/null +++ b/src/components/Settings/Notifications/NotificationsPushbullet/index.tsx @@ -0,0 +1,198 @@ +import React from 'react'; +import { Field, Form, Formik } from 'formik'; +import useSWR from 'swr'; +import LoadingSpinner from '../../../Common/LoadingSpinner'; +import Button from '../../../Common/Button'; +import { defineMessages, useIntl } from 'react-intl'; +import axios from 'axios'; +import * as Yup from 'yup'; +import { useToasts } from 'react-toast-notifications'; +import Alert from '../../../Common/Alert'; +import NotificationTypeSelector from '../../../NotificationTypeSelector'; + +const messages = defineMessages({ + save: 'Save Changes', + saving: 'Saving…', + agentEnabled: 'Enable Agent', + accessToken: 'Access Token', + validationAccessTokenRequired: 'You must provide an access token.', + pushbulletSettingsSaved: 'Pushbullet notification settings saved!', + pushbulletSettingsFailed: 'Pushbullet notification settings failed to save.', + testSent: 'Test notification sent!', + test: 'Test', + settingUpPushbullet: 'Setting Up Pushbullet Notifications', + settingUpPushbulletDescription: + 'To configure Pushbullet notifications, you need to create an access token and enter it below.', + notificationTypes: 'Notification Types', +}); + +const NotificationsPushbullet: React.FC = () => { + const intl = useIntl(); + const { addToast } = useToasts(); + const { data, error, revalidate } = useSWR( + '/api/v1/settings/notifications/pushbullet' + ); + + const NotificationsPushbulletSchema = Yup.object().shape({ + accessToken: Yup.string().required( + intl.formatMessage(messages.validationAccessTokenRequired) + ), + }); + + if (!data && !error) { + return ; + } + + return ( + { + try { + await axios.post('/api/v1/settings/notifications/pushbullet', { + enabled: values.enabled, + types: values.types, + options: { + accessToken: values.accessToken, + }, + }); + addToast(intl.formatMessage(messages.pushbulletSettingsSaved), { + appearance: 'success', + autoDismiss: true, + }); + } catch (e) { + addToast(intl.formatMessage(messages.pushbulletSettingsFailed), { + appearance: 'error', + autoDismiss: true, + }); + } finally { + revalidate(); + } + }} + > + {({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => { + const testSettings = async () => { + await axios.post('/api/v1/settings/notifications/pushbullet/test', { + enabled: true, + types: values.types, + options: { + accessToken: values.accessToken, + }, + }); + + addToast(intl.formatMessage(messages.testSent), { + appearance: 'info', + autoDismiss: true, + }); + }; + + return ( + <> + + {intl.formatMessage(messages.settingUpPushbulletDescription, { + CreateAccessTokenLink: function CreateAccessTokenLink(msg) { + return ( + + {msg} + + ); + }, + })} + +
+
+ +
+ +
+
+
+ +
+
+ +
+ {errors.accessToken && touched.accessToken && ( +
{errors.accessToken}
+ )} +
+
+
+
+ + {intl.formatMessage(messages.notificationTypes)} + +
+
+ + setFieldValue('types', newTypes) + } + /> +
+
+
+
+
+
+ + + + + + +
+
+
+ + ); + }} +
+ ); +}; + +export default NotificationsPushbullet; diff --git a/src/components/Settings/Notifications/NotificationsPushover/index.tsx b/src/components/Settings/Notifications/NotificationsPushover/index.tsx index 8faaa0c24..b89f21579 100644 --- a/src/components/Settings/Notifications/NotificationsPushover/index.tsx +++ b/src/components/Settings/Notifications/NotificationsPushover/index.tsx @@ -24,9 +24,9 @@ const messages = defineMessages({ test: 'Test', settinguppushover: 'Setting Up Pushover Notifications', settinguppushoverDescription: - 'To setup Pushover you need to register an application and get the access token.\ - When setting up the application you can use one of the icons in the public folder on github.\ - You also need the pushover user token which can be found on the start page when you log in.', + 'To configure Pushover notifications, you need to register an application and get the access token.\ + When setting up the application, you can use one of the icons in the public folder on GitHub.\ + You also need the Pushover user token, which can be found on the start page when you log in.', notificationtypes: 'Notification Types', }); diff --git a/src/components/Settings/Notifications/NotificationsSlack/index.tsx b/src/components/Settings/Notifications/NotificationsSlack/index.tsx index e7cfff3b8..1091be71d 100644 --- a/src/components/Settings/Notifications/NotificationsSlack/index.tsx +++ b/src/components/Settings/Notifications/NotificationsSlack/index.tsx @@ -22,7 +22,7 @@ const messages = defineMessages({ test: 'Test', settingupslack: 'Setting Up Slack Notifications', settingupslackDescription: - 'To use Slack notifications, you will need to create an Incoming Webhook integration and use the provided webhook URL below.', + 'To configure Slack notifications, you will need to create an Incoming Webhook integration and enter the webhook URL below.', notificationtypes: 'Notification Types', validationWebhookUrl: 'You must provide a valid URL', }); diff --git a/src/components/Settings/Notifications/NotificationsTelegram.tsx b/src/components/Settings/Notifications/NotificationsTelegram.tsx index b05030597..737db9847 100644 --- a/src/components/Settings/Notifications/NotificationsTelegram.tsx +++ b/src/components/Settings/Notifications/NotificationsTelegram.tsx @@ -24,9 +24,9 @@ const messages = defineMessages({ test: 'Test', settinguptelegram: 'Setting Up Telegram Notifications', settinguptelegramDescription: - 'To setup Telegram you need to create a bot and get the bot API key.\ - Additionally, you need the chat ID for the chat you want the bot to send notifications to.\ - You can do this by adding @get_id_bot to the chat or group chat.', + 'To configure Telegram notifications, you need to create a bot and get the bot API key.\ + Additionally, you need the chat ID for the chat where you would like the bot to send notifications.\ + You can get this by adding @get_id_bot to the chat or group chat.', notificationtypes: 'Notification Types', sendSilently: 'Send Silently', sendSilentlyTip: 'Send notifications with no sound', diff --git a/src/components/Settings/SettingsNotifications.tsx b/src/components/Settings/SettingsNotifications.tsx index 813a166c3..a28de2f6d 100644 --- a/src/components/Settings/SettingsNotifications.tsx +++ b/src/components/Settings/SettingsNotifications.tsx @@ -2,9 +2,10 @@ import Link from 'next/link'; import { useRouter } from 'next/router'; import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import DiscordLogo from '../../assets/extlogos/discord_white.svg'; +import DiscordLogo from '../../assets/extlogos/discord.svg'; import SlackLogo from '../../assets/extlogos/slack.svg'; import TelegramLogo from '../../assets/extlogos/telegram.svg'; +import PushbulletLogo from '../../assets/extlogos/pushbullet.svg'; import PushoverLogo from '../../assets/extlogos/pushover.svg'; import Bolt from '../../assets/bolt.svg'; import { Field, Form, Formik } from 'formik'; @@ -95,6 +96,17 @@ const settingsRoutes: SettingsRoute[] = [ route: '/settings/notifications/telegram', regex: /^\/settings\/notifications\/telegram/, }, + { + text: 'Pushbullet', + content: ( + + + Pushbullet + + ), + route: '/settings/notifications/pushbullet', + regex: /^\/settings\/notifications\/pushbullet/, + }, { text: 'Pushover', content: ( diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 73b99c8d1..bafd08576 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -236,6 +236,18 @@ "components.ResetPassword.validationpasswordrequired": "You must provide a password", "components.Search.search": "Search", "components.Search.searchresults": "Search Results", + "components.Settings.Notifications.NotificationsPushbullet.accessToken": "Access Token", + "components.Settings.Notifications.NotificationsPushbullet.agentEnabled": "Enable Agent", + "components.Settings.Notifications.NotificationsPushbullet.notificationTypes": "Notification Types", + "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Pushbullet notification settings failed to save.", + "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Pushbullet notification settings saved!", + "components.Settings.Notifications.NotificationsPushbullet.save": "Save Changes", + "components.Settings.Notifications.NotificationsPushbullet.saving": "Saving…", + "components.Settings.Notifications.NotificationsPushbullet.settingUpPushbullet": "Setting Up Pushbullet Notifications", + "components.Settings.Notifications.NotificationsPushbullet.settingUpPushbulletDescription": "To configure Pushbullet notifications, you need to create an access token and enter it below.", + "components.Settings.Notifications.NotificationsPushbullet.test": "Test", + "components.Settings.Notifications.NotificationsPushbullet.testSent": "Test notification sent!", + "components.Settings.Notifications.NotificationsPushbullet.validationAccessTokenRequired": "You must provide an access token.", "components.Settings.Notifications.NotificationsPushover.accessToken": "Access Token", "components.Settings.Notifications.NotificationsPushover.agentenabled": "Enable Agent", "components.Settings.Notifications.NotificationsPushover.notificationtypes": "Notification Types", @@ -244,7 +256,7 @@ "components.Settings.Notifications.NotificationsPushover.save": "Save Changes", "components.Settings.Notifications.NotificationsPushover.saving": "Saving…", "components.Settings.Notifications.NotificationsPushover.settinguppushover": "Setting Up Pushover Notifications", - "components.Settings.Notifications.NotificationsPushover.settinguppushoverDescription": "To set up Pushover, you need to register an application and get the access token. When setting up the application, you can use one of the icons in the public folder on GitHub. You also need the Pushover user token, which can be found on the start page when you log in.", + "components.Settings.Notifications.NotificationsPushover.settinguppushoverDescription": "To configure Pushover notifications, you need to register an application and get the access token. When setting up the application, you can use one of the icons in the public folder on GitHub. You also need the Pushover user token, which can be found on the start page when you log in.", "components.Settings.Notifications.NotificationsPushover.test": "Test", "components.Settings.Notifications.NotificationsPushover.testsent": "Test notification sent!", "components.Settings.Notifications.NotificationsPushover.userToken": "User Token", @@ -255,7 +267,7 @@ "components.Settings.Notifications.NotificationsSlack.save": "Save Changes", "components.Settings.Notifications.NotificationsSlack.saving": "Saving…", "components.Settings.Notifications.NotificationsSlack.settingupslack": "Setting Up Slack Notifications", - "components.Settings.Notifications.NotificationsSlack.settingupslackDescription": "To use Slack notifications, you will need to create an Incoming Webhook integration and use the provided webhook URL below.", + "components.Settings.Notifications.NotificationsSlack.settingupslackDescription": "To configure Slack notifications, you will need to create an Incoming Webhook integration and enter the webhook URL below.", "components.Settings.Notifications.NotificationsSlack.slacksettingsfailed": "Slack notification settings failed to save.", "components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Slack notification settings saved!", "components.Settings.Notifications.NotificationsSlack.test": "Test", @@ -299,7 +311,7 @@ "components.Settings.Notifications.sendSilentlyTip": "Send notifications with no sound", "components.Settings.Notifications.senderName": "Sender Name", "components.Settings.Notifications.settinguptelegram": "Setting Up Telegram Notifications", - "components.Settings.Notifications.settinguptelegramDescription": "To set up Telegram, you need to create a bot and get the bot API key. Additionally, you need the chat ID for the chat you want the bot to send notifications to. You can do this by adding @get_id_bot to the chat or group chat.", + "components.Settings.Notifications.settinguptelegramDescription": "To configure Telegram notifications, you need to create a bot and get the bot API key. Additionally, you need the chat ID for the chat where you would like the bot to send notifications. You can get this by adding @get_id_bot to the chat or group chat.", "components.Settings.Notifications.smtpHost": "SMTP Host", "components.Settings.Notifications.smtpPort": "SMTP Port", "components.Settings.Notifications.ssldisabletip": "SSL should be disabled on standard TLS connections (port 587)", diff --git a/src/pages/settings/notifications/pushbullet.tsx b/src/pages/settings/notifications/pushbullet.tsx new file mode 100644 index 000000000..f88205ea6 --- /dev/null +++ b/src/pages/settings/notifications/pushbullet.tsx @@ -0,0 +1,17 @@ +import { NextPage } from 'next'; +import React from 'react'; +import NotificationsPushbullet from '../../../components/Settings/Notifications/NotificationsPushbullet'; +import SettingsLayout from '../../../components/Settings/SettingsLayout'; +import SettingsNotifications from '../../../components/Settings/SettingsNotifications'; + +const NotificationsPage: NextPage = () => { + return ( + + + + + + ); +}; + +export default NotificationsPage;