notification popup
This commit is contained in:
parent
28f4523014
commit
1729d077ab
@ -24,7 +24,65 @@ let authToken = null;
|
|||||||
let lastAdminTab = 'overview';
|
let lastAdminTab = 'overview';
|
||||||
export { lastAdminTab };
|
export { lastAdminTab };
|
||||||
|
|
||||||
// --- EVENT HANDLERS (The "Logic") ---
|
// --- NOTIFICATION LOGIC ---
|
||||||
|
|
||||||
|
// This helper function converts the VAPID public key for the browser
|
||||||
|
function urlBase64ToUint8Array(base64String) {
|
||||||
|
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
||||||
|
const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/');
|
||||||
|
const rawData = window.atob(base64);
|
||||||
|
const outputArray = new Uint8Array(rawData.length);
|
||||||
|
for (let i = 0; i < rawData.length; ++i) {
|
||||||
|
outputArray[i] = rawData.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return outputArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function handles the actual subscription process
|
||||||
|
async function subscribeToNotifications() {
|
||||||
|
try {
|
||||||
|
const publicVapidKey = process.env.PUBLIC_VAPID_KEY; // Make sure this is set in your .env
|
||||||
|
const token = localStorage.getItem('authToken');
|
||||||
|
|
||||||
|
if (!token || !publicVapidKey) {
|
||||||
|
return console.error('Auth token or VAPID key is missing.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const register = await navigator.serviceWorker.register('/sw.js', { scope: '/' });
|
||||||
|
const subscription = await register.pushManager.subscribe({
|
||||||
|
userVisibleOnly: true,
|
||||||
|
applicationServerKey: urlBase64ToUint8Array(publicVapidKey)
|
||||||
|
});
|
||||||
|
|
||||||
|
await apiCall('/subscribe', 'POST', subscription);
|
||||||
|
showMessage('You are now subscribed to notifications!', 'success');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error subscribing to notifications:', error);
|
||||||
|
showMessage('Failed to subscribe. Please ensure notifications are allowed for this site.', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NEW: This function creates the pop-up prompt for the user
|
||||||
|
function promptForNotifications() {
|
||||||
|
// 1. Don't ask if permission is already granted or denied
|
||||||
|
if (Notification.permission !== 'default') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 2. Don't ask if we've already prompted them before
|
||||||
|
if (localStorage.getItem('notificationPrompted')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 3. Wait a couple of seconds after login to ask
|
||||||
|
setTimeout(() => {
|
||||||
|
if (confirm("Enable notifications to receive important updates about your time-off requests and notes?")) {
|
||||||
|
subscribeToNotifications();
|
||||||
|
}
|
||||||
|
// Remember that we've prompted them, so we don't ask again
|
||||||
|
localStorage.setItem('notificationPrompted', 'true');
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- EVENT HANDLERS ---
|
||||||
async function handleAuthSubmit(e) {
|
async function handleAuthSubmit(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const username = e.target.elements.username.value;
|
const username = e.target.elements.username.value;
|
||||||
@ -105,7 +163,7 @@ async function handleEditSubmit(e) {
|
|||||||
const handleArchive = () => {
|
const handleArchive = () => {
|
||||||
if (confirm('Are you sure you want to archive all completed time entries? This action cannot be undone.')) {
|
if (confirm('Are you sure you want to archive all completed time entries? This action cannot be undone.')) {
|
||||||
apiCall('/admin/archive', 'POST').then(res => {
|
apiCall('/admin/archive', 'POST').then(res => {
|
||||||
if(res.success) {
|
if (res.success) {
|
||||||
showMessage('Records archived successfully.', 'success');
|
showMessage('Records archived successfully.', 'success');
|
||||||
renderAdminDashboard();
|
renderAdminDashboard();
|
||||||
}
|
}
|
||||||
@ -227,9 +285,9 @@ function handleAdminDashboardClick(e) {
|
|||||||
if (target.classList.contains('delete-btn') && confirm('Delete this time entry?')) apiCall(`/admin/logs/${id}`, 'DELETE').then(res => res.success && renderAdminDashboard());
|
if (target.classList.contains('delete-btn') && confirm('Delete this time entry?')) apiCall(`/admin/logs/${id}`, 'DELETE').then(res => res.success && renderAdminDashboard());
|
||||||
if (target.classList.contains('force-clock-out-btn') && confirm(`Force clock out ${username}?`)) apiCall('/admin/force-clock-out', 'POST', { userId: userid }).then(res => res.success && renderAdminDashboard());
|
if (target.classList.contains('force-clock-out-btn') && confirm(`Force clock out ${username}?`)) apiCall('/admin/force-clock-out', 'POST', { userId: userid }).then(res => res.success && renderAdminDashboard());
|
||||||
if (target.classList.contains('reset-pw-btn')) renderResetPasswordModal(username, handleResetPassword);
|
if (target.classList.contains('reset-pw-btn')) renderResetPasswordModal(username, handleResetPassword);
|
||||||
if (target.classList.contains('change-role-btn')) { const newRole = role === 'admin' ? 'employee' : 'admin'; if(confirm(`Change ${username} to ${newRole}?`)) apiCall('/admin/update-role', 'POST', { username, newRole }).then(res => res.success && renderAdminDashboard()); }
|
if (target.classList.contains('change-role-btn')) { const newRole = role === 'admin' ? 'employee' : 'admin'; if (confirm(`Change ${username} to ${newRole}?`)) apiCall('/admin/update-role', 'POST', { username, newRole }).then(res => res.success && renderAdminDashboard()); }
|
||||||
if (target.classList.contains('delete-user-btn') && confirm(`PERMANENTLY DELETE user '${username}'?`)) apiCall(`/admin/delete-user/${username}`, 'DELETE').then(res => res.success && renderAdminDashboard());
|
if (target.classList.contains('delete-user-btn') && confirm(`PERMANENTLY DELETE user '${username}'?`)) apiCall(`/admin/delete-user/${username}`, 'DELETE').then(res => res.success && renderAdminDashboard());
|
||||||
if (target.classList.contains('delete-note-btn')) { if (confirm('Delete this note?')) { apiCall(`/admin/notes/${noteId}`, 'DELETE').then(res => { if (res.success) { showMessage('Note deleted.', 'success'); handleViewNotesClick(); }});}}
|
if (target.classList.contains('delete-note-btn')) { if (confirm('Delete this note?')) { apiCall(`/admin/notes/${noteId}`, 'DELETE').then(res => { if (res.success) { showMessage('Note deleted.', 'success'); handleViewNotesClick(); } }); } }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleEditTimeOffSubmit(e) {
|
async function handleEditTimeOffSubmit(e) {
|
||||||
@ -251,7 +309,6 @@ async function handleEditTimeOffSubmit(e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- NEW: Handler for clicks specifically on the Time Off History page ---
|
|
||||||
function handleTimeOffHistoryClick(e) {
|
function handleTimeOffHistoryClick(e) {
|
||||||
const target = e.target.closest('button');
|
const target = e.target.closest('button');
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
@ -264,7 +321,7 @@ function handleTimeOffHistoryClick(e) {
|
|||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
showMessage('Request status set to pending.', 'success');
|
showMessage('Request status set to pending.', 'success');
|
||||||
renderTimeOffHistoryView(); // Refresh the history view
|
renderTimeOffHistoryView();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -276,7 +333,7 @@ function handleTimeOffHistoryClick(e) {
|
|||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
showMessage('Request deleted.', 'success');
|
showMessage('Request deleted.', 'success');
|
||||||
renderTimeOffHistoryView(); // Refresh the history view
|
renderTimeOffHistoryView();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -334,7 +391,6 @@ export function attachAdminDashboardListeners() {
|
|||||||
setupTabbedInterface();
|
setupTabbedInterface();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- NEW: Listener attachment function for the history page ---
|
|
||||||
export function attachTimeOffHistoryListeners() {
|
export function attachTimeOffHistoryListeners() {
|
||||||
const historyView = document.getElementById('admin-time-off-history-view');
|
const historyView = document.getElementById('admin-time-off-history-view');
|
||||||
if (historyView) {
|
if (historyView) {
|
||||||
@ -348,8 +404,8 @@ function initializeApp() {
|
|||||||
const userString = localStorage.getItem('user');
|
const userString = localStorage.getItem('user');
|
||||||
user = userString ? JSON.parse(userString) : null;
|
user = userString ? JSON.parse(userString) : null;
|
||||||
|
|
||||||
if (authToken && user) {
|
|
||||||
const userControls = document.getElementById('nav-user-controls');
|
const userControls = document.getElementById('nav-user-controls');
|
||||||
|
if (authToken && user) {
|
||||||
userControls.classList.remove('hidden');
|
userControls.classList.remove('hidden');
|
||||||
userControls.querySelector('#welcome-message').textContent = `Welcome, ${user.username}`;
|
userControls.querySelector('#welcome-message').textContent = `Welcome, ${user.username}`;
|
||||||
if (user.role === 'admin') {
|
if (user.role === 'admin') {
|
||||||
@ -357,8 +413,10 @@ function initializeApp() {
|
|||||||
} else {
|
} else {
|
||||||
renderEmployeeDashboard();
|
renderEmployeeDashboard();
|
||||||
}
|
}
|
||||||
|
// Ask for notification permission after user is logged in
|
||||||
|
promptForNotifications();
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('nav-user-controls').classList.add('hidden');
|
userControls.classList.add('hidden');
|
||||||
renderAuthView();
|
renderAuthView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -388,72 +446,9 @@ function setupTabbedInterface() {
|
|||||||
document.getElementById(`tab-content-${tabTarget}`).classList.remove('hidden');
|
document.getElementById(`tab-content-${tabTarget}`).classList.remove('hidden');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// This converts the VAPID public key string to the format the browser needs.
|
|
||||||
function urlBase64ToUint8Array(base64String) {
|
|
||||||
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
|
||||||
const base64 = (base64String + padding)
|
|
||||||
.replace(/\-/g, '+')
|
|
||||||
.replace(/_/g, '/');
|
|
||||||
|
|
||||||
const rawData = window.atob(base64);
|
|
||||||
const outputArray = new Uint8Array(rawData.length);
|
|
||||||
|
|
||||||
for (let i = 0; i < rawData.length; ++i) {
|
|
||||||
outputArray[i] = rawData.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return outputArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Subscription Logic ---
|
|
||||||
async function subscribeToNotifications() {
|
|
||||||
// IMPORTANT: Replace with your actual public VAPID key
|
|
||||||
const publicVapidKey = 'YOUR_PUBLIC_VAPID_KEY';
|
|
||||||
const token = localStorage.getItem('token');
|
|
||||||
|
|
||||||
// Check if user is logged in first
|
|
||||||
if (!token) {
|
|
||||||
console.error('User is not logged in.');
|
|
||||||
alert('You must be logged in to enable notifications.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Register the service worker
|
|
||||||
const register = await navigator.serviceWorker.register('/sw.js', {
|
|
||||||
scope: '/'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. Get the push subscription
|
|
||||||
const subscription = await register.pushManager.subscribe({
|
|
||||||
userVisibleOnly: true,
|
|
||||||
applicationServerKey: urlBase64ToUint8Array(publicVapidKey)
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. Send the subscription to your authenticated backend
|
|
||||||
await fetch('/subscribe', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(subscription),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${token}` // The crucial update!
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
alert('You are now subscribed to notifications!');
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Attach to a Button ---
|
|
||||||
// Assuming you have a button in your HTML with id="notifyBtn"
|
|
||||||
const notifyBtn = document.getElementById('notifyBtn');
|
|
||||||
if (notifyBtn) {
|
|
||||||
notifyBtn.addEventListener('click', () => {
|
|
||||||
subscribeToNotifications().catch(error => {
|
|
||||||
console.error('Error subscribing to notifications:', error);
|
|
||||||
alert('Failed to subscribe. Please make sure notifications are allowed for this site.');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- START THE APP ---
|
// --- START THE APP ---
|
||||||
|
// Register the service worker when the page loads
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
navigator.serviceWorker.register('/sw.js')
|
navigator.serviceWorker.register('/sw.js')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user