Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion frontend/leads.html
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,9 @@ <h5 class="mt-3 mb-2">No leads found</h5>
statusEl.innerHTML = '<span class="text-success"><i class="bi bi-check-circle me-1" aria-hidden="true"></i>File uploaded. Processing now...</span>';
await loadLeads();
await loadImportHistory();
if (document.hidden && typeof window.notifyLeadOrbit === 'function') {
await window.notifyLeadOrbit('CSV import completed');
}
fileInput.value = '';
bootstrap.Modal.getOrCreateInstance(document.getElementById('uploadModal')).hide();
} else {
Expand All @@ -886,4 +889,4 @@ <h5 class="mt-3 mb-2">No leads found</h5>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>

</html>
</html>
121 changes: 120 additions & 1 deletion frontend/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import { fetchWithAuth, clearTokens, refreshAccessToken } from './api.js';
const THEME_STORAGE_KEY = 'theme';
const LEADORBIT_VERSION = 'v1.0.0-beta';
const LEADORBIT_REPO_URL = 'https://github.com/Kuldeeep18/LeadOrbit';
const ORIGINAL_PAGE_TITLE = document.title;
const ORIGINAL_FAVICON_HREF = (() => {
const faviconLink = document.querySelector("link[rel*='icon']");
return faviconLink?.href || '/favicon.png';
})();

let unreadNotificationCount = 0;
let faviconRenderPromise = null;

// ==========================================
// THEME MANAGEMENT
Expand Down Expand Up @@ -218,6 +226,106 @@ function initThemeToggle() {
});
}

function getOrCreateFaviconLink() {
let faviconLink = document.querySelector("link[rel*='icon']");
if (!faviconLink) {
faviconLink = document.createElement('link');
faviconLink.rel = 'icon';
faviconLink.type = 'image/png';
document.head.appendChild(faviconLink);
}
return faviconLink;
}

function restoreFavicon() {
getOrCreateFaviconLink().href = ORIGINAL_FAVICON_HREF;
}

function restorePageTitle() {
document.title = ORIGINAL_PAGE_TITLE;
}

function setAttentionTitle() {
document.title = 'Come back to LeadOrbit!';
}

async function renderNotificationFavicon() {
if (faviconRenderPromise) {
return faviconRenderPromise;
}

faviconRenderPromise = new Promise((resolve) => {
const faviconLink = getOrCreateFaviconLink();
const image = new Image();

image.onload = () => {
try {
const canvas = document.createElement('canvas');
const size = 32;
canvas.width = size;
canvas.height = size;

const context = canvas.getContext('2d');
if (!context) {
faviconLink.href = ORIGINAL_FAVICON_HREF;
resolve(faviconLink.href);
return;
}

context.drawImage(image, 0, 0, size, size);
context.beginPath();
context.fillStyle = '#ef4444';
context.arc(size - 9, 9, 6, 0, Math.PI * 2);
context.fill();
context.beginPath();
context.fillStyle = '#ffffff';
context.arc(size - 9, 9, 2, 0, Math.PI * 2);
context.fill();

faviconLink.href = canvas.toDataURL('image/png');
resolve(faviconLink.href);
} catch (error) {
console.error('Could not render notification favicon:', error);
faviconLink.href = ORIGINAL_FAVICON_HREF;
resolve(faviconLink.href);
}
};

image.onerror = () => {
faviconLink.href = ORIGINAL_FAVICON_HREF;
resolve(faviconLink.href);
};

image.src = ORIGINAL_FAVICON_HREF;
}).finally(() => {
faviconRenderPromise = null;
});

return faviconRenderPromise;
}

export async function notifyLeadOrbit(message = '') {
if (!document.hidden) {
return false;
}

unreadNotificationCount += 1;
setAttentionTitle();
await renderNotificationFavicon();

if (message) {
console.info(`LeadOrbit notification: ${message}`);
}

return true;
}

export function clearLeadOrbitNotifications() {
unreadNotificationCount = 0;
restorePageTitle();
restoreFavicon();
}

// Sync theme across multiple tabs
window.addEventListener('storage', (event) => {
if (event.key === THEME_STORAGE_KEY && event.newValue) {
Expand All @@ -232,6 +340,17 @@ window.addEventListener('storage', (event) => {
}
});

window.notifyLeadOrbit = notifyLeadOrbit;
window.clearLeadOrbitNotifications = clearLeadOrbitNotifications;
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
setAttentionTitle();
return;
}

clearLeadOrbitNotifications();
});

// ==========================================
// PASSWORD VISIBILITY TOGGLE
// ==========================================
Expand Down Expand Up @@ -605,4 +724,4 @@ if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initKeyboardShortcuts);
} else {
initKeyboardShortcuts();
}
}