From 59e8d6625527678b053831c1b650e295771d9785 Mon Sep 17 00:00:00 2001
From: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Date: Fri, 21 Feb 2025 19:15:47 +1100
Subject: [PATCH] Add email notification toggle (#2223)
* refactor system notification to dedicated file
* add hook for email notification status
* add toogle for email notifications in settings
---
 .../settings/notifications/Notifications.tsx  |  74 +-------
 .../notifications/SystemNotification.tsx      | 158 ++++++++++++++++++
 src/app/hooks/useEmailNotifications.ts        |  55 ++++++
 3 files changed, 215 insertions(+), 72 deletions(-)
 create mode 100644 src/app/features/settings/notifications/SystemNotification.tsx
 create mode 100644 src/app/hooks/useEmailNotifications.ts
diff --git a/src/app/features/settings/notifications/Notifications.tsx b/src/app/features/settings/notifications/Notifications.tsx
index 88e16d29..aa339a03 100644
--- a/src/app/features/settings/notifications/Notifications.tsx
+++ b/src/app/features/settings/notifications/Notifications.tsx
@@ -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 (
-    
-      System
-      
-        
-                {'Notification' in window
-                  ? 'Notification permission is blocked. Please allow notification permission from browser address bar.'
-                  : 'Notifications are not supported by the system.'}
-              
-            ) : (
-              Show desktop notifications when message arrive.
-            )
-          }
-          after={
-            notifPermission === 'prompt' ? (
-              
-            ) : (
-              
-            )
-          }
-        />
-      
-      
-        }
-        />
-      
-    
-  );
-}
-
 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
index 00000000..e0df06df
--- /dev/null
+++ b/src/app/features/settings/notifications/SystemNotification.tsx
@@ -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 (
+    
+          {result && !result.email && (
+            
+              Your account does not have any email attached.
+            
+          )}
+          {result && result.email && <>Send notification to your email. {`("${result.email}")`}>}
+          {result === null && (
+            
+              Unexpected Error!
+            
+          )}
+          {result === undefined && 'Send notification to your email.'}
+        >
+      }
+      after={
+        <>
+          {setState.status !== AsyncStatus.Loading &&
+            typeof result === 'object' &&
+            result?.email && }
+          {(setState.status === AsyncStatus.Loading || result === undefined) && (
+            
+          )}
+        >
+      }
+    />
+  );
+}
+
+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 (
+    
+      System
+      
+        
+                {'Notification' in window
+                  ? 'Notification permission is blocked. Please allow notification permission from browser address bar.'
+                  : 'Notifications are not supported by the system.'}
+              
+            ) : (
+              Show desktop notifications when message arrive.
+            )
+          }
+          after={
+            notifPermission === 'prompt' ? (
+              
+            ) : (
+              
+            )
+          }
+        />
+      
+      
+        }
+        />
+      
+      
+        
+      
+    
+  );
+}
diff --git a/src/app/hooks/useEmailNotifications.ts b/src/app/hooks/useEmailNotifications.ts
new file mode 100644
index 00000000..58639394
--- /dev/null
+++ b/src/app/hooks/useEmailNotifications.ts
@@ -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(
+    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];
+};