import { getSession, login, logout, sendPasswordReset, updatePassword, onAuthChange } from './auth.js'; import { renderDashboard } from './dashboard.js'; import { renderReports, openReportById } from './reports.js'; import { renderSearch } from './search.js'; import { renderSharing } from './sharing.js'; import { renderAccount } from './account.js'; import { renderSettings } from './settings.js'; import { loadCloudReports, refreshFromCloud, getLocalReportsPendingCloud, uploadLocalReportsToCloud } from './storage.js'; const authView = document.querySelector('#auth-view'); const dashboardView = document.querySelector('#dashboard-view'); const authMessage = document.querySelector('#auth-message'); const loginForm = document.querySelector('#login-form'); const resetForm = document.querySelector('#reset-form'); const updatePasswordForm = document.querySelector('#update-password-form'); const showReset = document.querySelector('#show-reset'); const backToLogin = document.querySelector('#back-to-login'); let currentModule = 'dashboard'; const CLOUD_MIGRATION_SKIP_KEY = 'streamline-itp-cloud-migration-skipped'; const AUTO_CLOUD_PULL_INTERVAL_MS = 120000; let autoCloudPullTimer = null; let autoCloudPullInProgress = false; const modules = { dashboard: document.querySelector('#dashboard-module'), reports: document.querySelector('#reports-module'), search: document.querySelector('#search-module'), sharing: document.querySelector('#sharing-module'), account: document.querySelector('#account-module'), settings: document.querySelector('#settings-module') }; init(); async function init() { bindAuthEvents(); bindNavigation(); renderAllModules(); bindAutoCloudSyncEvents(); const isRecovery = window.location.hash.includes('type=recovery') || window.location.search.includes('type=recovery'); const session = await getSession().catch(() => null); if (isRecovery) { showAuth(); showPasswordUpdate(); return; } session ? showApp() : showAuth(); onAuthChange((_event, sessionData) => { if (sessionData && dashboardView.classList.contains('hidden')) showApp(currentModule); if (!sessionData) showAuth(); }); } function renderAllModules() { renderDashboard(modules.dashboard, { onOpenReport: openDashboardReport }); renderReports(modules.reports, { onReportsChanged: refreshDashboardAndSearch }); renderSearch(modules.search); renderSharing(modules.sharing); renderSettings(modules.settings, refreshDashboardAndSearch); } function openDashboardReport(reportId) { const opened = openReportById(reportId); if (opened) showModule('reports'); } function refreshDashboardAndSearch() { renderDashboard(modules.dashboard, { onOpenReport: openDashboardReport }); renderSearch(modules.search); } function bindAuthEvents() { loginForm.addEventListener('submit', async (event) => { event.preventDefault(); setMessage('Logging in...'); try { await login(document.querySelector('#login-email').value, document.querySelector('#login-password').value); setMessage('Login successful.', 'success'); showApp(); } catch (error) { setMessage(error.message, 'error'); } }); resetForm.addEventListener('submit', async (event) => { event.preventDefault(); setMessage('Sending reset link...'); try { await sendPasswordReset(document.querySelector('#reset-email').value); setMessage('Password reset email sent. Check your inbox.', 'success'); } catch (error) { setMessage(error.message, 'error'); } }); updatePasswordForm.addEventListener('submit', async (event) => { event.preventDefault(); setMessage('Updating password...'); try { await updatePassword(document.querySelector('#new-password').value); setMessage('Password updated. You can now use the app.', 'success'); showApp(); } catch (error) { setMessage(error.message, 'error'); } }); showReset.addEventListener('click', () => { loginForm.classList.add('hidden'); resetForm.classList.remove('hidden'); updatePasswordForm.classList.add('hidden'); showReset.classList.add('hidden'); backToLogin.classList.remove('hidden'); setMessage(''); }); backToLogin.addEventListener('click', () => { resetForm.classList.add('hidden'); updatePasswordForm.classList.add('hidden'); loginForm.classList.remove('hidden'); showReset.classList.remove('hidden'); backToLogin.classList.add('hidden'); setMessage(''); }); document.querySelector('#logout-btn').addEventListener('click', async () => { await logout(); showAuth(); }); } function bindNavigation() { document.querySelectorAll('.nav-item[data-module]').forEach((button) => { button.addEventListener('click', () => { showModule(button.dataset.module); closeMobileMenu(); }); }); document.querySelector('#new-report-btn').addEventListener('click', () => showModule('reports')); const menuButton = document.querySelector('#mobile-menu-btn'); const sidebar = document.querySelector('.sidebar'); if (menuButton && sidebar) { menuButton.addEventListener('click', () => { const open = sidebar.classList.toggle('mobile-open'); menuButton.setAttribute('aria-expanded', String(open)); menuButton.textContent = open ? 'Close' : 'Menu'; }); } } function showModule(name) { currentModule = name; Object.entries(modules).forEach(([key, element]) => element.classList.toggle('hidden', key !== name)); document.querySelectorAll('.nav-item[data-module]').forEach((button) => { button.classList.toggle('active', button.dataset.module === name); }); const titles = { dashboard: 'Dashboard', reports: 'ITP Reports', search: 'Search', sharing: 'Sharing', account: 'My Account', settings: 'Settings' }; document.querySelector('#page-title').textContent = titles[name] || 'Dashboard'; if (name === 'account') renderAccount(modules.account, showAuth); if (name === 'dashboard') renderDashboard(modules.dashboard, { onOpenReport: openDashboardReport }); if (name === 'search') renderSearch(modules.search); } function showAuth() { stopAutoCloudPull(); dashboardView.classList.add('hidden'); authView.classList.remove('hidden'); } function showApp(moduleName = currentModule || 'dashboard') { authView.classList.add('hidden'); dashboardView.classList.remove('hidden'); showModule(moduleName); setCloudStatus('Saving/loading cloud reports...', 'saving'); // Full cloud sync: pull saved reports from Supabase onto this device after login. loadCloudReports() .then(async () => { renderAllModules(); showModule(moduleName); setCloudStatus('All changes saved', 'saved'); await maybeShowCloudMigrationPrompt(); startAutoCloudPull(); }) .catch((error) => { console.warn('Cloud sync failed. Local reports are still available.', error?.message || error); setCloudStatus('Offline - changes saved locally', 'offline'); }); } function bindAutoCloudSyncEvents() { window.addEventListener('online', () => runAutoCloudPull('Back online - pulling latest reports...')); window.addEventListener('focus', () => runAutoCloudPull('Checking cloud for latest reports...')); document.addEventListener('visibilitychange', () => { if (!document.hidden) runAutoCloudPull('Checking cloud for latest reports...'); }); } function startAutoCloudPull() { stopAutoCloudPull(); autoCloudPullTimer = window.setInterval(() => { runAutoCloudPull('Checking cloud for latest reports...'); }, AUTO_CLOUD_PULL_INTERVAL_MS); } function stopAutoCloudPull() { if (autoCloudPullTimer) { window.clearInterval(autoCloudPullTimer); autoCloudPullTimer = null; } } async function runAutoCloudPull(statusText = 'Checking cloud for latest reports...') { if (dashboardView.classList.contains('hidden')) return; if (autoCloudPullInProgress) return; if (!navigator.onLine) { setCloudStatus('Offline - changes saved locally', 'offline'); return; } autoCloudPullInProgress = true; setCloudStatus(statusText, 'saving'); try { await refreshFromCloud(); renderAllModules(); showModule(currentModule); setCloudStatus('All changes saved', 'saved'); } catch (error) { console.warn('Automatic cloud pull failed.', error?.message || error); setCloudStatus('Offline - changes saved locally', 'offline'); } finally { autoCloudPullInProgress = false; } } async function maybeShowCloudMigrationPrompt() { if (localStorage.getItem(CLOUD_MIGRATION_SKIP_KEY) === 'true') return; try { const pendingReports = await getLocalReportsPendingCloud(); if (!pendingReports.length) return; showCloudMigrationModal(pendingReports.length); } catch (error) { console.warn('Cloud migration prompt skipped.', error?.message || error); } } function showCloudMigrationModal(reportCount) { const existing = document.querySelector('#cloud-migration-modal'); if (existing) existing.remove(); const modal = document.createElement('div'); modal.id = 'cloud-migration-modal'; modal.className = 'cloud-modal'; modal.innerHTML = `
We've found ${reportCount} local ITP report${reportCount === 1 ? '' : 's'} on this device.
Would you like to upload ${reportCount === 1 ? 'it' : 'them'} to your Streamline ITP account so ${reportCount === 1 ? 'it is' : 'they are'} available on your mobile, tablet, laptop and desktop?
Your local copies will be kept as a backup.