@unbox-plus/cli 0.20.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +46 -0
- package/README.md +175 -0
- package/bin/cli.js +439 -0
- package/package.json +43 -0
- package/src/colors.js +100 -0
- package/src/presets.js +241 -0
- package/src/theme.js +253 -0
- package/template/.claude/agents/assets-marca.md +40 -0
- package/template/.claude/agents/avaliador-visual.md +48 -0
- package/template/.claude/agents/branding-briefing.md +401 -0
- package/template/.claude/agents/conteudo-secao.md +46 -0
- package/template/.claude/agents/paginas-legais.md +44 -0
- package/template/.claude/settings.json +3 -0
- package/template/.claude/skills/frontend-design/ATTRIBUTION.md +3 -0
- package/template/.claude/skills/frontend-design/LICENSE.txt +177 -0
- package/template/.claude/skills/frontend-design/SKILL.md +55 -0
- package/template/.claude/skills/web-design-guidelines/ATTRIBUTION.md +3 -0
- package/template/.claude/skills/web-design-guidelines/SKILL.md +50 -0
- package/template/.claude/skills/web-design-guidelines/guidelines-snapshot.md +180 -0
- package/template/.env.example +158 -0
- package/template/.mcp.json.example +14 -0
- package/template/.nvmrc +1 -0
- package/template/CLAUDE.md +333 -0
- package/template/COVERAGE.md +80 -0
- package/template/DEPLOY.md +179 -0
- package/template/QA.md +108 -0
- package/template/README.md +152 -0
- package/template/agents/CONSTRUCAO.md +207 -0
- package/template/agents/MANAGER.md +192 -0
- package/template/agents/PADROES.md +255 -0
- package/template/agents/definitions/00-scaffold.md +56 -0
- package/template/agents/definitions/01-layout.md +47 -0
- package/template/agents/definitions/02-homepage.md +70 -0
- package/template/agents/definitions/03-catalog.md +86 -0
- package/template/agents/definitions/04-pdp.md +97 -0
- package/template/agents/definitions/05-cart.md +116 -0
- package/template/agents/definitions/06-checkout.md +146 -0
- package/template/agents/definitions/07-auth.md +130 -0
- package/template/agents/definitions/08-customer.md +55 -0
- package/template/agents/definitions/09-promotions.md +62 -0
- package/template/agents/definitions/10-feedback.md +128 -0
- package/template/agents/definitions/11-seo-infra.md +159 -0
- package/template/agents/definitions/12-deploy.md +173 -0
- package/template/agents/definitions/13-cro.md +275 -0
- package/template/agents/definitions/14-seo.md +388 -0
- package/template/agents/definitions/15-branding.md +26 -0
- package/template/agents/definitions/16-qa-visual.md +138 -0
- package/template/agents/definitions/17-aeo.md +257 -0
- package/template/app/(loja)/busca/page.tsx +72 -0
- package/template/app/(loja)/carrinho/oferta/page.tsx +57 -0
- package/template/app/(loja)/carrinho/page.tsx +160 -0
- package/template/app/(loja)/categoria/[tagSlug]/layout.tsx +31 -0
- package/template/app/(loja)/categoria/[tagSlug]/loading.tsx +17 -0
- package/template/app/(loja)/categoria/[tagSlug]/page.tsx +49 -0
- package/template/app/(loja)/checkout/page.tsx +80 -0
- package/template/app/(loja)/checkout/pix/[ref]/page.tsx +133 -0
- package/template/app/(loja)/conta/assinaturas/[referenceId]/page.tsx +112 -0
- package/template/app/(loja)/conta/assinaturas/page.tsx +51 -0
- package/template/app/(loja)/conta/enderecos/page.tsx +20 -0
- package/template/app/(loja)/conta/entrar/page.tsx +119 -0
- package/template/app/(loja)/conta/page.tsx +45 -0
- package/template/app/(loja)/conta/pedidos/[referenceId]/page.tsx +49 -0
- package/template/app/(loja)/conta/pedidos/page.tsx +104 -0
- package/template/app/(loja)/conta/preferencias/page.tsx +24 -0
- package/template/app/(loja)/devolucoes/page.tsx +36 -0
- package/template/app/(loja)/layout.tsx +22 -0
- package/template/app/(loja)/oferta/page.tsx +57 -0
- package/template/app/(loja)/page.tsx +86 -0
- package/template/app/(loja)/pedido/[referenceId]/page.tsx +78 -0
- package/template/app/(loja)/privacidade/page.tsx +113 -0
- package/template/app/(loja)/produto/[productSlug]/layout.tsx +23 -0
- package/template/app/(loja)/produto/[productSlug]/loading.tsx +20 -0
- package/template/app/(loja)/produto/[productSlug]/page.tsx +372 -0
- package/template/app/(loja)/produtos/loading.tsx +16 -0
- package/template/app/(loja)/produtos/page.tsx +33 -0
- package/template/app/(loja)/termos/page.tsx +104 -0
- package/template/app/acesso/page.tsx +129 -0
- package/template/app/api/account/addresses/route.ts +39 -0
- package/template/app/api/account/exists/route.ts +21 -0
- package/template/app/api/account/me/route.ts +25 -0
- package/template/app/api/account/otp/route.ts +26 -0
- package/template/app/api/account/preferences/route.ts +19 -0
- package/template/app/api/account/signin/route.ts +27 -0
- package/template/app/api/account/signout/route.ts +9 -0
- package/template/app/api/acesso/route.ts +171 -0
- package/template/app/api/capi/route.ts +34 -0
- package/template/app/api/cart/coupon/route.ts +49 -0
- package/template/app/api/cart/items/route.ts +45 -0
- package/template/app/api/cart/link/route.ts +69 -0
- package/template/app/api/cart/route.ts +113 -0
- package/template/app/api/cart/share/route.ts +21 -0
- package/template/app/api/cep/[code]/route.ts +18 -0
- package/template/app/api/checkout/address/route.ts +24 -0
- package/template/app/api/checkout/email/route.ts +41 -0
- package/template/app/api/checkout/installments/route.ts +16 -0
- package/template/app/api/checkout/route.ts +110 -0
- package/template/app/api/checkout/shipping/route.ts +65 -0
- package/template/app/api/checkout-destination/route.ts +31 -0
- package/template/app/api/order/[ref]/route.ts +17 -0
- package/template/app/api/payment-link/route.ts +28 -0
- package/template/app/api/revalidate/route.ts +52 -0
- package/template/app/api/shipping/quote/route.ts +29 -0
- package/template/app/api/subscriptions/[id]/route.ts +52 -0
- package/template/app/api/track/route.ts +28 -0
- package/template/app/api/unbox/catalogo/route.ts +136 -0
- package/template/app/api/unbox/paginas/route.ts +167 -0
- package/template/app/api/unbox/vitrine/route.ts +84 -0
- package/template/app/api/webhooks/unbox/route.ts +97 -0
- package/template/app/apple-icon.svg +5 -0
- package/template/app/error.tsx +15 -0
- package/template/app/globals.css +457 -0
- package/template/app/icon.svg +4 -0
- package/template/app/layout.tsx +90 -0
- package/template/app/llms.txt/route.ts +43 -0
- package/template/app/manifest.ts +21 -0
- package/template/app/not-found.tsx +18 -0
- package/template/app/opengraph-image.tsx +35 -0
- package/template/app/robots.ts +31 -0
- package/template/app/sitemap.ts +42 -0
- package/template/bootstrap.sh +87 -0
- package/template/components/account/account-shell.tsx +47 -0
- package/template/components/account/address-book.tsx +147 -0
- package/template/components/account/preferences-form.tsx +77 -0
- package/template/components/account/reorder-button.tsx +47 -0
- package/template/components/account/signout-button.tsx +20 -0
- package/template/components/account/subscription-actions.tsx +204 -0
- package/template/components/account-nav.tsx +41 -0
- package/template/components/address-fields.tsx +102 -0
- package/template/components/analytics/data-layer-ready.tsx +16 -0
- package/template/components/analytics/purchase-tracker.tsx +32 -0
- package/template/components/brand-search.tsx +34 -0
- package/template/components/cart/cart-provider.tsx +340 -0
- package/template/components/cart/mini-cart.tsx +228 -0
- package/template/components/cart-button.tsx +35 -0
- package/template/components/catalog/catalog-client.tsx +523 -0
- package/template/components/catalog/grid-skeleton.tsx +19 -0
- package/template/components/catalog/pager.tsx +66 -0
- package/template/components/catalog/product-grid-card.tsx +52 -0
- package/template/components/catalog/product-grid.tsx +27 -0
- package/template/components/checkout/checkout-client.tsx +1333 -0
- package/template/components/chrome/announce-bar.tsx +57 -0
- package/template/components/chrome/chrome-recipe.ts +13 -0
- package/template/components/chrome/footers/captura-botao.tsx +28 -0
- package/template/components/chrome/footers/colunas.tsx +110 -0
- package/template/components/chrome/footers/conversao.tsx +90 -0
- package/template/components/chrome/footers/editorial.tsx +98 -0
- package/template/components/chrome/footers/minimal.tsx +55 -0
- package/template/components/chrome/header-bar-mobile.tsx +64 -0
- package/template/components/chrome/headers/centralizado.tsx +71 -0
- package/template/components/chrome/headers/classico.tsx +64 -0
- package/template/components/chrome/headers/compacto.tsx +66 -0
- package/template/components/chrome/headers/equilibrado.tsx +73 -0
- package/template/components/chrome/headers/imersivo.tsx +62 -0
- package/template/components/chrome/registry.ts +70 -0
- package/template/components/chrome/solid-on-scroll.tsx +39 -0
- package/template/components/empty-state.tsx +22 -0
- package/template/components/home/combo-card-compact.tsx +67 -0
- package/template/components/home/combos-home.tsx +64 -0
- package/template/components/home/combos-section.tsx +177 -0
- package/template/components/home/home-recipe.ts +41 -0
- package/template/components/home/sections/attributes-marquee.tsx +178 -0
- package/template/components/home/sections/benefits.tsx +80 -0
- package/template/components/home/sections/bloco-html.tsx +47 -0
- package/template/components/home/sections/catalogo.ts +165 -0
- package/template/components/home/sections/category-pills.tsx +32 -0
- package/template/components/home/sections/combos-carousel.tsx +141 -0
- package/template/components/home/sections/comparison.tsx +73 -0
- package/template/components/home/sections/founder-story.tsx +40 -0
- package/template/components/home/sections/hero.tsx +187 -0
- package/template/components/home/sections/kits.tsx +11 -0
- package/template/components/home/sections/media-cards.tsx +70 -0
- package/template/components/home/sections/newsletter.tsx +62 -0
- package/template/components/home/sections/product-showcase.tsx +175 -0
- package/template/components/home/sections/purchase-hero.tsx +187 -0
- package/template/components/home/sections/quote-banner.tsx +50 -0
- package/template/components/home/sections/registry.ts +186 -0
- package/template/components/home/sections/reviews-carousel.tsx +66 -0
- package/template/components/home/sections/reviews.tsx +68 -0
- package/template/components/home/sections/ritual.tsx +96 -0
- package/template/components/home/sections/savings.tsx +47 -0
- package/template/components/home/sections/social-row.tsx +65 -0
- package/template/components/home/sections/spec-table.tsx +117 -0
- package/template/components/home/sections/stars.tsx +12 -0
- package/template/components/home/sections/stats-grid.tsx +54 -0
- package/template/components/home/sections/trust-bar.tsx +56 -0
- package/template/components/home/sections/trust-strip.tsx +40 -0
- package/template/components/home/sections/video-wall.tsx +59 -0
- package/template/components/landing/landing-recipe.ts +20 -0
- package/template/components/landing/oferta-sections.tsx +40 -0
- package/template/components/landing/product-picker.tsx +257 -0
- package/template/components/mobile-nav.tsx +71 -0
- package/template/components/order-status.tsx +163 -0
- package/template/components/powered-by-unbox.tsx +27 -0
- package/template/components/product/pdp/buy-box.tsx +604 -0
- package/template/components/product/pdp/catalog-grid.tsx +113 -0
- package/template/components/product/pdp/faq-modelo.ts +86 -0
- package/template/components/product/pdp/gallery.tsx +87 -0
- package/template/components/product/pdp/interactive.tsx +200 -0
- package/template/components/product/pdp/newsletter.tsx +64 -0
- package/template/components/product/pdp/payment-chips.tsx +22 -0
- package/template/components/product/pdp/pdp-view.tsx +227 -0
- package/template/components/product/pdp/recommendations.tsx +144 -0
- package/template/components/product/pdp/sections.tsx +173 -0
- package/template/components/quantity-stepper.tsx +36 -0
- package/template/components/search-box.tsx +37 -0
- package/template/components/site-footer.tsx +59 -0
- package/template/components/site-header.tsx +64 -0
- package/template/components/ui/accordion.tsx +72 -0
- package/template/components/ui/alert-dialog.tsx +187 -0
- package/template/components/ui/alert.tsx +76 -0
- package/template/components/ui/aspect-ratio.tsx +22 -0
- package/template/components/ui/avatar.tsx +109 -0
- package/template/components/ui/badge.tsx +52 -0
- package/template/components/ui/breadcrumb.tsx +125 -0
- package/template/components/ui/button.tsx +58 -0
- package/template/components/ui/card.tsx +103 -0
- package/template/components/ui/carousel.tsx +242 -0
- package/template/components/ui/command.tsx +196 -0
- package/template/components/ui/dialog.tsx +160 -0
- package/template/components/ui/dropdown-menu.tsx +268 -0
- package/template/components/ui/foto.tsx +54 -0
- package/template/components/ui/input-group.tsx +158 -0
- package/template/components/ui/input-otp.tsx +87 -0
- package/template/components/ui/input.tsx +20 -0
- package/template/components/ui/label.tsx +20 -0
- package/template/components/ui/navigation-menu.tsx +168 -0
- package/template/components/ui/pagination.tsx +132 -0
- package/template/components/ui/popover.tsx +90 -0
- package/template/components/ui/progress.tsx +83 -0
- package/template/components/ui/radio-group.tsx +38 -0
- package/template/components/ui/scroll-area.tsx +55 -0
- package/template/components/ui/select.tsx +201 -0
- package/template/components/ui/separator.tsx +25 -0
- package/template/components/ui/sheet.tsx +138 -0
- package/template/components/ui/skeleton.tsx +13 -0
- package/template/components/ui/sonner.tsx +44 -0
- package/template/components/ui/switch.tsx +32 -0
- package/template/components/ui/table.tsx +116 -0
- package/template/components/ui/tabs.tsx +82 -0
- package/template/components/ui/textarea.tsx +18 -0
- package/template/components/ui/toggle-group.tsx +89 -0
- package/template/components/ui/toggle.tsx +45 -0
- package/template/components/ui/tooltip.tsx +66 -0
- package/template/components.json +25 -0
- package/template/eslint.config.mjs +36 -0
- package/template/gitignore +21 -0
- package/template/lib/analytics.ts +369 -0
- package/template/lib/api.ts +60 -0
- package/template/lib/capi.ts +87 -0
- package/template/lib/cart-link.ts +51 -0
- package/template/lib/cart-normalize.ts +142 -0
- package/template/lib/cart-recovery.ts +23 -0
- package/template/lib/cart-response.ts +48 -0
- package/template/lib/catalog-map.ts +54 -0
- package/template/lib/catalog.ts +3 -0
- package/template/lib/checkout-lock.ts +21 -0
- package/template/lib/checkout-nav.ts +22 -0
- package/template/lib/config.ts +51 -0
- package/template/lib/crm.ts +40 -0
- package/template/lib/customer-session.ts +21 -0
- package/template/lib/dataloader.ts +23 -0
- package/template/lib/editable/config.ts +11 -0
- package/template/lib/editable/document.ts +1762 -0
- package/template/lib/editable/index.ts +3 -0
- package/template/lib/editable/primitives.tsx +821 -0
- package/template/lib/editable/provider.tsx +861 -0
- package/template/lib/editable/rastreio-navegacao.tsx +65 -0
- package/template/lib/editable/rastreio.tsx +168 -0
- package/template/lib/editable/server.ts +189 -0
- package/template/lib/editable/tokens.ts +22 -0
- package/template/lib/editable/verify.ts +24 -0
- package/template/lib/enrichment/combos.ts +184 -0
- package/template/lib/enrichment/index.ts +188 -0
- package/template/lib/enrichment/products.json +1 -0
- package/template/lib/env-check.ts +47 -0
- package/template/lib/format.ts +130 -0
- package/template/lib/icons.ts +67 -0
- package/template/lib/json-ld.ts +9 -0
- package/template/lib/llms-txt.ts +75 -0
- package/template/lib/mockup.ts +24 -0
- package/template/lib/newsletter.ts +12 -0
- package/template/lib/orders.ts +160 -0
- package/template/lib/queries.ts +66 -0
- package/template/lib/ratelimit.ts +47 -0
- package/template/lib/rotas-editaveis.ts +192 -0
- package/template/lib/sanitize.ts +36 -0
- package/template/lib/schemas.ts +78 -0
- package/template/lib/session.ts +98 -0
- package/template/lib/store-config.ts +67 -0
- package/template/lib/unbox/client.ts +829 -0
- package/template/lib/unbox/customer.ts +224 -0
- package/template/lib/unbox/errors.ts +97 -0
- package/template/lib/unbox/index.ts +10 -0
- package/template/lib/unbox/store.ts +113 -0
- package/template/lib/unbox/types.ts +195 -0
- package/template/lib/unbox/webhooks.ts +66 -0
- package/template/lib/utils.ts +6 -0
- package/template/lib/vitrine.ts +242 -0
- package/template/lib/webhook-store.ts +22 -0
- package/template/middleware.ts +178 -0
- package/template/next.config.ts +63 -0
- package/template/package.json +62 -0
- package/template/postcss.config.mjs +5 -0
- package/template/public/brand/coll/default.png +0 -0
- package/template/public/brand/hero-desktop.svg +12 -0
- package/template/public/brand/hero-mobile.svg +12 -0
- package/template/public/brand/heros/boutique-desktop.svg +11 -0
- package/template/public/brand/heros/boutique-mobile.svg +11 -0
- package/template/public/brand/heros/editorial-desktop.svg +11 -0
- package/template/public/brand/heros/editorial-mobile.svg +11 -0
- package/template/public/brand/heros/essencial-desktop.svg +12 -0
- package/template/public/brand/heros/essencial-mobile.svg +12 -0
- package/template/public/brand/heros/promocional-desktop.svg +12 -0
- package/template/public/brand/heros/promocional-mobile.svg +12 -0
- package/template/public/brand/logo-chrome.svg +6 -0
- package/template/public/brand/logo-white.svg +6 -0
- package/template/public/brand/logo.svg +7 -0
- package/template/public/brand/pay-amex.webp +0 -0
- package/template/public/brand/pay-elo.webp +0 -0
- package/template/public/brand/pay-mastercard.webp +0 -0
- package/template/public/brand/pay-pix.webp +0 -0
- package/template/public/brand/pay-visa.webp +0 -0
- package/template/public/brand/ph/avatar-a.svg +5 -0
- package/template/public/brand/ph/avatar-b.svg +5 -0
- package/template/public/brand/ph/avatar-c.svg +5 -0
- package/template/public/brand/ph/avatar-d.svg +5 -0
- package/template/public/brand/ph/photo-a.svg +7 -0
- package/template/public/brand/ph/photo-b.svg +6 -0
- package/template/public/brand/ph/photo-c.svg +6 -0
- package/template/public/brand/ph/poster-a.svg +6 -0
- package/template/public/brand/ph/poster-b.svg +6 -0
- package/template/public/brand/ph/poster-c.svg +6 -0
- package/template/public/unbox/powered-by-fundo-preto.png +0 -0
- package/template/public/unbox/powered-by-transparente.png +0 -0
- package/template/public/unbox/powered-by.png +0 -0
- package/template/scripts/abandoned-cart.ts +82 -0
- package/template/scripts/check-editable.mjs +467 -0
- package/template/scripts/check-honestidade.mjs +111 -0
- package/template/scripts/check-placeholder.mjs +200 -0
- package/template/scripts/check-recipe.mjs +76 -0
- package/template/scripts/check-unbox-brand.mjs +298 -0
- package/template/scripts/contraste.js +177 -0
- package/template/scripts/dump-catalog.ts +95 -0
- package/template/scripts/load-env.ts +13 -0
- package/template/scripts/medir-sistema.mjs +56 -0
- package/template/scripts/place-order-pix.ts +61 -0
- package/template/scripts/qa-screenshots.mjs +150 -0
- package/template/scripts/sistema.py +231 -0
- package/template/scripts/subscribe-webhook.ts +36 -0
- package/template/scripts/test-live.ts +186 -0
- package/template/scripts/vocabulario.py +264 -0
- package/template/tsconfig.json +27 -0
- package/tools/LEIA-ME.md +39 -0
- package/tools/check-template-neutro.mjs +111 -0
- package/tools/notion-doc.mjs +191 -0
- package/tools/tokenize-neutrals.mjs +0 -0
- package/tools/tokenize-radius.mjs +52 -0
- package/tools/workflow-storefront.legado.js +1207 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# Mapa do projeto
|
|
2
|
+
|
|
3
|
+
Storefront Next.js 15 (App Router, Tailwind v4, shadcn) gerado pelo `create-unbox-store`,
|
|
4
|
+
conectado à API headless da Unbox. São ~220 arquivos e ~18 mil linhas: **não cabe em contexto,
|
|
5
|
+
nem perto.** Este arquivo existe pra você não precisar procurar.
|
|
6
|
+
|
|
7
|
+
## Antes de abrir qualquer componente
|
|
8
|
+
|
|
9
|
+
Três arquivos indexam quase tudo. Juntos custam ~2k tokens e substituem ~30k de leitura às
|
|
10
|
+
cegas. Leia o que for do seu caso **antes** de sair abrindo `.tsx`:
|
|
11
|
+
|
|
12
|
+
| Arquivo | O que indexa | Custo |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| `agents/PADROES.md` §2 | **o catálogo**: o que cada uma das 23 seções é, suas variantes e quando cabe | ~4,3k |
|
|
15
|
+
| `components/home/sections/registry.ts` | os nomes válidos de seção (union tipada, o `tsc` reprova nome inventado) | ~0,9k |
|
|
16
|
+
| `components/chrome/registry.ts` | os 5 headers e 4 rodapés, com quando usar cada um | ~0,7k |
|
|
17
|
+
|
|
18
|
+
Para **escolher** uma seção, leia o PADROES §2, não os componentes. Para **escrever** o nome dela
|
|
19
|
+
na receita, o registry é a fonte da verdade. Os arquivos de chrome (`headers/`, `footers/`) abrem
|
|
20
|
+
com um comentário de "quando usar / quando não usar"; os de seção não, por isso o catálogo.
|
|
21
|
+
|
|
22
|
+
## Quero mudar X, mexo em Y
|
|
23
|
+
|
|
24
|
+
### Identidade e aparência
|
|
25
|
+
| Quero | Arquivo |
|
|
26
|
+
|---|---|
|
|
27
|
+
| Cores da marca, tipografia, raio, ritmo vertical, largura de container | `app/globals.css` (tokens `--store-*` no topo; o resto do arquivo deriva daí) |
|
|
28
|
+
| Trocar o logo | `public/brand/logo.svg` (+ `logo-white.svg`, `logo-chrome.svg` para fundo escuro) |
|
|
29
|
+
| Intensidade de animação/parallax | token `--motion` em `app/globals.css` (`0` desliga tudo) |
|
|
30
|
+
| Qual header e qual rodapé a loja usa | `components/chrome/chrome-recipe.ts` (só troca os nomes; as variantes já existem) |
|
|
31
|
+
| Barra de aviso do topo | `components/chrome/announce-bar.tsx` |
|
|
32
|
+
| Menu mobile | `components/chrome/header-bar-mobile.tsx` |
|
|
33
|
+
|
|
34
|
+
**Não edite** `site-header.tsx` / `site-footer.tsx` para trocar aparência. Eles são a casca fixa:
|
|
35
|
+
fazem o fetch, derivam o nome da loja e montam o `<header>`/`<footer>` raiz. O miolo variável
|
|
36
|
+
está em `components/chrome/headers/` e `footers/`.
|
|
37
|
+
|
|
38
|
+
### Conteúdo e estrutura
|
|
39
|
+
| Quero | Arquivo |
|
|
40
|
+
|---|---|
|
|
41
|
+
| Quais seções a home tem, em que ordem, com que texto | `components/home/home-recipe.ts`, **um array só, com as props de conteúdo dentro** |
|
|
42
|
+
| Criar uma seção que não existe | novo arquivo em `components/home/sections/` + entrada no `registry.ts` + registro no `PADROES.md` |
|
|
43
|
+
| Página de produto (PDP) | `app/(loja)/produto/[productSlug]/page.tsx` + `components/product/` |
|
|
44
|
+
| Listagem, busca, categoria | `app/(loja)/{produtos,busca,categoria}/` + `components/catalog/` |
|
|
45
|
+
| Carrinho / checkout | `app/(loja)/{carrinho,checkout}/` + `components/{cart,checkout}/` |
|
|
46
|
+
| Textos de produto enriquecidos (FAQ, benefícios, combos) | `lib/enrichment/` (`products.json`, `combos.ts`) |
|
|
47
|
+
| Termos, privacidade, devoluções | `app/(loja)/{termos,privacidade,devolucoes}/` |
|
|
48
|
+
| Landing de campanha | `app/(loja)/oferta/` + `components/landing/` |
|
|
49
|
+
|
|
50
|
+
### Promessas comerciais: a área perigosa
|
|
51
|
+
`lib/store-config.ts` concentra desconto do Pix, limiar de frete grátis e régua de brinde.
|
|
52
|
+
Os defaults saem **zerados de propósito**: cada valor ali é uma promessa exibida ao cliente, e
|
|
53
|
+
prometer o que o backend não cumpre faz o checkout mostrar um total e cobrar outro. Só preencha
|
|
54
|
+
depois de confirmar a promoção real com `npm run unbox:dump`. O gate `unbox:honestidade` cobra
|
|
55
|
+
isso.
|
|
56
|
+
|
|
57
|
+
### Integração e infra
|
|
58
|
+
| Quero | Arquivo |
|
|
59
|
+
|---|---|
|
|
60
|
+
| O que o `/llms.txt` diz a agentes de IA | `lib/llms-txt.ts` (formato) + `app/llms.txt/route.ts` (dados). É ROTA, montada do catálogo real: nunca crie `public/llms.txt`, ele esconderia a rota |
|
|
61
|
+
| Credenciais, domínio, IDs de analytics | `.env.local` |
|
|
62
|
+
| Cliente da API Unbox, queries, tipos | `lib/unbox/` e `lib/queries.ts` |
|
|
63
|
+
| Porta de preview (prévia privada) | `middleware.ts` + `app/acesso/` |
|
|
64
|
+
| Recuperação de carrinho abandonado | `lib/cart-recovery.ts` (o link com id e token nasce aí) |
|
|
65
|
+
| Tracking (GA4, GTM, Meta Pixel, CAPI) | `lib/analytics.ts`, a camada ÚNICA; nenhum outro arquivo empurra no dataLayer |
|
|
66
|
+
| O que o cliente respondeu no formulário do CLI | `marca/briefing.json` |
|
|
67
|
+
| Editor da Unbox: o que o lojista edita sem código | `lib/editable/` (foundation, não edite à mão), `lib/rotas-editaveis.ts` (páginas e containers), `app/api/unbox/*`, `app/api/revalidate` |
|
|
68
|
+
|
|
69
|
+
## Comandos
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
npm run dev # servidor local
|
|
73
|
+
npm run typecheck # tsc --noEmit, o gate mais barato, rode primeiro
|
|
74
|
+
npm run build # dispara o prebuild de marca automaticamente
|
|
75
|
+
npm run unbox:honestidade # promessas comerciais sem lastro
|
|
76
|
+
npm run unbox:receita # variedade da receita (mede, não bloqueia)
|
|
77
|
+
npm run unbox:qa # screenshots do QA (emulação de dispositivo, servidor no ar)
|
|
78
|
+
npm run unbox:dump # imprime o catálogo e as promoções REAIS da loja
|
|
79
|
+
npm run unbox:editavel # gate do editor: cobertura editável por página (servidor no ar)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Ordem barata → cara: `typecheck` → `unbox:honestidade` → `unbox:receita` → `build` → só então
|
|
83
|
+
subir o servidor e capturar tela. Screenshot é o passo mais caro do ciclo; não use como primeiro
|
|
84
|
+
diagnóstico.
|
|
85
|
+
|
|
86
|
+
## Medidores de sistema visual (relatório, ainda não gate)
|
|
87
|
+
|
|
88
|
+
`npm run unbox:medir` roda dois medidores de Python que contam o que faz uma loja ter "cara de
|
|
89
|
+
template": quantos tamanhos de tipo, raios, hex fora de token e larguras de container existem
|
|
90
|
+
(`scripts/sistema.py`), e se o vocabulário gráfico da marca foi distribuído ou vive numa seção só
|
|
91
|
+
(`scripts/vocabulario.py`). Rodam no `prebuild` e **não bloqueiam**: imprimem e saem 0.
|
|
92
|
+
|
|
93
|
+
Não são gate ainda de propósito. Medido em cinco lojas geradas e na própria foundation, todas
|
|
94
|
+
estouram os tetos que vieram no script, a foundation inclusive: ligar como gate hoje reprovaria
|
|
95
|
+
até um scaffold recém-criado. Os números servem para escolher a régua depois, e para o agente de
|
|
96
|
+
branding ver o custo do que acrescenta enquanto trabalha.
|
|
97
|
+
|
|
98
|
+
Sem python3 na máquina, os medidores são pulados com aviso. Nunca derrubam build.
|
|
99
|
+
|
|
100
|
+
## Placeholder no ar (antes de publicar)
|
|
101
|
+
|
|
102
|
+
`npm run unbox:placeholder` varre o **HTML servido**, não o código-fonte. É a diferença que
|
|
103
|
+
decide dois casos: varredura de fonte acusa `TODO` em comentário de componente que nem está na
|
|
104
|
+
receita, e não acha o texto que vaza sem existir como string, como `[NOME DA LOJA]` vindo do
|
|
105
|
+
conteúdo ou um logotipo com o nome errado dentro de um SVG.
|
|
106
|
+
|
|
107
|
+
Precisa de build de produção. Sem base explícita ele sobe o próprio servidor a partir do `.next`
|
|
108
|
+
e mede a si mesmo: **nunca** aponte para "o que estiver na porta 3000", que foi como a primeira
|
|
109
|
+
versão deste gate mediu outro projeto e quase aprovou a loja errada. Para medir o que está no ar:
|
|
110
|
+
`PLACEHOLDER_BASE=https://a-loja.com.br npm run unbox:placeholder`.
|
|
111
|
+
|
|
112
|
+
**Scaffold recém-criado reprova de propósito**, porque as páginas legais nascem com `[NOME DA
|
|
113
|
+
LOJA]` e `[CNPJ]` para o lojista preencher. Por isso o gate NÃO está no `prebuild`: ele é da
|
|
114
|
+
publicação, não do build. Imagem raster não é varrida por texto, e o script avisa quando existe
|
|
115
|
+
alguma: logotipo errado dentro de um PNG só aparece olhando.
|
|
116
|
+
|
|
117
|
+
## Contraste e classificação de cor
|
|
118
|
+
|
|
119
|
+
`scripts/contraste.js` é para COLAR no console do DevTools com a loja aberta. Duas funções:
|
|
120
|
+
`contraste()` varre o texto visível e lista o que reprova; `classificar("#HEX")` diz se a cor
|
|
121
|
+
pode receber texto (**superfície**) ou só serve para contorno, ícone e faixa (**acento**).
|
|
122
|
+
|
|
123
|
+
A classificação é o uso principal: ao receber a paleta, meça cada cor contra a tinta e contra o
|
|
124
|
+
branco. Passa em algum dos dois (≥4,5) é superfície; não passa em nenhum é acento e **nunca**
|
|
125
|
+
vira fundo de texto. Se a marca quiser aquela cor como fundo, gere a versão funda e ponha branco
|
|
126
|
+
em cima. **Escreva o número ao lado do token no CSS**, senão o próximo agente refaz a mesma
|
|
127
|
+
avaliação e erra de novo.
|
|
128
|
+
|
|
129
|
+
O CLI não escolhe cor sozinho "para passar no WCAG": corrigir cor de marca em silêncio
|
|
130
|
+
descaracteriza a marca. Mede, aplica o que a marca pediu, e deixa o número e o custo escritos.
|
|
131
|
+
|
|
132
|
+
Duas coisas que a ferramenta não resolve, e estão ditas no cabeçalho dela: texto sobre foto (é
|
|
133
|
+
preciso amostrar os pixels da imagem) e conclusão tirada de captura de tela (esqueleto de
|
|
134
|
+
carregamento já enganou a leitura; confirme no DOM).
|
|
135
|
+
|
|
136
|
+
## Não negociável
|
|
137
|
+
|
|
138
|
+
O prebuild (`scripts/check-unbox-brand.mjs`) **bloqueia o build** se faltar:
|
|
139
|
+
- `<PoweredByUnbox />` em `components/site-footer.tsx`
|
|
140
|
+
- o container GTM em `app/layout.tsx`
|
|
141
|
+
|
|
142
|
+
Ele também avisa (sem bloquear) sobre logo, ícones, manifest e paleta ainda no placeholder, a
|
|
143
|
+
paleta default é canvas provisória, não escolha estética, e trocar pelas cores reais da marca é
|
|
144
|
+
o primeiro ato do briefing.
|
|
145
|
+
|
|
146
|
+
## Editor: o que não pode quebrar
|
|
147
|
+
|
|
148
|
+
A loja nasce ligada ao editor da Unbox (`editor.myunbox.com.br`): o lojista edita texto, foto, cor
|
|
149
|
+
e vitrine no painel, e a loja lê o publicado por HTTP e aplica por cima do código. A fiação é do
|
|
150
|
+
scaffold e tem oito pontos; mexer em qualquer um deles sem saber o que faz desliga o editor em
|
|
151
|
+
silêncio, sem erro de build:
|
|
152
|
+
|
|
153
|
+
- `lib/editable/` é cópia da foundation do editor. Não edite à mão: correção entra por versão da
|
|
154
|
+
foundation. A exceção é `lib/editable/tokens.ts`, a lista de cores que o lojista pode mudar.
|
|
155
|
+
- `lib/editable/config.ts` carrega o slug desta loja no editor (o CLI carimba). Tem de ser IGUAL ao
|
|
156
|
+
`slug` da entrada dela no `shops.json` do editor, senão o lojista publica e a loja nunca muda.
|
|
157
|
+
- `app/robots.ts` EXPORTA `DISALLOW`, e é essa lista que decide quais páginas o editor oferece
|
|
158
|
+
(`lib/rotas-editaveis.ts` varre `app/(loja)/` e tira o que o robots bloqueia). Não há segunda lista.
|
|
159
|
+
- Rota nova em `app/(loja)/` entra em `CONTAINERS_POR_ROTA` (e em `SO_CHROME` se for só texto legal
|
|
160
|
+
ou mecânica de compra), ou o gate reprova dizendo a rota.
|
|
161
|
+
- `next.config.ts`: `frame-ancestors` com `NEXT_PUBLIC_EDITOR_ORIGIN` (o editor abre a loja num
|
|
162
|
+
iframe; `X-Frame-Options` não aceita origem externa) e `outputFileTracingIncludes` dos `page.tsx`
|
|
163
|
+
(sem isso a varredura acha zero em produção).
|
|
164
|
+
- `middleware.ts` deixa passar `?unbox_editor_token=` (a prévia atrás da porta) e as rotas que o
|
|
165
|
+
editor chama de servidor (`/api/revalidate`, `/api/unbox/catalogo`, `/api/unbox/paginas`).
|
|
166
|
+
- `/api/revalidate` aceita `x-editor-token`, purga a tag `unbox-editor-content` e devolve o recibo
|
|
167
|
+
com `conteudo`: é com ele que o editor afirma "a loja está no ar com a versão N".
|
|
168
|
+
- `app/layout.tsx`: `EditableProvider` em volta do chrome e a linha única `<Rastreio>` no fim do
|
|
169
|
+
`<body>`. Nenhum snippet de GTM, GA4 ou Pixel escrito à mão fora dela, senão o provedor dispara
|
|
170
|
+
duas vezes.
|
|
171
|
+
|
|
172
|
+
Toda seção nova nasce editável pelos primitivos (`Editable.Text`, `Editable.Image`, `Editable.Icon`,
|
|
173
|
+
`Editable.Section` com `kind` e `label`). As regras completas, com o modo de falha de cada uma, estão
|
|
174
|
+
no README do editor da Unbox. O que NÃO vira primitivo: preço, produto do catálogo, texto legal,
|
|
175
|
+
rótulo de formulário e "Powered by Unbox".
|
|
176
|
+
|
|
177
|
+
Gate: `npm run unbox:editavel` com o servidor no ar e `NEXT_PUBLIC_EDITOR_ORIGIN` preenchida. Saída
|
|
178
|
+
`2` é NÃO RODOU, nunca aprovação.
|
|
179
|
+
|
|
180
|
+
## Subagentes disponíveis
|
|
181
|
+
|
|
182
|
+
Declarados em `.claude/agents/`, para uso **depois** que o contrato de design fecha (o
|
|
183
|
+
protocolo está em `agents/CONSTRUCAO.md`):
|
|
184
|
+
|
|
185
|
+
| Agente | Para | Escreve? |
|
|
186
|
+
|---|---|---|
|
|
187
|
+
| `avaliador-visual` | uma leitura do QA (composição, contraste, responsivo, copy ou honestidade) | não |
|
|
188
|
+
| `conteudo-secao` | o conteúdo de uma seção da home | não, devolve o objeto |
|
|
189
|
+
| `paginas-legais` | uma página institucional | sim, só a dela |
|
|
190
|
+
| `assets-marca` | preparar `public/brand/` | sim, só essa pasta |
|
|
191
|
+
|
|
192
|
+
Os dois primeiros não têm Write de propósito: `home-recipe.ts` e `globals.css` são arquivos de
|
|
193
|
+
dono único, e escrita concorrente neles perde trabalho em silêncio.
|
|
194
|
+
|
|
195
|
+
## Dado que a loja não tem, o bloco não renderiza
|
|
196
|
+
|
|
197
|
+
A regra veio de uma loja real em produção: a foundation preenchia vazio com conteúdo plausível
|
|
198
|
+
(nota 4,9 com 25 mil avaliações, "793 vendidos esta semana", depoimentos de "Cliente A" com
|
|
199
|
+
selo de compra verificada, estoque que descia sozinho enquanto a pessoa lia, "Sem glúten" por
|
|
200
|
+
default, prazo de devolução de 30 dias contradizendo a página do CDC). Tudo foi ao ar como fato,
|
|
201
|
+
e parte disso é violação de política do Google, da ANVISA ou do CDC. Nada disso existe mais.
|
|
202
|
+
Se um bloco depende de dado (avaliação, depoimento, estoque, prazo, atributo de produto), ele
|
|
203
|
+
**some** sem o dado. Nunca cai em default. O `unbox:honestidade` pega o que voltar.
|
|
204
|
+
|
|
205
|
+
Seções da home que somem sem props na receita: `reviews`, `reviews-carousel`, `savings`, `kits`,
|
|
206
|
+
`combos-carousel`, `category-pills`, `product-showcase`, `trust-bar` (sem `items` e sem frete
|
|
207
|
+
grátis real), `trust-strip`, `spec-table`, `comparison`, `attributes-marquee`, `founder-story`,
|
|
208
|
+
`video-wall`, `newsletter` (sem `title` + `action`, a URL que recebe o e-mail). `purchase-hero` só mostra
|
|
209
|
+
estrelas com `rating` e perks com `perks`. O que resta com
|
|
210
|
+
default é copy de interface (título do hero, rótulo de botão), nunca um fato sobre a loja.
|
|
211
|
+
|
|
212
|
+
## Copy sem travessão
|
|
213
|
+
|
|
214
|
+
Nada de travessão na copy da loja: use vírgula, dois-pontos, ponto ou parênteses, e `·` quando
|
|
215
|
+
for separador (endereço, título de aba, selo). Vale para a interface, para os textos que você
|
|
216
|
+
escreve na conversa com o lojista e para o que você deixa em comentário: o travessão em
|
|
217
|
+
comentário é o que ensina a próxima seção a nascer com um. O `npm run build` reprova travessão
|
|
218
|
+
na copy de `app/` e `components/`.
|
|
219
|
+
|
|
220
|
+
## Como falar da plataforma
|
|
221
|
+
|
|
222
|
+
A loja é de um cliente que comprou a Unbox, e **tudo o que você escreve chega nele**: a conversa,
|
|
223
|
+
o comentário no código, a copy da tela, o relatório do fim. Restrição de plataforma existe em
|
|
224
|
+
qualquer plataforma; o que não pode é você virar crítico dela dentro do produto que ela vende.
|
|
225
|
+
|
|
226
|
+
**Diga o que a regra é, o que ela significa para a loja e qual é a decisão.** Sem juízo de valor,
|
|
227
|
+
sem diagnóstico da qualidade da plataforma, sem dramatizar. Ficam de fora: "é pior do que parece",
|
|
228
|
+
"limitação da plataforma", "não dá pra resolver", "infelizmente", "a Unbox não deixa/não aceita",
|
|
229
|
+
"culpa/problema/falha da Unbox". Não é eufemismo: o fato inteiro continua dito, o julgamento é
|
|
230
|
+
que sai. Se a restrição realmente bloqueia o que o lojista pediu, diga o que dá pra fazer e
|
|
231
|
+
registre o pedido, quem leva isso adiante é o gerente de conta da Unbox, não a interface da loja.
|
|
232
|
+
|
|
233
|
+
**Para o comprador, nunca explique a plataforma.** Ele precisa da regra da loja, não do motivo
|
|
234
|
+
técnico dela, e o único lugar onde a Unbox aparece para ele é o selo "Powered by Unbox".
|
|
235
|
+
|
|
236
|
+
O caso que originou esta regra, frequência de assinatura, que é campo do pedido e vale para o
|
|
237
|
+
pedido inteiro:
|
|
238
|
+
|
|
239
|
+
| | |
|
|
240
|
+
|---|---|
|
|
241
|
+
| ✗ para o lojista | "É pior do que parece: a Unbox guarda um `recurringItemsFrequencyId` por pedido. É limitação da plataforma e não dá pra resolver no front." |
|
|
242
|
+
| ✓ para o lojista | "A frequência é do pedido, então vale para todos os itens assinados dele: escolher 60 dias num segundo produto passa o pedido todo para 60. Deixei isso explícito na tela antes de finalizar." |
|
|
243
|
+
| ✗ para o comprador | "Por limitação do sistema, só é possível uma frequência por pedido." |
|
|
244
|
+
| ✓ para o comprador | "Os itens assinados deste pedido chegam na mesma frequência." |
|
|
245
|
+
|
|
246
|
+
O `npm run build` reprova o julgamento sobre a plataforma em qualquer arquivo do projeto.
|
|
247
|
+
|
|
248
|
+
## A foundation é de ramo nenhum
|
|
249
|
+
|
|
250
|
+
Ela nasceu de uma loja de alimentação e por duas varreduras ainda entregava loja de outro ramo
|
|
251
|
+
com instrução de uso de um produto alimentício na PDP, aba nutricional com traços e selo "Sem
|
|
252
|
+
glúten" inventado. A regra que decorre disso:
|
|
253
|
+
|
|
254
|
+
**Nada específico de um ramo renderiza sem dado daquele ramo.** Tabela nutricional, modo de
|
|
255
|
+
uso, composição, selos de atributo, FAQ, produtos relacionados: tudo vem do enriquecimento
|
|
256
|
+
(`lib/enrichment/products.json`) ou de `lib/store-config.ts`, e o que não existir não
|
|
257
|
+
aparece. Não existe default de conteúdo na PDP. Copy com vocabulário de um ramo é escrita
|
|
258
|
+
pelo briefing, para a marca certa, nunca na foundation. O `npm pack` do CLI reprova se isso
|
|
259
|
+
voltar, inclusive em comentário, porque comentário ensina o agente a escrever igual.
|
|
260
|
+
|
|
261
|
+
## Armadilhas que já custaram caro
|
|
262
|
+
|
|
263
|
+
- **O prompt do briefing já está no seu contexto.** `.claude/settings.json` carrega
|
|
264
|
+
`.claude/agents/branding-briefing.md` como system prompt da sessão. Não abra esse arquivo pra
|
|
265
|
+
"conferir o que fazer": são ~8k tokens do que você já tem.
|
|
266
|
+
- **`home-recipe.ts` e `globals.css` são arquivos-gargalo.** Concentram, cada um, decisões de
|
|
267
|
+
várias frentes. Se houver trabalho paralelo, eles precisam de um dono só.
|
|
268
|
+
- **Header imersivo tem dois lados que precisam concordar:** a seção emite `.hero-imersivo` e o
|
|
269
|
+
header lê `[data-chrome="imersivo"]`. Mexer num sem o outro quebra silenciosamente.
|
|
270
|
+
- **`html { overflow-x: clip }`** é o que segura o marquee no layout. Por causa dele o header
|
|
271
|
+
imersivo usa `sticky` com margem negativa, não `fixed`.
|
|
272
|
+
- **O dataLayer fala GA4 padrão E o dialeto do GTM central da Unbox, no mesmo push.** O
|
|
273
|
+
container central lê `ecommerce.items` (GA4), `ecommerce.purchase.*` (Universal Analytics),
|
|
274
|
+
`transactionId`/`transactionTotal` (clássico) e `dataLayerReady { pageType, products[] }`. A
|
|
275
|
+
camada emite tudo; "limpar" para GA4 puro deixa a compra vazia no GA4 da Unbox, sem erro
|
|
276
|
+
visível. O build bloqueia. Nunca priorize o `gtag` sobre o dataLayer: foi assim que o GTM
|
|
277
|
+
central ficava cego em toda loja com GA4 próprio. `value` é número, `discount` vem do
|
|
278
|
+
carrinho, purchase só com pedido PAGO, dados de cliente saem hasheados.
|
|
279
|
+
- **A URL do checkout SEMPRE carrega `?id=&token=` do carrinho, e o build bloqueia sem isso.**
|
|
280
|
+
É o que permite recuperar carrinho abandonado: o CRM e o pixel leem a URL navegada, não o
|
|
281
|
+
cookie httpOnly. A garantia vive no `middleware.ts` (`urlDoCheckoutComPonteiro`), porque ele
|
|
282
|
+
roda em toda requisição, servidor, client-side, link direto, refresh, e não depende de
|
|
283
|
+
alguém lembrar de chamar o helper certo. A regra já se perdeu uma vez por viver só na página.
|
|
284
|
+
- **`loading.tsx` no segmento anula o `notFound()` da página.** Ele abre um `<Suspense>`; o
|
|
285
|
+
shell fica pronto na hora e sai com HTTP 200, e quando o `notFound()` acontece o status já
|
|
286
|
+
foi, soft-404, que faz o Google indexar página vazia. Por isso `categoria/[tagSlug]` e
|
|
287
|
+
`produto/[productSlug]` validam a existência num `layout.tsx`, que renderiza acima da
|
|
288
|
+
fronteira do Suspense. Criou rota dinâmica com `loading.tsx`? Valide no layout, não na page.
|
|
289
|
+
- **Nunca escreva cor literal em `box-shadow`.** Use os tokens `--store-shadow-*`. O verde da
|
|
290
|
+
foundation sobreviveu a uma varredura de 304 cores literais porque estava dentro de
|
|
291
|
+
`shadow-[...]` e de `boxShadow` inline, que ninguém olhou.
|
|
292
|
+
- **Fonte vive no `<body>`, não no `<html>`.** As variáveis do `next/font` são declaradas na
|
|
293
|
+
className do body; regra em `html` não as enxerga e o corpo cai em serif. Só aparecia no
|
|
294
|
+
build de produção.
|
|
295
|
+
- **Falha da API só vira "vazio" SEM credenciais.** Nunca `.catch(() => [])` em página, layout ou
|
|
296
|
+
chrome: use `mockupOr(promessa, fallback, "rótulo")` (`lib/mockup.ts`). Sem `.env`, devolve o
|
|
297
|
+
fallback (modo mockup). Com credenciais, loga e relança: o ISR mantém a versão anterior e o
|
|
298
|
+
`error.tsx` aparece. O `.catch` cego produzia home vazia com HTTP 200 cacheada por 5 minutos e,
|
|
299
|
+
nos layouts de produto/categoria, um 404 cacheado quando a Unbox oscilava.
|
|
300
|
+
- **`placeOrder` tem timeout próprio (90 s) e erro próprio (`PLACE_ORDER_TIMEOUT`, 504).** Abortar
|
|
301
|
+
no cliente não aborta no servidor: o pedido pode nascer depois que a loja desistiu. Com esse
|
|
302
|
+
código, o checkout-client NÃO reabre o botão de pagar e manda conferir em Meus pedidos. Não
|
|
303
|
+
"simplifique" isso num retry.
|
|
304
|
+
- **`alternates`/`openGraph` não fazem merge entre layout e página.** Canonical no `app/layout.tsx`
|
|
305
|
+
é herdado por TODAS as rotas que não declaram o seu (catálogo, categorias, legais viraram
|
|
306
|
+
canonical da home e saíram do índice). Canonical é por página, sempre relativo (`/produtos`),
|
|
307
|
+
e o `metadataBase` resolve. Nunca use `publishedUrl` do painel como canonical: pode ser outro domínio.
|
|
308
|
+
- **Posse de pedido é cookie ASSINADO.** `setOrderToken` grava `<token>.<HMAC(SESSION_SECRET)>` e
|
|
309
|
+
`getOrderToken` verifica. Sem isso qualquer valor de cookie abria qualquer pedido pelo
|
|
310
|
+
`referenceId` (modo parceiro nem valida o token). O CLI gera `SESSION_SECRET` no `.env.local`;
|
|
311
|
+
em produção ele precisa existir (o `env-check` avisa).
|
|
312
|
+
- **O preço final vem sempre do catálogo.** O servidor recalcula o carrinho a partir dele, então
|
|
313
|
+
o preço enviado no `addCartItems` não altera o que é cobrado.
|
|
314
|
+
Kit "com desconto" calculado no front (`combos.ts`) é promessa que o carrinho desmente: desconto
|
|
315
|
+
real de kit é regra de preço/cupom no painel Unbox. O mesmo vale pra desconto de assinatura: só
|
|
316
|
+
com `pricingPolicy` do backend (sem ele, `percentOff` é `null` e nenhum "-N%" aparece).
|
|
317
|
+
- **Foto de seção usa `<Foto>` (`components/ui/foto.tsx`), com `sizes` do tamanho real na tela.**
|
|
318
|
+
`next/image` direto quebra a página inteira quando o `src` da receita aponta para um host que o
|
|
319
|
+
`next.config.ts` não autoriza; o `<Foto>` cai em `<img>` nesse caso, sem otimizar mas sem
|
|
320
|
+
derrubar. E `sizes` errado é o mesmo que não ter: baixa grande demais (peso) ou pequena demais
|
|
321
|
+
(borrada). Logo do chrome, bandeira de pagamento e o hero em `<picture>` continuam em `<img>`
|
|
322
|
+
cru, cada um com o motivo escrito ao lado do `eslint-disable`.
|
|
323
|
+
- **`<img>` abaixo da dobra nasce `loading="lazy" decoding="async"`.** O React 19 emite
|
|
324
|
+
`<link rel="preload">` sozinho para `<img>` eager renderizado no servidor: as bandeiras do
|
|
325
|
+
RODAPÉ estavam disputando conexão com a primeira dobra. Não é sobre bytes, é sobre prioridade.
|
|
326
|
+
- **Em `<picture>`, cada `<source>` precisa da SUA dimensão e do SEU `sizes`.** Sem dimensão, o
|
|
327
|
+
navegador reserva a proporção do `<img>` (a de desktop) e a página pula quando a arte mobile
|
|
328
|
+
chega. Sem `sizes` no source que casou, ele escolhe uma variante pequena e a arte chega borrada.
|
|
329
|
+
- **ID de analytics passa por `idValido()`.** Placeholder é truthy: uma loja rodou 31 dias com
|
|
330
|
+
`NEXT_PUBLIC_GA_ID="x"`, script carregando e nada chegando em conta nenhuma. Campo vazio quebra
|
|
331
|
+
visivelmente; placeholder quebra em silêncio. Mesma razão da description nascer vazia.
|
|
332
|
+
- **`NEXT_PUBLIC_GTM_ID` desliga o Pixel do código.** Pixel no container da loja E no código conta
|
|
333
|
+
PageView duas vezes, e a inflação não dá sinal. Com container, tudo entra por ele.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Cobertura — SDK Unbox + 11 documentos → onde está no storefront
|
|
2
|
+
|
|
3
|
+
Mapa recurso → arquivo. Tudo validado: `npm run unbox:test` → **17/17**, `npm run build` limpo,
|
|
4
|
+
smoke test HTTP do BFF (carrinho→endereço→frete) **200** em todas as etapas.
|
|
5
|
+
|
|
6
|
+
## Métodos do SDK (`UnboxClient`) — todos usados
|
|
7
|
+
|
|
8
|
+
| Método | Onde |
|
|
9
|
+
|---|---|
|
|
10
|
+
| `signIn` + cache/re-signin | `lib/unbox/store.ts` (`getStoreClient`/`withStoreClient`) |
|
|
11
|
+
| `gql` (checa `errors` em HTTP 200, `x-captcha`) | `lib/unbox/client.ts` |
|
|
12
|
+
| `getCatalog` (first/offset/search/tagIds/sort) | `lib/queries.ts` → home, `/produtos`, `/busca`, `/categoria/[tagSlug]` |
|
|
13
|
+
| `getProductBySlug` | `app/(loja)/produto/[productSlug]/page.tsx` |
|
|
14
|
+
| `getProductById` | `lib/unbox/client.ts` (fallback/deep link) |
|
|
15
|
+
| `getTags` | `components/site-header.tsx`, home, categoria |
|
|
16
|
+
| `getShop` (shopSales, política de assinatura, settings) | `lib/queries.ts` → header, home, PDP, checkout |
|
|
17
|
+
| `getPaymentMethods` | `app/(loja)/checkout/page.tsx` |
|
|
18
|
+
| `listDiscountCodes` | `lib/queries.ts`, resolução de cupom no route |
|
|
19
|
+
| `createCart` / `addCartItems` / `updateItemQuantity` / `removeCartItems` | `app/api/cart/**` |
|
|
20
|
+
| `getCart` (rehidratar) | `app/api/cart` (GET), checkout, recuperação |
|
|
21
|
+
| `getFulfillmentGroupIds` (N grupos) | `app/api/checkout/address`, `shipping` |
|
|
22
|
+
| `setShippingAddress` | `app/api/checkout/address` |
|
|
23
|
+
| `quoteShipping` (obrigatório p/ cotar) | `app/api/checkout/shipping` (POST) |
|
|
24
|
+
| `selectShipping` | `app/api/checkout/shipping` (PUT) |
|
|
25
|
+
| `applyDiscount` / `findDiscountIdByCode` / `removeDiscount` | `app/api/cart/coupon` |
|
|
26
|
+
| `buildOrderItems` (exclui brindes) | `app/api/checkout` |
|
|
27
|
+
| `placeOrder` (Pix/Cartão + `orderRecurrence` + `device` antifraude no root) | `app/api/checkout` |
|
|
28
|
+
| API de PARCEIROS (nova): signIn GQL, tags, cupons, pedido, parcelas, `simpleInventory`, webhook, cart template — roteados via `UNBOX_PARTNER_API_KEY` | `lib/unbox/client.ts` (`usesPartnerApi`) |
|
|
29
|
+
| `getOrder` (com token de posse) | `lib/orders.ts`, `app/api/order/[ref]`, `/api/track` |
|
|
30
|
+
| `requestCustomerOtp` / `customerSignIn` / `customerAccountExists` | `app/api/account/{otp,signin,exists}` |
|
|
31
|
+
| `getAddressByPostalCode` | `app/api/cep/[code]`, `AddressFields` |
|
|
32
|
+
| `quoteShippingForProduct` (calcule-o-frete na PDP) | `app/api/shipping/quote` |
|
|
33
|
+
| `createPaymentLink` / `getPublicPaymentLink` | `app/api/payment-link` |
|
|
34
|
+
| `createCartByTemplate` | `lib/unbox/client.ts` (campanhas) |
|
|
35
|
+
| `subscribeWebhook` | `scripts/subscribe-webhook.ts` |
|
|
36
|
+
|
|
37
|
+
## `UnboxCustomerClient` — área do cliente
|
|
38
|
+
|
|
39
|
+
| Método | Onde |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `me` (currentCustomerAccount) | `/conta`, `/conta/enderecos`, `/conta/preferencias` |
|
|
42
|
+
| `updateAccount` | `app/api/account/preferences` |
|
|
43
|
+
| `orders` / `order` | `/conta/pedidos` + `[referenceId]`, `lib/orders.ts` |
|
|
44
|
+
| `subscriptions` / `subscription` / `subscriptionCycles` | `/conta/assinaturas` + `[referenceId]` |
|
|
45
|
+
| `pause` / `skipNextCycle` / `cancel` | `app/api/subscriptions/[id]` (+ `SubscriptionActions`) |
|
|
46
|
+
| `updateItems` / `updateCard` / `updateAddress` | idem |
|
|
47
|
+
| `addressBooks` / `upsertAddress` / `deleteAddresses` | `app/api/account/addresses` (+ `AddressBook`) |
|
|
48
|
+
| Rótulos `orderStatusLabel` / `paymentStatusLabel` / `subscriptionStatusLabel` | `components/order-status.tsx`, listas |
|
|
49
|
+
|
|
50
|
+
## Helpers do SDK
|
|
51
|
+
|
|
52
|
+
| Recurso | Onde |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `friendlyError` / `cartEventLabel` (feedback PT-BR) | `lib/api.ts`, `cart-provider` (toasts) |
|
|
55
|
+
| `datalayer.*` (GA4 + contrato do GTM central) | `lib/analytics.ts` — camada única (+ GTM/GA4 em `app/layout.tsx`) |
|
|
56
|
+
| `verifyUnboxWebhook` / `parseUnboxWebhook` (HMAC corpo cru) | `app/api/webhooks/unbox` |
|
|
57
|
+
|
|
58
|
+
## Documentos (01–11) — recursos cobertos
|
|
59
|
+
|
|
60
|
+
- **01 Autenticação** — signin REST, JWT Bearer, cache/renovação, `x-captcha-verification`, dois contextos de auth, shopId/slug do JWT, erros em HTTP 200 → `lib/config.ts`, `lib/unbox/store.ts`, `lib/unbox/client.ts`.
|
|
61
|
+
- **02 Catálogo** — `catalogItems`, `catalogItemProductBySlug`/`ById`, `tags`, variantes/preço, de/por, badges de estoque, min/max, mídia, HTML sanitizado → catálogo/PDP + `lib/sanitize.ts`.
|
|
62
|
+
- **03 Carrinho & assinatura** — create/add/update/remove/get, brindes, `recurringItemsFrequencyId`, cart templates → `app/api/cart/**`, PDP (assinar), checkout.
|
|
63
|
+
- **04 Promoções** — `shopSales` (banner), `applyDiscountCodeToCart` (antes do frete), `removeDiscountCodeFromCart` (por discountId), brinde automático, `discountCodes` → header/home + `app/api/cart/coupon`.
|
|
64
|
+
- **05 Checkout** — endereço → frete (N grupos) → Pix/Cartão → `placeOrder`, QR copia-e-cola, parcelas, releitura antes do pedido → `app/api/checkout/**`, `components/checkout/*`, `/checkout/pix/[ref]`.
|
|
65
|
+
- **06 Área do cliente** — OTP 2 passos, conta, pedidos/rastreio, assinaturas self-service, address book + CEP, guest tracking → `/conta/**`, `/pedido/[referenceId]`.
|
|
66
|
+
- **07 Webhooks & extras** — `subscribeToWebhook`, HMAC, PING, idempotência, ORDER_CREATED/STATUS_UPDATE, payment links, flags NF/entrega → `app/api/webhooks/unbox`, `scripts/subscribe-webhook.ts`, `OrderStatusCard`.
|
|
67
|
+
- **08 Feedback/erros** — ordem de sinais (errors→failures→cartEvents), `friendlyError`, rótulos de status, reconciliação de preço → `lib/api.ts`, `cart-provider`, `order-status`.
|
|
68
|
+
- **09 Segurança** — BFF, isolamento de tokens (cookies httpOnly), posse de pedido, rate-limit/anti-enumeração, headers/Referrer-Policy, PCI (PAN só passa pelo BFF) → `lib/session.ts`, `lib/ratelimit.ts`, `next.config.ts`, `app/api/**`.
|
|
69
|
+
- **10 URLs/SEO/carrinho** — `generateMetadata`, canonical (`publishedUrl`), `sitemap.ts` (paginado, só visíveis), `robots.ts`, JSON-LD, ISR, persistência/recuperação de carrinho → PDP, `app/sitemap.ts`, `app/robots.ts`, `lib/crm.ts`, `scripts/abandoned-cart.ts`.
|
|
70
|
+
- **11 Produção/resiliência** — idempotência (lock por cartId, anti-duplo-clique, sem retry cego), N grupos, releitura antes do placeOrder, confirmação de Pix (webhook + polling), cache de token + re-signin, CDC art. 49, a11y → `lib/checkout-lock.ts`, `app/api/checkout`, `/checkout/pix/[ref]`, `lib/unbox/store.ts`, `/devolucoes`.
|
|
71
|
+
|
|
72
|
+
## Limitações conhecidas (da API, não da implementação)
|
|
73
|
+
|
|
74
|
+
- O `Cart` lido (`anonymousCartByCartId`) **não expõe** os ids de cupons aplicados nem `cartEvents`;
|
|
75
|
+
o `discountId` para remoção é resolvido por código via `findDiscountIdByCode` e mantido na sessão.
|
|
76
|
+
- **N fulfillmentGroups**: a loja de exemplo retorna 1 grupo; o BFF cota/seleciona por grupo, mas o
|
|
77
|
+
`placeOrder` do SDK monta 1 grupo `SHIPPING` (suficiente aqui; multi-grupo exigiria estender o payload).
|
|
78
|
+
- **Payment links / cart templates / cancelOrderItem / refund total**: existem no SDK/doc mas dependem de
|
|
79
|
+
permissões admin/efeitos reais; expostos via rota/script protegidos, não no fluxo público.
|
|
80
|
+
- Infra de produção (KV, Redis, DB, CRM) está como implementação **em memória/cookie** com pontos `// PROD:`.
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Deploy no Vercel — Lojas Unbox/Next.js
|
|
2
|
+
|
|
3
|
+
> **A primeira versão no ar NÃO precisa de git nem de GitHub.** O `vercel` faz upload direto
|
|
4
|
+
> da pasta local, e a conta Vercel pode ser criada com **email** (na tela de login, ignore o
|
|
5
|
+
> "Continue with GitHub" e use "Continue with Email"). GitHub é um passo opcional DEPOIS, só
|
|
6
|
+
> pra quem quiser redeploy automático a cada push — veja a última seção.
|
|
7
|
+
|
|
8
|
+
## Pré-requisitos locais
|
|
9
|
+
|
|
10
|
+
- Node.js **v22 LTS** (`nvm install 22 && nvm use 22`)
|
|
11
|
+
> v26 causa startup lento no Next.js — sempre use a LTS.
|
|
12
|
+
- Conta Vercel (email serve) — o CLI roda via `npx vercel`, sem instalar nada global
|
|
13
|
+
- `.env.local` com todas as credenciais da loja (veja `.env.example`)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Checklist pré-deploy (na ordem certa)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# 1. Node version correta
|
|
21
|
+
nvm install 22 && nvm use 22
|
|
22
|
+
|
|
23
|
+
# 2. Remover lock file global que conflita com npm
|
|
24
|
+
rm -f ~/package-lock.json
|
|
25
|
+
|
|
26
|
+
# 3. Instalar dependências
|
|
27
|
+
npm install
|
|
28
|
+
|
|
29
|
+
# 4. Verificar conexão com a API antes de subir
|
|
30
|
+
npm run unbox:test
|
|
31
|
+
|
|
32
|
+
# 5. Rodar local — primeira vez demora 2-4 min (compilação inicial)
|
|
33
|
+
npm run dev
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Setup no Vercel (via dashboard)
|
|
39
|
+
|
|
40
|
+
### Framework Preset
|
|
41
|
+
|
|
42
|
+
> Settings → Build & Development Settings
|
|
43
|
+
|
|
44
|
+
Selecionar **Next.js** no dropdown "Framework Preset".
|
|
45
|
+
|
|
46
|
+
**Nunca deixar como "Other"** — sem isso o Vercel não configura o build command correto e o deploy falha silenciosamente.
|
|
47
|
+
|
|
48
|
+
### Variáveis de ambiente
|
|
49
|
+
|
|
50
|
+
> Settings → Environment Variables
|
|
51
|
+
|
|
52
|
+
Adicionar **antes do primeiro deploy** em **Production**, **Preview** e **Development**:
|
|
53
|
+
|
|
54
|
+
| Variável | Obrigatório | Descrição |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| `UNBOX_PARTNER_API_KEY` | ✅ (recomendado) | Api key única do PARCEIRO (`da2-...`) — modelo atual |
|
|
57
|
+
| `UNBOX_CAPTCHA_BYPASS` | ✅ com a key de parceiro | x-captcha-verification do signIn (pedir à Unbox) |
|
|
58
|
+
| `UNBOX_USER` | ✅ | Usuário de acesso à API da loja |
|
|
59
|
+
| `UNBOX_PASS` | ✅ | Senha de acesso à API |
|
|
60
|
+
| `SESSION_SECRET` | ✅ | String aleatória forte (≥ 32 chars) |
|
|
61
|
+
| `NEXT_PUBLIC_SITE_URL` | ✅ | URL pública da loja (ex: `https://minhaloja.com.br`) |
|
|
62
|
+
| `NEXT_PUBLIC_SITE_NAME` | ✅ | Nome de exibição (manifest/PWA) |
|
|
63
|
+
| `UNBOX_API_KEY` | — | Modelo ANTIGO (key por loja); só sem a key de parceiro |
|
|
64
|
+
| `UNBOX_SHOP_ID` | — | Extraído do JWT se vazio |
|
|
65
|
+
| `UNBOX_SHOP_SLUG` | — | Extraído do JWT se vazio |
|
|
66
|
+
| `UNBOX_WEBHOOK_SECRET` | — | Secret(s) dos webhooks (vírgula p/ múltiplos) |
|
|
67
|
+
| `REVALIDATE_SECRET` | — | Protege `/api/revalidate` |
|
|
68
|
+
| `CRM_WEBHOOK_URL` | — | Webhook do CRM (carrinho abandonado; ver `.env.example`) |
|
|
69
|
+
| `PIPEDRIVE_API_TOKEN` | — | Leads da porta de preview → Pessoa+Negócio no Pipedrive |
|
|
70
|
+
| `PREVIEW_PASSWORD` | — | Chave do time da porta de preview (padrão `unbox`) |
|
|
71
|
+
| `UNBOX_HOSTED_CHECKOUT_URL` | — | Só no modo checkout hospedado da Unbox. **Caminho completo** (`https://sualoja.com.br/carrinho/finalizar-pedido`, nunca só o domínio) e **mesmo domínio da loja**: em hosts diferentes o cliente cai no `/login`. |
|
|
72
|
+
| `NEXT_PUBLIC_GA_ID` | — | GA4 measurement ID |
|
|
73
|
+
| `NEXT_PUBLIC_META_PIXEL_ID` | — | Meta Pixel ID |
|
|
74
|
+
| `META_PIXEL_ID` / `META_CAPI_TOKEN` | — | Meta Conversions API (server) |
|
|
75
|
+
|
|
76
|
+
> **Sem env vars o build falha** com `UnboxError: signin HTTP 403: Forbidden` durante o prerender.
|
|
77
|
+
|
|
78
|
+
### Via CLI (alternativa)
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npx vercel env add UNBOX_API_KEY
|
|
82
|
+
npx vercel env add UNBOX_USER
|
|
83
|
+
npx vercel env add UNBOX_PASS
|
|
84
|
+
npx vercel env add SESSION_SECRET
|
|
85
|
+
npx vercel env add NEXT_PUBLIC_SITE_URL
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Primeiro deploy (sem git, sem GitHub)
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# 1. Entrar na conta (abre o browser; login por email funciona)
|
|
94
|
+
npx vercel login
|
|
95
|
+
|
|
96
|
+
# 2. Criar/linkar o projeto (responda os prompts; upload direto da pasta local)
|
|
97
|
+
npx vercel link
|
|
98
|
+
|
|
99
|
+
# 3. Deploy de produção
|
|
100
|
+
npx vercel --prod
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Pronto: o `vercel --prod` devolve a URL pública da loja. Nada de repositório envolvido.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Opcional (depois): redeploy automático via GitHub
|
|
108
|
+
|
|
109
|
+
Só faz sentido quando a loja já está no ar e o time quer que todo push em `main` redeploye
|
|
110
|
+
sozinho. Aí sim:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
git init
|
|
114
|
+
git add .
|
|
115
|
+
git commit -m "chore: initial storefront"
|
|
116
|
+
git remote add origin <repo-url>
|
|
117
|
+
git push -u origin main
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
E no dashboard do Vercel: Settings → Git → conectar o repositório. Sem isso, o redeploy é
|
|
121
|
+
manual (`npx vercel --prod` de novo) e funciona igual.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Erros comuns e soluções
|
|
126
|
+
|
|
127
|
+
| Erro | Causa | Solução |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| `UnboxError: signin HTTP 403` | Env vars ausentes no Vercel | Adicionar em Settings → Environment Variables (todos os 3 ambientes) |
|
|
130
|
+
| Build falha silenciosamente | Framework Preset como "Other" | Alterar para **Next.js** em Settings → Build |
|
|
131
|
+
| `Project already exists` ao criar | Nome já usado na conta Vercel | Usar nome diferente (ex: `nome-loja-app`) |
|
|
132
|
+
| Prerender error na home `/` | API inacessível durante build | Verificar credenciais e rodar `npm run unbox:test` |
|
|
133
|
+
| `npm ERR! ...` no install | `~/package-lock.json` global | `rm -f ~/package-lock.json` |
|
|
134
|
+
| Startup > 5 min em dev | Node.js v26 instalado | `nvm use 22` |
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Porta de preview (prévia privada) e o LANÇAMENTO da loja
|
|
139
|
+
|
|
140
|
+
A loja nasce travada atrás da tela `/acesso` ("Prévia privada"): quem preencher
|
|
141
|
+
nome/marca/WhatsApp/e-mail entra (cookie de 30 dias) e vira lead — com
|
|
142
|
+
`PIPEDRIVE_API_TOKEN` configurado, cada lead cria Pessoa + Negócio no Pipedrive
|
|
143
|
+
(título `"<slug> - Nome"`, no funil de Negócios).
|
|
144
|
+
|
|
145
|
+
**A porta se escopa pelo domínio sozinha**: só existe em host de preview
|
|
146
|
+
(`*.vercel.app`, `*.myunbox.com.br`, + sufixos de `PREVIEW_HOSTS`). Não precisa ligar
|
|
147
|
+
nem desligar nada:
|
|
148
|
+
|
|
149
|
+
- **No deploy de prévia** (URL vercel.app/myunbox): porta LIGADA automaticamente.
|
|
150
|
+
Configure `PIPEDRIVE_API_TOKEN` (production + preview) e, se quiser aviso em tempo
|
|
151
|
+
real, `PREVIEW_WEBHOOK_URL` (Slack/Zapier).
|
|
152
|
+
- **No lançamento** (domínio próprio da marca apontado): porta SOME automaticamente no
|
|
153
|
+
domínio próprio. A URL vercel.app continua com porta — o que é bom: ninguém indexa a
|
|
154
|
+
loja pelo endereço de preview. Kill switch manual, se precisar: `PREVIEW_DISABLED=1`.
|
|
155
|
+
- **Time interno não preenche formulário**: qualquer URL da loja com
|
|
156
|
+
`?chave=<PREVIEW_PASSWORD>` (padrão `unbox`) grava o cookie e segue direto — ex.:
|
|
157
|
+
`https://loja.vercel.app/?chave=unbox`. Compartilhe ESSE link internamente; o cookie
|
|
158
|
+
vale 30 dias por navegador.
|
|
159
|
+
- **Local**: localhost fica fora da porta por padrão (dev e QA não tropeçam nela). Pra
|
|
160
|
+
testar a porta localmente: `PREVIEW_FORCE=1` no `.env.local`.
|
|
161
|
+
- ⚠️ Env var nova só vale **depois de um redeploy** — o snapshot anterior não a enxerga.
|
|
162
|
+
- Conferência pós-deploy (não pule): abrir a URL vercel.app numa aba anônima → deve
|
|
163
|
+
cair em `/acesso`; preencher → deve entrar; nos logs, `[preview-lead-pipedrive]` com
|
|
164
|
+
`ok:true` e `dealId` preenchido (a recusa do Pipedrive vem com **status 200** — não
|
|
165
|
+
confie em "não deu erro"). Apague a Pessoa/Negócio de teste do CRM.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Redeploy após alterar env vars
|
|
170
|
+
|
|
171
|
+
1. Vercel dashboard → Deployments → último deploy → **Redeploy**
|
|
172
|
+
|
|
173
|
+
Ou pela CLI:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
npx vercel --prod
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
(Quem conectou GitHub também pode só dar `git push origin main`.)
|