@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.
Files changed (358) hide show
  1. package/LICENSE.md +46 -0
  2. package/README.md +175 -0
  3. package/bin/cli.js +439 -0
  4. package/package.json +43 -0
  5. package/src/colors.js +100 -0
  6. package/src/presets.js +241 -0
  7. package/src/theme.js +253 -0
  8. package/template/.claude/agents/assets-marca.md +40 -0
  9. package/template/.claude/agents/avaliador-visual.md +48 -0
  10. package/template/.claude/agents/branding-briefing.md +401 -0
  11. package/template/.claude/agents/conteudo-secao.md +46 -0
  12. package/template/.claude/agents/paginas-legais.md +44 -0
  13. package/template/.claude/settings.json +3 -0
  14. package/template/.claude/skills/frontend-design/ATTRIBUTION.md +3 -0
  15. package/template/.claude/skills/frontend-design/LICENSE.txt +177 -0
  16. package/template/.claude/skills/frontend-design/SKILL.md +55 -0
  17. package/template/.claude/skills/web-design-guidelines/ATTRIBUTION.md +3 -0
  18. package/template/.claude/skills/web-design-guidelines/SKILL.md +50 -0
  19. package/template/.claude/skills/web-design-guidelines/guidelines-snapshot.md +180 -0
  20. package/template/.env.example +158 -0
  21. package/template/.mcp.json.example +14 -0
  22. package/template/.nvmrc +1 -0
  23. package/template/CLAUDE.md +333 -0
  24. package/template/COVERAGE.md +80 -0
  25. package/template/DEPLOY.md +179 -0
  26. package/template/QA.md +108 -0
  27. package/template/README.md +152 -0
  28. package/template/agents/CONSTRUCAO.md +207 -0
  29. package/template/agents/MANAGER.md +192 -0
  30. package/template/agents/PADROES.md +255 -0
  31. package/template/agents/definitions/00-scaffold.md +56 -0
  32. package/template/agents/definitions/01-layout.md +47 -0
  33. package/template/agents/definitions/02-homepage.md +70 -0
  34. package/template/agents/definitions/03-catalog.md +86 -0
  35. package/template/agents/definitions/04-pdp.md +97 -0
  36. package/template/agents/definitions/05-cart.md +116 -0
  37. package/template/agents/definitions/06-checkout.md +146 -0
  38. package/template/agents/definitions/07-auth.md +130 -0
  39. package/template/agents/definitions/08-customer.md +55 -0
  40. package/template/agents/definitions/09-promotions.md +62 -0
  41. package/template/agents/definitions/10-feedback.md +128 -0
  42. package/template/agents/definitions/11-seo-infra.md +159 -0
  43. package/template/agents/definitions/12-deploy.md +173 -0
  44. package/template/agents/definitions/13-cro.md +275 -0
  45. package/template/agents/definitions/14-seo.md +388 -0
  46. package/template/agents/definitions/15-branding.md +26 -0
  47. package/template/agents/definitions/16-qa-visual.md +138 -0
  48. package/template/agents/definitions/17-aeo.md +257 -0
  49. package/template/app/(loja)/busca/page.tsx +72 -0
  50. package/template/app/(loja)/carrinho/oferta/page.tsx +57 -0
  51. package/template/app/(loja)/carrinho/page.tsx +160 -0
  52. package/template/app/(loja)/categoria/[tagSlug]/layout.tsx +31 -0
  53. package/template/app/(loja)/categoria/[tagSlug]/loading.tsx +17 -0
  54. package/template/app/(loja)/categoria/[tagSlug]/page.tsx +49 -0
  55. package/template/app/(loja)/checkout/page.tsx +80 -0
  56. package/template/app/(loja)/checkout/pix/[ref]/page.tsx +133 -0
  57. package/template/app/(loja)/conta/assinaturas/[referenceId]/page.tsx +112 -0
  58. package/template/app/(loja)/conta/assinaturas/page.tsx +51 -0
  59. package/template/app/(loja)/conta/enderecos/page.tsx +20 -0
  60. package/template/app/(loja)/conta/entrar/page.tsx +119 -0
  61. package/template/app/(loja)/conta/page.tsx +45 -0
  62. package/template/app/(loja)/conta/pedidos/[referenceId]/page.tsx +49 -0
  63. package/template/app/(loja)/conta/pedidos/page.tsx +104 -0
  64. package/template/app/(loja)/conta/preferencias/page.tsx +24 -0
  65. package/template/app/(loja)/devolucoes/page.tsx +36 -0
  66. package/template/app/(loja)/layout.tsx +22 -0
  67. package/template/app/(loja)/oferta/page.tsx +57 -0
  68. package/template/app/(loja)/page.tsx +86 -0
  69. package/template/app/(loja)/pedido/[referenceId]/page.tsx +78 -0
  70. package/template/app/(loja)/privacidade/page.tsx +113 -0
  71. package/template/app/(loja)/produto/[productSlug]/layout.tsx +23 -0
  72. package/template/app/(loja)/produto/[productSlug]/loading.tsx +20 -0
  73. package/template/app/(loja)/produto/[productSlug]/page.tsx +372 -0
  74. package/template/app/(loja)/produtos/loading.tsx +16 -0
  75. package/template/app/(loja)/produtos/page.tsx +33 -0
  76. package/template/app/(loja)/termos/page.tsx +104 -0
  77. package/template/app/acesso/page.tsx +129 -0
  78. package/template/app/api/account/addresses/route.ts +39 -0
  79. package/template/app/api/account/exists/route.ts +21 -0
  80. package/template/app/api/account/me/route.ts +25 -0
  81. package/template/app/api/account/otp/route.ts +26 -0
  82. package/template/app/api/account/preferences/route.ts +19 -0
  83. package/template/app/api/account/signin/route.ts +27 -0
  84. package/template/app/api/account/signout/route.ts +9 -0
  85. package/template/app/api/acesso/route.ts +171 -0
  86. package/template/app/api/capi/route.ts +34 -0
  87. package/template/app/api/cart/coupon/route.ts +49 -0
  88. package/template/app/api/cart/items/route.ts +45 -0
  89. package/template/app/api/cart/link/route.ts +69 -0
  90. package/template/app/api/cart/route.ts +113 -0
  91. package/template/app/api/cart/share/route.ts +21 -0
  92. package/template/app/api/cep/[code]/route.ts +18 -0
  93. package/template/app/api/checkout/address/route.ts +24 -0
  94. package/template/app/api/checkout/email/route.ts +41 -0
  95. package/template/app/api/checkout/installments/route.ts +16 -0
  96. package/template/app/api/checkout/route.ts +110 -0
  97. package/template/app/api/checkout/shipping/route.ts +65 -0
  98. package/template/app/api/checkout-destination/route.ts +31 -0
  99. package/template/app/api/order/[ref]/route.ts +17 -0
  100. package/template/app/api/payment-link/route.ts +28 -0
  101. package/template/app/api/revalidate/route.ts +52 -0
  102. package/template/app/api/shipping/quote/route.ts +29 -0
  103. package/template/app/api/subscriptions/[id]/route.ts +52 -0
  104. package/template/app/api/track/route.ts +28 -0
  105. package/template/app/api/unbox/catalogo/route.ts +136 -0
  106. package/template/app/api/unbox/paginas/route.ts +167 -0
  107. package/template/app/api/unbox/vitrine/route.ts +84 -0
  108. package/template/app/api/webhooks/unbox/route.ts +97 -0
  109. package/template/app/apple-icon.svg +5 -0
  110. package/template/app/error.tsx +15 -0
  111. package/template/app/globals.css +457 -0
  112. package/template/app/icon.svg +4 -0
  113. package/template/app/layout.tsx +90 -0
  114. package/template/app/llms.txt/route.ts +43 -0
  115. package/template/app/manifest.ts +21 -0
  116. package/template/app/not-found.tsx +18 -0
  117. package/template/app/opengraph-image.tsx +35 -0
  118. package/template/app/robots.ts +31 -0
  119. package/template/app/sitemap.ts +42 -0
  120. package/template/bootstrap.sh +87 -0
  121. package/template/components/account/account-shell.tsx +47 -0
  122. package/template/components/account/address-book.tsx +147 -0
  123. package/template/components/account/preferences-form.tsx +77 -0
  124. package/template/components/account/reorder-button.tsx +47 -0
  125. package/template/components/account/signout-button.tsx +20 -0
  126. package/template/components/account/subscription-actions.tsx +204 -0
  127. package/template/components/account-nav.tsx +41 -0
  128. package/template/components/address-fields.tsx +102 -0
  129. package/template/components/analytics/data-layer-ready.tsx +16 -0
  130. package/template/components/analytics/purchase-tracker.tsx +32 -0
  131. package/template/components/brand-search.tsx +34 -0
  132. package/template/components/cart/cart-provider.tsx +340 -0
  133. package/template/components/cart/mini-cart.tsx +228 -0
  134. package/template/components/cart-button.tsx +35 -0
  135. package/template/components/catalog/catalog-client.tsx +523 -0
  136. package/template/components/catalog/grid-skeleton.tsx +19 -0
  137. package/template/components/catalog/pager.tsx +66 -0
  138. package/template/components/catalog/product-grid-card.tsx +52 -0
  139. package/template/components/catalog/product-grid.tsx +27 -0
  140. package/template/components/checkout/checkout-client.tsx +1333 -0
  141. package/template/components/chrome/announce-bar.tsx +57 -0
  142. package/template/components/chrome/chrome-recipe.ts +13 -0
  143. package/template/components/chrome/footers/captura-botao.tsx +28 -0
  144. package/template/components/chrome/footers/colunas.tsx +110 -0
  145. package/template/components/chrome/footers/conversao.tsx +90 -0
  146. package/template/components/chrome/footers/editorial.tsx +98 -0
  147. package/template/components/chrome/footers/minimal.tsx +55 -0
  148. package/template/components/chrome/header-bar-mobile.tsx +64 -0
  149. package/template/components/chrome/headers/centralizado.tsx +71 -0
  150. package/template/components/chrome/headers/classico.tsx +64 -0
  151. package/template/components/chrome/headers/compacto.tsx +66 -0
  152. package/template/components/chrome/headers/equilibrado.tsx +73 -0
  153. package/template/components/chrome/headers/imersivo.tsx +62 -0
  154. package/template/components/chrome/registry.ts +70 -0
  155. package/template/components/chrome/solid-on-scroll.tsx +39 -0
  156. package/template/components/empty-state.tsx +22 -0
  157. package/template/components/home/combo-card-compact.tsx +67 -0
  158. package/template/components/home/combos-home.tsx +64 -0
  159. package/template/components/home/combos-section.tsx +177 -0
  160. package/template/components/home/home-recipe.ts +41 -0
  161. package/template/components/home/sections/attributes-marquee.tsx +178 -0
  162. package/template/components/home/sections/benefits.tsx +80 -0
  163. package/template/components/home/sections/bloco-html.tsx +47 -0
  164. package/template/components/home/sections/catalogo.ts +165 -0
  165. package/template/components/home/sections/category-pills.tsx +32 -0
  166. package/template/components/home/sections/combos-carousel.tsx +141 -0
  167. package/template/components/home/sections/comparison.tsx +73 -0
  168. package/template/components/home/sections/founder-story.tsx +40 -0
  169. package/template/components/home/sections/hero.tsx +187 -0
  170. package/template/components/home/sections/kits.tsx +11 -0
  171. package/template/components/home/sections/media-cards.tsx +70 -0
  172. package/template/components/home/sections/newsletter.tsx +62 -0
  173. package/template/components/home/sections/product-showcase.tsx +175 -0
  174. package/template/components/home/sections/purchase-hero.tsx +187 -0
  175. package/template/components/home/sections/quote-banner.tsx +50 -0
  176. package/template/components/home/sections/registry.ts +186 -0
  177. package/template/components/home/sections/reviews-carousel.tsx +66 -0
  178. package/template/components/home/sections/reviews.tsx +68 -0
  179. package/template/components/home/sections/ritual.tsx +96 -0
  180. package/template/components/home/sections/savings.tsx +47 -0
  181. package/template/components/home/sections/social-row.tsx +65 -0
  182. package/template/components/home/sections/spec-table.tsx +117 -0
  183. package/template/components/home/sections/stars.tsx +12 -0
  184. package/template/components/home/sections/stats-grid.tsx +54 -0
  185. package/template/components/home/sections/trust-bar.tsx +56 -0
  186. package/template/components/home/sections/trust-strip.tsx +40 -0
  187. package/template/components/home/sections/video-wall.tsx +59 -0
  188. package/template/components/landing/landing-recipe.ts +20 -0
  189. package/template/components/landing/oferta-sections.tsx +40 -0
  190. package/template/components/landing/product-picker.tsx +257 -0
  191. package/template/components/mobile-nav.tsx +71 -0
  192. package/template/components/order-status.tsx +163 -0
  193. package/template/components/powered-by-unbox.tsx +27 -0
  194. package/template/components/product/pdp/buy-box.tsx +604 -0
  195. package/template/components/product/pdp/catalog-grid.tsx +113 -0
  196. package/template/components/product/pdp/faq-modelo.ts +86 -0
  197. package/template/components/product/pdp/gallery.tsx +87 -0
  198. package/template/components/product/pdp/interactive.tsx +200 -0
  199. package/template/components/product/pdp/newsletter.tsx +64 -0
  200. package/template/components/product/pdp/payment-chips.tsx +22 -0
  201. package/template/components/product/pdp/pdp-view.tsx +227 -0
  202. package/template/components/product/pdp/recommendations.tsx +144 -0
  203. package/template/components/product/pdp/sections.tsx +173 -0
  204. package/template/components/quantity-stepper.tsx +36 -0
  205. package/template/components/search-box.tsx +37 -0
  206. package/template/components/site-footer.tsx +59 -0
  207. package/template/components/site-header.tsx +64 -0
  208. package/template/components/ui/accordion.tsx +72 -0
  209. package/template/components/ui/alert-dialog.tsx +187 -0
  210. package/template/components/ui/alert.tsx +76 -0
  211. package/template/components/ui/aspect-ratio.tsx +22 -0
  212. package/template/components/ui/avatar.tsx +109 -0
  213. package/template/components/ui/badge.tsx +52 -0
  214. package/template/components/ui/breadcrumb.tsx +125 -0
  215. package/template/components/ui/button.tsx +58 -0
  216. package/template/components/ui/card.tsx +103 -0
  217. package/template/components/ui/carousel.tsx +242 -0
  218. package/template/components/ui/command.tsx +196 -0
  219. package/template/components/ui/dialog.tsx +160 -0
  220. package/template/components/ui/dropdown-menu.tsx +268 -0
  221. package/template/components/ui/foto.tsx +54 -0
  222. package/template/components/ui/input-group.tsx +158 -0
  223. package/template/components/ui/input-otp.tsx +87 -0
  224. package/template/components/ui/input.tsx +20 -0
  225. package/template/components/ui/label.tsx +20 -0
  226. package/template/components/ui/navigation-menu.tsx +168 -0
  227. package/template/components/ui/pagination.tsx +132 -0
  228. package/template/components/ui/popover.tsx +90 -0
  229. package/template/components/ui/progress.tsx +83 -0
  230. package/template/components/ui/radio-group.tsx +38 -0
  231. package/template/components/ui/scroll-area.tsx +55 -0
  232. package/template/components/ui/select.tsx +201 -0
  233. package/template/components/ui/separator.tsx +25 -0
  234. package/template/components/ui/sheet.tsx +138 -0
  235. package/template/components/ui/skeleton.tsx +13 -0
  236. package/template/components/ui/sonner.tsx +44 -0
  237. package/template/components/ui/switch.tsx +32 -0
  238. package/template/components/ui/table.tsx +116 -0
  239. package/template/components/ui/tabs.tsx +82 -0
  240. package/template/components/ui/textarea.tsx +18 -0
  241. package/template/components/ui/toggle-group.tsx +89 -0
  242. package/template/components/ui/toggle.tsx +45 -0
  243. package/template/components/ui/tooltip.tsx +66 -0
  244. package/template/components.json +25 -0
  245. package/template/eslint.config.mjs +36 -0
  246. package/template/gitignore +21 -0
  247. package/template/lib/analytics.ts +369 -0
  248. package/template/lib/api.ts +60 -0
  249. package/template/lib/capi.ts +87 -0
  250. package/template/lib/cart-link.ts +51 -0
  251. package/template/lib/cart-normalize.ts +142 -0
  252. package/template/lib/cart-recovery.ts +23 -0
  253. package/template/lib/cart-response.ts +48 -0
  254. package/template/lib/catalog-map.ts +54 -0
  255. package/template/lib/catalog.ts +3 -0
  256. package/template/lib/checkout-lock.ts +21 -0
  257. package/template/lib/checkout-nav.ts +22 -0
  258. package/template/lib/config.ts +51 -0
  259. package/template/lib/crm.ts +40 -0
  260. package/template/lib/customer-session.ts +21 -0
  261. package/template/lib/dataloader.ts +23 -0
  262. package/template/lib/editable/config.ts +11 -0
  263. package/template/lib/editable/document.ts +1762 -0
  264. package/template/lib/editable/index.ts +3 -0
  265. package/template/lib/editable/primitives.tsx +821 -0
  266. package/template/lib/editable/provider.tsx +861 -0
  267. package/template/lib/editable/rastreio-navegacao.tsx +65 -0
  268. package/template/lib/editable/rastreio.tsx +168 -0
  269. package/template/lib/editable/server.ts +189 -0
  270. package/template/lib/editable/tokens.ts +22 -0
  271. package/template/lib/editable/verify.ts +24 -0
  272. package/template/lib/enrichment/combos.ts +184 -0
  273. package/template/lib/enrichment/index.ts +188 -0
  274. package/template/lib/enrichment/products.json +1 -0
  275. package/template/lib/env-check.ts +47 -0
  276. package/template/lib/format.ts +130 -0
  277. package/template/lib/icons.ts +67 -0
  278. package/template/lib/json-ld.ts +9 -0
  279. package/template/lib/llms-txt.ts +75 -0
  280. package/template/lib/mockup.ts +24 -0
  281. package/template/lib/newsletter.ts +12 -0
  282. package/template/lib/orders.ts +160 -0
  283. package/template/lib/queries.ts +66 -0
  284. package/template/lib/ratelimit.ts +47 -0
  285. package/template/lib/rotas-editaveis.ts +192 -0
  286. package/template/lib/sanitize.ts +36 -0
  287. package/template/lib/schemas.ts +78 -0
  288. package/template/lib/session.ts +98 -0
  289. package/template/lib/store-config.ts +67 -0
  290. package/template/lib/unbox/client.ts +829 -0
  291. package/template/lib/unbox/customer.ts +224 -0
  292. package/template/lib/unbox/errors.ts +97 -0
  293. package/template/lib/unbox/index.ts +10 -0
  294. package/template/lib/unbox/store.ts +113 -0
  295. package/template/lib/unbox/types.ts +195 -0
  296. package/template/lib/unbox/webhooks.ts +66 -0
  297. package/template/lib/utils.ts +6 -0
  298. package/template/lib/vitrine.ts +242 -0
  299. package/template/lib/webhook-store.ts +22 -0
  300. package/template/middleware.ts +178 -0
  301. package/template/next.config.ts +63 -0
  302. package/template/package.json +62 -0
  303. package/template/postcss.config.mjs +5 -0
  304. package/template/public/brand/coll/default.png +0 -0
  305. package/template/public/brand/hero-desktop.svg +12 -0
  306. package/template/public/brand/hero-mobile.svg +12 -0
  307. package/template/public/brand/heros/boutique-desktop.svg +11 -0
  308. package/template/public/brand/heros/boutique-mobile.svg +11 -0
  309. package/template/public/brand/heros/editorial-desktop.svg +11 -0
  310. package/template/public/brand/heros/editorial-mobile.svg +11 -0
  311. package/template/public/brand/heros/essencial-desktop.svg +12 -0
  312. package/template/public/brand/heros/essencial-mobile.svg +12 -0
  313. package/template/public/brand/heros/promocional-desktop.svg +12 -0
  314. package/template/public/brand/heros/promocional-mobile.svg +12 -0
  315. package/template/public/brand/logo-chrome.svg +6 -0
  316. package/template/public/brand/logo-white.svg +6 -0
  317. package/template/public/brand/logo.svg +7 -0
  318. package/template/public/brand/pay-amex.webp +0 -0
  319. package/template/public/brand/pay-elo.webp +0 -0
  320. package/template/public/brand/pay-mastercard.webp +0 -0
  321. package/template/public/brand/pay-pix.webp +0 -0
  322. package/template/public/brand/pay-visa.webp +0 -0
  323. package/template/public/brand/ph/avatar-a.svg +5 -0
  324. package/template/public/brand/ph/avatar-b.svg +5 -0
  325. package/template/public/brand/ph/avatar-c.svg +5 -0
  326. package/template/public/brand/ph/avatar-d.svg +5 -0
  327. package/template/public/brand/ph/photo-a.svg +7 -0
  328. package/template/public/brand/ph/photo-b.svg +6 -0
  329. package/template/public/brand/ph/photo-c.svg +6 -0
  330. package/template/public/brand/ph/poster-a.svg +6 -0
  331. package/template/public/brand/ph/poster-b.svg +6 -0
  332. package/template/public/brand/ph/poster-c.svg +6 -0
  333. package/template/public/unbox/powered-by-fundo-preto.png +0 -0
  334. package/template/public/unbox/powered-by-transparente.png +0 -0
  335. package/template/public/unbox/powered-by.png +0 -0
  336. package/template/scripts/abandoned-cart.ts +82 -0
  337. package/template/scripts/check-editable.mjs +467 -0
  338. package/template/scripts/check-honestidade.mjs +111 -0
  339. package/template/scripts/check-placeholder.mjs +200 -0
  340. package/template/scripts/check-recipe.mjs +76 -0
  341. package/template/scripts/check-unbox-brand.mjs +298 -0
  342. package/template/scripts/contraste.js +177 -0
  343. package/template/scripts/dump-catalog.ts +95 -0
  344. package/template/scripts/load-env.ts +13 -0
  345. package/template/scripts/medir-sistema.mjs +56 -0
  346. package/template/scripts/place-order-pix.ts +61 -0
  347. package/template/scripts/qa-screenshots.mjs +150 -0
  348. package/template/scripts/sistema.py +231 -0
  349. package/template/scripts/subscribe-webhook.ts +36 -0
  350. package/template/scripts/test-live.ts +186 -0
  351. package/template/scripts/vocabulario.py +264 -0
  352. package/template/tsconfig.json +27 -0
  353. package/tools/LEIA-ME.md +39 -0
  354. package/tools/check-template-neutro.mjs +111 -0
  355. package/tools/notion-doc.mjs +191 -0
  356. package/tools/tokenize-neutrals.mjs +0 -0
  357. package/tools/tokenize-radius.mjs +52 -0
  358. package/tools/workflow-storefront.legado.js +1207 -0
