// Customer login has no relationship to the protected staff workspace. var CustomerLogin = { create(options){ const overlay=document.getElementById('customer-login'); const form=document.getElementById('customer-login-form'); const username=document.getElementById('customer-username'); const password=document.getElementById('customer-password'); const submit=document.getElementById('customer-submit'); const cancel=document.getElementById('customer-cancel'); const error=document.getElementById('customer-error'); const account=document.getElementById('customer-account'); let user, pending=null, opener=null, busy=false, version=0; overlay.hidden=true; function status(message){ error.textContent=message || ''; error.hidden=!message; } function render(){ account.textContent=user ? user.name+' ยท '+options.text('customerLogout') : options.text('customerLogin'); submit.textContent=options.text(busy ? 'customerSigningIn' : 'customerLogin'); cancel.textContent=options.text('customerCancel'); document.getElementById('customer-login-title').textContent=options.text('customerLogin'); document.getElementById('customer-username-label').textContent=options.text('customerUsername'); document.getElementById('customer-password-label').textContent=options.text('customerPassword'); username.placeholder=options.text('customerUsernamePlaceholder'); password.placeholder=options.text('customerPasswordPlaceholder'); } function update(next){ const previous=user; user=next; render(); if(options.onChange && (previous === undefined || (previous && previous.id) !== (user && user.id))){ options.onChange(user,previous); } } async function request(path, data){ const controller=new AbortController(); const timer=setTimeout(()=>controller.abort(),10000); try{ const response=await options.fetch(path,{method:data === undefined ? 'GET' : 'POST', headers:{'Content-Type':'application/json'},body:data === undefined ? undefined : JSON.stringify(data),signal:controller.signal}); const result=await response.json(); if(!response.ok || !result.ok){ const failure=new Error(result.error || result.e || options.text('customerLoginFailed')); failure.publicMessage=true;throw failure; } return result; }finally{ clearTimeout(timer); } } async function refresh(){ if(busy) return; const token=++version; try{ const result=await request('/api/customer/session'); if(token === version) update(result.authenticated && result.user ? result.user : null); }catch(error){} // Query endpoints still enforce login if status cannot refresh. } function close(){ overlay.hidden=true; password.value=''; pending=null; if(opener && opener.isConnected) opener.focus({preventScroll:true}); opener=null; } function open(onSuccess){ if(busy) return; pending=typeof onSuccess === 'function' ? onSuccess : null; opener=document.activeElement; status(''); password.value=''; overlay.hidden=false; if(options.onOpen) options.onOpen(); render(); username.focus({preventScroll:true}); } function setBusy(value){ busy=value; submit.disabled=value; cancel.disabled=value; account.disabled=value; username.disabled=value; password.disabled=value; render(); } form.addEventListener('submit',async function(event){ event.preventDefault(); if(busy) return; const token=++version; setBusy(true);status(''); try{ const result=await request('/api/customer/login',{username:username.value,password:password.value}); if(token !== version) return; update(result.user); const resume=pending;close(); if(resume) await resume(); }catch(failure){ if(token === version) status(failure.publicMessage ? failure.message : options.text('customerLoginFailed')); }finally{ if(token === version) setBusy(false); } }); cancel.onclick=function(){ if(!busy) close(); }; overlay.addEventListener('click',function(event){ if(event.target === overlay && !busy) close(); }); overlay.addEventListener('keydown',function(event){ if(event.key === 'Escape' && !busy){ event.preventDefault();close(); } if(event.key === 'Tab'){ const controls=[username,password,submit,cancel].filter(control=>!control.disabled); if(!controls.length){ event.preventDefault();return; } const index=controls.indexOf(document.activeElement); const next=(index+(event.shiftKey ? -1 : 1)+controls.length)%controls.length; event.preventDefault();controls[next].focus({preventScroll:true}); } }); account.onclick=async function(){ if(busy) return; if(!user){ open(); return; } const token=++version;setBusy(true); try{ await request('/api/customer/logout',{}); if(token === version) update(null); }catch(failure){ if(options.onError) options.onError(options.text('customerLogoutFailed')); } finally{ if(token === version) setBusy(false); } }; window.addEventListener('focus',refresh); document.addEventListener('visibilitychange',function(){ if(!document.hidden) refresh(); }); render(); return {open:open,refresh:refresh,render:render,require:async function(resume, canContinue){ if(busy) return; await refresh(); if(canContinue && !canContinue(user)) return; if(user){ await resume(user); }else open(function(){ return resume(user); }); },viewport:function(height,top){ overlay.style.height=height || ''; overlay.style.top=top || ''; overlay.style.bottom=height ? 'auto' : ''; }}; } };