@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,104 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
import Image from "next/image";
|
|
4
|
+
import { redirect } from "next/navigation";
|
|
5
|
+
import { CaretRight, ArrowsClockwise, MapPin, Package } from "@phosphor-icons/react/dist/ssr";
|
|
6
|
+
import { getCustomerClient } from "@/lib/customer-session";
|
|
7
|
+
import { shapeOrderSummary, type ShapedOrderSummary } from "@/lib/orders";
|
|
8
|
+
import { AccountShell } from "@/components/account/account-shell";
|
|
9
|
+
import { EmptyState } from "@/components/empty-state";
|
|
10
|
+
import { StatusBadge, statusTone } from "@/components/order-status";
|
|
11
|
+
|
|
12
|
+
export const metadata: Metadata = { title: "Meus pedidos", robots: { index: false } };
|
|
13
|
+
|
|
14
|
+
export default async function PedidosPage() {
|
|
15
|
+
const me = await getCustomerClient();
|
|
16
|
+
if (!me) redirect("/conta/entrar");
|
|
17
|
+
|
|
18
|
+
const orders = await me.orders({ first: 30 }).catch(() => ({ nodes: [], totalCount: 0 }));
|
|
19
|
+
const list: ShapedOrderSummary[] = (orders.nodes ?? []).map((n: any) => shapeOrderSummary(n));
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<AccountShell title="Meus pedidos">
|
|
23
|
+
{list.length === 0 ? (
|
|
24
|
+
<EmptyState
|
|
25
|
+
title="Você ainda não tem pedidos"
|
|
26
|
+
description="Quando você fizer uma compra, ela aparece aqui com status, itens e rastreio."
|
|
27
|
+
>
|
|
28
|
+
<Link href="/produtos" className="font-display inline-flex h-11 items-center rounded-xl bg-[var(--store-primary,#18181B)] px-5 text-[14px] font-bold text-white no-underline transition-colors hover:bg-[var(--store-primary-dark,#09090B)]">
|
|
29
|
+
Ver produtos
|
|
30
|
+
</Link>
|
|
31
|
+
</EmptyState>
|
|
32
|
+
) : (
|
|
33
|
+
<div className="flex flex-col gap-3.5">
|
|
34
|
+
{list.map((o) => (
|
|
35
|
+
<Link
|
|
36
|
+
key={o.referenceId}
|
|
37
|
+
href={`/conta/pedidos/${o.referenceId}`}
|
|
38
|
+
className="group flex flex-col gap-3.5 rounded-lg border border-[var(--store-line)] bg-white p-4 no-underline transition-all hover:border-[var(--store-primary,#18181B)] hover:shadow-[0_8px_22px_rgba(24,24,27,.08)]"
|
|
39
|
+
>
|
|
40
|
+
{/* topo: ref + data + status */}
|
|
41
|
+
<div className="flex items-start justify-between gap-3">
|
|
42
|
+
<div className="min-w-0">
|
|
43
|
+
<p className="font-display text-[15.5px] font-extrabold text-[var(--store-ink)]">#{o.referenceId}</p>
|
|
44
|
+
<p className="mt-0.5 flex items-center gap-1.5 text-[12.5px] text-[var(--store-muted)]">
|
|
45
|
+
{o.createdAt ? new Date(o.createdAt).toLocaleDateString("pt-BR") : ""}
|
|
46
|
+
{o.recurring && (
|
|
47
|
+
<span className="inline-flex items-center gap-1 font-semibold text-[var(--store-primary,#18181B)]">
|
|
48
|
+
<ArrowsClockwise weight="bold" className="text-[12px]" />assinatura
|
|
49
|
+
</span>
|
|
50
|
+
)}
|
|
51
|
+
</p>
|
|
52
|
+
</div>
|
|
53
|
+
<div className="flex shrink-0 items-center gap-2">
|
|
54
|
+
{o.delivered ? (
|
|
55
|
+
<StatusBadge label="Entregue" tone="ok" />
|
|
56
|
+
) : o.dispatched ? (
|
|
57
|
+
<StatusBadge label="Despachado" tone="warn" />
|
|
58
|
+
) : (
|
|
59
|
+
<StatusBadge label={o.statusLabel} tone={statusTone(o.status)} />
|
|
60
|
+
)}
|
|
61
|
+
<CaretRight weight="bold" className="text-[var(--store-faint)] transition-transform group-hover:translate-x-0.5 group-hover:text-[var(--store-primary,#18181B)]" />
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
{/* produtos: miniaturas + resumo + total */}
|
|
66
|
+
<div className="flex items-center gap-3 border-t border-[var(--store-surface-2)] pt-3.5">
|
|
67
|
+
<div className="flex shrink-0 -space-x-2">
|
|
68
|
+
{o.thumbnails.length > 0 ? (
|
|
69
|
+
o.thumbnails.map((src, i) => (
|
|
70
|
+
<span key={i} className="relative h-11 w-11 overflow-hidden rounded-md border border-[var(--store-line)] bg-[var(--store-surface)]">
|
|
71
|
+
<Image src={src} alt="" fill sizes="44px" className="object-contain p-1" />
|
|
72
|
+
</span>
|
|
73
|
+
))
|
|
74
|
+
) : (
|
|
75
|
+
<span className="flex h-11 w-11 items-center justify-center rounded-md border border-[var(--store-line)] bg-[var(--store-surface)]"><Package className="text-[var(--store-faint)]" /></span>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
<div className="min-w-0 flex-1">
|
|
79
|
+
<p className="truncate text-[13px] font-semibold text-[var(--store-ink-2)]">
|
|
80
|
+
{o.firstTitles.join(" · ") || `${o.itemCount} item(ns)`}
|
|
81
|
+
</p>
|
|
82
|
+
<p className="text-[12px] text-[var(--store-muted)]">{o.itemCount} item(ns)</p>
|
|
83
|
+
</div>
|
|
84
|
+
{o.total && <span className="font-display shrink-0 text-[15px] font-extrabold text-[var(--store-primary,#18181B)]">{o.total}</span>}
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
{/* endereço de entrega */}
|
|
88
|
+
{o.address && (o.address.line || o.address.cityRegion) && (
|
|
89
|
+
<div className="flex items-start gap-2 rounded-md bg-[var(--store-surface)] px-3 py-2.5 text-[12.5px] text-[var(--store-ink-2)]">
|
|
90
|
+
<MapPin weight="fill" className="mt-px shrink-0 text-[14px] text-[var(--store-primary,#18181B)]" />
|
|
91
|
+
<span className="min-w-0">
|
|
92
|
+
{o.address.fullName && <b className="text-[var(--store-ink)]">{o.address.fullName}</b>}
|
|
93
|
+
{o.address.fullName && (o.address.line || o.address.cityRegion) ? " · " : ""}
|
|
94
|
+
{[o.address.line, o.address.cityRegion].filter(Boolean).join(" · ")}
|
|
95
|
+
</span>
|
|
96
|
+
</div>
|
|
97
|
+
)}
|
|
98
|
+
</Link>
|
|
99
|
+
))}
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
102
|
+
</AccountShell>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { redirect } from "next/navigation";
|
|
3
|
+
import { getCustomerClient } from "@/lib/customer-session";
|
|
4
|
+
import { AccountShell } from "@/components/account/account-shell";
|
|
5
|
+
import { PreferencesForm } from "@/components/account/preferences-form";
|
|
6
|
+
|
|
7
|
+
export const metadata: Metadata = { title: "Preferências", robots: { index: false } };
|
|
8
|
+
|
|
9
|
+
export default async function PreferenciasPage() {
|
|
10
|
+
const me = await getCustomerClient();
|
|
11
|
+
if (!me) redirect("/conta/entrar");
|
|
12
|
+
const account = await me.me().catch(() => null);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<AccountShell title="Preferências">
|
|
16
|
+
<PreferencesForm
|
|
17
|
+
initial={{
|
|
18
|
+
receiveNewOrderEmail: account?.metafields?.receiveNewOrderEmail ?? true,
|
|
19
|
+
reuseDataBetweenShops: account?.reuseDataBetweenShops ?? false,
|
|
20
|
+
}}
|
|
21
|
+
/>
|
|
22
|
+
</AccountShell>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
import { Button } from "@/components/ui/button";
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = { title: "Trocas e devoluções", alternates: { canonical: "/devolucoes" } };
|
|
6
|
+
|
|
7
|
+
export default function DevolucoesPage() {
|
|
8
|
+
return (
|
|
9
|
+
<article className="mx-auto max-w-2xl">
|
|
10
|
+
<h1 className="text-2xl font-bold">Trocas e devoluções</h1>
|
|
11
|
+
|
|
12
|
+
<h2 className="mt-6 text-lg font-semibold">Direito de arrependimento (CDC, art. 49)</h2>
|
|
13
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
14
|
+
Você pode desistir da compra em até <strong>7 dias corridos</strong> a partir do recebimento do
|
|
15
|
+
produto. Nesse caso, devolvemos o valor pago, incluindo o frete.
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<h2 className="mt-6 text-lg font-semibold">Como solicitar</h2>
|
|
19
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
20
|
+
Entre na sua conta, localize o pedido em <Link href="/conta/pedidos" className="text-primary underline">Meus pedidos</Link> e
|
|
21
|
+
solicite o cancelamento/devolução, ou fale com nosso atendimento informando o código do pedido.
|
|
22
|
+
O reembolso é processado pelo mesmo meio de pagamento (Pix ou cartão).
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
<p className="mt-3 text-xs text-muted-foreground">
|
|
26
|
+
Observação: o cancelamento/reembolso total é tratado pela equipe da loja. Itens individuais de um
|
|
27
|
+
pedido podem ser cancelados conforme disponibilidade.
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
<div className="mt-6 flex gap-2">
|
|
31
|
+
<Button nativeButton={false} render={<Link href="/conta/pedidos" />}>Meus pedidos</Button>
|
|
32
|
+
<Button variant="outline" nativeButton={false} render={<Link href="/conta/entrar" />}>Acompanhar pedido</Button>
|
|
33
|
+
</div>
|
|
34
|
+
</article>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Layout do GRUPO DE ROTAS da loja: header, barra de anúncio e rodapé vivem
|
|
2
|
+
// AQUI, não no layout raiz. Assim qualquer página fora da loja (tela de acesso, erro,
|
|
3
|
+
// landing avulsa) nasce limpa, sem chrome vazando por trás — basta criá-la fora de (loja).
|
|
4
|
+
// O grupo não muda as URLs: app/(loja)/produtos/page.tsx continua sendo /produtos.
|
|
5
|
+
import { CartProvider } from "@/components/cart/cart-provider";
|
|
6
|
+
import { SiteHeader } from "@/components/site-header";
|
|
7
|
+
import { SiteFooter } from "@/components/site-footer";
|
|
8
|
+
|
|
9
|
+
export default function StoreLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
|
10
|
+
return (
|
|
11
|
+
<CartProvider>
|
|
12
|
+
{/* Coluna flex de altura mínima igual à tela: em página curta (confirmação, conta vazia) o
|
|
13
|
+
rodapé encosta no fim da viewport em vez de flutuar com fundo branco embaixo. */}
|
|
14
|
+
<div className="flex min-h-[100svh] flex-col">
|
|
15
|
+
<SiteHeader />
|
|
16
|
+
{/* .site-main / chrome: ocultos/expandidos em /checkout via CSS :has(.checkout-root) */}
|
|
17
|
+
<main className="site-main mx-auto w-full max-w-[1240px] flex-1 px-4 py-6 sm:px-6">{children}</main>
|
|
18
|
+
<SiteFooter />
|
|
19
|
+
</div>
|
|
20
|
+
</CartProvider>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Landing de OFERTA — mesma seção purchase-hero usada na home (componente do registry),
|
|
2
|
+
// com a pilha de convencimento por receita (components/landing/landing-recipe.ts) abaixo.
|
|
3
|
+
// A página existe como deep-link de campanha; nas receitas dos presets o hero de compra
|
|
4
|
+
// pode entrar DIRETO na home (âncora #comprar), que é o padrão das lojas de produção.
|
|
5
|
+
import type { Metadata } from "next";
|
|
6
|
+
import { getCatalog, getTopTags } from "@/lib/queries";
|
|
7
|
+
import { buildTagMap, buildCategories, mapCatalogItems } from "@/lib/catalog-map";
|
|
8
|
+
import { FREE_SHIPPING_THRESHOLD } from "@/lib/store-config";
|
|
9
|
+
import { resolveCombos } from "@/lib/enrichment/combos";
|
|
10
|
+
import type { HomeData } from "@/components/home/sections/registry";
|
|
11
|
+
// EDITOR: a pilha da landing dentro do container PRÓPRIO dela ("oferta", declarado em
|
|
12
|
+
// lib/rotas-editaveis.ts). Renderizada solta, nada nela é editável e o gate reprova a página.
|
|
13
|
+
import { OfertaSections } from "@/components/landing/oferta-sections";
|
|
14
|
+
import { mockupOr } from "@/lib/mockup";
|
|
15
|
+
|
|
16
|
+
export const revalidate = 300;
|
|
17
|
+
|
|
18
|
+
export const metadata: Metadata = {
|
|
19
|
+
title: "Oferta especial",
|
|
20
|
+
description: "Leve mais, aproveite mais. Monte seu pedido com condições especiais.",
|
|
21
|
+
alternates: { canonical: "/oferta" },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const isCombo = (s: string) => /kit|combo/i.test(s);
|
|
25
|
+
|
|
26
|
+
export default async function OfertaPage() {
|
|
27
|
+
const [catalog, tags] = await Promise.all([
|
|
28
|
+
mockupOr(getCatalog({ first: 100 }), { nodes: [] as any[] }, "oferta/getCatalog"),
|
|
29
|
+
mockupOr(getTopTags(), [], "oferta/getTopTags"),
|
|
30
|
+
]);
|
|
31
|
+
const tagMap = buildTagMap(tags as any[]);
|
|
32
|
+
const items = mapCatalogItems(catalog.nodes ?? [], tagMap);
|
|
33
|
+
|
|
34
|
+
let combos = items.filter((it) => it.categories.some(isCombo));
|
|
35
|
+
if (combos.length < 2) {
|
|
36
|
+
const deals = items.filter((it) => it.oldPrice != null);
|
|
37
|
+
combos = deals.length >= 2 ? deals : items.slice(0, 8);
|
|
38
|
+
}
|
|
39
|
+
combos = combos.slice(0, 10);
|
|
40
|
+
|
|
41
|
+
const data: HomeData = {
|
|
42
|
+
combos,
|
|
43
|
+
featured: combos[0] ?? items[0] ?? null,
|
|
44
|
+
bundles: resolveCombos((catalog.nodes ?? []).map((n: any) => n.product ?? n)),
|
|
45
|
+
categories: buildCategories(tags as any[]),
|
|
46
|
+
combosTitle: "Destaques",
|
|
47
|
+
freeShipLabel: FREE_SHIPPING_THRESHOLD != null ? `R$${FREE_SHIPPING_THRESHOLD}` : null,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="store-layout full-bleed bg-white text-[var(--store-ink)]">
|
|
52
|
+
<h1 className="sr-only">Oferta especial</h1>
|
|
53
|
+
<OfertaSections data={data} />
|
|
54
|
+
<div className="pb-14" />
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { getCatalog, getTopTags } from "@/lib/queries";
|
|
3
|
+
import { buildTagMap, buildCategories, mapCatalogItems } from "@/lib/catalog-map";
|
|
4
|
+
import { FREE_SHIPPING_THRESHOLD } from "@/lib/store-config";
|
|
5
|
+
import { resolveCombos } from "@/lib/enrichment/combos";
|
|
6
|
+
import { CombosHome } from "@/components/home/combos-home";
|
|
7
|
+
import { mockupOr } from "@/lib/mockup";
|
|
8
|
+
import { DataLayerReady } from "@/components/analytics/data-layer-ready";
|
|
9
|
+
import { ldJson } from "@/lib/json-ld";
|
|
10
|
+
// EDITOR: o documento publicado (só a ESCOLHA das vitrines) e quem a transforma em produtos
|
|
11
|
+
import { getPublishedContent } from "@/lib/editable/server";
|
|
12
|
+
import { resolverVitrinesDoDocumento } from "@/lib/vitrine";
|
|
13
|
+
|
|
14
|
+
// Canonical por página: o layout raiz não declara (seria herdado por todas as rotas).
|
|
15
|
+
export const metadata: Metadata = { alternates: { canonical: "/" } };
|
|
16
|
+
|
|
17
|
+
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:3000";
|
|
18
|
+
// Mesmo literal do app/layout.tsx: o create-unbox-store reescreve "Minha Loja" com o nome real.
|
|
19
|
+
const SITE_NAME = "Minha Loja";
|
|
20
|
+
|
|
21
|
+
export const revalidate = 300; // ISR — catálogo público e estável
|
|
22
|
+
|
|
23
|
+
const isCombo = (s: string) => /kit|combo/i.test(s);
|
|
24
|
+
|
|
25
|
+
export default async function HomePage() {
|
|
26
|
+
const [catalog, tags] = await Promise.all([
|
|
27
|
+
mockupOr(getCatalog({ first: 100 }), { nodes: [] as any[] }, "home/getCatalog"),
|
|
28
|
+
mockupOr(getTopTags(), [], "home/getTopTags"),
|
|
29
|
+
]);
|
|
30
|
+
const tagMap = buildTagMap(tags as any[]);
|
|
31
|
+
const items = mapCatalogItems(catalog.nodes ?? [], tagMap);
|
|
32
|
+
|
|
33
|
+
// Combos reais: categoria "Kits & Combos" → senão produtos em oferta (compareAtPrice) → senão destaques.
|
|
34
|
+
let combos = items.filter((it) => it.categories.some(isCombo));
|
|
35
|
+
let title = "Nossos Combos em Destaque";
|
|
36
|
+
if (combos.length < 2) {
|
|
37
|
+
const deals = items.filter((it) => it.oldPrice != null);
|
|
38
|
+
if (deals.length >= 2) { combos = deals; title = "Ofertas em destaque"; }
|
|
39
|
+
else { combos = items.slice(0, 8); title = "Destaques"; }
|
|
40
|
+
}
|
|
41
|
+
combos = combos.slice(0, 10);
|
|
42
|
+
const featured = combos[0] ?? items[0] ?? null;
|
|
43
|
+
|
|
44
|
+
// Kits temáticos (combos de linha, edição de data…) montados a partir do products.json.
|
|
45
|
+
const bundles = resolveCombos((catalog.nodes ?? []).map((n: any) => n.product ?? n));
|
|
46
|
+
|
|
47
|
+
// ── AS VITRINES DA HOME (editor de loja) ─────────────────────────────────
|
|
48
|
+
// O documento publicado guarda só a ESCOLHA do lojista ("quais produtos esta vitrine mostra");
|
|
49
|
+
// quem a transforma em produto com preço e estoque é a LOJA, aqui, com o cliente da Unbox de
|
|
50
|
+
// sempre. Quais caminhos existem sai do PRÓPRIO DOCUMENTO, não de constante escrita à mão: a
|
|
51
|
+
// vitrine também é uma seção ADICIONÁVEL, cujo id só nasce quando o lojista clica no "+"
|
|
52
|
+
// (`novo-vitrine-de-produtos-2`), e nenhuma constante do repositório o conheceria.
|
|
53
|
+
//
|
|
54
|
+
// Sem editor, sem escolha, ou com a Unbox fora: `{}`, e cada vitrine mostra os produtos do
|
|
55
|
+
// CÓDIGO. Ligar isto não muda um pixel enquanto ninguém escolher nada.
|
|
56
|
+
const doc = await getPublishedContent();
|
|
57
|
+
const vitrines = await resolverVitrinesDoDocumento(doc, "home");
|
|
58
|
+
|
|
59
|
+
// Organization + WebSite: dá à marca uma entidade citável (busca e resposta de IA) e liga a
|
|
60
|
+
// busca interna ao Google (SearchAction). Só fatos deriváveis: nome, URL, logo, busca.
|
|
61
|
+
const jsonLd = [
|
|
62
|
+
{ "@context": "https://schema.org", "@type": "Organization", name: SITE_NAME, url: siteUrl, logo: `${siteUrl}/brand/logo.svg` },
|
|
63
|
+
{
|
|
64
|
+
"@context": "https://schema.org", "@type": "WebSite", name: SITE_NAME, url: siteUrl,
|
|
65
|
+
potentialAction: { "@type": "SearchAction", target: { "@type": "EntryPoint", urlTemplate: `${siteUrl}/busca?q={search_term_string}` }, "query-input": "required name=search_term_string" },
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<>
|
|
71
|
+
{/* dataLayerReady: é o ÚNICO gatilho de tipo de página do container central (medido: ele não
|
|
72
|
+
tem gatilho de `page_view`). Sem isto, a home fica invisível pro remarketing do Ads. */}
|
|
73
|
+
<DataLayerReady pageType="home" products={combos.slice(0, 6).map((c) => ({ id: c.productId, name: c.title, price: c.price }))} />
|
|
74
|
+
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: ldJson(jsonLd) }} />
|
|
75
|
+
<CombosHome
|
|
76
|
+
combos={combos}
|
|
77
|
+
featured={featured}
|
|
78
|
+
bundles={bundles}
|
|
79
|
+
categories={buildCategories(tags as any[])}
|
|
80
|
+
combosTitle={title}
|
|
81
|
+
freeShipLabel={FREE_SHIPPING_THRESHOLD != null ? `R$${FREE_SHIPPING_THRESHOLD}` : null}
|
|
82
|
+
vitrines={vitrines}
|
|
83
|
+
/>
|
|
84
|
+
</>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
import { CheckCircle } from "@phosphor-icons/react/dist/ssr";
|
|
4
|
+
import { getOwnedOrder } from "@/lib/orders";
|
|
5
|
+
import { OrderStatusCard } from "@/components/order-status";
|
|
6
|
+
import { EmptyState } from "@/components/empty-state";
|
|
7
|
+
import { PurchaseTracker } from "@/components/analytics/purchase-tracker";
|
|
8
|
+
import { DataLayerReady } from "@/components/analytics/data-layer-ready";
|
|
9
|
+
import { parseBRL } from "@/lib/format";
|
|
10
|
+
import { mockupOr } from "@/lib/mockup";
|
|
11
|
+
|
|
12
|
+
export const metadata: Metadata = { title: "Pedido", robots: { index: false } };
|
|
13
|
+
|
|
14
|
+
export default async function PedidoPage({ params }: { params: Promise<{ referenceId: string }> }) {
|
|
15
|
+
const { referenceId } = await params;
|
|
16
|
+
const order = await mockupOr(getOwnedOrder(referenceId), null, "pedido/getOwnedOrder");
|
|
17
|
+
|
|
18
|
+
if (!order) {
|
|
19
|
+
return (
|
|
20
|
+
<div className="store-layout full-bleed bg-white text-[var(--store-ink)]">
|
|
21
|
+
<div className="mx-auto max-w-[560px] px-4 py-12 sm:px-6">
|
|
22
|
+
<EmptyState
|
|
23
|
+
title="Não foi possível exibir este pedido"
|
|
24
|
+
description="Entre na sua conta para ver este pedido."
|
|
25
|
+
>
|
|
26
|
+
<div className="flex flex-wrap justify-center gap-2.5">
|
|
27
|
+
<Link href="/conta/entrar" className="font-display inline-flex h-11 items-center rounded-xl bg-[var(--store-primary,#18181B)] px-5 text-[14px] font-bold text-white no-underline transition-colors hover:bg-[var(--store-primary-dark,#09090B)]">Entrar</Link>
|
|
28
|
+
</div>
|
|
29
|
+
</EmptyState>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="store-layout full-bleed bg-white text-[var(--store-ink)]">
|
|
37
|
+
<div className="mx-auto max-w-[560px] px-4 py-12 sm:px-6">
|
|
38
|
+
<div className="mb-6 flex items-center gap-3.5">
|
|
39
|
+
<span className="flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-[var(--store-primary-soft,#F1F1F3)] text-[var(--store-primary,#18181B)]">
|
|
40
|
+
<CheckCircle weight="fill" className="text-[30px]" />
|
|
41
|
+
</span>
|
|
42
|
+
<div>
|
|
43
|
+
<h1 className="font-display text-[26px] font-extrabold leading-[1.1]">Pedido recebido</h1>
|
|
44
|
+
<p className="mt-0.5 text-[14px] text-[var(--store-muted)]">
|
|
45
|
+
{order.paid ? "Pagamento confirmado." : "Acompanhe o status do pagamento abaixo."}
|
|
46
|
+
</p>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<OrderStatusCard order={order} />
|
|
51
|
+
|
|
52
|
+
{/* purchase / Purchase — só quando pago (ex.: cartão aprovado); dedupe por referenceId */}
|
|
53
|
+
<DataLayerReady
|
|
54
|
+
pageType={order.paid ? "purchase" : "other"}
|
|
55
|
+
products={order.items.map((it) => ({ id: it.productId ?? it.variantId ?? it.title, name: it.title, price: it.price, quantity: it.quantity }))}
|
|
56
|
+
/>
|
|
57
|
+
<PurchaseTracker
|
|
58
|
+
transactionId={order.referenceId}
|
|
59
|
+
paid={order.paid}
|
|
60
|
+
value={parseBRL(order.total) ?? undefined}
|
|
61
|
+
items={order.items.map((it) => ({
|
|
62
|
+
id: it.productId ?? it.variantId ?? it.title,
|
|
63
|
+
name: it.title,
|
|
64
|
+
variant: it.variantTitle,
|
|
65
|
+
price: it.price,
|
|
66
|
+
quantity: it.quantity,
|
|
67
|
+
}))}
|
|
68
|
+
user={{ email: order.email, firstName: order.address?.fullName?.split(" ")[0], lastName: order.address?.fullName?.split(" ").slice(1).join(" ") || undefined }}
|
|
69
|
+
/>
|
|
70
|
+
|
|
71
|
+
<div className="mt-6 flex flex-wrap gap-2.5">
|
|
72
|
+
<Link href="/produtos" className="font-display inline-flex h-12 items-center rounded-xl bg-[var(--store-primary,#18181B)] px-5 text-[14px] font-bold text-white no-underline transition-colors hover:bg-[var(--store-primary-dark,#09090B)]">Continuar comprando</Link>
|
|
73
|
+
<Link href="/conta/pedidos" className="inline-flex h-12 items-center rounded-xl border-[1.5px] border-[var(--store-line-2)] bg-white px-5 text-[14px] font-bold text-[var(--store-ink-2)] no-underline transition-colors hover:border-[var(--store-primary,#18181B)] hover:text-[var(--store-primary,#18181B)]">Meus pedidos</Link>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
|
|
3
|
+
export const metadata: Metadata = {
|
|
4
|
+
title: "Política de Privacidade",
|
|
5
|
+
robots: { index: true, follow: true },
|
|
6
|
+
alternates: { canonical: "/privacidade" },
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// TODO [JURÍDICO]: revisar e completar este texto antes do lançamento.
|
|
10
|
+
// Este é um placeholder estruturado para LGPD (Lei 13.709/2018).
|
|
11
|
+
// Substituir [NOME DA LOJA], [CNPJ], [ENDEREÇO], [EMAIL DPO] pelos dados reais.
|
|
12
|
+
|
|
13
|
+
export default function PrivacidadePage() {
|
|
14
|
+
return (
|
|
15
|
+
<article className="richtext mx-auto max-w-2xl py-8 px-4">
|
|
16
|
+
<h1>Política de Privacidade</h1>
|
|
17
|
+
<p className="text-sm text-muted-foreground">Última atualização: preencher data</p>
|
|
18
|
+
|
|
19
|
+
<h2>1. Quem somos</h2>
|
|
20
|
+
<p>
|
|
21
|
+
<strong>[NOME DA LOJA]</strong>, inscrita no CNPJ sob o nº <strong>[CNPJ]</strong>,
|
|
22
|
+
com sede em <strong>[ENDEREÇO COMPLETO]</strong>, é a controladora dos dados pessoais
|
|
23
|
+
tratados neste site, nos termos da Lei Geral de Proteção de Dados (LGPD, Lei 13.709/2018).
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<h2>2. Dados coletados</h2>
|
|
27
|
+
<p>Coletamos os seguintes dados para operar a loja:</p>
|
|
28
|
+
<ul>
|
|
29
|
+
<li><strong>Cadastro e compra:</strong> nome, e-mail, CPF, telefone, endereço de entrega</li>
|
|
30
|
+
<li><strong>Pagamento:</strong> os dados de cartão são processados diretamente pela UnboxPay, e não armazenamos número de cartão</li>
|
|
31
|
+
<li><strong>Navegação:</strong> endereço IP, cookies de sessão, preferências de carrinho</li>
|
|
32
|
+
<li><strong>Analytics:</strong> eventos de navegação e compra (via Google Analytics / Meta Pixel)</li>
|
|
33
|
+
</ul>
|
|
34
|
+
|
|
35
|
+
<h2>3. Finalidade do tratamento</h2>
|
|
36
|
+
<ul>
|
|
37
|
+
<li>Processar pedidos e pagamentos</li>
|
|
38
|
+
<li>Enviar confirmações de pedido e atualizações de entrega por e-mail</li>
|
|
39
|
+
<li>Gerir assinaturas recorrentes, se aplicável</li>
|
|
40
|
+
<li>Personalizar a experiência de navegação</li>
|
|
41
|
+
<li>Cumprir obrigações legais (nota fiscal, CDC)</li>
|
|
42
|
+
<li>Melhorar nossos produtos e serviços com base em dados agregados</li>
|
|
43
|
+
</ul>
|
|
44
|
+
|
|
45
|
+
<h2>4. Base legal</h2>
|
|
46
|
+
<p>
|
|
47
|
+
O tratamento é baseado em: (a) execução de contrato, para processar seus pedidos;
|
|
48
|
+
(b) obrigação legal, para emissão de nota fiscal e cumprimento do CDC;
|
|
49
|
+
(c) legítimo interesse, para analytics e segurança; (d) consentimento, para envio de
|
|
50
|
+
ofertas por e-mail (você pode revogar a qualquer momento).
|
|
51
|
+
</p>
|
|
52
|
+
|
|
53
|
+
<h2>5. Compartilhamento</h2>
|
|
54
|
+
<p>Seus dados podem ser compartilhados com:</p>
|
|
55
|
+
<ul>
|
|
56
|
+
<li><strong>Unbox:</strong> plataforma de e-commerce que opera o back-end da loja</li>
|
|
57
|
+
<li><strong>Transportadoras:</strong> nome e endereço de entrega para despacho do pedido</li>
|
|
58
|
+
<li><strong>Gateway de pagamento (UnboxPay):</strong> dados de cobrança</li>
|
|
59
|
+
<li><strong>Google e Meta:</strong> dados de analytics e conversão (pseudonimizados)</li>
|
|
60
|
+
</ul>
|
|
61
|
+
<p>Não vendemos seus dados a terceiros.</p>
|
|
62
|
+
|
|
63
|
+
<h2>6. Cookies</h2>
|
|
64
|
+
<p>
|
|
65
|
+
Utilizamos cookies essenciais (sessão, carrinho) e opcionais de analytics (GA4, Meta Pixel).
|
|
66
|
+
Você pode gerenciar suas preferências a qualquer momento pelo banner de cookies ou nas
|
|
67
|
+
configurações do seu navegador.
|
|
68
|
+
</p>
|
|
69
|
+
|
|
70
|
+
<h2>7. Seus direitos (LGPD, art. 18)</h2>
|
|
71
|
+
<p>Você tem direito a:</p>
|
|
72
|
+
<ul>
|
|
73
|
+
<li>Confirmar a existência de tratamento dos seus dados</li>
|
|
74
|
+
<li>Acessar, corrigir ou atualizar seus dados</li>
|
|
75
|
+
<li>Solicitar a anonimização, bloqueio ou eliminação de dados desnecessários</li>
|
|
76
|
+
<li>Solicitar portabilidade dos seus dados</li>
|
|
77
|
+
<li>Revogar o consentimento para comunicações de marketing</li>
|
|
78
|
+
<li>Opor-se ao tratamento baseado em legítimo interesse</li>
|
|
79
|
+
</ul>
|
|
80
|
+
<p>
|
|
81
|
+
Para exercer seus direitos, entre em contato com nosso Encarregado de Proteção de Dados (DPO)
|
|
82
|
+
pelo e-mail <strong>[EMAIL DPO]</strong>.
|
|
83
|
+
</p>
|
|
84
|
+
|
|
85
|
+
<h2>8. Retenção dos dados</h2>
|
|
86
|
+
<p>
|
|
87
|
+
Mantemos seus dados pelo tempo necessário para cumprir as finalidades descritas acima e
|
|
88
|
+
obrigações legais (ex: registros fiscais por 5 anos). Após esse prazo, os dados são
|
|
89
|
+
eliminados ou anonimizados.
|
|
90
|
+
</p>
|
|
91
|
+
|
|
92
|
+
<h2>9. Segurança</h2>
|
|
93
|
+
<p>
|
|
94
|
+
Utilizamos HTTPS em todas as comunicações. Senhas de conta não são armazenadas em texto
|
|
95
|
+
puro. Dados de pagamento trafegam diretamente pela UnboxPay, certificada PCI-DSS.
|
|
96
|
+
</p>
|
|
97
|
+
|
|
98
|
+
<h2>10. Alterações nesta política</h2>
|
|
99
|
+
<p>
|
|
100
|
+
Podemos atualizar esta política periodicamente. A data de “última atualização” no topo da
|
|
101
|
+
página indica quando ocorreu a revisão mais recente. Alterações relevantes serão
|
|
102
|
+
comunicadas por e-mail ou banner no site.
|
|
103
|
+
</p>
|
|
104
|
+
|
|
105
|
+
<h2>11. Contato e DPO</h2>
|
|
106
|
+
<p>
|
|
107
|
+
Dúvidas, solicitações ou reclamações: <strong>[EMAIL DPO]</strong><br />
|
|
108
|
+
Você também pode registrar queixas na Autoridade Nacional de Proteção de Dados (ANPD):
|
|
109
|
+
<a href="https://www.gov.br/anpd" target="_blank" rel="noopener noreferrer">www.gov.br/anpd</a>
|
|
110
|
+
</p>
|
|
111
|
+
</article>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Mesma razão do layout de categoria: o loading.tsx deste segmento abre um <Suspense>,
|
|
2
|
+
// o shell sai com 200, e o notFound() da página chega tarde demais para mudar o status.
|
|
3
|
+
// Produto inexistente ou despublicado precisa de 404 de verdade — é a rota mais
|
|
4
|
+
// indexada da loja, e soft-404 aqui enche o índice do Google de página vazia.
|
|
5
|
+
// getProductBySlug é React cache(): sem requisição extra.
|
|
6
|
+
import { notFound } from "next/navigation";
|
|
7
|
+
import { getProductBySlug } from "@/lib/queries";
|
|
8
|
+
import { mockupOr } from "@/lib/mockup";
|
|
9
|
+
|
|
10
|
+
export default async function ProdutoLayout({
|
|
11
|
+
children,
|
|
12
|
+
params,
|
|
13
|
+
}: {
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
params: Promise<{ productSlug: string }>;
|
|
16
|
+
}) {
|
|
17
|
+
const { productSlug } = await params;
|
|
18
|
+
// mockupOr: falha da API com credenciais RELANÇA (500 não cacheado) — nunca vira 404 cacheado.
|
|
19
|
+
const wrapper = await mockupOr(getProductBySlug(decodeURIComponent(productSlug)), null, `produto/${productSlug}`);
|
|
20
|
+
const p = wrapper?.product;
|
|
21
|
+
if (!p || p.isVisible === false) notFound();
|
|
22
|
+
return <>{children}</>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Fronteira de Suspense da PDP: aparece imediatamente ao clicar num produto,
|
|
2
|
+
// enquanto o servidor busca os dados do catálogo.
|
|
3
|
+
export default function Loading() {
|
|
4
|
+
return (
|
|
5
|
+
<div>
|
|
6
|
+
<div className="mb-4 h-5 w-56 animate-pulse rounded bg-muted" />
|
|
7
|
+
<div className="grid gap-8 lg:grid-cols-2">
|
|
8
|
+
<div className="aspect-square animate-pulse rounded-xl bg-muted" />
|
|
9
|
+
<div className="space-y-4">
|
|
10
|
+
<div className="h-5 w-24 animate-pulse rounded bg-muted" />
|
|
11
|
+
<div className="h-9 w-3/4 animate-pulse rounded bg-muted" />
|
|
12
|
+
<div className="h-5 w-1/2 animate-pulse rounded bg-muted" />
|
|
13
|
+
<div className="h-8 w-1/3 animate-pulse rounded bg-muted" />
|
|
14
|
+
<div className="h-11 w-full animate-pulse rounded-lg bg-muted" />
|
|
15
|
+
<div className="h-11 w-full animate-pulse rounded-lg bg-muted" />
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
}
|