create-nextblock 0.16.1 → 0.16.2

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 (258) hide show
  1. package/docker-template/.dockerignore +1 -1
  2. package/libs/db/tsconfig.lib.json +3 -3
  3. package/libs/editor/tsconfig.lib.json +3 -3
  4. package/libs/ui/tsconfig.lib.json +3 -3
  5. package/libs/utils/tsconfig.json +3 -3
  6. package/package.json +50 -50
  7. package/project.json +19 -19
  8. package/templates/nextblock-template/.dockerignore +1 -1
  9. package/templates/nextblock-template/.swcrc +30 -30
  10. package/templates/nextblock-template/AGENTS.md +9 -9
  11. package/templates/nextblock-template/CLAUDE.md +1 -1
  12. package/templates/nextblock-template/app/(auth-pages)/layout.tsx +9 -9
  13. package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +27 -27
  14. package/templates/nextblock-template/app/.well-known/ucp/route.ts +16 -16
  15. package/templates/nextblock-template/app/ToasterProvider.tsx +26 -26
  16. package/templates/nextblock-template/app/[slug]/pageClientActions.ts +7 -7
  17. package/templates/nextblock-template/app/actions/contactSellerActions.test.ts +280 -280
  18. package/templates/nextblock-template/app/actions/contactSellerActions.ts +222 -222
  19. package/templates/nextblock-template/app/actions/email-retry.test.ts +62 -62
  20. package/templates/nextblock-template/app/actions/email.ts +241 -241
  21. package/templates/nextblock-template/app/actions/formActions.ts +245 -245
  22. package/templates/nextblock-template/app/actions/interactions.ts +489 -489
  23. package/templates/nextblock-template/app/actions/threadActions.ts +166 -166
  24. package/templates/nextblock-template/app/actions/visibilityActions.ts +210 -210
  25. package/templates/nextblock-template/app/api/ai/seo/alt-text/route.ts +221 -221
  26. package/templates/nextblock-template/app/api/ai/seo/metadata/route.ts +186 -186
  27. package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -29
  28. package/templates/nextblock-template/app/api/checkout/route.ts +162 -162
  29. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +3092 -3092
  30. package/templates/nextblock-template/app/api/mcp/route.ts +415 -415
  31. package/templates/nextblock-template/app/api/media/record/route.ts +160 -160
  32. package/templates/nextblock-template/app/api/search/route.ts +43 -43
  33. package/templates/nextblock-template/app/api/view/route.ts +114 -114
  34. package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -47
  35. package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -47
  36. package/templates/nextblock-template/app/auth/callback/route.ts +31 -31
  37. package/templates/nextblock-template/app/cart/page.tsx +7 -7
  38. package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -20
  39. package/templates/nextblock-template/app/checkout/page.tsx +57 -57
  40. package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +558 -558
  41. package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +241 -241
  42. package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +149 -149
  43. package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +304 -304
  44. package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +406 -406
  45. package/templates/nextblock-template/app/cms/components/ContactReminderBanner.tsx +75 -75
  46. package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -23
  47. package/templates/nextblock-template/app/cms/components/CortexAiPageContext.tsx +58 -58
  48. package/templates/nextblock-template/app/cms/components/FeatureImageField.tsx +254 -254
  49. package/templates/nextblock-template/app/cms/components/FeedbackModal.tsx +36 -36
  50. package/templates/nextblock-template/app/cms/components/PaymentsReminderBanner.tsx +58 -58
  51. package/templates/nextblock-template/app/cms/components/VisibilityBadge.tsx +62 -62
  52. package/templates/nextblock-template/app/cms/components/VisibilityControl.tsx +542 -542
  53. package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -16
  54. package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -16
  55. package/templates/nextblock-template/app/cms/dashboard/actions.ts +228 -228
  56. package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -200
  57. package/templates/nextblock-template/app/cms/inquiries/actions.ts +66 -66
  58. package/templates/nextblock-template/app/cms/inquiries/page.tsx +12 -12
  59. package/templates/nextblock-template/app/cms/interactions/page.tsx +12 -12
  60. package/templates/nextblock-template/app/cms/layout.tsx +101 -101
  61. package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -273
  62. package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -122
  63. package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +69 -69
  64. package/templates/nextblock-template/app/cms/messages/MessagesClient.tsx +661 -661
  65. package/templates/nextblock-template/app/cms/messages/actions.ts +404 -404
  66. package/templates/nextblock-template/app/cms/messages/loadInbox.ts +333 -333
  67. package/templates/nextblock-template/app/cms/messages/page.tsx +87 -87
  68. package/templates/nextblock-template/app/cms/messages/require-admin.ts +37 -37
  69. package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +3 -3
  70. package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +649 -649
  71. package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +618 -618
  72. package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +370 -370
  73. package/templates/nextblock-template/app/cms/products/attributes/page.tsx +12 -12
  74. package/templates/nextblock-template/app/cms/products/inventory/page.tsx +13 -13
  75. package/templates/nextblock-template/app/cms/products/productFormData.ts +133 -133
  76. package/templates/nextblock-template/app/cms/products/settings/page.tsx +5 -5
  77. package/templates/nextblock-template/app/cms/revisions/actions.ts +332 -332
  78. package/templates/nextblock-template/app/cms/revisions/service.test.ts +498 -498
  79. package/templates/nextblock-template/app/cms/revisions/service.ts +569 -569
  80. package/templates/nextblock-template/app/cms/revisions/utils.ts +304 -304
  81. package/templates/nextblock-template/app/cms/settings/cortex-ai/CortexAiSettingsClient.tsx +948 -948
  82. package/templates/nextblock-template/app/cms/settings/cortex-ai/McpServerSettingsCard.tsx +628 -628
  83. package/templates/nextblock-template/app/cms/settings/cortex-ai/mcp-actions.ts +230 -230
  84. package/templates/nextblock-template/app/cms/settings/cortex-ai/require-admin.ts +34 -34
  85. package/templates/nextblock-template/app/cms/settings/currencies/actions.ts +331 -331
  86. package/templates/nextblock-template/app/cms/settings/currencies/page.tsx +494 -494
  87. package/templates/nextblock-template/app/cms/settings/email/components/EmailForm.tsx +227 -227
  88. package/templates/nextblock-template/app/cms/settings/extra-translations/ExtraTranslationsWorkspace.tsx +767 -767
  89. package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +276 -276
  90. package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +93 -93
  91. package/templates/nextblock-template/app/cms/settings/global-css/components/ThemeEditor.tsx +382 -382
  92. package/templates/nextblock-template/app/cms/settings/global-css/components/ThemeManager.tsx +267 -267
  93. package/templates/nextblock-template/app/cms/settings/global-css/page.tsx +40 -40
  94. package/templates/nextblock-template/app/cms/settings/global-css/theme-actions.ts +259 -259
  95. package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +7 -7
  96. package/templates/nextblock-template/app/cms/settings/logos/components/BrandingSettingsForm.tsx +339 -339
  97. package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +8 -8
  98. package/templates/nextblock-template/app/cms/settings/seo/RedirectsCard.tsx +514 -514
  99. package/templates/nextblock-template/app/cms/settings/seo/RobotsCard.tsx +529 -529
  100. package/templates/nextblock-template/app/cms/settings/seo/SeoSettingsClient.tsx +57 -57
  101. package/templates/nextblock-template/app/cms/settings/seo/actions.ts +448 -448
  102. package/templates/nextblock-template/app/cms/settings/seo/mappers.ts +93 -93
  103. package/templates/nextblock-template/app/cms/settings/seo/page.tsx +46 -46
  104. package/templates/nextblock-template/app/cms/settings/seo/require-admin.ts +47 -47
  105. package/templates/nextblock-template/app/cms/settings/site-scripts/page.tsx +51 -51
  106. package/templates/nextblock-template/app/cms/settings/taxes/page.tsx +21 -21
  107. package/templates/nextblock-template/app/cms/shipping/page.tsx +20 -20
  108. package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +12 -12
  109. package/templates/nextblock-template/app/layout.tsx +671 -671
  110. package/templates/nextblock-template/app/lib/seo.ts +319 -319
  111. package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -190
  112. package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -56
  113. package/templates/nextblock-template/app/product/[slug]/page.tsx +502 -502
  114. package/templates/nextblock-template/app/profile/ProfilePageHeader.tsx +16 -16
  115. package/templates/nextblock-template/app/profile/ProfilePageMissingState.tsx +9 -9
  116. package/templates/nextblock-template/app/profile/account-links.ts +22 -22
  117. package/templates/nextblock-template/app/profile/orders/CustomerOrdersPageClient.tsx +124 -124
  118. package/templates/nextblock-template/app/profile/orders/page.tsx +19 -19
  119. package/templates/nextblock-template/app/profile/password/PasswordSettingsPageClient.tsx +128 -128
  120. package/templates/nextblock-template/app/profile/password/actions.ts +59 -59
  121. package/templates/nextblock-template/app/profile/password/page.tsx +27 -27
  122. package/templates/nextblock-template/app/providers.tsx +96 -96
  123. package/templates/nextblock-template/app/robots.ts +123 -123
  124. package/templates/nextblock-template/app/thread/ThreadView.tsx +164 -164
  125. package/templates/nextblock-template/app/thread/[token]/route.ts +57 -57
  126. package/templates/nextblock-template/app/thread/layout.tsx +15 -15
  127. package/templates/nextblock-template/app/thread/page.tsx +98 -98
  128. package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -38
  129. package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -68
  130. package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -35
  131. package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -35
  132. package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -35
  133. package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -34
  134. package/templates/nextblock-template/components/BlockRenderer.tsx +312 -312
  135. package/templates/nextblock-template/components/CartDrawerLoader.tsx +7 -7
  136. package/templates/nextblock-template/components/CartTranslator.tsx +210 -210
  137. package/templates/nextblock-template/components/ContactSellerSection.tsx +188 -188
  138. package/templates/nextblock-template/components/DeferredCartDrawer.tsx +23 -23
  139. package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -51
  140. package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -68
  141. package/templates/nextblock-template/components/DeferredGoogleTagManager.tsx +70 -70
  142. package/templates/nextblock-template/components/DeferredSpeedInsights.tsx +69 -69
  143. package/templates/nextblock-template/components/FooterNavigation.tsx +32 -32
  144. package/templates/nextblock-template/components/GlobalSearch.tsx +557 -557
  145. package/templates/nextblock-template/components/Header.tsx +38 -38
  146. package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -47
  147. package/templates/nextblock-template/components/LanguageSwitcher.tsx +2 -2
  148. package/templates/nextblock-template/components/PostCommentsSection.tsx +378 -378
  149. package/templates/nextblock-template/components/ProductReviewsSection.tsx +426 -426
  150. package/templates/nextblock-template/components/SiteScripts.tsx +56 -56
  151. package/templates/nextblock-template/components/StaffReplies.tsx +102 -102
  152. package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +12 -12
  153. package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +12 -12
  154. package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +48 -48
  155. package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +9 -9
  156. package/templates/nextblock-template/components/blocks/ecommerceRendererLoaders.ts +23 -23
  157. package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -25
  158. package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -92
  159. package/templates/nextblock-template/components/blocks/renderers/CartBlockRenderer.tsx +18 -18
  160. package/templates/nextblock-template/components/blocks/renderers/CheckoutBlockRenderer.tsx +20 -20
  161. package/templates/nextblock-template/components/blocks/renderers/FeaturedProductBlockRenderer.tsx +25 -25
  162. package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +385 -385
  163. package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +24 -24
  164. package/templates/nextblock-template/components/blocks/renderers/ProductDetailsBlockRenderer.tsx +157 -157
  165. package/templates/nextblock-template/components/blocks/renderers/ProductGridBlockRenderer.tsx +34 -34
  166. package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +612 -612
  167. package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -57
  168. package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +2 -2
  169. package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +2 -2
  170. package/templates/nextblock-template/components/blocks/types.ts +7 -7
  171. package/templates/nextblock-template/components/commerce/PaymentReadinessBoundary.tsx +32 -32
  172. package/templates/nextblock-template/components/env-var-warning.tsx +3 -3
  173. package/templates/nextblock-template/components/form-message.tsx +32 -32
  174. package/templates/nextblock-template/components/seo/GenerateMetaButton.tsx +137 -137
  175. package/templates/nextblock-template/components/seo/PageSeoAuditSection.tsx +244 -244
  176. package/templates/nextblock-template/components/seo/SeoAuditPanel.tsx +749 -749
  177. package/templates/nextblock-template/components/seo/SeoIssueList.tsx +195 -195
  178. package/templates/nextblock-template/components/seo/SeoScoreDial.tsx +144 -144
  179. package/templates/nextblock-template/components/seo/SocialPreview.tsx +243 -243
  180. package/templates/nextblock-template/components/seo/SocialPreviewDialog.tsx +110 -110
  181. package/templates/nextblock-template/components/submit-button.tsx +23 -23
  182. package/templates/nextblock-template/components/theme-icon.tsx +78 -78
  183. package/templates/nextblock-template/components/theme-switcher.tsx +85 -85
  184. package/templates/nextblock-template/context/AuthContext.tsx +23 -23
  185. package/templates/nextblock-template/context/ThemeCatalogContext.tsx +44 -44
  186. package/templates/nextblock-template/docs/01-PROJECT-OVERVIEW.md +94 -94
  187. package/templates/nextblock-template/docs/03-CMS-AND-EDITOR.md +77 -77
  188. package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +372 -372
  189. package/templates/nextblock-template/docs/14-MESSAGES-INBOX.md +309 -309
  190. package/templates/nextblock-template/docs/README.md +42 -42
  191. package/templates/nextblock-template/docs/TECHNICAL_SPECIFICATION.md +12506 -12506
  192. package/templates/nextblock-template/hooks/use-hotkeys.ts +21 -21
  193. package/templates/nextblock-template/hooks/useGlobalSearch.ts +101 -101
  194. package/templates/nextblock-template/index.d.ts +7 -7
  195. package/templates/nextblock-template/lib/auth-redirects.ts +46 -46
  196. package/templates/nextblock-template/lib/blocks/blockColors.test.ts +134 -134
  197. package/templates/nextblock-template/lib/blocks/blockColors.ts +175 -175
  198. package/templates/nextblock-template/lib/blocks/blockRegistry.ts +761 -761
  199. package/templates/nextblock-template/lib/blocks/inlineScriptNonce.ts +20 -20
  200. package/templates/nextblock-template/lib/cms/contact-reminder.ts +64 -64
  201. package/templates/nextblock-template/lib/cms/payments-reminder.test.ts +135 -0
  202. package/templates/nextblock-template/lib/cms/payments-reminder.ts +30 -23
  203. package/templates/nextblock-template/lib/cms/unread-messages.ts +42 -42
  204. package/templates/nextblock-template/lib/commerce/seller-contact.ts +162 -162
  205. package/templates/nextblock-template/lib/config/email-settings.ts +323 -323
  206. package/templates/nextblock-template/lib/config/email-tls.test.ts +57 -57
  207. package/templates/nextblock-template/lib/cortex-ai/alt-text-request.ts +86 -86
  208. package/templates/nextblock-template/lib/cortex-ai/sandbox-headers.ts +60 -60
  209. package/templates/nextblock-template/lib/email/placeholder-address.test.ts +59 -59
  210. package/templates/nextblock-template/lib/email/placeholder-address.ts +39 -39
  211. package/templates/nextblock-template/lib/messages/thread-reference.test.ts +70 -70
  212. package/templates/nextblock-template/lib/messages/thread-token.test.ts +93 -93
  213. package/templates/nextblock-template/lib/messages/thread-token.ts +157 -157
  214. package/templates/nextblock-template/lib/messages/threads.ts +579 -579
  215. package/templates/nextblock-template/lib/posts/readTime.ts +60 -60
  216. package/templates/nextblock-template/lib/publishing/viewUrl.ts +26 -26
  217. package/templates/nextblock-template/lib/search/types.ts +27 -27
  218. package/templates/nextblock-template/lib/seo/alt-text-write-back.test.ts +154 -154
  219. package/templates/nextblock-template/lib/seo/alt-text-write-back.ts +109 -109
  220. package/templates/nextblock-template/lib/seo/block-content.ts +123 -123
  221. package/templates/nextblock-template/lib/seo/fix-prompts.test.ts +242 -242
  222. package/templates/nextblock-template/lib/seo/fix-prompts.ts +204 -204
  223. package/templates/nextblock-template/lib/seo/page-audit-context.tsx +140 -140
  224. package/templates/nextblock-template/lib/seo/page-document.test.ts +350 -350
  225. package/templates/nextblock-template/lib/seo/page-document.ts +412 -412
  226. package/templates/nextblock-template/lib/seo/redirect-store.test.ts +479 -479
  227. package/templates/nextblock-template/lib/seo/redirect-store.ts +466 -466
  228. package/templates/nextblock-template/lib/seo/robots-settings-signature.test.ts +102 -102
  229. package/templates/nextblock-template/lib/seo/robots-settings-signature.ts +41 -41
  230. package/templates/nextblock-template/lib/seo/robots-txt.test.ts +370 -370
  231. package/templates/nextblock-template/lib/seo/robots-txt.ts +510 -510
  232. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +177 -177
  233. package/templates/nextblock-template/lib/site-scripts/revisions.ts +71 -71
  234. package/templates/nextblock-template/lib/site-scripts/types.ts +46 -46
  235. package/templates/nextblock-template/lib/site-url.test.ts +89 -89
  236. package/templates/nextblock-template/lib/site-url.ts +102 -102
  237. package/templates/nextblock-template/lib/themes/buildThemeCss.ts +124 -124
  238. package/templates/nextblock-template/lib/themes/tokenColor.ts +31 -31
  239. package/templates/nextblock-template/lib/themes/tokens.ts +143 -143
  240. package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -105
  241. package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -42
  242. package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -143
  243. package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -94
  244. package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -81
  245. package/templates/nextblock-template/lib/zod-config.ts +5 -5
  246. package/templates/nextblock-template/next-env.d.ts +1 -2
  247. package/templates/nextblock-template/package.json +1 -1
  248. package/templates/nextblock-template/postcss.config.js +6 -6
  249. package/templates/nextblock-template/scripts/backup.js +115 -115
  250. package/templates/nextblock-template/scripts/restore.js +385 -385
  251. package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -112
  252. package/templates/nextblock-template/tailwind.config.js +25 -25
  253. package/templates/nextblock-template/tools/build-migrate.mjs +102 -102
  254. package/templates/nextblock-template/tools/configure-supabase-auth.js +282 -282
  255. package/templates/nextblock-template/tools/deploy-supabase.js +159 -159
  256. package/templates/nextblock-template/tools/lib/migrate-core.mjs +569 -569
  257. package/templates/nextblock-template/tools/update.mjs +1303 -1303
  258. package/tsconfig.base.json +3 -3
