Skip to content

Commit 104c727

Browse files
committed
feat: add notification warning alert to Tasks page
1 parent 6882c43 commit 104c727

File tree

3 files changed

+108
-26
lines changed

3 files changed

+108
-26
lines changed

site/src/modules/notifications/utils.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import { MailIcon, WebhookIcon } from "lucide-react";
2+
import type {
3+
NotificationPreference,
4+
NotificationTemplate,
5+
} from "../../api/typesGenerated";
26

37
// TODO: This should be provided by the auto generated types from codersdk
48
const notificationMethods = ["smtp", "webhook"] as const;
@@ -26,3 +30,35 @@ export const castNotificationMethod = (value: string) => {
2630
)}`,
2731
);
2832
};
33+
34+
export function isTaskNotification(tmpl: NotificationTemplate): boolean {
35+
return tmpl.group === "Task Events";
36+
}
37+
38+
// Determines if a notification is disabled based on user preferences and system defaults
39+
// A notification is considered disabled if:
40+
// 1. It's NOT enabled by default AND the user hasn't set any preference (undefined), OR
41+
// 2. The user has explicitly disabled it in their preferences
42+
// Returns true if disabled, false if enabled
43+
export function notificationIsDisabled(
44+
disabledPreferences: Record<string, boolean>,
45+
tmpl: NotificationTemplate,
46+
): boolean {
47+
return (
48+
(!tmpl.enabled_by_default && disabledPreferences[tmpl.id] === undefined) ||
49+
disabledPreferences[tmpl.id]
50+
);
51+
}
52+
53+
// Transforms an array of NotificationPreference objects into a map
54+
// where the key is the template ID and the value is whether it's disabled
55+
// Example: [{ id: "abc", disabled: true }, { id: "def", disabled: false }]
56+
export function selectDisabledPreferences(data: NotificationPreference[]) {
57+
return data.reduce(
58+
(acc, pref) => {
59+
acc[pref.id] = pref.disabled;
60+
return acc;
61+
},
62+
{} as Record<string, boolean>,
63+
);
64+
}

site/src/pages/TasksPage/TasksPage.tsx

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { API } from "api/api";
22
import { templates } from "api/queries/templates";
33
import type { TasksFilter } from "api/typesGenerated";
4+
import { Alert } from "components/Alert/Alert";
45
import { Badge } from "components/Badge/Badge";
56
import { Button, type ButtonProps } from "components/Button/Button";
67
import { FeatureStageBadge } from "components/FeatureStageBadge/FeatureStageBadge";
8+
import { Link } from "components/Link/Link";
79
import { Margins } from "components/Margins/Margins";
810
import {
911
PageHeader,
@@ -13,10 +15,19 @@ import {
1315
import { useAuthenticated } from "hooks";
1416
import { useSearchParamsKey } from "hooks/useSearchParamsKey";
1517
import { TaskPrompt } from "modules/tasks/TaskPrompt/TaskPrompt";
16-
import type { FC } from "react";
17-
import { useQuery } from "react-query";
18+
import { type FC, useState } from "react";
19+
import { useQueries, useQuery } from "react-query";
1820
import { cn } from "utils/cn";
1921
import { pageTitle } from "utils/page";
22+
import {
23+
systemNotificationTemplates,
24+
userNotificationPreferences,
25+
} from "../../api/queries/notifications";
26+
import {
27+
isTaskNotification,
28+
notificationIsDisabled,
29+
selectDisabledPreferences,
30+
} from "../../modules/notifications/utils";
2031
import { TasksTable } from "./TasksTable";
2132
import { UsersCombobox } from "./UsersCombobox";
2233

@@ -50,10 +61,54 @@ const TasksPage: FC = () => {
5061
const displayedTasks =
5162
tab.value === "waiting-for-input" ? idleTasks : tasksQuery.data;
5263

64+
// Fetch notification preferences and templates
65+
const [disabledPreferencesQuery, systemTemplatesQuery] = useQueries({
66+
queries: [
67+
{
68+
...userNotificationPreferences(user.id),
69+
select: selectDisabledPreferences,
70+
},
71+
systemNotificationTemplates(),
72+
],
73+
});
74+
75+
const disabledPreferences = disabledPreferencesQuery.data ?? {};
76+
77+
// Check if ALL task notifications are disabled
78+
// Returns true only when all task notification templates are disabled.
79+
// If even one is enabled, returns false and the warning won't show.
80+
const taskNotificationsDisabled = systemTemplatesQuery.data
81+
?.filter(isTaskNotification)
82+
.every((template) => notificationIsDisabled(disabledPreferences, template));
83+
84+
// Check localStorage for task notifications warning dismissal
85+
const [alertDismissed, setAlertDismissed] = useState(
86+
localStorage.getItem("tasksNotificationWarningDismissed") === "true",
87+
);
88+
5389
return (
5490
<>
5591
<title>{pageTitle("AI Tasks")}</title>
5692
<Margins>
93+
{taskNotificationsDisabled && !alertDismissed && (
94+
<div className="mt-6">
95+
<Alert
96+
severity="warning"
97+
dismissible
98+
onDismiss={() => {
99+
setAlertDismissed(true);
100+
localStorage.setItem(
101+
"tasksNotificationWarningDismissed",
102+
"true",
103+
);
104+
}}
105+
>
106+
Your notifications for tasks status changes are disabled. Go to{" "}
107+
<Link href="/settings/notifications">Account Settings</Link> to
108+
change it.
109+
</Alert>
110+
</div>
111+
)}
57112
<PageHeader>
58113
<span className="flex flex-row gap-2">
59114
<PageHeaderTitle>Tasks</PageHeaderTitle>

site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.tsx

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,18 @@ import {
1616
updateUserNotificationPreferences,
1717
userNotificationPreferences,
1818
} from "api/queries/notifications";
19-
import type {
20-
NotificationPreference,
21-
NotificationTemplate,
22-
} from "api/typesGenerated";
19+
import type { NotificationTemplate } from "api/typesGenerated";
2320
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
2421
import { Loader } from "components/Loader/Loader";
2522
import { Stack } from "components/Stack/Stack";
2623
import { useAuthenticated } from "hooks";
2724
import {
2825
castNotificationMethod,
26+
isTaskNotification,
2927
methodIcons,
3028
methodLabels,
29+
notificationIsDisabled,
30+
selectDisabledPreferences,
3131
} from "modules/notifications/utils";
3232
import type { Permissions } from "modules/permissions";
3333
import { type FC, Fragment, useEffect } from "react";
@@ -181,6 +181,17 @@ const NotificationsPage: FC = () => {
181181
[tmpl.id]: !checked,
182182
},
183183
});
184+
185+
// Clear the Tasks page warning dismissal when enabling a task notification
186+
// This ensures that if the user disables task notifications again later,
187+
// they will see the warning banner again.
188+
if (isTaskNotification(tmpl) && checked) {
189+
localStorage.setItem(
190+
"tasksNotificationWarningDismissed",
191+
"false",
192+
);
193+
}
194+
184195
displaySuccess(
185196
"Notification preferences updated",
186197
);
@@ -241,26 +252,6 @@ function canSeeNotificationGroup(
241252
}
242253
}
243254

244-
function notificationIsDisabled(
245-
disabledPreferences: Record<string, boolean>,
246-
tmpl: NotificationTemplate,
247-
): boolean {
248-
return (
249-
(!tmpl.enabled_by_default && disabledPreferences[tmpl.id] === undefined) ||
250-
!!disabledPreferences[tmpl.id]
251-
);
252-
}
253-
254-
function selectDisabledPreferences(data: NotificationPreference[]) {
255-
return data.reduce(
256-
(acc, pref) => {
257-
acc[pref.id] = pref.disabled;
258-
return acc;
259-
},
260-
{} as Record<string, boolean>,
261-
);
262-
}
263-
264255
const styles = {
265256
listHeader: (theme) => ({
266257
background: theme.palette.background.paper,

0 commit comments

Comments
 (0)