From 9be3be771232072c3a0d63b848bad09c61a6be12 Mon Sep 17 00:00:00 2001 From: Michelle Date: Mon, 2 Mar 2026 18:32:47 +0100 Subject: [PATCH] feat(notifications): add notification sound playback for new alerts and include sound file documentation --- public/sounds/README.md | 13 +++++++++++++ public/sounds/notification.mp3 | Bin 0 -> 8623 bytes src/contexts/NotificationContext.jsx | 21 +++++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 public/sounds/README.md create mode 100644 public/sounds/notification.mp3 diff --git a/public/sounds/README.md b/public/sounds/README.md new file mode 100644 index 0000000..4b361d1 --- /dev/null +++ b/public/sounds/README.md @@ -0,0 +1,13 @@ +# Notification Sound + +Pop-up Sound by BeezleFM -- https://freesound.org/s/512135/ -- License: Attribution 4.0 + +Place your notification sound file here as: + + `notification.mp3` + +The file is served at `/sounds/notification.mp3` and played automatically +whenever a new in-app notification arrives. + +Supported formats: MP3, OGG, WAV — MP3 recommended for broadest browser support. +Keep the file short (< 2 s) and not too loud. diff --git a/public/sounds/notification.mp3 b/public/sounds/notification.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..eaff032b02b98e7950039de4a3e6a5f0b15aafcd GIT binary patch literal 8623 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 b|I>y?UP|A>rPbv5{{1$f_y75SuE75QZppL4 literal 0 HcmV?d00001 diff --git a/src/contexts/NotificationContext.jsx b/src/contexts/NotificationContext.jsx index 643ed98..fd8b300 100644 --- a/src/contexts/NotificationContext.jsx +++ b/src/contexts/NotificationContext.jsx @@ -3,6 +3,24 @@ import toast from 'react-hot-toast'; import { useAuth } from './AuthContext'; import api from '../services/api'; +// Lazily created Audio instance — reused across calls to avoid memory churn +let _audio = null; +function playNotificationSound() { + try { + if (!_audio) { + _audio = new Audio('/sounds/notification.mp3'); + _audio.volume = 0.5; + } + // Reset to start so rapid arrivals always play from beginning + _audio.currentTime = 0; + _audio.play().catch(() => { + // Autoplay blocked (user hasn't interacted yet) or file missing — silent fail + }); + } catch { + // Ignore any other errors (e.g. unsupported format) + } +} + const NotificationContext = createContext(); export function NotificationProvider({ children }) { @@ -30,6 +48,9 @@ export function NotificationProvider({ children }) { // Subsequent fetches: toast new unread notifications const newItems = incoming.filter(n => !n.read && !seenIds.current.has(n.id)); + if (newItems.length > 0) { + playNotificationSound(); + } newItems.forEach(n => { seenIds.current.add(n.id); const icon = notificationIcon(n.type);