mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-02-26 15:13:05 +01: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 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 { Page, PageContent, PageHeader } from '../../../components/page';
|
||||||
import { SequenceCard } from '../../../components/sequence-card';
|
import { SystemNotification } from './SystemNotification';
|
||||||
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 { AllMessagesNotifications } from './AllMessages';
|
import { AllMessagesNotifications } from './AllMessages';
|
||||||
import { SpecialMessagesNotifications } from './SpecialMessages';
|
import { SpecialMessagesNotifications } from './SpecialMessages';
|
||||||
import { KeywordMessagesNotifications } from './KeywordMessages';
|
import { KeywordMessagesNotifications } from './KeywordMessages';
|
||||||
import { IgnoredUserList } from './IgnoredUserList';
|
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 = {
|
type NotificationsProps = {
|
||||||
requestClose: () => void;
|
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…
Reference in a new issue