@@ -1,210 +1,210 @@
1
- 'use client';
2
-
3
- import { useEffect, useRef } from 'react';
4
- import { useLanguage } from '../context/LanguageContext';
5
- import { useCartStore } from '@nextblock-cms/ecommerce/cart-store';
6
- import { isDigitalItem, type CartItem } from '@nextblock-cms/ecommerce/types';
7
- import { useIsCartHydrated } from '@nextblock-cms/ecommerce/use-cart';
8
- import { mapRawVariantRelations } from '@nextblock-cms/ecommerce/variation-utils';
9
- import { getTranslatedProductsForCart } from '@nextblock-cms/ecommerce/server-actions/product-actions';
10
-
11
- const R2_BASE_URL = process.env.NEXT_PUBLIC_R2_BASE_URL || '';
12
-
13
- function resolveMediaUrl(filePath?: string | null) {
14
- if (!filePath) {
15
- return null;
16
- }
17
-
18
- if (filePath.startsWith('http')) {
19
- return filePath;
20
- }
21
-
22
- return R2_BASE_URL ? `${R2_BASE_URL}/${filePath}` : filePath;
23
- }
24
-
25
- function resolveProductImageUrl(product: any, fallback?: string | null) {
26
- const firstMedia = product?.product_media?.[0]?.media;
27
- return resolveMediaUrl(firstMedia?.file_path || firstMedia?.object_key) || fallback || null;
28
- }
29
-
30
- function buildCartTranslationSignature(items: CartItem[]) {
31
- return JSON.stringify(
32
- items.map((item) => ({
33
- id: item.id,
34
- product_id: item.product_id,
35
- variant_id: item.variant_id ?? null,
36
- sku: item.sku,
37
- quantity: item.quantity,
38
- language_id: item.language_id ?? null,
39
- translation_group_id: item.translation_group_id ?? null,
40
- }))
41
- );
42
- }
43
-
44
- function syncCartItem(item: CartItem, translatedProduct: any, currentLocale: string): CartItem {
45
- const productImageUrl = resolveProductImageUrl(translatedProduct, item.image_url);
46
- const nextBase: CartItem = {
47
- ...item,
48
- id: translatedProduct.id,
49
- product_id: translatedProduct.id,
50
- title: translatedProduct.title,
51
- slug: translatedProduct.slug,
52
- sku: translatedProduct.sku || item.sku,
53
- stock: typeof translatedProduct.stock === 'number' ? translatedProduct.stock : item.stock,
54
- language_id: translatedProduct.language_id,
55
- translation_group_id: translatedProduct.translation_group_id,
56
- image_url: productImageUrl || item.image_url,
57
- is_taxable: translatedProduct.is_taxable ?? item.is_taxable,
58
- product_type: translatedProduct.product_type ?? item.product_type,
59
- payment_provider: translatedProduct.payment_provider ?? item.payment_provider,
60
- provider: translatedProduct.payment_provider ?? item.provider,
61
- freemius_product_id: translatedProduct.freemius_product_id ?? item.freemius_product_id,
62
- freemius_plan_id: translatedProduct.freemius_plan_id ?? item.freemius_plan_id,
63
- trial_period_days: translatedProduct.trial_period_days ?? item.trial_period_days ?? 0,
64
- trial_requires_payment_method:
65
- translatedProduct.trial_requires_payment_method ??
66
- item.trial_requires_payment_method ??
67
- false,
68
- prices: translatedProduct.prices || item.prices,
69
- sale_prices: translatedProduct.sale_prices || item.sale_prices,
70
- has_variants: Boolean(translatedProduct.product_variants?.length),
71
- };
72
-
73
- if (isDigitalItem(item)) {
74
- return nextBase;
75
- }
76
-
77
- const isVariantLine = Boolean(item.variant_id || item.variant_label);
78
- if (!isVariantLine) {
79
- return {
80
- ...nextBase,
81
- price: translatedProduct.price,
82
- prices: translatedProduct.prices || item.prices,
83
- sale_price: translatedProduct.sale_price ?? null,
84
- sale_prices: translatedProduct.sale_prices || item.sale_prices,
85
- };
86
- }
87
-
88
- const { variants } = mapRawVariantRelations(translatedProduct.product_variants || [], currentLocale);
89
- const matchedVariant = variants.find((variant) => variant.sku === item.sku);
90
-
91
- if (!matchedVariant) {
92
- return item;
93
- }
94
-
95
- return {
96
- ...nextBase,
97
- id: matchedVariant.id,
98
- sku: matchedVariant.sku,
99
- price: matchedVariant.price,
100
- prices: matchedVariant.prices || item.prices,
101
- sale_price: matchedVariant.sale_price ?? null,
102
- sale_prices: matchedVariant.sale_prices || item.sale_prices,
103
- stock: matchedVariant.stock_quantity,
104
- image_url: matchedVariant.image_url || productImageUrl || item.image_url,
105
- variant_id: matchedVariant.id,
106
- variant_label: matchedVariant.label,
107
- selected_options: matchedVariant.selected_options,
108
- };
109
- }
110
-
111
- export function CartTranslator() {
112
- const { currentLocale, availableLanguages } = useLanguage();
113
- const { items, setItems } = useCartStore();
114
- const isHydrated = useIsCartHydrated();
115
- const prevLocaleRef = useRef<string | null>(null);
116
-
117
- useEffect(() => {
118
- async function translateCart() {
119
- if (!items || items.length === 0) {
120
- return;
121
- }
122
-
123
- const requestSignature = buildCartTranslationSignature(items);
124
-
125
- const translationGroupIds = items
126
- .map((item) => item.translation_group_id)
127
- .filter(Boolean) as string[];
128
- const skus = items
129
- .map((item) => item.sku)
130
- .filter(Boolean) as string[];
131
- const productIds = items
132
- .map((item) => item.product_id)
133
- .filter(Boolean) as string[];
134
-
135
- if (translationGroupIds.length === 0 && skus.length === 0 && productIds.length === 0) {
136
- return;
137
- }
138
-
139
- try {
140
- const translatedProducts = await getTranslatedProductsForCart(
141
- translationGroupIds,
142
- currentLocale,
143
- skus,
144
- productIds
145
- ) as any[];
146
-
147
- if (!translatedProducts || translatedProducts.length === 0) {
148
- return;
149
- }
150
-
151
- const latestItems = useCartStore.getState().items;
152
-
153
- if (buildCartTranslationSignature(latestItems) !== requestSignature) {
154
- return;
155
- }
156
-
157
- const newItems = latestItems.map((item) => {
158
- const translated = translatedProducts.find((product) =>
159
- (item.translation_group_id && product.translation_group_id === item.translation_group_id) ||
160
- product.id === item.product_id ||
161
- product.sku === item.sku
162
- );
163
-
164
- if (!translated) {
165
- return item;
166
- }
167
-
168
- return syncCartItem(item, translated, currentLocale);
169
- });
170
-
171
- const mergedItems = newItems.reduce((accumulator: CartItem[], current) => {
172
- const existingIndex = accumulator.findIndex((item) => item.id === current.id);
173
- if (existingIndex > -1) {
174
- accumulator[existingIndex] = {
175
- ...accumulator[existingIndex],
176
- quantity: accumulator[existingIndex].quantity + current.quantity,
177
- };
178
- return accumulator;
179
- }
180
-
181
- accumulator.push({ ...current });
182
- return accumulator;
183
- }, []);
184
-
185
- if (JSON.stringify(mergedItems) !== JSON.stringify(latestItems)) {
186
- setItems(mergedItems);
187
- }
188
- } catch (error) {
189
- console.error("[CartTranslator] Translation failed:", error);
190
- }
191
- }
192
-
193
- if (!isHydrated || availableLanguages.length === 0) {
194
- return;
195
- }
196
-
197
- const currentLanguage = availableLanguages.find((language) => language.code === currentLocale);
198
- const hasLocaleMismatch = items.some((item) => item.language_id !== currentLanguage?.id);
199
-
200
- if (currentLocale !== prevLocaleRef.current || hasLocaleMismatch) {
201
- const timeoutId = setTimeout(() => {
202
- translateCart();
203
- }, 300);
204
- prevLocaleRef.current = currentLocale;
205
- return () => clearTimeout(timeoutId);
206
- }
207
- }, [availableLanguages, currentLocale, isHydrated, items, setItems]);
208
-
209
- return null;
210
- }
1
+ 'use client';
2
+
3
+ import { useEffect, useRef } from 'react';
4
+ import { useLanguage } from '../context/LanguageContext';
5
+ import { useCartStore } from '@nextblock-cms/ecommerce/cart-store';
6
+ import { isDigitalItem, type CartItem } from '@nextblock-cms/ecommerce/types';
7
+ import { useIsCartHydrated } from '@nextblock-cms/ecommerce/use-cart';
8
+ import { mapRawVariantRelations } from '@nextblock-cms/ecommerce/variation-utils';
9
+ import { getTranslatedProductsForCart } from '@nextblock-cms/ecommerce/server-actions/product-actions';
10
+
11
+ const R2_BASE_URL = process.env.NEXT_PUBLIC_R2_BASE_URL || '';
12
+
13
+ function resolveMediaUrl(filePath?: string | null) {
14
+ if (!filePath) {
15
+ return null;
16
+ }
17
+
18
+ if (filePath.startsWith('http')) {
19
+ return filePath;
20
+ }
21
+
22
+ return R2_BASE_URL ? `${R2_BASE_URL}/${filePath}` : filePath;
23
+ }
24
+
25
+ function resolveProductImageUrl(product: any, fallback?: string | null) {
26
+ const firstMedia = product?.product_media?.[0]?.media;
27
+ return resolveMediaUrl(firstMedia?.file_path || firstMedia?.object_key) || fallback || null;
28
+ }
29
+
30
+ function buildCartTranslationSignature(items: CartItem[]) {
31
+ return JSON.stringify(
32
+ items.map((item) => ({
33
+ id: item.id,
34
+ product_id: item.product_id,
35
+ variant_id: item.variant_id ?? null,
36
+ sku: item.sku,
37
+ quantity: item.quantity,
38
+ language_id: item.language_id ?? null,
39
+ translation_group_id: item.translation_group_id ?? null,
40
+ }))
41
+ );
42
+ }
43
+
44
+ function syncCartItem(item: CartItem, translatedProduct: any, currentLocale: string): CartItem {
45
+ const productImageUrl = resolveProductImageUrl(translatedProduct, item.image_url);
46
+ const nextBase: CartItem = {
47
+ ...item,
48
+ id: translatedProduct.id,
49
+ product_id: translatedProduct.id,
50
+ title: translatedProduct.title,
51
+ slug: translatedProduct.slug,
52
+ sku: translatedProduct.sku || item.sku,
53
+ stock: typeof translatedProduct.stock === 'number' ? translatedProduct.stock : item.stock,
54
+ language_id: translatedProduct.language_id,
55
+ translation_group_id: translatedProduct.translation_group_id,
56
+ image_url: productImageUrl || item.image_url,
57
+ is_taxable: translatedProduct.is_taxable ?? item.is_taxable,
58
+ product_type: translatedProduct.product_type ?? item.product_type,
59
+ payment_provider: translatedProduct.payment_provider ?? item.payment_provider,
60
+ provider: translatedProduct.payment_provider ?? item.provider,
61
+ freemius_product_id: translatedProduct.freemius_product_id ?? item.freemius_product_id,
62
+ freemius_plan_id: translatedProduct.freemius_plan_id ?? item.freemius_plan_id,
63
+ trial_period_days: translatedProduct.trial_period_days ?? item.trial_period_days ?? 0,
64
+ trial_requires_payment_method:
65
+ translatedProduct.trial_requires_payment_method ??
66
+ item.trial_requires_payment_method ??
67
+ false,
68
+ prices: translatedProduct.prices || item.prices,
69
+ sale_prices: translatedProduct.sale_prices || item.sale_prices,
70
+ has_variants: Boolean(translatedProduct.product_variants?.length),
71
+ };
72
+
73
+ if (isDigitalItem(item)) {
74
+ return nextBase;
75
+ }
76
+
77
+ const isVariantLine = Boolean(item.variant_id || item.variant_label);
78
+ if (!isVariantLine) {
79
+ return {
80
+ ...nextBase,
81
+ price: translatedProduct.price,
82
+ prices: translatedProduct.prices || item.prices,
83
+ sale_price: translatedProduct.sale_price ?? null,
84
+ sale_prices: translatedProduct.sale_prices || item.sale_prices,
85
+ };
86
+ }
87
+
88
+ const { variants } = mapRawVariantRelations(translatedProduct.product_variants || [], currentLocale);
89
+ const matchedVariant = variants.find((variant) => variant.sku === item.sku);
90
+
91
+ if (!matchedVariant) {
92
+ return item;
93
+ }
94
+
95
+ return {
96
+ ...nextBase,
97
+ id: matchedVariant.id,
98
+ sku: matchedVariant.sku,
99
+ price: matchedVariant.price,
100
+ prices: matchedVariant.prices || item.prices,
101
+ sale_price: matchedVariant.sale_price ?? null,
102
+ sale_prices: matchedVariant.sale_prices || item.sale_prices,
103
+ stock: matchedVariant.stock_quantity,
104
+ image_url: matchedVariant.image_url || productImageUrl || item.image_url,
105
+ variant_id: matchedVariant.id,
106
+ variant_label: matchedVariant.label,
107
+ selected_options: matchedVariant.selected_options,
108
+ };
109
+ }
110
+
111
+ export function CartTranslator() {
112
+ const { currentLocale, availableLanguages } = useLanguage();
113
+ const { items, setItems } = useCartStore();
114
+ const isHydrated = useIsCartHydrated();
115
+ const prevLocaleRef = useRef<string | null>(null);
116
+
117
+ useEffect(() => {
118
+ async function translateCart() {
119
+ if (!items || items.length === 0) {
120
+ return;
121
+ }
122
+
123
+ const requestSignature = buildCartTranslationSignature(items);
124
+
125
+ const translationGroupIds = items
126
+ .map((item) => item.translation_group_id)
127
+ .filter(Boolean) as string[];
128
+ const skus = items
129
+ .map((item) => item.sku)
130
+ .filter(Boolean) as string[];
131
+ const productIds = items
132
+ .map((item) => item.product_id)
133
+ .filter(Boolean) as string[];
134
+
135
+ if (translationGroupIds.length === 0 && skus.length === 0 && productIds.length === 0) {
136
+ return;
137
+ }
138
+
139
+ try {
140
+ const translatedProducts = await getTranslatedProductsForCart(
141
+ translationGroupIds,
142
+ currentLocale,
143
+ skus,
144
+ productIds
145
+ ) as any[];
146
+
147
+ if (!translatedProducts || translatedProducts.length === 0) {
148
+ return;
149
+ }
150
+
151
+ const latestItems = useCartStore.getState().items;
152
+
153
+ if (buildCartTranslationSignature(latestItems) !== requestSignature) {
154
+ return;
155
+ }
156
+
157
+ const newItems = latestItems.map((item) => {
158
+ const translated = translatedProducts.find((product) =>
159
+ (item.translation_group_id && product.translation_group_id === item.translation_group_id) ||
160
+ product.id === item.product_id ||
161
+ product.sku === item.sku
162
+ );
163
+
164
+ if (!translated) {
165
+ return item;
166
+ }
167
+
168
+ return syncCartItem(item, translated, currentLocale);
169
+ });
170
+
171
+ const mergedItems = newItems.reduce((accumulator: CartItem[], current) => {
172
+ const existingIndex = accumulator.findIndex((item) => item.id === current.id);
173
+ if (existingIndex > -1) {
174
+ accumulator[existingIndex] = {
175
+ ...accumulator[existingIndex],
176
+ quantity: accumulator[existingIndex].quantity + current.quantity,
177
+ };
178
+ return accumulator;
179
+ }
180
+
181
+ accumulator.push({ ...current });
182
+ return accumulator;
183
+ }, []);
184
+
185
+ if (JSON.stringify(mergedItems) !== JSON.stringify(latestItems)) {
186
+ setItems(mergedItems);
187
+ }
188
+ } catch (error) {
189
+ console.error("[CartTranslator] Translation failed:", error);
190
+ }
191
+ }
192
+
193
+ if (!isHydrated || availableLanguages.length === 0) {
194
+ return;
195
+ }
196
+
197
+ const currentLanguage = availableLanguages.find((language) => language.code === currentLocale);
198
+ const hasLocaleMismatch = items.some((item) => item.language_id !== currentLanguage?.id);
199
+
200
+ if (currentLocale !== prevLocaleRef.current || hasLocaleMismatch) {
201
+ const timeoutId = setTimeout(() => {
202
+ translateCart();
203
+ }, 300);
204
+ prevLocaleRef.current = currentLocale;
205
+ return () => clearTimeout(timeoutId);
206
+ }
207
+ }, [availableLanguages, currentLocale, isHydrated, items, setItems]);
208
+
209
+ return null;
210
+ }