@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,204 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { toast } from "sonner";
6
+ import { Pause, Play, SkipForward, XCircle, MapPin, CreditCard, ListChecks, CircleNotch } from "@phosphor-icons/react/dist/ssr";
7
+ import {
8
+ Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription,
9
+ } from "@/components/ui/dialog";
10
+ import {
11
+ AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription,
12
+ AlertDialogFooter, AlertDialogAction, AlertDialogCancel,
13
+ } from "@/components/ui/alert-dialog";
14
+ import { AddressFields, emptyAddress, type AddressValue } from "@/components/address-fields";
15
+ import { QuantityStepper } from "@/components/quantity-stepper";
16
+
17
+ const BTN_PRIMARY = "font-display inline-flex h-11 cursor-pointer items-center justify-center gap-2 rounded-xl bg-[var(--store-primary,#18181B)] px-5 text-[14px] font-bold text-white transition-colors hover:bg-[var(--store-primary-dark,#09090B)] disabled:opacity-60";
18
+ const BTN_OUTLINE = "inline-flex h-11 cursor-pointer items-center justify-center gap-2 rounded-xl border-[1.5px] border-[var(--store-line-2)] bg-white px-4 text-[14px] font-bold text-[var(--store-ink-2)] transition-colors hover:border-[var(--store-primary,#18181B)] hover:text-[var(--store-primary,#18181B)]";
19
+ const CHIP = "inline-flex h-10 cursor-pointer items-center gap-2 rounded-xl border-[1.5px] border-[var(--store-line-2)] bg-white px-3.5 text-[13.5px] font-bold text-[var(--store-ink-2)] transition-colors hover:border-[var(--store-primary,#18181B)] hover:text-[var(--store-primary,#18181B)] disabled:opacity-60";
20
+ const CHIP_DANGER = "inline-flex h-10 cursor-pointer items-center gap-2 rounded-xl border-[1.5px] border-[var(--store-sale-soft)] bg-white px-3.5 text-[13.5px] font-bold text-[var(--store-sale)] transition-colors hover:border-[var(--store-sale)] hover:bg-[var(--store-sale-soft)]";
21
+ const INPUT = "h-11 w-full rounded-xl border-[1.5px] border-[var(--store-line-2)] bg-white px-3.5 text-[14px] font-medium text-[var(--store-ink)] outline-none transition-colors focus:border-[var(--store-primary,#18181B)]";
22
+
23
+ interface Actions {
24
+ canPause?: boolean; canSkipCycle?: boolean; canChangeAddress?: boolean;
25
+ canChangeProductQuantity?: boolean; canAddProducts?: boolean; canRemoveProducts?: boolean;
26
+ }
27
+ interface Item { productId: string; variantId: string; quantity: number }
28
+
29
+ export function SubscriptionActions({
30
+ recurringOrderId,
31
+ status,
32
+ actions,
33
+ items,
34
+ }: {
35
+ recurringOrderId: string;
36
+ status: string;
37
+ actions: Actions;
38
+ items: Item[];
39
+ }) {
40
+ const router = useRouter();
41
+ const [busy, setBusy] = React.useState<string | null>(null);
42
+ const [cancelOpen, setCancelOpen] = React.useState(false);
43
+ const [addrOpen, setAddrOpen] = React.useState(false);
44
+ const [cardOpen, setCardOpen] = React.useState(false);
45
+ const [itemsOpen, setItemsOpen] = React.useState(false);
46
+
47
+ const [addr, setAddr] = React.useState<AddressValue>(emptyAddress);
48
+ const [card, setCard] = React.useState({ cardHolder: "", cardNumber: "", expirationMonth: "", expirationYear: "", securityCode: "" });
49
+ const [editItems, setEditItems] = React.useState<Item[]>(items);
50
+
51
+ const canceled = status === "CANCELED";
52
+ const paused = status === "PAUSED";
53
+
54
+ async function run(action: string, payload: Record<string, any> = {}, label = "Pronto") {
55
+ setBusy(action);
56
+ try {
57
+ const res = await fetch(`/api/subscriptions/${recurringOrderId}`, {
58
+ method: "POST",
59
+ headers: { "Content-Type": "application/json" },
60
+ body: JSON.stringify({ action, ...payload }),
61
+ });
62
+ const data = await res.json().catch(() => ({}));
63
+ if (!res.ok) throw new Error(data?.error || "Erro inesperado.");
64
+ toast.success(label);
65
+ router.refresh();
66
+ return true;
67
+ } catch (e: any) {
68
+ toast.error(e.message);
69
+ return false;
70
+ } finally {
71
+ setBusy(null);
72
+ }
73
+ }
74
+
75
+ if (canceled) {
76
+ return <p className="mt-4 text-sm text-[var(--store-muted)]">Esta assinatura está cancelada.</p>;
77
+ }
78
+
79
+ return (
80
+ <div className="mt-6">
81
+ <h2 className="font-display mb-3 text-[16px] font-bold text-[var(--store-ink)]">Gerenciar assinatura</h2>
82
+ <div className="flex flex-wrap gap-2">
83
+ {actions.canPause && (
84
+ <button type="button" className={CHIP} disabled={!!busy} onClick={() => run("pause", {}, paused ? "Assinatura retomada." : "Assinatura pausada.")}>
85
+ {busy === "pause" ? <CircleNotch className="animate-spin" /> : paused ? <Play weight="fill" /> : <Pause weight="bold" />} {paused ? "Retomar" : "Pausar"}
86
+ </button>
87
+ )}
88
+ {actions.canSkipCycle && (
89
+ <button type="button" className={CHIP} disabled={!!busy} onClick={() => run("skip", {}, "Próximo ciclo adiado.")}>
90
+ {busy === "skip" ? <CircleNotch className="animate-spin" /> : <SkipForward weight="bold" />} Pular próximo ciclo
91
+ </button>
92
+ )}
93
+ {actions.canChangeAddress && (
94
+ <button type="button" className={CHIP} onClick={() => setAddrOpen(true)}><MapPin weight="bold" /> Trocar endereço</button>
95
+ )}
96
+ <button type="button" className={CHIP} onClick={() => setCardOpen(true)}><CreditCard weight="bold" /> Trocar cartão</button>
97
+ {(actions.canChangeProductQuantity || actions.canAddProducts || actions.canRemoveProducts) && (
98
+ <button type="button" className={CHIP} onClick={() => { setEditItems(items); setItemsOpen(true); }}>
99
+ <ListChecks weight="bold" /> Alterar itens
100
+ </button>
101
+ )}
102
+ <button type="button" className={CHIP_DANGER} onClick={() => setCancelOpen(true)}><XCircle weight="bold" /> Cancelar</button>
103
+ </div>
104
+
105
+ {/* Cancelar */}
106
+ <AlertDialog open={cancelOpen} onOpenChange={setCancelOpen}>
107
+ <AlertDialogContent>
108
+ <AlertDialogHeader>
109
+ <AlertDialogTitle>Cancelar assinatura?</AlertDialogTitle>
110
+ <AlertDialogDescription>Você deixará de receber os próximos ciclos. Esta ação não pode ser desfeita.</AlertDialogDescription>
111
+ </AlertDialogHeader>
112
+ <AlertDialogFooter>
113
+ <AlertDialogCancel className={BTN_OUTLINE}>Voltar</AlertDialogCancel>
114
+ <AlertDialogAction
115
+ className="font-display inline-flex h-11 cursor-pointer items-center justify-center gap-2 rounded-xl bg-[var(--store-sale)] px-5 text-[14px] font-bold text-white transition-colors hover:bg-[var(--store-sale)]"
116
+ onClick={() => run("cancel", {}, "Assinatura cancelada.").then((ok) => ok && setCancelOpen(false))}
117
+ >
118
+ Cancelar assinatura
119
+ </AlertDialogAction>
120
+ </AlertDialogFooter>
121
+ </AlertDialogContent>
122
+ </AlertDialog>
123
+
124
+ {/* Endereço */}
125
+ <Dialog open={addrOpen} onOpenChange={setAddrOpen}>
126
+ <DialogContent className="max-w-lg">
127
+ <DialogHeader>
128
+ <DialogTitle>Trocar endereço de entrega</DialogTitle>
129
+ <DialogDescription>O novo endereço valerá para os próximos ciclos.</DialogDescription>
130
+ </DialogHeader>
131
+ <AddressFields value={addr} onChange={setAddr} />
132
+ <DialogFooter>
133
+ <button type="button" className={BTN_OUTLINE} onClick={() => setAddrOpen(false)}>Voltar</button>
134
+ <button type="button" className={BTN_PRIMARY} disabled={busy === "address"} onClick={() => run("address", { address: addr }, "Endereço atualizado.").then((ok) => ok && setAddrOpen(false))}>
135
+ {busy === "address" ? <CircleNotch className="animate-spin" /> : null} Salvar
136
+ </button>
137
+ </DialogFooter>
138
+ </DialogContent>
139
+ </Dialog>
140
+
141
+ {/* Cartão */}
142
+ <Dialog open={cardOpen} onOpenChange={setCardOpen}>
143
+ <DialogContent>
144
+ <DialogHeader>
145
+ <DialogTitle>Trocar cartão</DialogTitle>
146
+ <DialogDescription>O novo cartão será usado nas próximas cobranças.</DialogDescription>
147
+ </DialogHeader>
148
+ <div className="grid gap-3">
149
+ <Field id="s-ch" label="Nome no cartão" value={card.cardHolder} onChange={(v) => setCard({ ...card, cardHolder: v })} />
150
+ <Field id="s-cn" label="Número" inputMode="numeric" value={card.cardNumber} onChange={(v) => setCard({ ...card, cardNumber: v })} />
151
+ <div className="grid grid-cols-3 gap-2">
152
+ <Field id="s-mm" label="Mês" inputMode="numeric" value={card.expirationMonth} onChange={(v) => setCard({ ...card, expirationMonth: v })} />
153
+ <Field id="s-yy" label="Ano" inputMode="numeric" value={card.expirationYear} onChange={(v) => setCard({ ...card, expirationYear: v })} />
154
+ <Field id="s-cvv" label="CVV" inputMode="numeric" value={card.securityCode} onChange={(v) => setCard({ ...card, securityCode: v })} />
155
+ </div>
156
+ </div>
157
+ <DialogFooter>
158
+ <button type="button" className={BTN_OUTLINE} onClick={() => setCardOpen(false)}>Voltar</button>
159
+ <button type="button" className={BTN_PRIMARY} disabled={busy === "card"} onClick={() => run("card", { card }, "Cartão atualizado.").then((ok) => ok && setCardOpen(false))}>
160
+ {busy === "card" ? <CircleNotch className="animate-spin" /> : null} Salvar
161
+ </button>
162
+ </DialogFooter>
163
+ </DialogContent>
164
+ </Dialog>
165
+
166
+ {/* Itens */}
167
+ <Dialog open={itemsOpen} onOpenChange={setItemsOpen}>
168
+ <DialogContent>
169
+ <DialogHeader>
170
+ <DialogTitle>Alterar itens</DialogTitle>
171
+ <DialogDescription>Ajuste as quantidades dos próximos ciclos.</DialogDescription>
172
+ </DialogHeader>
173
+ <ul className="flex flex-col gap-3">
174
+ {editItems.map((it, i) => (
175
+ <li key={i} className="flex items-center justify-between gap-3 text-sm">
176
+ <span className="truncate font-medium text-[var(--store-ink-2)]">{it.productId}</span>
177
+ <QuantityStepper
178
+ value={it.quantity}
179
+ min={0}
180
+ onChange={(q) => setEditItems((arr) => arr.map((x, j) => (j === i ? { ...x, quantity: q } : x)))}
181
+ />
182
+ </li>
183
+ ))}
184
+ </ul>
185
+ <DialogFooter>
186
+ <button type="button" className={BTN_OUTLINE} onClick={() => setItemsOpen(false)}>Voltar</button>
187
+ <button type="button" className={BTN_PRIMARY} disabled={busy === "items"} onClick={() => run("items", { items: editItems }, "Itens atualizados.").then((ok) => ok && setItemsOpen(false))}>
188
+ {busy === "items" ? <CircleNotch className="animate-spin" /> : null} Salvar
189
+ </button>
190
+ </DialogFooter>
191
+ </DialogContent>
192
+ </Dialog>
193
+ </div>
194
+ );
195
+ }
196
+
197
+ function Field({ id, label, value, onChange, ...rest }: { id: string; label: string; value: string; onChange: (v: string) => void } & Omit<React.InputHTMLAttributes<HTMLInputElement>, "value" | "onChange" | "id">) {
198
+ return (
199
+ <label htmlFor={id} className="flex flex-col gap-1.5">
200
+ <span className="text-[13px] font-bold text-[var(--store-ink-2)]">{label}</span>
201
+ <input id={id} value={value} onChange={(e) => onChange(e.target.value)} className={INPUT} {...rest} />
202
+ </label>
203
+ );
204
+ }
@@ -0,0 +1,41 @@
1
+ "use client";
2
+
3
+ // Link de conta no header. Mantém o layout estático (ISR): busca o nome do cliente no client.
4
+ // Logado → primeiro nome com uma pimentinha; senão → "Entrar".
5
+ import * as React from "react";
6
+ import Link from "next/link";
7
+ import { User, UserCircle } from "@phosphor-icons/react/dist/ssr";
8
+
9
+ export function AccountNav({
10
+ /** "ink" = sobre fundo claro (padrão). "chrome" = sobre a cor de chrome (header sobreposto). */
11
+ tone = "ink",
12
+ /** Só o ícone, sem o rótulo — pra headers que alinham 3 ações em ícone. */
13
+ iconOnly = false,
14
+ }: { tone?: "ink" | "chrome"; iconOnly?: boolean } = {}) {
15
+ const cor = tone === "chrome" ? "text-[var(--store-chrome-text,#ffffff)]" : "text-[var(--store-ink)]";
16
+ const [firstName, setFirstName] = React.useState<string | null>(null);
17
+
18
+ React.useEffect(() => {
19
+ let alive = true;
20
+ fetch("/api/account/me")
21
+ .then((r) => (r.ok ? r.json() : null))
22
+ .then((d) => { if (alive && d?.firstName) setFirstName(d.firstName); })
23
+ .catch(() => {});
24
+ return () => { alive = false; };
25
+ }, []);
26
+
27
+ if (firstName) {
28
+ return (
29
+ <Link href="/conta" aria-label={`Conta de ${firstName}`} className={`flex min-h-11 min-w-11 shrink-0 items-center justify-center gap-2 text-[15px] font-semibold ${cor}`}>
30
+ <UserCircle weight="fill" className={tone === "chrome" ? "text-[22px]" : "text-[22px] text-[var(--store-primary,#18181B)]"} />
31
+ {!iconOnly && firstName}
32
+ </Link>
33
+ );
34
+ }
35
+ return (
36
+ <Link href="/conta" aria-label="Entrar na minha conta" className={`flex min-h-11 min-w-11 shrink-0 items-center justify-center gap-2 text-[15px] font-medium ${cor}`}>
37
+ <User className="text-[22px]" />
38
+ {!iconOnly && "Entrar"}
39
+ </Link>
40
+ );
41
+ }
@@ -0,0 +1,102 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Loader2 } from "@/lib/icons";
5
+ import { Input } from "@/components/ui/input";
6
+ import { Label } from "@/components/ui/label";
7
+ import { maskCep, onlyDigits } from "@/lib/format";
8
+
9
+ export interface AddressValue {
10
+ fullName: string;
11
+ taxPayerId: string;
12
+ postal: string;
13
+ address1: string;
14
+ number: string;
15
+ address2?: string;
16
+ neighborhood: string;
17
+ city: string;
18
+ region: string;
19
+ phone: string;
20
+ }
21
+
22
+ export const emptyAddress: AddressValue = {
23
+ fullName: "", taxPayerId: "", postal: "", address1: "", number: "",
24
+ address2: "", neighborhood: "", city: "", region: "", phone: "",
25
+ };
26
+
27
+ // Campos de endereço com autofill por CEP (/api/cep/[code]). a11y: cada input tem <Label> + autocomplete.
28
+ export function AddressFields({
29
+ value,
30
+ onChange,
31
+ }: {
32
+ value: AddressValue;
33
+ onChange: (v: AddressValue) => void;
34
+ }) {
35
+ const [cepLoading, setCepLoading] = React.useState(false);
36
+ const set = (patch: Partial<AddressValue>) => onChange({ ...value, ...patch });
37
+
38
+ const onCepBlur = async () => {
39
+ const cep = onlyDigits(value.postal);
40
+ if (cep.length !== 8) return;
41
+ setCepLoading(true);
42
+ try {
43
+ const res = await fetch(`/api/cep/${cep}`);
44
+ const data = await res.json();
45
+ if (res.ok && data.address) {
46
+ set({
47
+ address1: data.address.address1 || value.address1,
48
+ neighborhood: data.address.neighborhood || value.neighborhood,
49
+ city: data.address.city || value.city,
50
+ region: data.address.region || value.region,
51
+ });
52
+ }
53
+ } finally {
54
+ setCepLoading(false);
55
+ }
56
+ };
57
+
58
+ return (
59
+ <div className="grid gap-4 sm:grid-cols-2">
60
+ <Field className="sm:col-span-2" id="fullName" label="Nome completo" autoComplete="name"
61
+ value={value.fullName} onChange={(v) => set({ fullName: v })} />
62
+ <Field id="taxPayerId" label="CPF" inputMode="numeric" autoComplete="off"
63
+ value={value.taxPayerId} onChange={(v) => set({ taxPayerId: v })} />
64
+ <Field id="phone" label="Telefone (com DDD)" inputMode="tel" autoComplete="tel"
65
+ value={value.phone} onChange={(v) => set({ phone: v })} />
66
+
67
+ <div className="grid gap-1.5">
68
+ <Label htmlFor="postal">CEP</Label>
69
+ <div className="relative">
70
+ <Input id="postal" inputMode="numeric" autoComplete="postal-code" value={value.postal}
71
+ onChange={(e) => set({ postal: maskCep(e.target.value) })} onBlur={onCepBlur} />
72
+ {cepLoading && <Loader2 className="absolute right-2 top-1/2 size-4 -translate-y-1/2 animate-spin text-muted-foreground" />}
73
+ </div>
74
+ </div>
75
+ <Field id="region" label="UF" autoComplete="address-level1" maxLength={2}
76
+ value={value.region} onChange={(v) => set({ region: v.toUpperCase().slice(0, 2) })} />
77
+
78
+ <Field className="sm:col-span-2" id="address1" label="Endereço" autoComplete="address-line1"
79
+ value={value.address1} onChange={(v) => set({ address1: v })} />
80
+ <Field id="number" label="Número" autoComplete="off" value={value.number} onChange={(v) => set({ number: v })} />
81
+ <Field id="address2" label="Complemento (opcional)" autoComplete="address-line2"
82
+ value={value.address2 ?? ""} onChange={(v) => set({ address2: v })} />
83
+ <Field id="neighborhood" label="Bairro" autoComplete="address-level3"
84
+ value={value.neighborhood} onChange={(v) => set({ neighborhood: v })} />
85
+ <Field id="city" label="Cidade" autoComplete="address-level2"
86
+ value={value.city} onChange={(v) => set({ city: v })} />
87
+ </div>
88
+ );
89
+ }
90
+
91
+ function Field({
92
+ id, label, value, onChange, className, ...rest
93
+ }: {
94
+ id: string; label: string; value: string; onChange: (v: string) => void; className?: string;
95
+ } & Omit<React.ComponentProps<typeof Input>, "value" | "onChange" | "id">) {
96
+ return (
97
+ <div className={`grid gap-1.5 ${className ?? ""}`}>
98
+ <Label htmlFor={id}>{label}</Label>
99
+ <Input id={id} value={value} onChange={(e) => onChange(e.target.value)} {...rest} />
100
+ </div>
101
+ );
102
+ }
@@ -0,0 +1,16 @@
1
+ "use client";
2
+
3
+ // Emite `dataLayerReady { pageType, products[] }` uma vez por carregamento. É o evento que o
4
+ // container central da Unbox usa para tipo de página e remarketing do Google Ads; sem ele, a
5
+ // loja gerada pelo CLI é invisível para esses gatilhos. Não renderiza nada.
6
+ import * as React from "react";
7
+ import { trackPageType, type PageType, type TrackItem } from "@/lib/analytics";
8
+
9
+ export function DataLayerReady({ pageType, products = [] }: { pageType: PageType; products?: TrackItem[] }) {
10
+ const key = pageType + "|" + products.map((p) => p.id).join(",");
11
+ React.useEffect(() => {
12
+ trackPageType(pageType, products);
13
+ // eslint-disable-next-line react-hooks/exhaustive-deps
14
+ }, [key]);
15
+ return null;
16
+ }
@@ -0,0 +1,32 @@
1
+ "use client";
2
+
3
+ // Dispara purchase / Purchase quando o pedido está PAGO (página /pedido/[ref]). Dedupe por
4
+ // transactionId (localStorage) em trackPurchase → não conta duas vezes se o cliente também viu
5
+ // a confirmação inline do Pix. Pix pendente NUNCA dispara: `paid` decide. Não renderiza nada.
6
+ import * as React from "react";
7
+ import { trackPurchase, type TrackItem, type TrackUser } from "@/lib/analytics";
8
+
9
+ export function PurchaseTracker({
10
+ transactionId,
11
+ paid,
12
+ value,
13
+ items,
14
+ shipping,
15
+ coupon,
16
+ user,
17
+ }: {
18
+ transactionId: string;
19
+ paid: boolean;
20
+ value?: number;
21
+ items: TrackItem[];
22
+ shipping?: number;
23
+ coupon?: string;
24
+ user?: TrackUser;
25
+ }) {
26
+ React.useEffect(() => {
27
+ if (!paid || !transactionId) return;
28
+ trackPurchase({ transactionId, value, items, shipping, coupon, user });
29
+ // eslint-disable-next-line react-hooks/exhaustive-deps
30
+ }, [paid, transactionId]);
31
+ return null;
32
+ }
@@ -0,0 +1,34 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { MagnifyingGlass } from "@phosphor-icons/react/dist/ssr";
6
+
7
+ export function BrandSearch({ className }: { className?: string }) {
8
+ const router = useRouter();
9
+ const [q, setQ] = React.useState("");
10
+ return (
11
+ <form
12
+ role="search"
13
+ className={`flex h-[50px] items-center overflow-hidden rounded-full border border-[var(--store-line-2)] bg-[var(--store-surface)] pl-1 ${className ?? ""}`}
14
+ onSubmit={(e) => {
15
+ e.preventDefault();
16
+ const term = q.trim();
17
+ router.push(term ? `/busca?q=${encodeURIComponent(term)}` : "/produtos");
18
+ }}
19
+ >
20
+ <input
21
+ type="search"
22
+ name="q"
23
+ value={q}
24
+ onChange={(e) => setQ(e.target.value)}
25
+ placeholder="Buscar produtos..."
26
+ aria-label="Buscar produtos"
27
+ className="min-w-0 flex-1 border-none bg-transparent px-4 text-[15px] text-[var(--store-ink)] outline-none"
28
+ />
29
+ <button type="submit" aria-label="Buscar" className="flex h-[50px] w-[62px] shrink-0 items-center justify-center rounded-full bg-[var(--store-primary,#18181B)] text-white transition-colors hover:bg-[var(--store-primary-dark,#09090B)]">
30
+ <MagnifyingGlass weight="bold" className="text-[19px]" />
31
+ </button>
32
+ </form>
33
+ );
34
+ }