2. Initialise the client (replace with your project URL + anon key): const supabase = window.supabase.createClient( 'https://YOUR-PROJECT.supabase.co', 'YOUR-PUBLIC-ANON-KEY' ); 3. Replace the four "DEMO MODE" blocks below (signIn email, signIn Google, signIn Microsoft, signOut, resetPassword, and the auth gate) with the real Supabase calls noted in each block. 4. Set up Google + Microsoft OAuth in Supabase dashboard (Authentication → Providers). 5. Configure the password-reset email template + redirect URL in Supabase dashboard (Authentication → Email Templates). =========================================================== */ // ---- Auth gate ---- var loginScreen = document.getElementById('loginScreen'); var dashShell = document.getElementById('dashShell'); function showDash() { loginScreen.style.display = 'none'; dashShell.style.display = 'grid'; } function showLogin() { loginScreen.style.display = 'flex'; dashShell.style.display = 'none'; } // [SUPABASE] Replace this sessionStorage check with: // const { data: { session } } = await supabase.auth.getSession(); // if (session) showDash(); else showLogin(); if (sessionStorage.getItem('responza_customer') === '1') showDash(); else showLogin(); // ---- Email + password sign-in ---- document.getElementById('adminLoginForm').addEventListener('submit', function (e) { e.preventDefault(); var email = document.getElementById('loginEmail').value.trim(); var pass = document.getElementById('loginPass').value.trim(); if (!email || !pass) { document.getElementById('adminLoginError').style.display = 'block'; return; } // [SUPABASE] Replace this DEMO MODE block with: // supabase.auth.signInWithPassword({ email, password: pass }) // .then(function (res) { // if (res.error) { showError(res.error.message); return; } // showDash(); location.hash = '#profil'; // }); // ---- DEMO MODE: any non-empty email + password works ---- sessionStorage.setItem('responza_customer', '1'); showDash(); location.hash = '#profil'; }); // ---- Google OAuth sign-in ---- document.getElementById('loginGoogle').addEventListener('click', function () { // [SUPABASE] Replace this DEMO MODE block with: // supabase.auth.signInWithOAuth({ // provider: 'google', // options: { redirectTo: window.location.origin + '/admin.html' } // }); // ---- DEMO MODE ---- sessionStorage.setItem('responza_customer', '1'); showDash(); location.hash = '#profil'; }); // ---- Microsoft OAuth sign-in ---- document.getElementById('loginMicrosoft').addEventListener('click', function () { // [SUPABASE] Replace this DEMO MODE block with: // supabase.auth.signInWithOAuth({ // provider: 'azure', // options: { redirectTo: window.location.origin + '/admin.html', scopes: 'email' } // }); // ---- DEMO MODE ---- sessionStorage.setItem('responza_customer', '1'); showDash(); location.hash = '#profil'; }); // ---- Forgot password ---- document.getElementById('forgotPassword').addEventListener('click', function (e) { e.preventDefault(); var email = prompt('Skriv inn e-postadressen din, så sender vi deg en lenke for å sette nytt passord:'); if (!email) return; // [SUPABASE] Replace this DEMO MODE block with: // supabase.auth.resetPasswordForEmail(email, { // redirectTo: window.location.origin + '/reset-password.html' // }).then(function () { // alert('Hvis kontoen finnes, har vi sendt en lenke til ' + email); // }); // ---- DEMO MODE ---- alert('DEMO: vil sende reset-lenke til ' + email + '. Wire up supabase.auth.resetPasswordForEmail() to make this real.'); }); // ---- Sign out ---- document.getElementById('adminLogout').addEventListener('click', function (e) { e.preventDefault(); // [SUPABASE] Replace this DEMO MODE block with: // supabase.auth.signOut().then(function () { // showLogin(); location.hash = ''; // }); // ---- DEMO MODE ---- sessionStorage.removeItem('responza_customer'); sessionStorage.removeItem('responza_first_login'); sessionStorage.removeItem('responza_onboarding_done'); showLogin(); location.hash = ''; }); /* =========================================================== ONBOARDING WIZARD (5 steps) =========================================================== Shown automatically on first login (sessionStorage flag set by signup.html). After completion, sends data to backend: [SUPABASE] On final step: const { error } = await supabase .from('onboarding_submissions') .insert({ tenant_id: currentTenantId, business_info: { ... }, common_questions: [...], tone_of_voice: '...', brand: { color, logo_url, bot_name, greeting }, knowledge_files: [...], extra_notes: '...' }); // Then trigger an email to the Responza team via Resend/Postmark =========================================================== */ var onbStep = 1; var onbTotalSteps = 5; function showOnbStep(n) { onbStep = Math.max(1, Math.min(onbTotalSteps, n)); document.querySelectorAll('.onb-step').forEach(function (el) { el.style.display = el.getAttribute('data-step') === String(onbStep) ? 'block' : 'none'; }); document.getElementById('onbStepNum').textContent = onbStep; document.getElementById('onbProgress').style.width = (onbStep / onbTotalSteps * 100) + '%'; var nextBtn = document.getElementById('onbNext'); var lang = (localStorage.getItem('responza_lang') === 'en') ? 'en' : 'no'; if (onbStep === onbTotalSteps) { nextBtn.textContent = lang === 'en' ? '✓ Submit and start setup' : '✓ Send inn og start oppsett'; } else { nextBtn.textContent = lang === 'en' ? 'Continue →' : 'Fortsett →'; } document.getElementById('onbBack').style.visibility = onbStep === 1 ? 'hidden' : 'visible'; } document.getElementById('onbNext').addEventListener('click', function () { if (onbStep < onbTotalSteps) { showOnbStep(onbStep + 1); window.scrollTo({top:0,behavior:'smooth'}); } else { /* [SUPABASE] On final submit, gather form data + insert into onboarding_submissions table. Then mark the bot as 'building' status, and trigger an email to the team. */ sessionStorage.setItem('responza_onboarding_done', '1'); sessionStorage.removeItem('responza_first_login'); // Update bot status tile to "Building" updateBotStatus('building'); window.respShowToast && respShowToast('Onboarding sendt! Vi bygger boten din nå.'); showSection('profil'); location.hash = '#profil'; } }); document.getElementById('onbBack').addEventListener('click', function () { if (onbStep > 1) { showOnbStep(onbStep - 1); window.scrollTo({top:0,behavior:'smooth'}); } }); // Auto-show onboarding on first login if (sessionStorage.getItem('responza_first_login') === '1' && sessionStorage.getItem('responza_onboarding_done') !== '1') { showSection('welcome'); location.hash = '#welcome'; var bizName = sessionStorage.getItem('responza_business_name'); if (bizName) { var inp = document.getElementById('onbBizName'); if (inp) inp.value = bizName; } showOnbStep(1); } /* =========================================================== BOT STATUS TILE — three states =========================================================== Reads tenant.bot_status from Supabase. Possible values: 'pending' — paid but onboarding not done 'building' — onboarding done, team is configuring 'live' — embed is on customer site, bot answering 'paused' — subscription cancelled or payment failed [SUPABASE] On page load: const { data } = await supabase.from('tenants') .select('bot_status, embedded_url, last_response_at') .eq('owner_id', user.id).single(); updateBotStatus(data.bot_status, data.embedded_url, data.last_response_at); =========================================================== */ function updateBotStatus(status, url, lastResponse) { var icon = document.getElementById('botStatusIcon'); var title = document.getElementById('botStatusTitle'); var sub = document.getElementById('botStatusSub'); if (!icon) return; var lang = (localStorage.getItem('responza_lang') === 'en') ? 'en' : 'no'; var svgs = { pending: '', building: '', live: '', paused: '' }; var titles = { pending: { no: 'Vent på onboarding', en: 'Onboarding pending' }, building: { no: 'Vi bygger boten din', en: 'Building your bot' }, live: { no: 'Boten din er live', en: 'Your bot is live' }, paused: { no: 'Boten er pauset', en: 'Bot is paused' } }; var subs = { pending: { no: 'Fullfør onboarding for å starte oppsettet.', en: 'Complete onboarding to start setup.' }, building: { no: 'Snitt-leveringstid: 24-48 timer. Du får e-post når den er live.', en: 'Average build time: 24-48 hours. You will get an email when live.' }, live: { no: 'Bygget inn på minbedrift.no · siste svar for 4 minutter siden', en: 'Embedded on minbedrift.no · last response 4 minutes ago' }, paused: { no: 'Aktivt abonnement kreves. Sjekk fakturering.', en: 'Active subscription required. Check billing.' } }; var colors = { pending: '#F3A424', building: '#5B5BD6', live: '#00C6A5', paused: '#DC2626' }; icon.style.background = colors[status] || colors.live; icon.innerHTML = svgs[status] || svgs.live; title.textContent = (titles[status] && titles[status][lang]) || titles.live[lang]; sub.textContent = (subs[status] && subs[status][lang]) || subs.live[lang]; } // Default state: live (in real app, fetch from Supabase tenant.bot_status) updateBotStatus('live'); // ---- Section routing ---- function showSection(id) { document.querySelectorAll('.admin-section').forEach(function (s) { s.classList.toggle('active', s.id === id); }); document.querySelectorAll('.admin-nav a').forEach(function (a) { a.classList.toggle('active', a.getAttribute('data-section') === id); }); window.scrollTo({ top: 0, behavior: 'smooth' }); } document.querySelectorAll('.admin-nav a[data-section]').forEach(function (a) { a.addEventListener('click', function (e) { e.preventDefault(); var s = a.getAttribute('data-section'); showSection(s); location.hash = '#' + s; }); }); if (location.hash) { var h = location.hash.slice(1); if (document.getElementById(h)) showSection(h); } // ---- Time pills (Analytics) ---- var periodData = { day: { visitors: '142', chatted: '63', engagement: '44,4%', leads: '11', label: 'I dag', labelEn: 'Today' }, week: { visitors: '781', chatted: '342', engagement: '43,8%', leads: '59', label: 'Denne uken', labelEn: 'This week' }, month: { visitors: '3 247', chatted: '1 412', engagement: '43,5%', leads: '229', label: 'Denne måneden', labelEn: 'This month' }, all: { visitors: '34 218',chatted: '14 803',engagement: '43,3%', leads: '2 541',label: 'Totalt', labelEn: 'All time' } }; function applyPeriod(p) { var d = periodData[p]; document.querySelectorAll('[data-metric]').forEach(function (el) { var key = el.getAttribute('data-metric'); if (d[key] !== undefined) el.textContent = d[key]; }); var lang = (localStorage.getItem('responza_lang') === 'en') ? 'en' : 'no'; document.querySelectorAll('[data-period-label]').forEach(function (el) { el.textContent = lang === 'en' ? d.labelEn : d.label; }); } document.querySelectorAll('.time-pills').forEach(function (group) { group.querySelectorAll('button').forEach(function (b) { b.addEventListener('click', function () { group.querySelectorAll('button').forEach(function (x) { x.classList.toggle('active', x === b); }); applyPeriod(b.getAttribute('data-period')); }); }); }); // ---- Bar chart ---- var chart = document.getElementById('activityChart'); if (chart) { var heights = [22,18,28,34,29,25,40,36,42,55,48,52,58,46,62,71,68,55,48,52,60,72,68,80,76,82,79,88,92,76]; chart.innerHTML = heights.map(function (h, i) { var d = 30 - i; return ''; }).join(''); } // ---- Customizer ---- var widget = document.getElementById('ccWidget'); function setVar(name, value) { if (widget) widget.style.setProperty(name, value); } function syncColorPair(picker, text) { var p = document.getElementById(picker); var t = document.getElementById(text); if (!p || !t) return; p.addEventListener('input', function () { t.value = p.value.toUpperCase(); applyAll(); }); t.addEventListener('input', function () { var v = t.value.trim(); if (/^#[0-9A-Fa-f]{6}$/.test(v)) { p.value = v; applyAll(); } }); } syncColorPair('primaryColor', 'primaryColorText'); syncColorPair('accentColor', 'accentColorText'); document.querySelectorAll('[data-pos]').forEach(function (b) { b.addEventListener('click', function () { document.querySelectorAll('[data-pos]').forEach(function (x) { x.classList.toggle('active', x === b); }); applyAll(); }); }); document.querySelectorAll('[data-size]').forEach(function (b) { b.addEventListener('click', function () { document.querySelectorAll('[data-size]').forEach(function (x) { x.classList.toggle('active', x === b); }); applyAll(); }); }); var nameInput = document.getElementById('botName'); if (nameInput) nameInput.addEventListener('input', function (e) { document.getElementById('ccBotName').textContent = e.target.value || 'Responza'; updateEmbed(); }); var greetInput = document.getElementById('greetingMsg'); if (greetInput) greetInput.addEventListener('input', function (e) { document.getElementById('ccGreeting').textContent = e.target.value || 'Hei!'; }); var powToggle = document.getElementById('poweredByToggle'); if (powToggle) powToggle.addEventListener('change', function (e) { document.getElementById('ccPowered').style.display = e.target.checked ? '' : 'none'; }); function applyAll() { if (!widget) return; var primary = document.getElementById('primaryColor').value; var accent = document.getElementById('accentColor').value; var pos = (document.querySelector('[data-pos].active') || {}).getAttribute && document.querySelector('[data-pos].active').getAttribute('data-pos') || 'br'; var size = (document.querySelector('[data-size].active') || {}).getAttribute && document.querySelector('[data-size].active').getAttribute('data-size') || 'm'; setVar('--cc-primary', primary); setVar('--cc-accent', accent); widget.classList.remove('pos-bl', 'pos-br', 'size-s', 'size-m', 'size-l'); widget.classList.add('pos-' + pos, 'size-' + size); updateEmbed(); } function updateEmbed() { var primary = (document.getElementById('primaryColor') || {}).value || '#00C6A5'; var accent = (document.getElementById('accentColor') || {}).value || '#10B981'; var posEl = document.querySelector('[data-pos].active'); var sizeEl = document.querySelector('[data-size].active'); var pos = posEl ? posEl.getAttribute('data-pos') : 'br'; var size = sizeEl ? sizeEl.getAttribute('data-size') : 'm'; var code = '\n<' + '/script>'; var pre = document.getElementById('embedCode'); if (pre) pre.textContent = code; } var saveBtn = document.getElementById('saveCustomizer'); if (saveBtn) saveBtn.addEventListener('click', function () { showToast('Endringer lagret. Chatboten oppdateres umiddelbart.'); }); var copyBtn = document.getElementById('copyEmbed'); if (copyBtn) copyBtn.addEventListener('click', function () { var btn = this; var code = document.getElementById('embedCode').textContent; if (navigator.clipboard) { navigator.clipboard.writeText(code).then(function () { btn.classList.add('copied'); var span = btn.querySelector('span'); var orig = span.textContent; span.textContent = 'Kopiert ✓'; showToast('Embed-kode kopiert.'); setTimeout(function () { btn.classList.remove('copied'); span.textContent = orig; }, 2000); }); } }); function showToast(msg) { var t = document.getElementById('toast'); if (!t) return; t.textContent = msg; t.classList.add('show'); setTimeout(function () { t.classList.remove('show'); }, 2400); } window.respShowToast = showToast; })();