@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,35 @@
|
|
|
1
|
+
import { ImageResponse } from "next/og";
|
|
2
|
+
|
|
3
|
+
// Imagem de compartilhamento (Open Graph / Twitter) gerada na hora: nome da loja sobre a cor
|
|
4
|
+
// primária. Existe para NENHUM compartilhamento sair sem imagem — antes saía. Quando a marca
|
|
5
|
+
// tiver uma imagem própria (1200×630), troque por um arquivo public/brand/og.png e aponte
|
|
6
|
+
// `openGraph.images` no layout; até lá, esta serve e nunca é placeholder de outra loja.
|
|
7
|
+
export const runtime = "edge";
|
|
8
|
+
export const alt = "Minha Loja";
|
|
9
|
+
export const size = { width: 1200, height: 630 };
|
|
10
|
+
export const contentType = "image/png";
|
|
11
|
+
|
|
12
|
+
const SITE_NAME = "Minha Loja";
|
|
13
|
+
// Hex literal é obrigatório aqui: o satori (ImageResponse) não lê variável de CSS. O CLI
|
|
14
|
+
// reescreve estas duas linhas com a cor da marca no scaffold (src/theme.js). Se o medidor de
|
|
15
|
+
// sistema acusar estes dois hex, é exceção declarada, não vazamento.
|
|
16
|
+
const PRIMARY = "#18181B";
|
|
17
|
+
const PRIMARY_FG = "#FFFFFF";
|
|
18
|
+
|
|
19
|
+
export default function OgImage() {
|
|
20
|
+
return new ImageResponse(
|
|
21
|
+
(
|
|
22
|
+
<div
|
|
23
|
+
style={{
|
|
24
|
+
width: "100%", height: "100%", display: "flex", flexDirection: "column",
|
|
25
|
+
alignItems: "flex-start", justifyContent: "flex-end", padding: 72,
|
|
26
|
+
background: PRIMARY, color: PRIMARY_FG, fontFamily: "system-ui, sans-serif",
|
|
27
|
+
}}
|
|
28
|
+
>
|
|
29
|
+
<div style={{ fontSize: 84, fontWeight: 800, letterSpacing: -2, lineHeight: 1.05 }}>{SITE_NAME}</div>
|
|
30
|
+
<div style={{ marginTop: 18, fontSize: 30, opacity: 0.8 }}>Loja online</div>
|
|
31
|
+
</div>
|
|
32
|
+
),
|
|
33
|
+
size,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { MetadataRoute } from "next";
|
|
2
|
+
|
|
3
|
+
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:3000";
|
|
4
|
+
|
|
5
|
+
// Áreas privadas/transacionais não indexáveis (doc 10).
|
|
6
|
+
// EXPORTADA de propósito: é a MESMA lista que decide quais páginas o editor da Unbox oferece ao
|
|
7
|
+
// lojista (ver lib/rotas-editaveis.ts). Uma lista só, mantida por quem cuida de SEO: bloquear uma
|
|
8
|
+
// área aqui a tira do editor junto, e não existe segunda lista para alguém esquecer.
|
|
9
|
+
export const DISALLOW = ["/conta", "/carrinho", "/checkout", "/pedido", "/api"];
|
|
10
|
+
|
|
11
|
+
// Agentes de IA explicitamente bem-vindos (descoberta de agentes): conteúdo público —
|
|
12
|
+
// catálogo (/produto/, /categoria/, /produtos) — liberado para indexação e respostas.
|
|
13
|
+
const AI_AGENTS = [
|
|
14
|
+
"GPTBot", "OAI-SearchBot", "ChatGPT-User",
|
|
15
|
+
"ClaudeBot", "Claude-Web", "anthropic-ai",
|
|
16
|
+
"PerplexityBot", "Perplexity-User",
|
|
17
|
+
"Google-Extended", "Applebot-Extended",
|
|
18
|
+
"Amazonbot", "meta-externalagent", "CCBot",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export default function robots(): MetadataRoute.Robots {
|
|
22
|
+
return {
|
|
23
|
+
rules: [
|
|
24
|
+
{ userAgent: "*", allow: "/", disallow: DISALLOW },
|
|
25
|
+
// mesma política para os agentes de IA — público liberado, privado bloqueado
|
|
26
|
+
{ userAgent: AI_AGENTS, allow: ["/", "/produto/", "/categoria/", "/produtos"], disallow: DISALLOW },
|
|
27
|
+
],
|
|
28
|
+
sitemap: `${siteUrl}/sitemap.xml`,
|
|
29
|
+
host: siteUrl,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { MetadataRoute } from "next";
|
|
2
|
+
import { loadAllCatalogItems } from "@/lib/dataloader";
|
|
3
|
+
import { getTopTags } from "@/lib/queries";
|
|
4
|
+
|
|
5
|
+
export const revalidate = 3600;
|
|
6
|
+
|
|
7
|
+
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:3000";
|
|
8
|
+
|
|
9
|
+
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
10
|
+
const base: MetadataRoute.Sitemap = [
|
|
11
|
+
{ url: siteUrl, changeFrequency: "daily", priority: 1 },
|
|
12
|
+
{ url: `${siteUrl}/produtos`, changeFrequency: "daily", priority: 0.9 },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
// produtos (paginado por offset até hasNextPage===false; só os visíveis — doc 10)
|
|
16
|
+
let products: MetadataRoute.Sitemap = [];
|
|
17
|
+
try {
|
|
18
|
+
const items = await loadAllCatalogItems();
|
|
19
|
+
products = items
|
|
20
|
+
.map((n: any) => n.product)
|
|
21
|
+
.filter((p: any) => p && p.isVisible !== false && p.slug)
|
|
22
|
+
.map((p: any) => ({
|
|
23
|
+
url: `${siteUrl}/produto/${encodeURIComponent(p.slug)}`,
|
|
24
|
+
changeFrequency: "weekly" as const,
|
|
25
|
+
priority: 0.8,
|
|
26
|
+
}));
|
|
27
|
+
} catch {
|
|
28
|
+
/* falha de rede não deve quebrar o sitemap inteiro */
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let categories: MetadataRoute.Sitemap = [];
|
|
32
|
+
try {
|
|
33
|
+
const tags = await getTopTags();
|
|
34
|
+
categories = (tags as any[])
|
|
35
|
+
.filter((t) => t.isVisible !== false && t.slug)
|
|
36
|
+
.map((t) => ({ url: `${siteUrl}/categoria/${encodeURIComponent(t.slug)}`, changeFrequency: "weekly" as const, priority: 0.6 }));
|
|
37
|
+
} catch {
|
|
38
|
+
/* idem */
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return [...base, ...categories, ...products];
|
|
42
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
echo ""
|
|
5
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
6
|
+
echo " Unbox Storefront — Bootstrap"
|
|
7
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
8
|
+
echo ""
|
|
9
|
+
|
|
10
|
+
# ── 1. Credenciais ───────────────────────────────────────────────────────────
|
|
11
|
+
echo "Você já tem as credenciais da loja Unbox?"
|
|
12
|
+
echo "(UNBOX_API_KEY, UNBOX_USER, UNBOX_PASS)"
|
|
13
|
+
read -rp " [s/N] " HAS_CREDS
|
|
14
|
+
echo ""
|
|
15
|
+
|
|
16
|
+
# ── 2. CRO Package ───────────────────────────────────────────────────────────
|
|
17
|
+
echo "Gostaria de ativar o Unbox AI CRO Package?"
|
|
18
|
+
echo ""
|
|
19
|
+
echo " Módulos disponíveis:"
|
|
20
|
+
echo " [1] Prova Social no PDP — badge de vendas + avaliações"
|
|
21
|
+
echo " [2] Urgência de Estoque — 'Restam X unidades'"
|
|
22
|
+
echo " [3] Countdown Flash Sale — banner com temporizador"
|
|
23
|
+
echo " [4] Barra de Frete Grátis — progress bar no carrinho"
|
|
24
|
+
echo " [5] Upsell Pós-Checkout — recomendações na confirmação"
|
|
25
|
+
echo " [6] Recently Viewed — produtos vistos (localStorage)"
|
|
26
|
+
echo " [7] Sticky CTA Mobile — botão fixo no scroll do PDP"
|
|
27
|
+
echo " [8] Trust Strip Customizável — ícones de confiança"
|
|
28
|
+
echo " [9] Kits & Combos com Cross-sell — seção de kits na home"
|
|
29
|
+
echo " [10] Brindes (Gift with Purchase) — brinde ao atingir valor mínimo"
|
|
30
|
+
echo ""
|
|
31
|
+
echo " ⚠️ Módulos 4 e 10 precisam de configuração no painel Unbox antes do lançamento."
|
|
32
|
+
echo ""
|
|
33
|
+
echo " Digite 'all', números separados por vírgula (ex: 1,4,7), ou Enter para pular."
|
|
34
|
+
read -rp " CRO modules: " CRO_CHOICE
|
|
35
|
+
echo ""
|
|
36
|
+
|
|
37
|
+
# Salva escolha para o agente 13-cro usar depois
|
|
38
|
+
if [ -n "$CRO_CHOICE" ] && [ "$CRO_CHOICE" != "none" ]; then
|
|
39
|
+
echo "$CRO_CHOICE" > .cro-modules
|
|
40
|
+
echo " ✓ Escolha salva em .cro-modules"
|
|
41
|
+
echo " Execute o agente 13-cro após o setup para implementar os módulos."
|
|
42
|
+
echo " (agents/definitions/13-cro.md)"
|
|
43
|
+
else
|
|
44
|
+
echo " CRO Package pulado. Você pode ativar depois com o agente 13-cro."
|
|
45
|
+
fi
|
|
46
|
+
echo ""
|
|
47
|
+
|
|
48
|
+
# ── 3. Node 22 LTS ───────────────────────────────────────────────────────────
|
|
49
|
+
echo "→ Garantindo Node 22 LTS..."
|
|
50
|
+
nvm install 22 --silent && nvm use 22 --silent
|
|
51
|
+
|
|
52
|
+
# ── 4. Remove lock file global que conflita com npm ──────────────────────────
|
|
53
|
+
rm -f ~/package-lock.json
|
|
54
|
+
|
|
55
|
+
# ── 5. Dependências ──────────────────────────────────────────────────────────
|
|
56
|
+
echo "→ Instalando dependências (pode demorar ~2 min)..."
|
|
57
|
+
npm install --silent
|
|
58
|
+
|
|
59
|
+
# ── 6. .env.local ────────────────────────────────────────────────────────────
|
|
60
|
+
if [ ! -f .env.local ]; then
|
|
61
|
+
cp .env.example .env.local
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# ── 7. Modo com ou sem credenciais ───────────────────────────────────────────
|
|
65
|
+
if [[ "$HAS_CREDS" =~ ^[Ss]$ ]]; then
|
|
66
|
+
echo ""
|
|
67
|
+
echo " Preencha .env.local com as credenciais antes de continuar:"
|
|
68
|
+
echo " UNBOX_API_KEY, UNBOX_USER, UNBOX_PASS, SESSION_SECRET, NEXT_PUBLIC_SITE_URL"
|
|
69
|
+
echo ""
|
|
70
|
+
read -rp " Pressione Enter quando .env.local estiver preenchido..."
|
|
71
|
+
echo ""
|
|
72
|
+
echo "→ Testando conexão com a API Unbox..."
|
|
73
|
+
npm run unbox:test
|
|
74
|
+
echo ""
|
|
75
|
+
echo " Conectado. Rodando em modo completo."
|
|
76
|
+
else
|
|
77
|
+
echo " MODO MOCKUP — o layout vai abrir mas páginas que dependem"
|
|
78
|
+
echo " da API (catálogo, produto, checkout) vão mostrar erro."
|
|
79
|
+
echo " Preencha .env.local e rode 'npm run unbox:test' antes do QA."
|
|
80
|
+
echo ""
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
echo ""
|
|
84
|
+
echo "→ Iniciando dev server..."
|
|
85
|
+
echo " (primeira compilação demora ~3 min — aguarde o 'Ready' aparecer)"
|
|
86
|
+
echo ""
|
|
87
|
+
npm run dev
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { House, Package, Gear, CaretRight } from "@phosphor-icons/react/dist/ssr";
|
|
3
|
+
import { SignOutButton } from "@/components/account/signout-button";
|
|
4
|
+
|
|
5
|
+
// Menu enxuto por decisão de produto: Assinaturas e Endereços saíram da navegação
|
|
6
|
+
// (as rotas /conta/assinaturas e /conta/enderecos continuam existindo pra deep links,
|
|
7
|
+
// ex.: e-mail de assinatura). Pra reexibir, basta recolocar os itens aqui.
|
|
8
|
+
const NAV = [
|
|
9
|
+
{ href: "/conta", label: "Visão geral", icon: House },
|
|
10
|
+
{ href: "/conta/pedidos", label: "Pedidos", icon: Package },
|
|
11
|
+
{ href: "/conta/preferencias", label: "Preferências", icon: Gear },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
export function AccountShell({ title, children }: { title: string; children: React.ReactNode }) {
|
|
15
|
+
return (
|
|
16
|
+
<div className="store-layout full-bleed bg-white text-[var(--store-ink)]">
|
|
17
|
+
<div className="mx-auto max-w-[1240px] px-4 py-7 sm:px-6">
|
|
18
|
+
{/* breadcrumb */}
|
|
19
|
+
<div className="flex items-center gap-2 text-[13px] font-medium text-[var(--store-muted)]">
|
|
20
|
+
<Link href="/" className="no-underline hover:text-[var(--store-ink)]">Início</Link>
|
|
21
|
+
<CaretRight className="text-[11px]" />
|
|
22
|
+
<span className="font-semibold text-[var(--store-ink)]">Minha conta</span>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div className="mt-6 grid items-start gap-7 md:grid-cols-[248px_1fr]">
|
|
26
|
+
<aside className="h-fit rounded-xl border border-[var(--store-line)] bg-white p-3 md:sticky md:top-6">
|
|
27
|
+
<nav className="flex flex-col gap-1">
|
|
28
|
+
{NAV.map(({ href, label, icon: Icon }) => (
|
|
29
|
+
<Link key={href} href={href} className="flex items-center gap-2.5 rounded-xl px-3.5 py-2.5 text-[14px] font-semibold text-[var(--store-ink-2)] no-underline transition-colors hover:bg-[var(--store-primary-soft,#F1F1F3)] hover:text-[var(--store-primary,#18181B)]">
|
|
30
|
+
<Icon weight="bold" className="text-[18px] text-[var(--store-primary,#18181B)]" /> {label}
|
|
31
|
+
</Link>
|
|
32
|
+
))}
|
|
33
|
+
<div className="mt-1.5 border-t border-[var(--store-surface-2)] pt-1.5">
|
|
34
|
+
<SignOutButton />
|
|
35
|
+
</div>
|
|
36
|
+
</nav>
|
|
37
|
+
</aside>
|
|
38
|
+
|
|
39
|
+
<section>
|
|
40
|
+
<h1 className="font-display mb-5 text-[27px] font-extrabold leading-[1.12]">{title}</h1>
|
|
41
|
+
{children}
|
|
42
|
+
</section>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { toast } from "sonner";
|
|
6
|
+
import { Plus, PencilSimple, Trash, CircleNotch } from "@phosphor-icons/react/dist/ssr";
|
|
7
|
+
import {
|
|
8
|
+
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription,
|
|
9
|
+
} from "@/components/ui/dialog";
|
|
10
|
+
import { AddressFields, emptyAddress, type AddressValue } from "@/components/address-fields";
|
|
11
|
+
import { EmptyState } from "@/components/empty-state";
|
|
12
|
+
|
|
13
|
+
const BTN_PRIMARY = "font-display inline-flex h-11 cursor-pointer items-center justify-center gap-2 rounded-xl bg-[var(--store-primary,#18181B)] px-5 text-[14px] font-bold text-white transition-colors hover:bg-[var(--store-primary-dark,#09090B)] disabled:opacity-60";
|
|
14
|
+
const BTN_OUTLINE = "inline-flex h-11 cursor-pointer items-center justify-center gap-2 rounded-xl border-[1.5px] border-[var(--store-line-2)] bg-white px-4 text-[14px] font-bold text-[var(--store-ink-2)] transition-colors hover:border-[var(--store-primary,#18181B)] hover:text-[var(--store-primary,#18181B)]";
|
|
15
|
+
|
|
16
|
+
interface AddressBookItem {
|
|
17
|
+
_id: string;
|
|
18
|
+
alias?: string;
|
|
19
|
+
fullName?: string;
|
|
20
|
+
postal?: string;
|
|
21
|
+
address1?: string;
|
|
22
|
+
number?: string;
|
|
23
|
+
neighborhood?: string;
|
|
24
|
+
city?: string;
|
|
25
|
+
region?: string;
|
|
26
|
+
isShippingDefault?: boolean;
|
|
27
|
+
isBillingDefault?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function AddressBook({ initial }: { initial: AddressBookItem[] }) {
|
|
31
|
+
const router = useRouter();
|
|
32
|
+
const [open, setOpen] = React.useState(false);
|
|
33
|
+
const [editingId, setEditingId] = React.useState<string | undefined>();
|
|
34
|
+
const [value, setValue] = React.useState<AddressValue>(emptyAddress);
|
|
35
|
+
const [busy, setBusy] = React.useState(false);
|
|
36
|
+
|
|
37
|
+
const openNew = () => {
|
|
38
|
+
setEditingId(undefined);
|
|
39
|
+
setValue(emptyAddress);
|
|
40
|
+
setOpen(true);
|
|
41
|
+
};
|
|
42
|
+
const openEdit = (a: AddressBookItem) => {
|
|
43
|
+
setEditingId(a._id);
|
|
44
|
+
setValue({
|
|
45
|
+
...emptyAddress,
|
|
46
|
+
fullName: a.fullName ?? "",
|
|
47
|
+
postal: a.postal ?? "",
|
|
48
|
+
address1: a.address1 ?? "",
|
|
49
|
+
number: a.number ?? "",
|
|
50
|
+
neighborhood: a.neighborhood ?? "",
|
|
51
|
+
city: a.city ?? "",
|
|
52
|
+
region: a.region ?? "",
|
|
53
|
+
});
|
|
54
|
+
setOpen(true);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
async function save() {
|
|
58
|
+
setBusy(true);
|
|
59
|
+
try {
|
|
60
|
+
const res = await fetch("/api/account/addresses", {
|
|
61
|
+
method: "POST",
|
|
62
|
+
headers: { "Content-Type": "application/json" },
|
|
63
|
+
body: JSON.stringify({ address: { ...value, _id: editingId } }),
|
|
64
|
+
});
|
|
65
|
+
const data = await res.json().catch(() => ({}));
|
|
66
|
+
if (!res.ok) throw new Error(data?.error || "Erro ao salvar.");
|
|
67
|
+
toast.success("Endereço salvo.");
|
|
68
|
+
setOpen(false);
|
|
69
|
+
router.refresh();
|
|
70
|
+
} catch (e: any) {
|
|
71
|
+
toast.error(e.message);
|
|
72
|
+
} finally {
|
|
73
|
+
setBusy(false);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function remove(id: string) {
|
|
78
|
+
try {
|
|
79
|
+
const res = await fetch("/api/account/addresses", {
|
|
80
|
+
method: "DELETE",
|
|
81
|
+
headers: { "Content-Type": "application/json" },
|
|
82
|
+
body: JSON.stringify({ ids: [id] }),
|
|
83
|
+
});
|
|
84
|
+
if (!res.ok) throw new Error((await res.json().catch(() => ({})))?.error || "Erro ao remover.");
|
|
85
|
+
toast.success("Endereço removido.");
|
|
86
|
+
router.refresh();
|
|
87
|
+
} catch (e: any) {
|
|
88
|
+
toast.error(e.message);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div>
|
|
94
|
+
<div className="mb-4 flex justify-end">
|
|
95
|
+
<button type="button" onClick={openNew} className={BTN_PRIMARY}><Plus weight="bold" /> Novo endereço</button>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{initial.length === 0 ? (
|
|
99
|
+
<EmptyState title="Nenhum endereço salvo" description="Adicione um endereço para agilizar suas compras." />
|
|
100
|
+
) : (
|
|
101
|
+
<ul className="grid gap-3 sm:grid-cols-2">
|
|
102
|
+
{initial.map((a) => (
|
|
103
|
+
<li key={a._id} className="flex flex-col rounded-lg border border-[var(--store-line)] bg-white p-4">
|
|
104
|
+
<div className="text-sm">
|
|
105
|
+
<p className="font-display text-[15px] font-bold text-[var(--store-ink)]">{a.alias || a.fullName}</p>
|
|
106
|
+
<p className="mt-0.5 text-[var(--store-ink-2)]">{a.address1}, {a.number} · {a.neighborhood}</p>
|
|
107
|
+
<p className="text-[var(--store-muted)]">{a.city}/{a.region} · {a.postal}</p>
|
|
108
|
+
{(a.isShippingDefault || a.isBillingDefault) && (
|
|
109
|
+
<div className="mt-2.5 flex flex-wrap gap-1.5">
|
|
110
|
+
{a.isShippingDefault && <Tag>Entrega padrão</Tag>}
|
|
111
|
+
{a.isBillingDefault && <Tag>Cobrança padrão</Tag>}
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
<div className="mt-3.5 flex gap-2 border-t border-[var(--store-surface-2)] pt-3">
|
|
116
|
+
<button type="button" onClick={() => openEdit(a)} className="inline-flex cursor-pointer items-center gap-1.5 text-[13px] font-bold text-[var(--store-primary,#18181B)]"><PencilSimple weight="bold" /> Editar</button>
|
|
117
|
+
<button type="button" onClick={() => remove(a._id)} className="inline-flex cursor-pointer items-center gap-1.5 text-[13px] font-bold text-[var(--store-muted)] transition-colors hover:text-[var(--store-sale)]"><Trash weight="bold" /> Remover</button>
|
|
118
|
+
</div>
|
|
119
|
+
</li>
|
|
120
|
+
))}
|
|
121
|
+
</ul>
|
|
122
|
+
)}
|
|
123
|
+
|
|
124
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
125
|
+
<DialogContent className="max-w-lg">
|
|
126
|
+
<DialogHeader>
|
|
127
|
+
<DialogTitle>{editingId ? "Editar endereço" : "Novo endereço"}</DialogTitle>
|
|
128
|
+
<DialogDescription>Preencha os dados de entrega.</DialogDescription>
|
|
129
|
+
</DialogHeader>
|
|
130
|
+
<AddressFields value={value} onChange={setValue} />
|
|
131
|
+
<DialogFooter>
|
|
132
|
+
<button type="button" onClick={() => setOpen(false)} className={BTN_OUTLINE}>Cancelar</button>
|
|
133
|
+
<button type="button" onClick={save} disabled={busy} className={BTN_PRIMARY}>
|
|
134
|
+
{busy ? <CircleNotch className="animate-spin" /> : null} Salvar
|
|
135
|
+
</button>
|
|
136
|
+
</DialogFooter>
|
|
137
|
+
</DialogContent>
|
|
138
|
+
</Dialog>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function Tag({ children }: { children: React.ReactNode }) {
|
|
144
|
+
return (
|
|
145
|
+
<span className="inline-flex items-center rounded-full border border-[var(--store-primary-soft)] bg-[var(--store-primary-soft,#F1F1F3)] px-2.5 py-0.5 text-[11.5px] font-bold text-[var(--store-primary,#18181B)]">{children}</span>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { toast } from "sonner";
|
|
5
|
+
|
|
6
|
+
export function PreferencesForm({
|
|
7
|
+
initial,
|
|
8
|
+
}: {
|
|
9
|
+
initial: { receiveNewOrderEmail: boolean; reuseDataBetweenShops: boolean };
|
|
10
|
+
}) {
|
|
11
|
+
const [state, setState] = React.useState(initial);
|
|
12
|
+
const [saving, setSaving] = React.useState(false);
|
|
13
|
+
|
|
14
|
+
async function update(patch: Partial<typeof state>) {
|
|
15
|
+
const prev = state;
|
|
16
|
+
const next = { ...state, ...patch };
|
|
17
|
+
setState(next);
|
|
18
|
+
setSaving(true);
|
|
19
|
+
try {
|
|
20
|
+
const res = await fetch("/api/account/preferences", {
|
|
21
|
+
method: "PATCH",
|
|
22
|
+
headers: { "Content-Type": "application/json" },
|
|
23
|
+
body: JSON.stringify(patch),
|
|
24
|
+
});
|
|
25
|
+
if (!res.ok) throw new Error((await res.json().catch(() => ({})))?.error || "Erro ao salvar.");
|
|
26
|
+
toast.success("Preferências atualizadas.");
|
|
27
|
+
} catch (e: any) {
|
|
28
|
+
toast.error(e.message);
|
|
29
|
+
setState(prev); // reverte
|
|
30
|
+
} finally {
|
|
31
|
+
setSaving(false);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="flex flex-col divide-y divide-[var(--store-surface-2)] rounded-xl border border-[var(--store-line)] bg-white px-5">
|
|
37
|
+
<Row
|
|
38
|
+
label="Receber e-mail a cada novo pedido"
|
|
39
|
+
desc="Avisamos quando um pedido é criado."
|
|
40
|
+
checked={state.receiveNewOrderEmail}
|
|
41
|
+
disabled={saving}
|
|
42
|
+
onChange={(v) => update({ receiveNewOrderEmail: v })}
|
|
43
|
+
/>
|
|
44
|
+
<Row
|
|
45
|
+
label="Reutilizar meus dados entre lojas Unbox"
|
|
46
|
+
desc="Agiliza o checkout em outras lojas da rede."
|
|
47
|
+
checked={state.reuseDataBetweenShops}
|
|
48
|
+
disabled={saving}
|
|
49
|
+
onChange={(v) => update({ reuseDataBetweenShops: v })}
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function Row({ label, desc, checked, onChange, disabled }: {
|
|
56
|
+
label: string; desc: string; checked: boolean; onChange: (v: boolean) => void; disabled?: boolean;
|
|
57
|
+
}) {
|
|
58
|
+
return (
|
|
59
|
+
<div className="flex items-center justify-between gap-4 py-4">
|
|
60
|
+
<div>
|
|
61
|
+
<div className="text-[14.5px] font-bold text-[var(--store-ink)]">{label}</div>
|
|
62
|
+
<div className="mt-0.5 text-[13px] text-[var(--store-muted)]">{desc}</div>
|
|
63
|
+
</div>
|
|
64
|
+
<button
|
|
65
|
+
type="button"
|
|
66
|
+
role="switch"
|
|
67
|
+
aria-checked={checked}
|
|
68
|
+
disabled={disabled}
|
|
69
|
+
onClick={() => onChange(!checked)}
|
|
70
|
+
className="relative h-[26px] w-[46px] shrink-0 cursor-pointer rounded-full transition-colors disabled:opacity-60"
|
|
71
|
+
style={{ background: checked ? "var(--store-primary,#18181B)" : "var(--store-line-2)" }}
|
|
72
|
+
>
|
|
73
|
+
<span className="absolute top-[3px] h-[20px] w-[20px] rounded-full bg-white shadow-[0_1px_3px_rgba(0,0,0,.2)] transition-all" style={{ left: checked ? 23 : 3 }} />
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { ArrowsClockwise, Spinner } from "@phosphor-icons/react/dist/ssr";
|
|
5
|
+
import { useCart } from "@/components/cart/cart-provider";
|
|
6
|
+
|
|
7
|
+
export interface ReorderItem {
|
|
8
|
+
productId: string;
|
|
9
|
+
variantId: string;
|
|
10
|
+
price: number;
|
|
11
|
+
quantity: number;
|
|
12
|
+
title?: string;
|
|
13
|
+
thumbnail?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function ReorderButton({ items }: { items: ReorderItem[] }) {
|
|
17
|
+
const { add, setOpen } = useCart();
|
|
18
|
+
const [busy, setBusy] = React.useState(false);
|
|
19
|
+
|
|
20
|
+
if (items.length === 0) return null;
|
|
21
|
+
|
|
22
|
+
async function reorder() {
|
|
23
|
+
setBusy(true);
|
|
24
|
+
try {
|
|
25
|
+
for (const it of items) {
|
|
26
|
+
await add(
|
|
27
|
+
{ productId: it.productId, variantId: it.variantId, price: it.price, quantity: it.quantity, title: it.title, thumbnail: it.thumbnail },
|
|
28
|
+
{ silent: true },
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
setOpen(true);
|
|
32
|
+
} finally {
|
|
33
|
+
setBusy(false);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<button
|
|
39
|
+
type="button"
|
|
40
|
+
onClick={reorder}
|
|
41
|
+
disabled={busy}
|
|
42
|
+
className="font-display inline-flex h-11 cursor-pointer items-center justify-center gap-2 rounded-xl bg-[var(--store-primary,#18181B)] px-5 text-[14px] font-bold text-white transition-colors hover:bg-[var(--store-primary-dark,#09090B)] disabled:opacity-60"
|
|
43
|
+
>
|
|
44
|
+
{busy ? <Spinner className="animate-spin" /> : <ArrowsClockwise weight="bold" />} Refazer pedido
|
|
45
|
+
</button>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { SignOut } from "@phosphor-icons/react/dist/ssr";
|
|
4
|
+
|
|
5
|
+
export function SignOutButton() {
|
|
6
|
+
return (
|
|
7
|
+
<button
|
|
8
|
+
type="button"
|
|
9
|
+
onClick={async () => {
|
|
10
|
+
await fetch("/api/account/signout", { method: "POST" });
|
|
11
|
+
// Documento inteiro (não router.push): garante que nenhum payload RSC logado
|
|
12
|
+
// sobreviva ao fim da sessão — mesma classe de bug do pós-login.
|
|
13
|
+
window.location.assign("/");
|
|
14
|
+
}}
|
|
15
|
+
className="flex w-full cursor-pointer items-center gap-2.5 rounded-xl px-3.5 py-2.5 text-[14px] font-semibold text-[var(--store-muted)] transition-colors hover:bg-[var(--store-sale-soft)] hover:text-[var(--store-sale)]"
|
|
16
|
+
>
|
|
17
|
+
<SignOut weight="bold" className="text-[18px]" /> Sair
|
|
18
|
+
</button>
|
|
19
|
+
);
|
|
20
|
+
}
|