Files
cinny/src/app/features/settings/notifications/Notifications.tsx
T
Lotus CIandClaude Opus 5.5 5d15013a27
CI / Build & Quality Checks (push) Skipped
CI / Secret scan (gitleaks) (push) Skipped
CI / Playwright smoke (e2e) (push) Skipped
CI / Build & Quality Checks (pull_request) Successful in 3m18s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 12s
CI / Playwright smoke (e2e) (pull_request) Successful in 24m56s
feat(settings): quick wins from the settings audit (#453)
- #456 Copy pass: sentence case for every setting title and section,
  plainer names ("Hide joins and leaves", "Link previews", "Emoji
  style: Twemoji", "Rejoin calls after a restart", "Also use the
  browser's noise suppression", "Push-to-talk key"...) and grammar fixes
  ("when messages arrive", "Play a sound when a new message arrives",
  blocked users "Enter their Matrix ID"). Labels only: no setting is
  renamed in storage.
- #455 One theme control: Theme: System / Light / Dark. System shows the
  light and dark variant pickers, Light or Dark one picker of that kind.
  Replaces the System switch plus a Theme dropdown that was greyed out
  whenever the switch was on. Uses the existing settings (no migration);
  picking a variant also remembers it for System.
- #457 Remove the "Block Messages" stub from Notifications; the GIF
  picker row only appears in builds with a GIF key.
- #458 (part 1) "Export/Import Messages Data" -> "Export/Import
  encryption keys", with a sentence on when you need it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
2026-10-25 01:37:37 -04:00

159 lines
5.1 KiB
TypeScript

import React from 'react';
import { useAtomValue, useSetAtom } from 'jotai';
import { Box, Button, Text, IconButton, Icon, Icons, IconSrc, Scroll, config, toRem } from 'folds';
import { Page, PageContent, PageHeader } from '../../../components/page';
import { SystemNotification } from './SystemNotification';
import { AllMessagesNotifications } from './AllMessages';
import { SpecialMessagesNotifications } from './SpecialMessages';
import { KeywordMessagesNotifications } from './KeywordMessages';
import { PushRuleEditor } from './PushRuleEditor';
import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../styles.css';
import { settingsAtom, Settings } from '../../../state/settings';
import { notificationSnoozeUntilAtom } from '../../../state/notificationSnooze';
const PRESETS: Array<{
label: string;
icon: IconSrc;
description: string;
patch: Partial<Settings>;
// Whether applying this preset should also clear an active "Pause
// Notifications" snooze. Work/Gaming both claim notifications end up on,
// so a leftover snooze would silently contradict them (#48). Sleep's
// description ("All notifications off") is still true with a snooze left
// active, so it does not need to touch it.
clearSnooze: boolean;
}> = [
{
label: 'Gaming',
icon: Icons.Ball,
description: 'Notifications on, sounds off',
patch: {
showNotifications: true,
isNotificationSounds: false,
messageSoundId: 'none',
inviteSoundId: 'none',
quietHoursEnabled: false,
},
clearSnooze: true,
},
{
label: 'Work',
icon: Icons.Monitor,
description: 'All notifications and sounds on',
patch: {
showNotifications: true,
isNotificationSounds: true,
messageSoundId: 'notification',
inviteSoundId: 'invite',
quietHoursEnabled: false,
},
clearSnooze: true,
},
{
label: 'Sleep',
icon: Icons.BellMute,
description: 'All notifications off',
patch: {
showNotifications: false,
isNotificationSounds: false,
quietHoursEnabled: false,
},
clearSnooze: false,
},
];
function NotificationPresets() {
const settings = useAtomValue(settingsAtom);
const setSettings = useSetAtom(settingsAtom);
const setSnoozeUntil = useSetAtom(notificationSnoozeUntilAtom);
const applyPreset = (patch: Partial<Settings>, clearSnooze: boolean) => {
setSettings({ ...settings, ...patch });
// Work/Gaming promise notifications are on; an active snooze from an
// earlier "Pause Notifications" would otherwise keep them silently
// suppressed despite the preset applying successfully (#48).
if (clearSnooze) {
setSnoozeUntil(0);
}
};
return (
<Box direction="Column" gap="100">
<Text size="L400">Quick presets</Text>
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<Box gap="300" style={{ padding: config.space.S300, flexWrap: 'wrap' }}>
{PRESETS.map((preset) => (
<Button
key={preset.label}
type="button"
onClick={() => applyPreset(preset.patch, preset.clearSnooze)}
title={preset.description}
variant="Secondary"
fill="Soft"
radii="300"
style={{
height: 'unset',
minWidth: toRem(80),
padding: `${config.space.S200} ${config.space.S400}`,
}}
>
<Box direction="Column" alignItems="Center" gap="100">
<Icon size="400" src={preset.icon} />
<Text size="T300" style={{ fontWeight: config.fontWeight.W600 }}>
{preset.label}
</Text>
<Text
size="T200"
priority="300"
style={{ textAlign: 'center', maxWidth: toRem(120) }}
>
{preset.description}
</Text>
</Box>
</Button>
))}
</Box>
</SequenceCard>
</Box>
);
}
type NotificationsProps = {
requestClose: () => void;
};
export function Notifications({ requestClose }: NotificationsProps) {
return (
<Page>
<PageHeader outlined={false}>
<Box grow="Yes" gap="200">
<Box grow="Yes" alignItems="Center" gap="200">
<Text size="H3" truncate>
Notifications
</Text>
</Box>
<Box shrink="No">
<IconButton onClick={requestClose} variant="Surface" aria-label="Close">
<Icon src={Icons.Cross} />
</IconButton>
</Box>
</Box>
</PageHeader>
<Box grow="Yes">
<Scroll hideTrack visibility="Hover">
<PageContent>
<Box direction="Column" gap="700">
<NotificationPresets />
<SystemNotification />
<AllMessagesNotifications />
<SpecialMessagesNotifications />
<KeywordMessagesNotifications />
<PushRuleEditor />
</Box>
</PageContent>
</Scroll>
</Box>
</Page>
);
}