import { NETLIFY_URL, SUPABASE_URL, SUPABASE_PROJECT_URL, SUPABASE_ANON_KEY, getBasePath } from './config.js'; import { updateNavState } from './ui.js'; import { closeProfileModal, openProfileModal } from './profile.js'; import { getSession, forceLogin, clearSession, supabase } from './state.js'; export { supabase }; export async function signInWithSocial(provider) { const isIframe = window.self !== window.top; const basePath = getBasePath(); const redirectTo = window.location.origin + basePath + (isIframe ? '/auth_callback.html' : '/index.html'); if (isIframe) { const { data, error } = await supabase.auth.signInWithOAuth({ provider: provider, options: { redirectTo: redirectTo, skipBrowserRedirect: true } }); if (error) { console.error('Social login error:', error); alert('Login failed: ' + error.message); return; } if (data && data.url) { const width = 600, height = 700; const left = (window.screen.width - width) / 2; const top = (window.screen.height - height) / 2; window.open(data.url, 'tinytorch_auth', `width=${width},height=${height},left=${left},top=${top}`); } } else { const { data, error } = await supabase.auth.signInWithOAuth({ provider: provider, options: { redirectTo: redirectTo } }); if (error) { console.error('Social login error:', error); alert('Login failed: ' + error.message); } } } // Listen for popup auth messages window.addEventListener('message', async (event) => { if (event.origin !== window.location.origin) return; if (event.data && event.data.type === 'TINY_TORCH_AUTH_SUCCESS') { const session = event.data.session; if (session) { console.log("Auth success message received in iframe, syncing session..."); const { error } = await supabase.auth.setSession({ access_token: session.access_token, refresh_token: session.refresh_token }); if (error) { console.error("Error setting session from popup:", error); } else { // If we are on community page, just close the modal. // onAuthStateChange in app.js will handle the rest. if (window.location.pathname.includes('community.html')) { closeModal(); } } } } }); let currentMode = 'signup'; export async function refreshToken() { const refreshToken = localStorage.getItem("tinytorch_refresh_token"); if (!refreshToken) { return false; } try { const { data, error } = await supabase.auth.refreshSession({ refresh_token: refreshToken }); if (error || !data.session) { console.error("Supabase refresh failed:", error); return false; } const session = data.session; localStorage.setItem("tinytorch_token", session.access_token); if (session.refresh_token) { localStorage.setItem("tinytorch_refresh_token", session.refresh_token); } if (session.user) { localStorage.setItem("tinytorch_user", JSON.stringify(session.user)); } return session.access_token; } catch (e) { console.error("Token refresh failed", e); } return false; } export async function verifySession() { const { token } = getSession(); const refreshTokenStr = localStorage.getItem("tinytorch_refresh_token"); if (!token) return; // 1. Ensure Supabase Client is synced // If the page reloaded, the Supabase client might not have initialized the session // from its own storage yet, or persistence might have failed. // We check if it knows about the session. const { data: { session: sbSession } } = await supabase.auth.getSession(); // If Supabase client is empty but we have tokens (from Direct Email login), restore it. if (!sbSession && refreshTokenStr) { const { error } = await supabase.auth.setSession({ access_token: token, refresh_token: refreshTokenStr }); if (error) { console.warn("Auto-sync setSession failed:", error); } } try { // Use a lightweight call to check validity. // We use get-profile-details but we could also just decode if we trusted client time. // A network call is safer. const response = await fetch(`${SUPABASE_URL}/get-profile-details`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (response.ok) { return; // Token is valid } if (response.status === 401 || response.status === 403) { console.log("Session verification failed. Attempting refresh..."); const newToken = await refreshToken(); if (newToken) { console.log("Session refreshed successfully."); updateNavState(); // Update UI with new state if needed } else { console.warn("Session refresh failed. Clearing session."); clearSession(); updateNavState(); } } } catch (e) { console.error("Session verification error", e); } } export function setMode(mode) { const emailInput = document.getElementById('authEmail'); const passwordInput = document.getElementById('authPassword'); const authTitle = document.getElementById('authTitle'); const authSubmit = document.getElementById('authSubmit'); const authToggle = document.getElementById('authToggle'); const forgotContainer = document.getElementById('authForgotContainer'); const authError = document.getElementById('authError'); const authForm = document.getElementById('authForm'); if (!emailInput) return; // Guard if DOM not ready const previousEmail = emailInput.value; currentMode = mode; authForm.reset(); authError.style.display = 'none'; authError.textContent = ''; if (mode === 'forgot') { emailInput.value = ''; } else { emailInput.value = previousEmail; } if (mode === 'login') { authTitle.textContent = 'Login'; // Check for confirmed_email param const params = new URLSearchParams(window.location.search); if (params.get('confirmed_email') === 'true') { authTitle.textContent = 'Thank you for confirming your email. Please login.'; // Clean up URL const newUrl = window.location.pathname + '?action=login'; window.history.replaceState({}, '', newUrl); } authSubmit.textContent = 'Login'; authToggle.textContent = 'Need an account? Create Account'; passwordInput.classList.remove('hidden'); passwordInput.required = true; forgotContainer.classList.remove('hidden'); } else if (mode === 'signup') { authTitle.textContent = 'Create Account'; authSubmit.textContent = 'Create Account'; authToggle.textContent = 'Already have an account? Login'; passwordInput.classList.remove('hidden'); passwordInput.required = true; forgotContainer.classList.add('hidden'); } else if (mode === 'forgot') { authTitle.textContent = 'Reset Password'; authSubmit.textContent = 'Send Reset Link'; authToggle.textContent = 'Back to Login'; passwordInput.classList.add('hidden'); passwordInput.required = false; forgotContainer.classList.add('hidden'); } } export function handleToggle() { if (currentMode === 'login') { setMode('signup'); } else if (currentMode === 'signup') { setMode('login'); } else if (currentMode === 'forgot') { setMode('login'); } } export function showMessageModal(title, body, onCloseCallback = null) { const overlay = document.getElementById('messageOverlay'); const titleEl = document.getElementById('messageTitle'); const bodyEl = document.getElementById('messageBody'); const btnEl = document.getElementById('messageBtn'); if (overlay && titleEl && bodyEl) { titleEl.textContent = title; bodyEl.textContent = body; overlay.classList.add('active'); // Remove previous listeners to avoid duplicates if reused const newBtn = btnEl.cloneNode(true); btnEl.parentNode.replaceChild(newBtn, btnEl); newBtn.addEventListener('click', () => { overlay.classList.remove('active'); if (onCloseCallback) onCloseCallback(); }); } } export async function handleAuth(e) { e.preventDefault(); const emailInput = document.getElementById('authEmail'); const passwordInput = document.getElementById('authPassword'); const authError = document.getElementById('authError'); const authSubmit = document.getElementById('authSubmit'); const basePath = getBasePath(); const email = emailInput.value; const password = passwordInput.value; authError.style.display = 'none'; // Save original text const originalBtnText = authSubmit.textContent; // Show spinner authSubmit.disabled = true; authSubmit.innerHTML = '
'; try { // Direct Supabase flow for Signup if (currentMode === 'signup') { const redirectUrl = window.location.origin + basePath + '/index.html?action=login&confirmed_email=true'; console.log("Requesting signup with redirect to:", redirectUrl); const { error } = await supabase.auth.signUp({ email, password, options: { emailRedirectTo: redirectUrl } }); if (error) throw error; closeModal(); showMessageModal( 'Check your Email', 'If you don\'t already have an account, we have sent you an email. Please check your inbox to confirm your signup.' ); return; } // Direct Supabase flow for Password Reset if (currentMode === 'forgot') { const redirectUrl = window.location.origin + basePath + '/index.html?action=reset-password'; console.log("Requesting password reset with redirect to:", redirectUrl); const { error } = await supabase.auth.resetPasswordForEmail(email, { redirectTo: redirectUrl }); if (error) throw error; showMessageModal('Reset Link Sent', 'If an account exists, a reset link has been sent.'); setMode('login'); return; } // Keep existing API flow for Login const endpoint = '/api/auth/login'; const body = { email, password, redirect_to: window.location.origin + basePath + '/index.html?action=login&confirmed_email=true' }; const url = `${NETLIFY_URL}${endpoint}`; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), credentials: 'include' }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || 'Login failed'); } if (data.access_token) { localStorage.setItem("tinytorch_token", data.access_token); if (data.refresh_token) localStorage.setItem("tinytorch_refresh_token", data.refresh_token); localStorage.setItem("tinytorch_user", JSON.stringify(data.user)); // Sync Supabase Client so it doesn't trigger SIGNED_OUT if (data.refresh_token) { const { error: sessionError } = await supabase.auth.setSession({ access_token: data.access_token, refresh_token: data.refresh_token }); if (sessionError) { console.error("Supabase setSession error during login:", sessionError); } } updateNavState(); // Check Profile Completeness immediately const { data: profile } = await supabase .from('profiles') .select('display_name, institution, location') .eq('id', data.user.id) .single(); const hasName = profile && profile.display_name; const hasInst = profile && profile.institution && (Array.isArray(profile.institution) ? profile.institution.length > 0 : !!profile.institution); const hasLoc = profile && profile.location; const params = new URLSearchParams(window.location.search); const nextParam = params.get('next'); closeModal(); // Always close modal on success if (nextParam) { // Ensure nextParam is a clean path if it was encoded const cleanNext = decodeURIComponent(nextParam).split('?')[0]; window.location.href = cleanNext; return; } if (!hasName || !hasInst || !hasLoc) { window.location.href = basePath + '/profile_setup.html'; return; } if (params.get('action') === 'profile') { openProfileModal(); } else { window.location.href = basePath + '/dashboard.html'; } } } catch (error) { console.error("Auth error:", error); authError.textContent = error.message; authError.style.display = 'block'; } finally { authSubmit.disabled = false; // Restore button text based on current mode, as logic might have changed mode or just finished if (currentMode === 'login') authSubmit.textContent = 'Login'; else if (currentMode === 'signup') authSubmit.textContent = 'Create Account'; else authSubmit.textContent = 'Send Reset Link'; } } export async function handleLogout() { const basePath = getBasePath(); if (confirm('Are you sure you want to logout?')) { await supabase.auth.signOut(); localStorage.removeItem("tinytorch_token"); localStorage.removeItem("tinytorch_refresh_token"); localStorage.removeItem("tinytorch_user"); sessionStorage.removeItem("tinytorch_location_checked"); updateNavState(); closeProfileModal(); window.location.href = basePath + '/index.html'; } } export function openModal(mode = 'signup') { const authOverlay = document.getElementById('authOverlay'); authOverlay.classList.add('active'); setMode(mode); } export function closeModal() { const authOverlay = document.getElementById('authOverlay'); authOverlay.classList.remove('active'); }