feat: add notification warning alert to Tasks page (#20900)

## Problem

Users may not realize that task notifications are disabled by default.
To improve awareness, we show a warning alert on the Tasks page when all
task notifications are disabled.

**Alert visibility logic:**
- Shows when **all** task notification templates (Task Working, Task
Idle, Task Completed, Task Failed) are disabled
- Can be dismissed by the user, which stores the dismissal in the user
preferences API
- If the user later enables any task notification in Account Settings,
the dismissal state is cleared so the alert will show again if they
disable all notifications in the future

<img width="2980" height="1588" alt="Screenshot 2025-11-25 at 17 48 17"
src="https://github.com/user-attachments/assets/316bf097-d9d2-4489-bc16-2987ba45f45c"
/>

## Changes

- Added a warning alert to the Tasks page when all task notifications
are disabled
- Introduced new `/users/{user}/preferences` endpoint to manage user
preferences (stored in `user_configs` table)
- Alert is dismissible and stores the dismissal state via the new user
preferences API endpoint
- Enabling any task notification in Account Settings clears the
dismissal state via the preferences API
- Added comprehensive Storybook stories for both TasksPage and
NotificationsPage to test all alert visibility states and interactions

Closes: https://github.com/coder/internal/issues/1089
This commit is contained in:
Susana Ferreira
2025-11-28 16:50:59 +00:00
committed by GitHub
parent a8862be546
commit f8d9a8046f
24 changed files with 1028 additions and 40 deletions
+36
View File
@@ -217,6 +217,14 @@ type UpdateUserAppearanceSettingsRequest struct {
TerminalFont TerminalFontName `json:"terminal_font" validate:"required"`
}
type UserPreferenceSettings struct {
TaskNotificationAlertDismissed bool `json:"task_notification_alert_dismissed"`
}
type UpdateUserPreferenceSettingsRequest struct {
TaskNotificationAlertDismissed bool `json:"task_notification_alert_dismissed"`
}
type UpdateUserPasswordRequest struct {
OldPassword string `json:"old_password" validate:""`
Password string `json:"password" validate:"required"`
@@ -514,6 +522,34 @@ func (c *Client) UpdateUserAppearanceSettings(ctx context.Context, user string,
return resp, json.NewDecoder(res.Body).Decode(&resp)
}
// GetUserPreferenceSettings fetches the preference settings for a user.
func (c *Client) GetUserPreferenceSettings(ctx context.Context, user string) (UserPreferenceSettings, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s/preferences", user), nil)
if err != nil {
return UserPreferenceSettings{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return UserPreferenceSettings{}, ReadBodyAsError(res)
}
var resp UserPreferenceSettings
return resp, json.NewDecoder(res.Body).Decode(&resp)
}
// UpdateUserPreferenceSettings updates the preference settings for a user.
func (c *Client) UpdateUserPreferenceSettings(ctx context.Context, user string, req UpdateUserPreferenceSettingsRequest) (UserPreferenceSettings, error) {
res, err := c.Request(ctx, http.MethodPut, fmt.Sprintf("/api/v2/users/%s/preferences", user), req)
if err != nil {
return UserPreferenceSettings{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return UserPreferenceSettings{}, ReadBodyAsError(res)
}
var resp UserPreferenceSettings
return resp, json.NewDecoder(res.Body).Decode(&resp)
}
// UpdateUserPassword updates a user password.
// It calls PUT /users/{user}/password
func (c *Client) UpdateUserPassword(ctx context.Context, user string, req UpdateUserPasswordRequest) error {