feat: give each Site product and the cart its own page
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m28s
Build and deploy / Secret scan and release gate (push) Successful in 8s
Build and deploy / Publish images (push) Successful in 1m52s
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m28s
Build and deploy / Secret scan and release gate (push) Successful in 8s
Build and deploy / Publish images (push) Successful in 1m52s
The home, each product's Montagem and the cart now have their own addresses (/artes-avulsas, /arquivo-por-metro, /uv-artes-avulsas, /uv-arquivo-por-metro, /carrinho) and show only their own content, with Back, Forward, reload and direct links working as in any store. They stay one document so uploaded artworks survive moving between pages; nginx serves index.html for these addresses. "Adicionar ao carrinho" puts the item in the cart and opens it, and an empty cart says so. Portal quote links open in the cart. Also fixes the "57 cm" line break on the ready-sheet option, returns "Novo pedido" to the home, and says PDF depends on the product. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,52 +1,112 @@
|
||||
/* Site DTF — the order progress bar, the product card prices and the phone
|
||||
total bar. This file only reads the page's state (which panels are open,
|
||||
the cart total, the checkout status); it never changes an order, a price or
|
||||
a file, so the flow in the other site-*.js files behaves exactly as before. */
|
||||
(function(){
|
||||
// Card prices come from the same table the checkout uses, so they cannot drift.
|
||||
document.querySelectorAll('[data-de]').forEach(el=>{ el.textContent=rs(pisoEscada(el.dataset.de)); });
|
||||
document.querySelectorAll('[data-ate]').forEach(el=>{ el.textContent=rs(TABELA[el.dataset.ate]); });
|
||||
/* Site DTF — moving between the Site's pages (addresses in site-pages.js), the
|
||||
order progress bar, the product card prices and the phone total bar. It only
|
||||
switches which page is shown and reads the page's state (the open product,
|
||||
the cart, the checkout status); orders, prices and files stay with the other
|
||||
site-*.js files. */
|
||||
// Card prices come from the same table the checkout uses, so they cannot drift.
|
||||
document.querySelectorAll('[data-de]').forEach(el=>{ el.textContent=rs(pisoEscada(el.dataset.de)); });
|
||||
document.querySelectorAll('[data-ate]').forEach(el=>{ el.textContent=rs(TABELA[el.dataset.ate]); });
|
||||
|
||||
const TITULO_INICIO=document.title;
|
||||
function tituloPagina(){
|
||||
const p=paginaDe(location.pathname);
|
||||
if(p.pagina==='carrinho') return 'Carrinho · DTF Dropstar';
|
||||
if(p.pagina==='produto') return MODOS[p.modo].tit+' · DTF Dropstar';
|
||||
return TITULO_INICIO;
|
||||
}
|
||||
|
||||
// Shows the page for the current address; `ancora` scrolls to a section,
|
||||
// otherwise the page opens at `y` (the saved position on Back and Forward).
|
||||
function mostraPagina(ancora,y,suave){
|
||||
const p=paginaDe(location.pathname);
|
||||
if(p.pagina==='produto' && modo!==p.modo) abrir(p.modo);
|
||||
document.documentElement.dataset.rota=p.pagina;
|
||||
$('cards').style.display='';
|
||||
document.title=tituloPagina();
|
||||
window.dispatchEvent(new Event('dtf-page-changed'));
|
||||
const el=ancora && document.getElementById(ancora);
|
||||
if(el) el.scrollIntoView({behavior:suave?'smooth':'auto',block:'start'});
|
||||
else window.scrollTo(0,y||0);
|
||||
}
|
||||
function vaiPara(url){
|
||||
const u=new URL(url,location.href);
|
||||
const ancora=u.hash.slice(1)||null;
|
||||
if(u.pathname+u.search===location.pathname+location.search){
|
||||
mostraPagina(ancora,0,true);
|
||||
return;
|
||||
}
|
||||
try{ history.replaceState({...(history.state||{}),y:scrollY},''); }catch(e){}
|
||||
history.pushState({y:0},'',u.pathname+u.search+u.hash);
|
||||
mostraPagina(ancora,0,false);
|
||||
}
|
||||
|
||||
(function(){
|
||||
if('scrollRestoration' in history) history.scrollRestoration='manual';
|
||||
addEventListener('popstate',e=>mostraPagina(e.state?null:location.hash.slice(1)||null,e.state?.y));
|
||||
|
||||
// Links to the Site's own pages move inside this document; everything else
|
||||
// (the account portal, the store, e-mail) is a normal link.
|
||||
document.addEventListener('click',e=>{
|
||||
const a=e.target.closest && e.target.closest('a[href]');
|
||||
if(!a || e.defaultPrevented || e.button || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || a.target) return;
|
||||
const u=new URL(a.href,location.href);
|
||||
if(u.origin!==location.origin) return;
|
||||
const caminho=u.pathname.replace(/\/+$/,'')||'/';
|
||||
if(caminho!=='/' && caminho!=='/index.html' && paginaDe(caminho).pagina==='inicio') return;
|
||||
e.preventDefault(); vaiPara(u.pathname+u.search+u.hash);
|
||||
});
|
||||
|
||||
const passos=$('passos'), barra=$('barraM'), vazio=$('carrVazio');
|
||||
const foco=$('foco'), carr=$('carr'), status=$('checkoutStatus'), acoes=$('checkoutActions');
|
||||
const rota=()=>document.documentElement.dataset.rota;
|
||||
const temCheckout=()=>!!(status.textContent||'').trim() || acoes.children.length>0;
|
||||
|
||||
const passos=$('passos'), barra=$('barraM');
|
||||
// The store header is sticky; the progress bar and the side panels sit below it.
|
||||
function alturas(){
|
||||
const topo=document.querySelector('.topo')?.offsetHeight||0;
|
||||
document.documentElement.style.setProperty('--topo',topo+'px');
|
||||
document.documentElement.style.setProperty('--topo-passos',(topo+(passos.hidden?0:passos.offsetHeight)+16)+'px');
|
||||
}
|
||||
const foco=$('foco'), carr=$('carr'), status=$('checkoutStatus'), acoes=$('checkoutActions');
|
||||
|
||||
function naTela(el){
|
||||
const r=el.getBoundingClientRect();
|
||||
return r.top < innerHeight*0.55 && r.bottom > innerHeight*0.2;
|
||||
return r.height>0 && r.top<innerHeight && r.bottom>0;
|
||||
}
|
||||
function etapa(){
|
||||
// Once the cart is sent, the quote, the payment and the order live in the
|
||||
// checkout status: that is the payment step.
|
||||
if((status.textContent||'').trim() || acoes.children.length) return 3;
|
||||
if(carr.classList.contains('on') && (naTela(carr) || !foco.classList.contains('on'))) return 2;
|
||||
return 1;
|
||||
// Where the phone bar's button leads: the button that finishes the item on a
|
||||
// product page, the order summary in the cart.
|
||||
function alvoBarra(){
|
||||
if(rota()==='produto' && itemAtual)
|
||||
return $('prev').classList.contains('on') ? $('pOk') : $('qOk');
|
||||
if(rota()==='carrinho' && (pedido.length || itemAtual) && !temCheckout())
|
||||
return document.querySelector('.resumo');
|
||||
return null;
|
||||
}
|
||||
let pendente=false;
|
||||
function pinta(){
|
||||
pendente=false;
|
||||
const ativo=foco.classList.contains('on') || carr.classList.contains('on') || (status.textContent||'').trim();
|
||||
passos.hidden=!ativo; alturas();
|
||||
if(ativo){
|
||||
const atual=etapa();
|
||||
const r=rota();
|
||||
// With nothing in it, the cart page says so instead of showing an empty form.
|
||||
const semItens = !pedido.length && !itemAtual;
|
||||
const carrinhoVazio = r==='carrinho' && semItens && !temCheckout();
|
||||
document.documentElement.toggleAttribute('data-sem-itens', semItens);
|
||||
vazio.hidden = !carrinhoVazio;
|
||||
passos.hidden = r==='inicio' || carrinhoVazio;
|
||||
alturas();
|
||||
if(!passos.hidden){
|
||||
const atual = r==='produto' ? 1 : temCheckout() ? 3 : 2;
|
||||
passos.querySelectorAll('[data-passo]').forEach(a=>{
|
||||
const n=Number(a.dataset.passo);
|
||||
a.classList.toggle('atual',n===atual); a.classList.toggle('feito',n<atual);
|
||||
if(n===atual) a.setAttribute('aria-current','step'); else a.removeAttribute('aria-current');
|
||||
});
|
||||
passos.querySelector('[data-passo="1"]').href =
|
||||
modo && foco.classList.contains('on') ? PAGINAS[modo] : '/#envio';
|
||||
}
|
||||
// Phone bar: the running total while the cart itself is off screen.
|
||||
const temPedido=carr.classList.contains('on');
|
||||
const mostrar=temPedido && !naTela(carr) && !(status.textContent||'').trim();
|
||||
const alvo=alvoBarra();
|
||||
const mostrar=!!alvo && !naTela(alvo);
|
||||
barra.hidden=!mostrar;
|
||||
if(mostrar){
|
||||
$('barraMTot').textContent=$('carrTot').textContent;
|
||||
$('barraMSub').textContent=foco.classList.contains('on')?'Total até agora':'Seu pedido';
|
||||
$('barraMSub').textContent= r==='produto' ? 'Total até agora' : 'Seu pedido';
|
||||
}
|
||||
}
|
||||
function agenda(){ if(!pendente){ pendente=true; requestAnimationFrame(pinta); } }
|
||||
@@ -54,19 +114,24 @@
|
||||
const obs=new MutationObserver(agenda);
|
||||
obs.observe(foco,{attributes:true,attributeFilter:['class']});
|
||||
obs.observe(carr,{attributes:true,attributeFilter:['class']});
|
||||
obs.observe($('prev'),{attributes:true,attributeFilter:['class']});
|
||||
obs.observe(status,{childList:true,characterData:true,subtree:true});
|
||||
obs.observe(acoes,{childList:true});
|
||||
obs.observe($('carrTot'),{childList:true,characterData:true,subtree:true});
|
||||
addEventListener('scroll',agenda,{passive:true});
|
||||
addEventListener('resize',agenda);
|
||||
addEventListener('dtf-cart-changed',agenda);
|
||||
addEventListener('dtf-page-changed',agenda);
|
||||
|
||||
// Steps are links; step 3 only makes sense once there is something to pay.
|
||||
passos.querySelectorAll('a').forEach(a=>a.addEventListener('click',e=>{
|
||||
const destino=document.querySelector(a.getAttribute('href'));
|
||||
if(!destino || destino.offsetParent===null){ e.preventDefault(); return; }
|
||||
e.preventDefault(); destino.scrollIntoView({behavior:'smooth',block:'start'});
|
||||
}));
|
||||
$('barraMBtn').addEventListener('click',e=>{ e.preventDefault(); carr.scrollIntoView({behavior:'smooth',block:'start'}); });
|
||||
pinta();
|
||||
// Payment is a step only once the order has been sent.
|
||||
passos.querySelector('[data-passo="3"]').addEventListener('click',e=>{
|
||||
if(!temCheckout()) e.preventDefault();
|
||||
});
|
||||
$('barraMBtn').addEventListener('click',e=>{
|
||||
e.preventDefault();
|
||||
alvoBarra()?.scrollIntoView({behavior:'smooth',block:'center'});
|
||||
});
|
||||
|
||||
try{ history.replaceState({...(history.state||{}),y:0},''); }catch(e){}
|
||||
mostraPagina(location.hash.slice(1)||null,0);
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user