Add email notification toggle (#2223)
authorAjay Bura <32841439+ajbura@users.noreply.github.com>
Fri, 21 Feb 2025 08:15:47 +0000 (19:15 +1100)
committerGitHub <noreply@github.com>
Fri, 21 Feb 2025 08:15:47 +0000 (19:15 +1100)
* refactor system notification to dedicated file

* add hook for email notification status

* add toogle for email notifications in settings

src/app/features/settings/notifications/Notifications.tsx
src/app/features/settings/notifications/SystemNotification.tsx [new file with mode: 0644]
src/app/hooks/useEmailNotifications.ts [new file with mode: 0644]

index 88e16d296e15ed157a4ced575dd8212c8513d274..aa339a03116cd572df34c21b8bffa7c89edc5226 100644 (file)
@@ -1,82 +1,12 @@
 import React from 'react';
-import { Box, Text, IconButton, Icon, Icons, Scroll, Switch, Button, color } from 'folds';
+import { Box, Text, IconButton, Icon, Icons, Scroll } from 'folds';
 import { Page, PageContent, PageHeader } from '../../../components/page';
-import { SequenceCard } from '../../../components/sequence-card';
-import { SequenceCardStyle } from '../styles.css';
-import { SettingTile } from '../../../components/setting-tile';
-import { useSetting } from '../../../state/hooks/settings';
-import { settingsAtom } from '../../../state/settings';
-import { getNotificationState, usePermissionState } from '../../../hooks/usePermission';
+import { SystemNotification } from './SystemNotification';
 import { AllMessagesNotifications } from './AllMessages';
 import { SpecialMessagesNotifications } from './SpecialMessages';
 import { KeywordMessagesNotifications } from './KeywordMessages';
 import { IgnoredUserList } from './IgnoredUserList';
 
-function SystemNotification() {
-  const notifPermission = usePermissionState('notifications', getNotificationState());
-  const [showNotifications, setShowNotifications] = useSetting(settingsAtom, 'showNotifications');
-  const [isNotificationSounds, setIsNotificationSounds] = useSetting(
-    settingsAtom,
-    'isNotificationSounds'
-  );
-
-  const requestNotificationPermission = () => {
-    window.Notification.requestPermission();
-  };
-
-  return (
-    <Box direction="Column" gap="100">
-      <Text size="L400">System</Text>
-      <SequenceCard
-        className={SequenceCardStyle}
-        variant="SurfaceVariant"
-        direction="Column"
-        gap="400"
-      >
-        <SettingTile
-          title="Desktop Notifications"
-          description={
-            notifPermission === 'denied' ? (
-              <Text as="span" style={{ color: color.Critical.Main }} size="T200">
-                {'Notification' in window
-                  ? 'Notification permission is blocked. Please allow notification permission from browser address bar.'
-                  : 'Notifications are not supported by the system.'}
-              </Text>
-            ) : (
-              <span>Show desktop notifications when message arrive.</span>
-            )
-          }
-          after={
-            notifPermission === 'prompt' ? (
-              <Button size="300" radii="300" onClick={requestNotificationPermission}>
-                <Text size="B300">Enable</Text>
-              </Button>
-            ) : (
-              <Switch
-                disabled={notifPermission !== 'granted'}
-                value={showNotifications}
-                onChange={setShowNotifications}
-              />
-            )
-          }
-        />
-      </SequenceCard>
-      <SequenceCard
-        className={SequenceCardStyle}
-        variant="SurfaceVariant"
-        direction="Column"
-        gap="400"
-      >
-        <SettingTile
-          title="Notification Sound"
-          description="Play sound when new message arrive."
-          after={<Switch value={isNotificationSounds} onChange={setIsNotificationSounds} />}
-        />
-      </SequenceCard>
-    </Box>
-  );
-}
-
 type NotificationsProps = {
   requestClose: () => void;
 };
diff --git a/src/app/features/settings/notifications/SystemNotification.tsx b/src/app/features/settings/notifications/SystemNotification.tsx
new file mode 100644 (file)
index 0000000..e0df06d
--- /dev/null
@@ -0,0 +1,158 @@
+import React, { useCallback } from 'react';
+import { Box, Text, Switch, Button, color, Spinner } from 'folds';
+import { IPusherRequest } from 'matrix-js-sdk';
+import { SequenceCard } from '../../../components/sequence-card';
+import { SequenceCardStyle } from '../styles.css';
+import { SettingTile } from '../../../components/setting-tile';
+import { useSetting } from '../../../state/hooks/settings';
+import { settingsAtom } from '../../../state/settings';
+import { getNotificationState, usePermissionState } from '../../../hooks/usePermission';
+import { useEmailNotifications } from '../../../hooks/useEmailNotifications';
+import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
+import { useMatrixClient } from '../../../hooks/useMatrixClient';
+
+function EmailNotification() {
+  const mx = useMatrixClient();
+  const [result, refreshResult] = useEmailNotifications();
+
+  const [setState, setEnable] = useAsyncCallback(
+    useCallback(
+      async (email: string, enable: boolean) => {
+        if (enable) {
+          await mx.setPusher({
+            kind: 'email',
+            app_id: 'm.email',
+            pushkey: email,
+            app_display_name: 'Email Notifications',
+            device_display_name: email,
+            lang: 'en',
+            data: {
+              brand: 'Cinny',
+            },
+            append: true,
+          });
+          return;
+        }
+        await mx.setPusher({
+          pushkey: email,
+          app_id: 'm.email',
+          kind: null,
+        } as unknown as IPusherRequest);
+      },
+      [mx]
+    )
+  );
+
+  const handleChange = (value: boolean) => {
+    if (result && result.email) {
+      setEnable(result.email, value).then(() => {
+        refreshResult();
+      });
+    }
+  };
+
+  return (
+    <SettingTile
+      title="Email Notification"
+      description={
+        <>
+          {result && !result.email && (
+            <Text as="span" style={{ color: color.Critical.Main }} size="T200">
+              Your account does not have any email attached.
+            </Text>
+          )}
+          {result && result.email && <>Send notification to your email. {`("${result.email}")`}</>}
+          {result === null && (
+            <Text as="span" style={{ color: color.Critical.Main }} size="T200">
+              Unexpected Error!
+            </Text>
+          )}
+          {result === undefined && 'Send notification to your email.'}
+        </>
+      }
+      after={
+        <>
+          {setState.status !== AsyncStatus.Loading &&
+            typeof result === 'object' &&
+            result?.email && <Switch value={result.enabled} onChange={handleChange} />}
+          {(setState.status === AsyncStatus.Loading || result === undefined) && (
+            <Spinner variant="Secondary" />
+          )}
+        </>
+      }
+    />
+  );
+}
+
+export function SystemNotification() {
+  const notifPermission = usePermissionState('notifications', getNotificationState());
+  const [showNotifications, setShowNotifications] = useSetting(settingsAtom, 'showNotifications');
+  const [isNotificationSounds, setIsNotificationSounds] = useSetting(
+    settingsAtom,
+    'isNotificationSounds'
+  );
+
+  const requestNotificationPermission = () => {
+    window.Notification.requestPermission();
+  };
+
+  return (
+    <Box direction="Column" gap="100">
+      <Text size="L400">System</Text>
+      <SequenceCard
+        className={SequenceCardStyle}
+        variant="SurfaceVariant"
+        direction="Column"
+        gap="400"
+      >
+        <SettingTile
+          title="Desktop Notifications"
+          description={
+            notifPermission === 'denied' ? (
+              <Text as="span" style={{ color: color.Critical.Main }} size="T200">
+                {'Notification' in window
+                  ? 'Notification permission is blocked. Please allow notification permission from browser address bar.'
+                  : 'Notifications are not supported by the system.'}
+              </Text>
+            ) : (
+              <span>Show desktop notifications when message arrive.</span>
+            )
+          }
+          after={
+            notifPermission === 'prompt' ? (
+              <Button size="300" radii="300" onClick={requestNotificationPermission}>
+                <Text size="B300">Enable</Text>
+              </Button>
+            ) : (
+              <Switch
+                disabled={notifPermission !== 'granted'}
+                value={showNotifications}
+                onChange={setShowNotifications}
+              />
+            )
+          }
+        />
+      </SequenceCard>
+      <SequenceCard
+        className={SequenceCardStyle}
+        variant="SurfaceVariant"
+        direction="Column"
+        gap="400"
+      >
+        <SettingTile
+          title="Notification Sound"
+          description="Play sound when new message arrive."
+          after={<Switch value={isNotificationSounds} onChange={setIsNotificationSounds} />}
+        />
+      </SequenceCard>
+      <SequenceCard
+        className={SequenceCardStyle}
+        variant="SurfaceVariant"
+        direction="Column"
+        gap="400"
+      >
+        <EmailNotification />
+      </SequenceCard>
+    </Box>
+  );
+}
diff --git a/src/app/hooks/useEmailNotifications.ts b/src/app/hooks/useEmailNotifications.ts
new file mode 100644 (file)
index 0000000..5863939
--- /dev/null
@@ -0,0 +1,55 @@
+import { useCallback } from 'react';
+import { AsyncStatus, useAsyncCallbackValue } from './useAsyncCallback';
+import { useMatrixClient } from './useMatrixClient';
+
+type RefreshHandler = () => void;
+
+type EmailNotificationResult = {
+  enabled: boolean;
+  email?: string;
+};
+
+export const useEmailNotifications = (): [
+  EmailNotificationResult | undefined | null,
+  RefreshHandler
+] => {
+  const mx = useMatrixClient();
+
+  const [emailState, refresh] = useAsyncCallbackValue<EmailNotificationResult, Error>(
+    useCallback(async () => {
+      const tpIDs = (await mx.getThreePids())?.threepids;
+      const emailAddresses = tpIDs.filter((id) => id.medium === 'email').map((id) => id.address);
+      if (emailAddresses.length === 0)
+        return {
+          enabled: false,
+        };
+
+      const pushers = (await mx.getPushers())?.pushers;
+      const emailPusher = pushers.find(
+        (pusher) => pusher.app_id === 'm.email' && emailAddresses.includes(pusher.pushkey)
+      );
+
+      if (emailPusher?.pushkey) {
+        return {
+          enabled: true,
+          email: emailPusher.pushkey,
+        };
+      }
+
+      return {
+        enabled: false,
+        email: emailAddresses[0],
+      };
+    }, [mx])
+  );
+
+  if (emailState.status === AsyncStatus.Success) {
+    return [emailState.data, refresh];
+  }
+
+  if (emailState.status === AsyncStatus.Error) {
+    return [null, refresh];
+  }
+
+  return [undefined, refresh];
+};