feat(i18n): add German and English email templates for invitations and verifications
All checks were successful
Build & Push Docker Image / build (push) Successful in 6m29s
All checks were successful
Build & Push Docker Image / build (push) Successful in 6m29s
This commit is contained in:
@@ -5,16 +5,38 @@ import api from '../services/api';
|
||||
|
||||
// Lazily created Audio instance — reused across calls to avoid memory churn
|
||||
let _audio = null;
|
||||
let _audioUnlocked = false;
|
||||
|
||||
function getAudio() {
|
||||
if (!_audio) {
|
||||
_audio = new Audio('/sounds/notification.mp3');
|
||||
_audio.volume = 0.5;
|
||||
}
|
||||
return _audio;
|
||||
}
|
||||
|
||||
/** Called once on the first user gesture to silently play→pause the element,
|
||||
* which "unlocks" it so later timer-based .play() calls are not blocked. */
|
||||
function unlockAudio() {
|
||||
if (_audioUnlocked) return;
|
||||
_audioUnlocked = true;
|
||||
const audio = getAudio();
|
||||
audio.muted = true;
|
||||
audio.play().then(() => {
|
||||
audio.pause();
|
||||
audio.muted = false;
|
||||
audio.currentTime = 0;
|
||||
}).catch(() => {
|
||||
audio.muted = false;
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
const audio = getAudio();
|
||||
audio.currentTime = 0;
|
||||
audio.play().catch(() => {
|
||||
// Autoplay still blocked — silent fail
|
||||
});
|
||||
} catch {
|
||||
// Ignore any other errors (e.g. unsupported format)
|
||||
@@ -61,6 +83,19 @@ export function NotificationProvider({ children }) {
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
// Unlock audio playback on the first real user interaction.
|
||||
// Browsers block audio from timer callbacks unless the element was previously
|
||||
// "touched" inside a gesture handler — this one-time listener does exactly that.
|
||||
useEffect(() => {
|
||||
const events = ['click', 'keydown', 'pointerdown'];
|
||||
const handler = () => {
|
||||
unlockAudio();
|
||||
events.forEach(e => window.removeEventListener(e, handler));
|
||||
};
|
||||
events.forEach(e => window.addEventListener(e, handler, { once: true }));
|
||||
return () => events.forEach(e => window.removeEventListener(e, handler));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
setNotifications([]);
|
||||
|
||||
Reference in New Issue
Block a user