@@ -0,0 +1,257 @@
1
+ "use client";
2
+
3
+ // Passo 2 do funil (/carrinho/oferta) — tela split no padrão das lojas Unbox de produção:
4
+ // imagem da marca à esquerda (desktop), à direita logo + confiança, escolha de produtos
5
+ // com stepper, frequência de envio (assinatura REAL da política da loja vs compra única)
6
+ // e CTA que só libera quando a quantidade alvo fecha. Envia tudo num único POST /api/cart
7
+ // → /checkout. Tela .checkout-root (o CSS global esconde o chrome do site).
8
+ import * as React from "react";
9
+ import Link from "next/link";
10
+ import { useRouter, useSearchParams } from "next/navigation";
11
+ import { CaretLeft, Minus, Plus, CheckCircle, ShieldCheck, Truck, LockSimple } from "@phosphor-icons/react/dist/ssr";
12
+ import { formatBRL } from "@/lib/format";
13
+ import { goToCheckout } from "@/lib/checkout-nav";
14
+ import { PoweredByUnbox } from "@/components/powered-by-unbox";
15
+ import type { CatalogProductItem } from "@/components/catalog/catalog-client";
16
+
17
+ export interface PickerSubscription {
18
+ percentOff: number | null;
19
+ frequencies: { id: string; title: string; note?: string }[];
20
+ }
21
+
22
+ export function ProductPicker({
23
+ products,
24
+ shopName,
25
+ subscription,
26
+ recurrableIds = [],
27
+ sideImage = "/brand/hero-desktop.svg",
28
+ tagline = "escolha o que combina com você",
29
+ }: {
30
+ products: CatalogProductItem[];
31
+ shopName: string;
32
+ subscription: PickerSubscription | null;
33
+ recurrableIds?: string[];
34
+ sideImage?: string;
35
+ tagline?: string;
36
+ }) {
37
+ const router = useRouter();
38
+ const params = useSearchParams();
39
+ const target = Math.max(1, Math.min(12, Number(params.get("quantity")) || 1));
40
+ const [qty, setQty] = React.useState<Record<string, number>>(() =>
41
+ products[0]?.variantId ? { [products[0].variantId]: target } : {},
42
+ );
43
+ const [mode, setMode] = React.useState<"assinatura" | "unica">(subscription ? "assinatura" : "unica");
44
+ const [freqId, setFreqId] = React.useState(subscription?.frequencies[0]?.id ?? "");
45
+ const [busy, setBusy] = React.useState(false);
46
+ const [error, setError] = React.useState<string | null>(null);
47
+
48
+ const picked = Object.values(qty).reduce((a, b) => a + b, 0);
49
+ const remaining = target - picked;
50
+ // assinatura só vale pros produtos que a permitem; o desconto REAL vem da política da loja
51
+ const pct = subscription?.percentOff ?? 0;
52
+ const factor = mode === "assinatura" && pct ? 1 - pct / 100 : 1;
53
+ const isRecurrable = (p: CatalogProductItem) => recurrableIds.includes(p.productId ?? "");
54
+ const totalFull = products.reduce((acc, p) => acc + (qty[p.variantId ?? ""] ?? 0) * p.price, 0);
55
+ const total = products.reduce((acc, p) => {
56
+ const n = qty[p.variantId ?? ""] ?? 0;
57
+ const f = mode === "assinatura" && pct && isRecurrable(p) ? 1 - pct / 100 : 1;
58
+ return acc + n * p.price * f;
59
+ }, 0);
60
+ const perUnit = picked > 0 ? total / picked : products[0]?.price ?? 0;
61
+
62
+ function bump(p: CatalogProductItem, d: number) {
63
+ if (!p.variantId || p.soldOut) return;
64
+ setQty((q) => {
65
+ const cur = q[p.variantId!] ?? 0;
66
+ if (d > 0 && picked >= target) return q;
67
+ return { ...q, [p.variantId!]: Math.max(0, cur + d) };
68
+ });
69
+ }
70
+
71
+ async function checkout() {
72
+ setBusy(true);
73
+ setError(null);
74
+ try {
75
+ const assinatura = mode === "assinatura" && !!subscription;
76
+ const items = products
77
+ .filter((p) => (qty[p.variantId ?? ""] ?? 0) > 0 && p.productId && p.variantId)
78
+ .map((p) => ({
79
+ productId: p.productId,
80
+ productVariantId: p.variantId,
81
+ price: p.price,
82
+ quantity: qty[p.variantId!],
83
+ thumbnail: p.imageUrl ?? "",
84
+ isRecurring: assinatura && isRecurrable(p),
85
+ }));
86
+ const body: Record<string, unknown> = { items };
87
+ if (assinatura && freqId && items.some((it) => it.isRecurring)) body.recurringItemsFrequencyId = freqId;
88
+ const res = await fetch("/api/cart", {
89
+ method: "POST",
90
+ headers: { "Content-Type": "application/json" },
91
+ body: JSON.stringify(body),
92
+ });
93
+ if (!res.ok) throw new Error((await res.json().catch(() => ({})))?.error || "Não foi possível montar o carrinho.");
94
+ // goToCheckout: o SERVIDOR decide o destino (interno ou hosted) e monta a URL com
95
+ // ?id=&token= do carrinho — contrato da recuperação. push("/checkout") cru perderia
96
+ // os params e quebraria no modo hosted (rota removida).
97
+ await goToCheckout(router);
98
+ } catch (e: any) {
99
+ setError(e.message);
100
+ setBusy(false);
101
+ }
102
+ }
103
+
104
+ return (
105
+ <div className="checkout-root store-layout min-h-screen bg-[var(--store-surface)]">
106
+ {/* progresso full-width */}
107
+ <div className="sticky top-0 z-10 border-b border-[var(--store-line)] bg-[var(--store-surface)]/95 backdrop-blur">
108
+ <div className="mx-auto flex max-w-[var(--container-max,1240px)] items-center justify-center gap-2.5 px-4 py-3.5 text-[13px] font-bold">
109
+ <span className="flex items-center gap-1.5 text-[var(--store-ink)]"><span className="flex h-6 w-6 items-center justify-center rounded-full bg-[var(--store-cta,#D97706)] text-[12px] text-[var(--store-cta-fg,#1C1207)]">1</span> quantidade</span>
110
+ <span className="h-[2px] w-10 rounded bg-[var(--store-cta,#D97706)]" />
111
+ <span className="flex items-center gap-1.5 text-[var(--store-ink)]"><span className="flex h-6 w-6 items-center justify-center rounded-full bg-[var(--store-cta,#D97706)] text-[12px] text-[var(--store-cta-fg,#1C1207)]">2</span> escolha</span>
112
+ <span className="h-[2px] w-10 rounded bg-[var(--store-line-2)]" />
113
+ <span className="flex items-center gap-1.5 text-[var(--store-faint)]"><span className="flex h-6 w-6 items-center justify-center rounded-full border border-[var(--store-faint)] text-[12px]">✓</span> pagamento</span>
114
+ </div>
115
+ </div>
116
+
117
+ <div className="grid lg:grid-cols-[1fr_1.1fr]">
118
+ {/* painel visual (desktop) */}
119
+ <div className="relative hidden min-h-[calc(100vh-49px)] lg:block">
120
+ <img src={sideImage} alt="" className="absolute inset-0 h-full w-full object-cover" />
121
+ </div>
122
+
123
+ {/* conteúdo */}
124
+ <div className="mx-auto w-full max-w-[680px] px-4 py-7 sm:px-8">
125
+ <button type="button" onClick={() => router.back()} className="flex items-center gap-1.5 text-[13px] font-semibold text-[var(--store-muted)] hover:text-[var(--store-ink)]"><CaretLeft weight="bold" />voltar</button>
126
+
127
+ <div className="mt-4 flex items-center gap-3.5">
128
+ <img src="/brand/logo.svg" alt={shopName} className="h-10 w-auto" />
129
+ <div>
130
+ <div className="font-display text-[15px] font-extrabold leading-tight">{tagline}</div>
131
+ <div className="mt-1 flex flex-wrap gap-x-3.5 gap-y-1 text-[11.5px] font-semibold text-[var(--store-muted)]">
132
+ <span className="flex items-center gap-1"><ShieldCheck weight="fill" className="text-[var(--store-primary,#18181B)]" />Compra segura</span>
133
+ <span className="flex items-center gap-1"><Truck weight="fill" className="text-[var(--store-primary,#18181B)]" />Entrega em todo o Brasil</span>
134
+ <span className="flex items-center gap-1"><LockSimple weight="fill" className="text-[var(--store-primary,#18181B)]" />Checkout seguro</span>
135
+ </div>
136
+ </div>
137
+ </div>
138
+
139
+ <h1 className="font-display mt-6 text-[19px] font-extrabold">2. Selecione os produtos</h1>
140
+ <p className="mt-0.5 text-[13.5px] text-[var(--store-muted)]">
141
+ {remaining > 0 ? `Escolha mais ${remaining} ${remaining === 1 ? "item" : "itens"} pra completar seu pedido.` : "Tudo escolhido! Revise abaixo e siga pro pagamento."}
142
+ </p>
143
+
144
+ <div className="mt-4 flex flex-col gap-3">
145
+ {products.map((p) => {
146
+ const n = qty[p.variantId ?? ""] ?? 0;
147
+ return (
148
+ <div key={p.slug} className={`relative flex items-center gap-3.5 rounded-2xl border-2 bg-white p-3.5 ${n > 0 ? "border-[var(--store-cta,#D97706)]" : "border-[var(--store-line-2)]"} ${p.soldOut ? "opacity-50" : ""}`}>
149
+ {p.badge && <span className="absolute -top-2.5 right-4 rounded-full px-2.5 py-0.5 text-[10px] font-extrabold uppercase tracking-[0.4px]" style={{ background: p.badge.bg, color: p.badge.fg }}>{p.badge.label}</span>}
150
+ <div className="flex h-[62px] w-[62px] flex-none items-center justify-center overflow-hidden rounded-xl bg-[var(--store-surface-2)]">
151
+ {p.imageUrl ? <img src={p.imageUrl} alt="" className="h-full w-full object-contain p-1" /> : <span className="font-display text-[20px] font-extrabold text-[var(--store-primary,#18181B)]/30">{p.title[0]}</span>}
152
+ </div>
153
+ <div className="min-w-0 flex-1">
154
+ {p.weight && <div className="text-[11.5px] text-[var(--store-muted)]">{p.weight}</div>}
155
+ <div className="font-display truncate text-[15px] font-extrabold">{p.title}</div>
156
+ <Link href={`/produto/${encodeURIComponent(p.slug)}`} className="text-[12px] font-semibold text-[var(--store-muted)] underline underline-offset-2">ver produto</Link>
157
+ </div>
158
+ {n > 0 ? (
159
+ <div className="flex items-center gap-2">
160
+ <button type="button" onClick={() => bump(p, -1)} aria-label={`Tirar ${p.title}`} className="flex h-9 w-9 items-center justify-center rounded-full bg-[var(--store-chrome-bg,#18181B)] text-[var(--store-chrome-text,#ffffff)]"><Minus weight="bold" /></button>
161
+ <span className="font-display w-6 text-center text-[16px] font-extrabold">{n}</span>
162
+ <button type="button" onClick={() => bump(p, 1)} disabled={remaining <= 0} aria-label={`Adicionar ${p.title}`} className="flex h-9 w-9 items-center justify-center rounded-full bg-[var(--store-chrome-bg,#18181B)] text-[var(--store-chrome-text,#ffffff)] disabled:opacity-40"><Plus weight="bold" /></button>
163
+ </div>
164
+ ) : (
165
+ <button type="button" onClick={() => bump(p, 1)} disabled={p.soldOut || remaining <= 0} className="font-display rounded-full bg-[var(--store-chrome-bg,#18181B)] px-5 py-2.5 text-[13px] font-extrabold text-[var(--store-chrome-text,#ffffff)] disabled:opacity-40">Adicionar +</button>
166
+ )}
167
+ </div>
168
+ );
169
+ })}
170
+ </div>
171
+
172
+ {/* frequência de envio — só com política de assinatura REAL na loja */}
173
+ {subscription && (
174
+ <>
175
+ <h2 className="font-display mt-7 text-[19px] font-extrabold">3. Selecione a frequência de envio</h2>
176
+ <div className="mt-3 flex flex-col gap-3">
177
+ <button type="button" onClick={() => setMode("assinatura")} className={`rounded-2xl border-2 px-5 py-4 text-left ${mode === "assinatura" ? "border-[var(--store-chrome-bg,#18181B)] bg-white" : "border-[var(--store-line-2)] bg-white"}`}>
178
+ <span className="flex items-center justify-between">
179
+ <span className="flex items-center gap-2.5">
180
+ <span className={`flex h-5 w-5 items-center justify-center rounded-full border-2 ${mode === "assinatura" ? "border-[var(--store-chrome-bg,#18181B)]" : "border-[var(--store-faint)]"}`}>{mode === "assinatura" && <span className="h-2.5 w-2.5 rounded-full bg-[var(--store-chrome-bg,#18181B)]" />}</span>
181
+ <span className="font-display text-[16px] font-extrabold">Assinatura {subscription.frequencies.length === 1 ? subscription.frequencies[0].title : ""}</span>
182
+ </span>
183
+ {pct ? <span className="font-display text-[15px] font-extrabold text-[var(--store-ink)]">{formatBRL((products[0]?.price ?? 0) * (1 - pct / 100))}<span className="text-[12px] font-semibold text-[var(--store-muted)]">/unidade</span></span> : null}
184
+ </span>
185
+ {mode === "assinatura" && (
186
+ <span className="mt-2.5 flex flex-col gap-1.5 text-[13px] font-semibold text-[var(--store-ink-2)]">
187
+ {pct ? <span className="flex items-center gap-1.5"><CheckCircle weight="fill" className="text-[var(--store-primary,#18181B)]" />Economize {formatBRL(totalFull - total)} <span className="rounded-full bg-[var(--store-cta-soft,#FDF0DC)] px-2 py-0.5 text-[11px] font-extrabold text-[var(--store-cta-fg,#1C1207)]">{pct}% OFF</span></span> : null}
188
+ <span className="flex items-center gap-1.5"><CheckCircle weight="fill" className="text-[var(--store-primary,#18181B)]" />Escolha quando quer receber</span>
189
+ <span className="flex items-center gap-1.5"><CheckCircle weight="fill" className="text-[var(--store-primary,#18181B)]" />Pause ou modifique quando quiser</span>
190
+ {subscription.frequencies.length > 1 && (
191
+ <span className="mt-1 block text-[11.5px] font-medium leading-[1.4] text-[var(--store-muted)]">A frequência vale para todos os itens assinados deste pedido.</span>
192
+ )}
193
+ {subscription.frequencies.length > 1 && (
194
+ <span className="mt-1 flex flex-wrap gap-2">
195
+ {subscription.frequencies.map((f) => (
196
+ <button key={f.id} type="button" onClick={(e) => { e.stopPropagation(); setFreqId(f.id); }} className={`rounded-full border px-3.5 py-1.5 text-[12.5px] font-bold ${freqId === f.id ? "border-[var(--store-chrome-bg,#18181B)] bg-[var(--store-chrome-bg,#18181B)] text-[var(--store-chrome-text,#ffffff)]" : "border-[var(--store-line-2)] bg-white text-[var(--store-ink-2)]"}`}>{f.title}</button>
197
+ ))}
198
+ </span>
199
+ )}
200
+ </span>
201
+ )}
202
+ </button>
203
+ <button type="button" onClick={() => setMode("unica")} className={`flex items-center justify-between rounded-2xl border-2 px-5 py-4 text-left ${mode === "unica" ? "border-[var(--store-chrome-bg,#18181B)] bg-white" : "border-[var(--store-line-2)] bg-white"}`}>
204
+ <span className="flex items-center gap-2.5">
205
+ <span className={`flex h-5 w-5 items-center justify-center rounded-full border-2 ${mode === "unica" ? "border-[var(--store-chrome-bg,#18181B)]" : "border-[var(--store-faint)]"}`}>{mode === "unica" && <span className="h-2.5 w-2.5 rounded-full bg-[var(--store-chrome-bg,#18181B)]" />}</span>
206
+ <span className="font-display text-[16px] font-extrabold">Compra Única</span>
207
+ </span>
208
+ <span className="font-display text-[15px] font-extrabold">{formatBRL(products[0]?.price ?? 0)}<span className="text-[12px] font-semibold text-[var(--store-muted)]">/unidade</span></span>
209
+ </button>
210
+ </div>
211
+ </>
212
+ )}
213
+
214
+ {error && <p className="mt-4 rounded-xl bg-[var(--store-sale-soft)] px-4 py-3 text-[13.5px] font-semibold text-[var(--store-sale)]">{error}</p>}
215
+
216
+ <div className="mt-5 flex flex-wrap items-center justify-between gap-2">
217
+ <span className="flex items-center gap-2">
218
+ {picked > 0 && <span className="rounded-full bg-[var(--store-primary-soft,#F1F1F3)] px-3 py-1 text-[12px] font-bold text-[var(--store-primary,#18181B)]">{formatBRL(perUnit)}/unidade</span>}
219
+ {mode === "assinatura" && pct > 0 && <span className="rounded-full bg-[var(--store-cta-soft,#FDF0DC)] px-3 py-1 text-[12px] font-extrabold text-[var(--store-cta-fg,#1C1207)]">{pct}%OFF</span>}
220
+ </span>
221
+ <span className="text-right">
222
+ {total < totalFull && <span className="mr-2 text-[12.5px] text-[var(--store-faint)] line-through">De {formatBRL(totalFull)}</span>}
223
+ <span className="font-display text-[20px] font-extrabold">{formatBRL(total)}</span>
224
+ </span>
225
+ </div>
226
+
227
+ <button type="button" onClick={checkout} disabled={busy || remaining > 0 || picked === 0}
228
+ className="font-display mt-4 w-full rounded-full bg-[var(--store-cta,#D97706)] py-4 text-[15.5px] font-extrabold tracking-[0.3px] text-[var(--store-cta-fg,#1C1207)] transition-colors hover:bg-[var(--store-cta-dark,#B45309)] disabled:bg-[var(--store-faint)] disabled:text-white">
229
+ {busy ? "Preparando o pagamento..." : remaining > 0 ? `Selecione mais ${remaining} ${remaining === 1 ? "pacote" : "pacotes"} →` : "Ir pro pagamento →"}
230
+ </button>
231
+
232
+ <div className="mt-4 mb-8 flex items-start gap-3.5 rounded-2xl border-2 border-dashed border-[var(--store-cta,#D97706)]/60 bg-white px-5 py-4">
233
+ <span className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-[var(--store-primary-soft,#F1F1F3)] text-[var(--store-primary,#18181B)]"><ShieldCheck weight="fill" className="text-[22px]" /></span>
234
+ <div className="text-[13px] leading-[1.5] text-[var(--store-muted)]">
235
+ <span className="block text-[11.5px]">Seu pedido está protegido por</span>
236
+ <span className="font-display block text-[15px] font-extrabold text-[var(--store-ink)]">Compra segura e troca fácil</span>
237
+ Arrependimento em até 7 dias após o recebimento (CDC).
238
+ </div>
239
+ </div>
240
+ </div>
241
+ </div>
242
+
243
+ {/* mini rodapé da marca */}
244
+ <footer className="bg-[var(--store-chrome-bg,#18181B)]">
245
+ <div className="mx-auto flex max-w-[var(--container-max,1240px)] flex-wrap items-center justify-between gap-3 px-6 py-5 text-[12.5px] font-semibold text-[var(--store-chrome-muted)]">
246
+ <span className="font-display text-[15px] font-extrabold text-[var(--store-chrome-text,#ffffff)]">{shopName}</span>
247
+ <span className="flex items-center gap-5">
248
+ <Link href="/privacidade" className="no-underline hover:text-[var(--store-chrome-text,#ffffff)]">Política de Privacidade</Link>
249
+ <Link href="/termos" className="no-underline hover:text-[var(--store-chrome-text,#ffffff)]">Termos de Uso</Link>
250
+ {/* obrigatório (contrato Unbox) — não remova */}
251
+ <PoweredByUnbox />
252
+ </span>
253
+ </div>
254
+ </footer>
255
+ </div>
256
+ );
257
+ }
@@ -0,0 +1,71 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import Link from "next/link";
5
+ import { List, Storefront, Tag, Package, User, CaretRight } from "@phosphor-icons/react/dist/ssr";
6
+ import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
7
+
8
+ export interface NavCategory { id: string; name: string; slug: string }
9
+
10
+ export function MobileNav({
11
+ categories,
12
+ shopName,
13
+ /** Classes do BOTÃO que abre o menu. O default é o comportamento histórico (só mobile);
14
+ * variantes de header que usam drawer também no desktop passam o seu (ver
15
+ * components/chrome/headers/). O painel em si é igual em qualquer largura. */
16
+ triggerClassName = "text-[var(--store-ink)] md:hidden",
17
+ }: {
18
+ categories: NavCategory[];
19
+ shopName: string;
20
+ triggerClassName?: string;
21
+ }) {
22
+ const [open, setOpen] = React.useState(false);
23
+ const close = () => setOpen(false);
24
+
25
+ return (
26
+ <Sheet open={open} onOpenChange={setOpen}>
27
+ <SheetTrigger
28
+ render={
29
+ <button type="button" aria-label="Abrir menu" className={`flex h-10 w-10 items-center justify-center rounded-xl ${triggerClassName}`}>
30
+ <List weight="bold" className="text-[24px]" />
31
+ </button>
32
+ }
33
+ />
34
+ <SheetContent side="left" showCloseButton className="store-layout w-[86vw] max-w-[340px] gap-0 bg-white p-0 text-[var(--store-ink)]">
35
+ <SheetHeader className="border-b border-[var(--store-surface-2)] px-5 py-4">
36
+ <SheetTitle className="text-left">
37
+ {/* eslint-disable-next-line @next/next/no-img-element -- logo do chrome: SVG de poucos KB. O next/image marcaria lazy num elemento que aparece em toda página (o preload scanner perde o recurso) e o reencode come o traço fino do lettering. Otimizar poucos KB não paga essas duas contas. */}
38
+ <img src="/brand/logo.svg" alt={shopName} className="h-9 w-auto" />
39
+ </SheetTitle>
40
+ </SheetHeader>
41
+
42
+ <div className="flex flex-col gap-1 overflow-y-auto p-3">
43
+ <Row href="/produtos" icon={Storefront} label="Todos os produtos" onClick={close} />
44
+
45
+ <div className="mt-2 px-3 pb-1 text-[11px] font-extrabold uppercase tracking-[1px] text-[var(--store-faint)]">Categorias</div>
46
+ {categories.map((c) => (
47
+ <Row key={c.id} href={`/categoria/${encodeURIComponent(c.slug)}`} icon={Tag} label={c.name} onClick={close} />
48
+ ))}
49
+
50
+ <div className="my-2 border-t border-[var(--store-surface-2)]" />
51
+ <Row href="/conta/entrar" icon={Package} label="Acompanhar pedido" onClick={close} />
52
+ <Row href="/conta" icon={User} label="Minha conta" onClick={close} />
53
+ </div>
54
+ </SheetContent>
55
+ </Sheet>
56
+ );
57
+ }
58
+
59
+ function Row({ href, icon: Icon, label, onClick }: { href: string; icon: React.ComponentType<any>; label: string; onClick: () => void }) {
60
+ return (
61
+ <Link
62
+ href={href}
63
+ onClick={onClick}
64
+ className="flex items-center gap-3 rounded-xl px-3 py-2.5 text-[14.5px] font-semibold text-[var(--store-ink-2)] no-underline transition-colors hover:bg-[var(--store-primary-soft,#F1F1F3)] hover:text-[var(--store-primary,#18181B)]"
65
+ >
66
+ <Icon weight="bold" className="text-[19px] text-[var(--store-primary,#18181B)]" />
67
+ <span className="flex-1">{label}</span>
68
+ <CaretRight weight="bold" className="text-[12px] text-[var(--store-faint)]" />
69
+ </Link>
70
+ );
71
+ }
@@ -0,0 +1,163 @@
1
+ import Link from "next/link";
2
+ import Image from "next/image";
3
+ import { Truck, Receipt, Package, CheckCircle, MapPin } from "@phosphor-icons/react/dist/ssr";
4
+ import type { ShapedOrder } from "@/lib/orders";
5
+
6
+ type Tone = "ok" | "bad" | "warn" | "muted";
7
+
8
+ const TONES: Record<Tone, { bg: string; fg: string; bd: string }> = {
9
+ ok: { bg: "var(--store-primary-soft,#F1F1F3)", fg: "var(--store-primary,#18181B)", bd: "var(--store-primary-soft)" },
10
+ bad: { bg: "var(--store-sale-soft)", fg: "var(--store-sale)", bd: "var(--store-sale-soft)" },
11
+ warn: { bg: "var(--store-cta-soft)", fg: "var(--store-cta-fg)", bd: "var(--store-cta-soft)" },
12
+ muted: { bg: "var(--store-surface)", fg: "var(--store-ink-2)", bd: "var(--store-line-2)" },
13
+ };
14
+
15
+ /** Tom (cor) a partir do status cru do pedido. */
16
+ export function statusTone(status?: string): Tone {
17
+ switch (status) {
18
+ case "COMPLETED":
19
+ case "PROCESSING":
20
+ return "ok";
21
+ case "CANCELED":
22
+ case "FAILED":
23
+ return "bad";
24
+ case "REFUNDED":
25
+ return "muted";
26
+ default:
27
+ return "warn"; // PENDING e afins
28
+ }
29
+ }
30
+
31
+ /** Badge de status. */
32
+ export function StatusBadge({ label, tone = "muted" }: { label: string; tone?: Tone }) {
33
+ const t = TONES[tone];
34
+ return (
35
+ <span
36
+ className="inline-flex items-center rounded-full px-3 py-1 text-[12px] font-extrabold tracking-[0.2px]"
37
+ style={{ background: t.bg, color: t.fg, border: `1px solid ${t.bd}` }}
38
+ >
39
+ {label}
40
+ </span>
41
+ );
42
+ }
43
+
44
+ export function OrderStatusCard({ order }: { order: ShapedOrder }) {
45
+ return (
46
+ <div className="rounded-xl border border-[var(--store-line)] bg-white p-6">
47
+ <div className="flex items-center justify-between gap-3">
48
+ <div>
49
+ <p className="text-[12.5px] font-semibold text-[var(--store-muted)]">Pedido</p>
50
+ <p className="font-display text-[20px] font-extrabold text-[var(--store-ink)]">#{order.referenceId}</p>
51
+ </div>
52
+ <StatusBadge label={order.statusLabel} tone={statusTone(order.status)} />
53
+ </div>
54
+
55
+ <div className="my-4 border-t border-[var(--store-surface-2)]" />
56
+
57
+ <dl className="flex flex-col gap-2.5 text-sm">
58
+ {order.total && (
59
+ <div className="flex justify-between gap-6">
60
+ <dt className="text-[var(--store-muted)]">Total</dt>
61
+ <dd className="font-display text-[15px] font-extrabold text-[var(--store-primary,#18181B)]">{order.total}</dd>
62
+ </div>
63
+ )}
64
+ {order.payment && (
65
+ <div className="flex justify-between gap-6">
66
+ <dt className="text-[var(--store-muted)]">Pagamento</dt>
67
+ <dd className="text-right font-semibold text-[var(--store-ink)]">
68
+ {order.payment.displayName ?? "—"}
69
+ {order.payment.cardBrand ? ` · ${order.payment.cardBrand}` : ""}
70
+ {order.payment.statusLabel ? ` · ${order.payment.statusLabel}` : ""}
71
+ </dd>
72
+ </div>
73
+ )}
74
+ {order.payment?.errorMessage && (
75
+ <p className="rounded-lg bg-[var(--store-sale-soft)] px-3 py-2 text-[13px] font-semibold text-[var(--store-sale)]">{order.payment.errorMessage}</p>
76
+ )}
77
+ </dl>
78
+
79
+ {order.items.length > 0 && (
80
+ <>
81
+ <div className="my-4 border-t border-[var(--store-surface-2)]" />
82
+ <p className="mb-2.5 text-[13px] font-bold text-[var(--store-ink-2)]">Itens do pedido</p>
83
+ <ul className="flex flex-col gap-2.5">
84
+ {order.items.map((it, i) => {
85
+ const row = (
86
+ <>
87
+ <span className="relative flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-md border border-[var(--store-line)] bg-[var(--store-surface)]">
88
+ {it.thumbnail ? <Image src={it.thumbnail} alt={it.title} fill sizes="48px" className="object-contain p-1" /> : <Package className="text-[var(--store-faint)]" />}
89
+ </span>
90
+ <div className="min-w-0 flex-1">
91
+ <div className="text-[14px] font-bold leading-[1.25] text-[var(--store-ink)] line-clamp-2">{it.title}</div>
92
+ <div className="mt-0.5 text-[12.5px] text-[var(--store-muted)]">
93
+ {it.quantity}× {it.displayPrice ? `· ${it.displayPrice}` : ""}
94
+ </div>
95
+ </div>
96
+ {it.displaySubtotal && <span className="shrink-0 text-[14px] font-extrabold text-[var(--store-primary,#18181B)]">{it.displaySubtotal}</span>}
97
+ </>
98
+ );
99
+ return it.slug ? (
100
+ <li key={i}>
101
+ <Link href={`/produto/${encodeURIComponent(it.slug)}`} className="flex items-center gap-3 rounded-md border border-[var(--store-line)] bg-white p-2.5 no-underline transition-colors hover:border-[var(--store-primary,#18181B)]">{row}</Link>
102
+ </li>
103
+ ) : (
104
+ <li key={i} className="flex items-center gap-3 rounded-md border border-[var(--store-line)] bg-white p-2.5">{row}</li>
105
+ );
106
+ })}
107
+ </ul>
108
+ </>
109
+ )}
110
+
111
+ {order.address && (order.address.line || order.address.cityRegion) && (
112
+ <>
113
+ <div className="my-4 border-t border-[var(--store-surface-2)]" />
114
+ <div className="flex items-start gap-2 text-sm">
115
+ <MapPin weight="fill" className="mt-0.5 shrink-0 text-[var(--store-primary,#18181B)]" />
116
+ <div>
117
+ <p className="font-bold text-[var(--store-ink-2)]">Endereço de entrega</p>
118
+ <p className="mt-0.5 text-[var(--store-ink-2)]">
119
+ {order.address.fullName && <span className="font-semibold text-[var(--store-ink)]">{order.address.fullName}<br /></span>}
120
+ {[order.address.line, order.address.cityRegion].filter(Boolean).join(" · ")}
121
+ </p>
122
+ </div>
123
+ </div>
124
+ </>
125
+ )}
126
+
127
+ {order.fulfillment.length > 0 && (
128
+ <>
129
+ <div className="my-4 border-t border-[var(--store-surface-2)]" />
130
+ <div className="flex flex-col gap-2.5 text-sm">
131
+ <p className="flex items-center gap-2 font-bold text-[var(--store-ink-2)]"><Truck weight="bold" className="text-[var(--store-primary,#18181B)]" />Entrega</p>
132
+ {order.fulfillment.map((f, i) => (
133
+ <div key={i} className="flex items-center justify-between gap-4">
134
+ <span className="text-[var(--store-muted)]">{f.type ?? "Envio"}{f.status ? ` · ${f.status}` : ""}</span>
135
+ {f.trackingCode ? (
136
+ <span className="rounded-md bg-[var(--store-surface)] px-2 py-0.5 font-mono text-xs text-[var(--store-ink-2)]">Rastreio: {f.trackingCode}</span>
137
+ ) : (
138
+ <span className="text-[12.5px] text-[var(--store-faint)]">Sem rastreio ainda</span>
139
+ )}
140
+ </div>
141
+ ))}
142
+ </div>
143
+ </>
144
+ )}
145
+
146
+ {(order.invoiceIssued || order.dispatched || order.delivered) && (
147
+ <div className="mt-4 flex flex-wrap gap-2">
148
+ {order.invoiceIssued && <PillIcon icon={Receipt} label="Nota fiscal emitida" />}
149
+ {order.dispatched && <PillIcon icon={Package} label="Despachado" />}
150
+ {order.delivered && <PillIcon icon={CheckCircle} label="Entregue" />}
151
+ </div>
152
+ )}
153
+ </div>
154
+ );
155
+ }
156
+
157
+ function PillIcon({ icon: Icon, label }: { icon: React.ComponentType<any>; label: string }) {
158
+ return (
159
+ <span className="inline-flex items-center gap-1.5 rounded-full border border-[var(--store-line-2)] bg-[var(--store-surface)] px-3 py-1 text-[12px] font-semibold text-[var(--store-ink-2)]">
160
+ <Icon weight="fill" className="text-[var(--store-primary,#18181B)]" />{label}
161
+ </span>
162
+ );
163
+ }
@@ -0,0 +1,27 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════
2
+ // POWERED BY UNBOX — OBRIGATÓRIO EM TODA LOJA (parte do contrato com a Unbox).
3
+ // NÃO remova este componente do rodapé nem o asset /unbox/powered-by.png.
4
+ // O build FALHA sem ele (scripts/check-unbox-brand.mjs, rodado no prebuild) —
5
+ // ou seja, o site não deploya sem o selo. Restyle é permitido (tamanho/opacity),
6
+ // remoção não.
7
+ // ═══════════════════════════════════════════════════════════════════════════
8
+
9
+ export function PoweredByUnbox({ className = "" }: { className?: string }) {
10
+ return (
11
+ <a
12
+ href="https://www.unbox.com.br?utm_source=storefront&utm_medium=powered-by"
13
+ target="_blank"
14
+ rel="noopener"
15
+ className={`inline-flex items-center gap-2 no-underline opacity-80 transition-opacity hover:opacity-100 ${className}`}
16
+ >
17
+ <span className="text-[11px] font-semibold tracking-[0.3px]">Powered by</span>
18
+ {/* REGRA DAS VARIANTES (definida pela Unbox): /unbox/powered-by.png é o arquivo ATIVO.
19
+ Rodapé ESCURO → copie powered-by-transparente.png (logo neon transparente) por cima.
20
+ Rodapé CLARO → copie powered-by-fundo-preto.png (logo sobre fundo preto) por cima.
21
+ O CLI já escolhe a variante certa pelo chrome do preset; se você mudar a cor do
22
+ rodapé depois, troque o arquivo ativo pela variante correspondente. */}
23
+ {/* eslint-disable-next-line @next/next/no-img-element -- selo fixo de 67x23 px, servido do próprio domínio. */}
24
+ <img src="/unbox/powered-by.png" alt="Unbox" width={67} height={23} loading="lazy" decoding="async" className="h-[23px] w-auto" />
25
+ </a>
26
+ );
27
+ }