View and update the authenticated user’s sound preferences. Requires a valid JWT access token. Preferences are loaded as part of GET /me and saved individually via PUT /me/sound-preferences with auto-save on every change.
- All 4 boolean toggles default to the values defined in the table below; master volume defaults to 50.
- Each toggle and slider change triggers an immediate API call (debounced 300 ms for the slider).
- Optimistic update: FE applies the change locally before the API response; on error it rolls back and shows a toast notification.
- Sound files are served from
public/sounds/{sound_id}.mp3at master volume level. - Sound preferences are part of the
preferencesobject returned byGET /me— see UC-01006 for the full response shape.
sequenceDiagram
actor User
%% --- GET preferences (via /me) ---
User->>+FE: opens Sounds section in Settings
FE->>+BE: GET /api/v1/users/me <br> Authorization: Bearer {accessToken}
BE->>BE: validate JWT access token
alt access token invalid or missing
BE-->>FE: 401 Unauthorized <br> ErrorResponse
end
BE->>DB: find user by id (from token)
BE->>-FE: 200 OK <br> UserProfileResponse (incl. preferences)
FE->>-User: render 4 ToggleSwitches + master volume slider <br> pre-filled from preferences
%% --- PUT sound preferences ---
User->>+FE: toggles a switch or moves slider
FE->>FE: optimistic update (apply change in local state immediately)
FE->>FE: debounce 300 ms (slider only)
FE->>+BE: PUT /api/v1/users/me/sound-preferences <br> Authorization: Bearer {accessToken} <br> UpdateSoundPreferencesRequest
BE->>BE: validate JWT access token
alt access token invalid or missing
BE-->>FE: 401 Unauthorized <br> ErrorResponse
FE-->>User: rollback optimistic update <br> show error toast
end
BE->>BE: validate request
alt request is invalid
BE-->>FE: 400 Bad Request <br> ErrorResponse
FE-->>User: rollback optimistic update <br> show error toast
end
BE->>DB: update sound preferences for user
BE->>-FE: 204 No Content
FE->>-User: change confirmed (no additional UI feedback on success)
%% --- Test sound button ---
User->>+FE: clicks test sound button next to a toggle
FE->>FE: play public/sounds/{sound_id}.mp3 <br> at current soundMasterVolume level (0-100 → gain 0.0-1.0)
FE->>-User: audio plays in browser
UX Guidelines
Page Layout
The Sounds section appears as item 3 in the Settings sidebar (after Profile and Account). Sidebar icon: Volume2 from lucide-vue-next (or Bell — icon selection deferred to Sára).
Content area follows the same padding: 32px 32px 64px layout as other sections.
Sound Toggles
Four rows using the existing ToggleSwitch.vue component (ProfileScreen/components/ToggleSwitch.vue). Each row uses FieldRow.vue layout:
| ID | Label (en) | Label (cs) | Default |
|---|---|---|---|
| sound_new_message | New message | Nová zpráva | on |
| sound_send_message | Sent message | Odeslaná zpráva | off |
| sound_task_completed | Task completed | Dokončený úkol | on |
| sound_task_error | Task failed | Selhání úkolu | on |
Each row layout: label (13px, 500) + ToggleSwitch on the right + a small “Test” button (ghost, 12px, lucide Play icon) inline after the toggle. Clicking “Test” plays public/sounds/{sound_id}.mp3 at the current master volume level — no API call, purely client-side AudioContext or HTMLAudioElement.
border-bottom: 1px solid var(--line-1), padding: 20px 0
Master Volume Slider
Below the 4 toggle rows, a fifth FieldRow renders the master volume control:
- Label: “Master volume” (en) / “Hlavní hlasitost” (cs)
- HTML
<input type="range" min="0" max="100" step="5"> - Current value displayed as a numeric suffix: e.g.
"50 %" - Debounce: 300 ms before firing
PUT /me/sound-preferences - Changing the slider also affects the volume of any subsequent “Test” button clicks
Auto-save Behaviour
- Toggle change → immediate API call (no debounce)
- Slider change → debounced 300 ms
- Optimistic update on FE before response arrives
- On API error: roll back to previous value in local state + display toast
"Could not save. Please try again."(var(--rose)background) - No explicit “Save” button — the section has no footer
Get Sound Preferences
Sound preferences are loaded via GET /api/v1/users/me — no separate endpoint. See UC-01006 — Get Profile for the full response shape.
Relevant fragment of 200 OK UserProfileResponse:
{
"data": {
"preferences": {
"locale": "en",
"soundNewMessage": true,
"soundSendMessage": false,
"soundTaskCompleted": true,
"soundTaskError": true,
"soundMasterVolume": 50
}
}
}
401 Unauthorized (missing or invalid access token) ErrorResponse:
{
"status": 401,
"code": "AUTHENTICATION_FAILED",
"message": "Access token is missing or invalid"
}
Update Sound Preferences
PUT /api/v1/users/me/sound-preferences UpdateSoundPreferencesRequest:
{
"soundNewMessage": true,
"soundSendMessage": false,
"soundTaskCompleted": true,
"soundTaskError": true,
"soundMasterVolume": 50
}
204 No Content (success, no body)
400 Bad Request (validation) ErrorResponse:
{
"status": 400,
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"errors": [
{ "field": "soundMasterVolume", "message": "must be between 0 and 100" }
]
}
401 Unauthorized (missing or invalid access token) ErrorResponse:
{
"status": 401,
"code": "AUTHENTICATION_FAILED",
"message": "Access token is missing or invalid"
}
Frontend
Validations
| Field | Constraints | Size | Pattern | Note |
|---|---|---|---|---|
| soundNewMessage | not_null | boolean; validated before sending API request | ||
| soundSendMessage | not_null | boolean; validated before sending API request | ||
| soundTaskCompleted | not_null | boolean; validated before sending API request | ||
| soundTaskError | not_null | boolean; validated before sending API request | ||
| soundMasterVolume | not_null | 0-100 | integer, step 5; slider enforces range client-side |
Backend
Validations
| Field | Constraints | Size | Pattern | Note |
|---|---|---|---|---|
| soundNewMessage | not_null | boolean | ||
| soundSendMessage | not_null | boolean | ||
| soundTaskCompleted | not_null | boolean | ||
| soundTaskError | not_null | boolean | ||
| soundMasterVolume | not_null, min=0, max=100 | integer inclusive |
Test Cases
| GIVEN | WHEN | THEN |
|---|---|---|
| authenticated user | GET /me is called | 200 OK, preferences object contains all 5 sound fields |
| no Authorization header | GET /me is called | 401 AUTHENTICATION_FAILED error response is returned |
| authenticated user, all fields valid | PUT /me/sound-preferences is called | 204 No Content, preferences updated in DB |
| authenticated user, soundMasterVolume = 0 | PUT /me/sound-preferences is called | 204 No Content (boundary — valid) |
| authenticated user, soundMasterVolume = 100 | PUT /me/sound-preferences is called | 204 No Content (boundary — valid) |
| soundMasterVolume = -1 | PUT /me/sound-preferences is called | 400 VALIDATION_ERROR error response is returned |
| soundMasterVolume = 101 | PUT /me/sound-preferences is called | 400 VALIDATION_ERROR error response is returned |
| soundNewMessage field missing from request body | PUT /me/sound-preferences is called | 400 VALIDATION_ERROR error response is returned |
| empty request body | PUT /me/sound-preferences is called | 400 VALIDATION_ERROR error response is returned |
| no Authorization header | PUT /me/sound-preferences is called | 401 AUTHENTICATION_FAILED error response is returned |
| authenticated user saves preferences, then refreshes page | GET /me is called after save | preferences object reflects previously saved values |
Thanks for the feedback.