feat: add user preference for audio notifications and play sound on new alerts
All checks were successful
Build and Deploy / build-and-push (push) Successful in 1m53s

- Added sound_enabled column to users table with a default of true.

- Implemented a pleasant pop sound (notification.mp3) that plays when a new unread notification arrives.

- Added a toggle in the User Profile page allowing users to enable/disable the sound.
This commit is contained in:
Cauê Faleiros
2026-03-10 10:38:03 -03:00
parent ccbba312bb
commit 754c1e2a21
6 changed files with 187 additions and 42 deletions

View File

@@ -399,11 +399,11 @@ apiRouter.post('/users', requireRole(['admin', 'manager', 'super_admin']), async
});
apiRouter.put('/users/:id', async (req, res) => {
const { name, bio, role, team_id, status, email } = req.body;
const { name, bio, role, team_id, status, email, sound_enabled } = req.body;
try {
const [existing] = await pool.query('SELECT * FROM users WHERE id = ?', [req.params.id]);
if (existing.length === 0) return res.status(404).json({ error: 'Not found' });
const isSelf = req.user.id === req.params.id;
const isManagerOrAdmin = ['admin', 'owner', 'manager', 'super_admin'].includes(req.user.role);
@@ -419,6 +419,7 @@ apiRouter.put('/users/:id', async (req, res) => {
const finalTeamId = isManagerOrAdmin && team_id !== undefined ? team_id : existing[0].team_id;
const finalStatus = isManagerOrAdmin && status !== undefined ? status : existing[0].status;
const finalEmail = email !== undefined ? email : existing[0].email;
const finalSoundEnabled = isSelf && sound_enabled !== undefined ? sound_enabled : (existing[0].sound_enabled ?? true);
if (finalEmail !== existing[0].email) {
const [emailCheck] = await pool.query('SELECT id FROM users WHERE email = ? AND id != ?', [finalEmail, req.params.id]);
@@ -426,12 +427,11 @@ apiRouter.put('/users/:id', async (req, res) => {
}
await pool.query(
'UPDATE users SET name = ?, bio = ?, email = ?, role = ?, team_id = ?, status = ? WHERE id = ?',
[name || existing[0].name, bio !== undefined ? bio : existing[0].bio, finalEmail, finalRole, finalTeamId || null, finalStatus, req.params.id]
'UPDATE users SET name = ?, bio = ?, email = ?, role = ?, team_id = ?, status = ?, sound_enabled = ? WHERE id = ?',
[name || existing[0].name, bio !== undefined ? bio : existing[0].bio, finalEmail, finalRole, finalTeamId || null, finalStatus, finalSoundEnabled, req.params.id]
);
res.json({ message: 'User updated successfully.' });
} catch (error) {
console.error('Update user error:', error);
} catch (error) { console.error('Update user error:', error);
res.status(500).json({ error: error.message });
}
});
@@ -510,6 +510,30 @@ apiRouter.put('/notifications/read-all', async (req, res) => {
}
});
apiRouter.delete('/notifications/:id', async (req, res) => {
try {
await pool.query(
'DELETE FROM notifications WHERE id = ? AND user_id = ?',
[req.params.id, req.user.id]
);
res.json({ message: 'Notification deleted' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
apiRouter.delete('/notifications', async (req, res) => {
try {
await pool.query(
'DELETE FROM notifications WHERE user_id = ?',
[req.user.id]
);
res.json({ message: 'All notifications deleted' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// --- Global Search ---
apiRouter.get('/search', async (req, res) => {
const { q } = req.query;
@@ -887,6 +911,13 @@ const provisionSuperAdmin = async (retries = 10, delay = 10000) => {
console.log('Schema update note (populate slugs):', err.message);
}
// Add sound_enabled column if it doesn't exist
try {
await connection.query('ALTER TABLE users ADD COLUMN sound_enabled BOOLEAN DEFAULT true');
} catch (err) {
if (err.code !== 'ER_DUP_FIELDNAME') console.log('Schema update note (sound_enabled):', err.message);
}
// Update origin enum
try {
await connection.query("ALTER TABLE attendances MODIFY COLUMN origin ENUM('WhatsApp','Instagram','Website','LinkedIn','Indicação') NOT NULL");