mirror of
				https://github.com/cinnyapp/cinny.git
				synced 2025-11-04 06:20:28 +03:00 
			
		
		
		
	Add email notification toggle (#2223)
* refactor system notification to dedicated file * add hook for email notification status * add toogle for email notifications in settings
This commit is contained in:
		
							parent
							
								
									1b200eb676
								
							
						
					
					
						commit
						59e8d66255
					
				
					 3 changed files with 215 additions and 72 deletions
				
			
		| 
						 | 
				
			
			@ -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;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										158
									
								
								src/app/features/settings/notifications/SystemNotification.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								src/app/features/settings/notifications/SystemNotification.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								src/app/hooks/useEmailNotifications.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/app/hooks/useEmailNotifications.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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];
 | 
			
		||||
};
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue