@revenexx/cover 0.1.0

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 (408) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -0
  3. package/app/api/account.ts +8 -0
  4. package/app/api/categories.ts +3 -0
  5. package/app/api/checkout.ts +6 -0
  6. package/app/api/images.ts +4 -0
  7. package/app/api/markets.ts +3 -0
  8. package/app/api/product.ts +3 -0
  9. package/app/api/products.ts +4 -0
  10. package/app/api/search.ts +4 -0
  11. package/app/app.config.ts +29 -0
  12. package/app/assets/css/cover.css +25 -0
  13. package/app/components/account/AccountMenu.vue +30 -0
  14. package/app/components/account/AccountShell.vue +18 -0
  15. package/app/components/account/AccountSidebar.vue +90 -0
  16. package/app/components/account/address/AccountAddressCard.vue +287 -0
  17. package/app/components/account/carts/AccountCartsTable.vue +284 -0
  18. package/app/components/account/dashboard/AccountDashboard.vue +351 -0
  19. package/app/components/account/directorder/AccountDirectOrder.vue +512 -0
  20. package/app/components/account/governance/AccountApprovalLimitsTable.vue +221 -0
  21. package/app/components/account/governance/AccountCostCenterDetail.vue +276 -0
  22. package/app/components/account/governance/AccountCostCentersTable.vue +252 -0
  23. package/app/components/account/governance/AccountRequisitionDetail.vue +295 -0
  24. package/app/components/account/governance/AccountRequisitionsTable.vue +255 -0
  25. package/app/components/account/governance/AccountWorkflowDetail.vue +215 -0
  26. package/app/components/account/governance/AccountWorkflowsTable.vue +168 -0
  27. package/app/components/account/governance/GovernanceApprovalLimitModal.vue +183 -0
  28. package/app/components/account/governance/GovernanceCostCenterModal.vue +188 -0
  29. package/app/components/account/governance/GovernanceWorkflowModal.vue +191 -0
  30. package/app/components/account/orderlists/AccountOrderListDetail.vue +349 -0
  31. package/app/components/account/orderlists/AccountOrderListsTable.vue +352 -0
  32. package/app/components/account/orders/AccountOrderDetail.vue +376 -0
  33. package/app/components/account/orders/AccountOrdersTable.vue +281 -0
  34. package/app/components/account/preferences/AccountPreferences.vue +50 -0
  35. package/app/components/auth/AuthForgotPasswordPanel.vue +88 -0
  36. package/app/components/auth/AuthLoginPanel.vue +103 -0
  37. package/app/components/auth/AuthLoginTeaser.vue +24 -0
  38. package/app/components/auth/AuthPageHeader.vue +21 -0
  39. package/app/components/auth/AuthRegisterPanel.vue +431 -0
  40. package/app/components/auth/AuthRegisterTeaser.vue +24 -0
  41. package/app/components/auth/AuthResetPasswordPanel.vue +115 -0
  42. package/app/components/cart/CartButton.vue +42 -0
  43. package/app/components/cart/actions/CartCostCenterModal.vue +110 -0
  44. package/app/components/cart/actions/CartExportModal.vue +82 -0
  45. package/app/components/cart/actions/CartPositionTextsModal.vue +92 -0
  46. package/app/components/cart/actions/CartSaveToListModal.vue +177 -0
  47. package/app/components/cart/actions/CartWorkflowPickerModal.vue +64 -0
  48. package/app/components/cart/drawer/CartDrawer.vue +49 -0
  49. package/app/components/cart/item/CartItem.vue +318 -0
  50. package/app/components/cart/manage/CartNameModal.vue +68 -0
  51. package/app/components/cart/position/CartPosition.vue +369 -0
  52. package/app/components/cart/quickadd/CartQuickAdd.vue +145 -0
  53. package/app/components/cart/recommend/CartCrossSell.vue +102 -0
  54. package/app/components/cart/recommend/CartRecommendCard.vue +59 -0
  55. package/app/components/cart/recommend/CartReorderStrip.vue +108 -0
  56. package/app/components/cart/requisition/CartRequisitionGroup.vue +74 -0
  57. package/app/components/cart/skeleton/CartSkeleton.vue +18 -0
  58. package/app/components/cart/summary/CartSummary.vue +25 -0
  59. package/app/components/cart/summary/CartSummaryBreakdown.vue +35 -0
  60. package/app/components/cart/switcher/CartSwitcher.vue +147 -0
  61. package/app/components/cart/target/CartTargetModal.vue +129 -0
  62. package/app/components/cart/view/CartApprovalsPanel.vue +89 -0
  63. package/app/components/cart/view/CartListHeader.vue +86 -0
  64. package/app/components/cart/view/CartPositionList.vue +94 -0
  65. package/app/components/cart/view/CartSummaryPanel.vue +254 -0
  66. package/app/components/cart/view/CartView.vue +310 -0
  67. package/app/components/category/info/CategoryPrice.vue +47 -0
  68. package/app/components/category/info/CategorySku.vue +15 -0
  69. package/app/components/category/info/CategoryStock.vue +25 -0
  70. package/app/components/category/list/CategoryItemCount.vue +27 -0
  71. package/app/components/category/list/CategoryListPagination.vue +81 -0
  72. package/app/components/category/skeleton/CategoryDetailSkeleton.vue +12 -0
  73. package/app/components/category/skeleton/CategoryListSkeleton.vue +65 -0
  74. package/app/components/category/view/CategoryViewToggle.vue +33 -0
  75. package/app/components/category/view/GridView.vue +78 -0
  76. package/app/components/category/view/ListView.vue +80 -0
  77. package/app/components/checkout/confirmation/CheckoutConfirmationView.vue +289 -0
  78. package/app/components/checkout/form/CheckoutSchemaForm.vue +72 -0
  79. package/app/components/checkout/onepage/CheckoutAddressPickerModal.vue +260 -0
  80. package/app/components/checkout/onepage/CheckoutAddressSection.vue +186 -0
  81. package/app/components/checkout/onepage/CheckoutDeliverySection.vue +158 -0
  82. package/app/components/checkout/onepage/CheckoutGate.vue +67 -0
  83. package/app/components/checkout/onepage/CheckoutGuestDetailsSection.vue +154 -0
  84. package/app/components/checkout/onepage/CheckoutNotesSection.vue +62 -0
  85. package/app/components/checkout/onepage/CheckoutOnePageView.vue +77 -0
  86. package/app/components/checkout/onepage/CheckoutPaymentSection.vue +181 -0
  87. package/app/components/checkout/onepage/CheckoutSectionTitle.vue +18 -0
  88. package/app/components/checkout/onepage/CheckoutSummarySection.vue +211 -0
  89. package/app/components/checkout/onepage/CheckoutWorkflowPanel.vue +38 -0
  90. package/app/components/layout/AppLogo.vue +21 -0
  91. package/app/components/layout/footer/Footer.vue +148 -0
  92. package/app/components/layout/header/AppBar.vue +75 -0
  93. package/app/components/layout/header/CategoryNav.vue +93 -0
  94. package/app/components/layout/header/HeaderActionItem.vue +81 -0
  95. package/app/components/layout/header/LocaleSwitcher.vue +123 -0
  96. package/app/components/layout/header/MainNav.vue +61 -0
  97. package/app/components/layout/header/MobileMenu.vue +81 -0
  98. package/app/components/layout/header/TopBar.vue +63 -0
  99. package/app/components/listing/ListingView.vue +227 -0
  100. package/app/components/product/detail/ProductDetailAddToCart.vue +188 -0
  101. package/app/components/product/detail/ProductDetailDescription.vue +20 -0
  102. package/app/components/product/detail/ProductDetailImageGallery.vue +58 -0
  103. package/app/components/product/detail/ProductDetailPricing.vue +108 -0
  104. package/app/components/product/detail/ProductDetailSaveToList.vue +291 -0
  105. package/app/components/product/detail/ProductDetailSpecifications.vue +33 -0
  106. package/app/components/product/detail/ProductDetailStock.vue +36 -0
  107. package/app/components/search/filter/SearchFacets.vue +256 -0
  108. package/app/components/search/filter/SearchFacetsDrawer.vue +58 -0
  109. package/app/components/ui/AppShell.vue +7 -0
  110. package/app/components/ui/debug/PiniaStateCard.vue +106 -0
  111. package/app/components/ui/feedback/EmptyState.vue +30 -0
  112. package/app/components/ui/feedback/ErrorBoundary.vue +44 -0
  113. package/app/components/ui/feedback/NotFound.vue +34 -0
  114. package/app/components/ui/forms/CountrySelection.vue +14 -0
  115. package/app/components/ui/forms/FormKitDatePicker.vue +124 -0
  116. package/app/components/ui/forms/PasswordInput.vue +136 -0
  117. package/app/components/ui/forms/SearchInput.vue +213 -0
  118. package/app/components/ui/table/TableSortButton.vue +51 -0
  119. package/app/components/ui/table/TableToolbar.vue +66 -0
  120. package/app/composables/useAccount.ts +27 -0
  121. package/app/composables/useAccountAddresses.ts +116 -0
  122. package/app/composables/useAccountOrders.ts +18 -0
  123. package/app/composables/useAppUrl.ts +38 -0
  124. package/app/composables/useAuthStore.ts +202 -0
  125. package/app/composables/useB2BContext.ts +23 -0
  126. package/app/composables/useCartActions.ts +25 -0
  127. package/app/composables/useCartCalculation.ts +90 -0
  128. package/app/composables/useCartSelection.ts +46 -0
  129. package/app/composables/useCartStore.ts +837 -0
  130. package/app/composables/useCartSummaryFormatting.ts +28 -0
  131. package/app/composables/useCategories.ts +29 -0
  132. package/app/composables/useCheckoutOnePage.ts +438 -0
  133. package/app/composables/useCheckoutProfileSource.ts +8 -0
  134. package/app/composables/useCheckoutSchema.ts +28 -0
  135. package/app/composables/useDataTable.ts +14 -0
  136. package/app/composables/useFormValidation.ts +29 -0
  137. package/app/composables/useIcons.ts +17 -0
  138. package/app/composables/useLocalePreferences.ts +45 -0
  139. package/app/composables/useMarkets.ts +15 -0
  140. package/app/composables/useProduct.ts +89 -0
  141. package/app/composables/useProductCard.ts +8 -0
  142. package/app/composables/useProductCardActions.ts +106 -0
  143. package/app/composables/useProductImage.ts +12 -0
  144. package/app/composables/useProductListing.ts +210 -0
  145. package/app/composables/useProductUrl.ts +17 -0
  146. package/app/composables/useProducts.ts +130 -0
  147. package/app/composables/useSearch.ts +12 -0
  148. package/app/composables/useThemeStore.ts +81 -0
  149. package/app/composables/useViewMode.ts +18 -0
  150. package/app/config/countries.ts +74 -0
  151. package/app/config/icons.ts +283 -0
  152. package/app/config/navigation.ts +191 -0
  153. package/app/config/palette.ts +13 -0
  154. package/app/config/themes.ts +20 -0
  155. package/app/formkit.config.ts +50 -0
  156. package/app/interfaces/account/address-list.ts +34 -0
  157. package/app/interfaces/account/order-list.ts +47 -0
  158. package/app/interfaces/account/order-lists.ts +35 -0
  159. package/app/interfaces/account/profile.ts +17 -0
  160. package/app/interfaces/account.ts +3 -0
  161. package/app/interfaces/address.ts +9 -0
  162. package/app/interfaces/auth.ts +104 -0
  163. package/app/interfaces/b2b.ts +234 -0
  164. package/app/interfaces/cart-calculation.ts +131 -0
  165. package/app/interfaces/cart-item.ts +45 -0
  166. package/app/interfaces/checkout-draft.ts +4 -0
  167. package/app/interfaces/checkout.ts +43 -0
  168. package/app/interfaces/delivery.ts +13 -0
  169. package/app/interfaces/market.ts +24 -0
  170. package/app/interfaces/payment.ts +19 -0
  171. package/app/interfaces/persisted-cart.ts +10 -0
  172. package/app/interfaces/product-detail.ts +54 -0
  173. package/app/interfaces/product-list.ts +33 -0
  174. package/app/interfaces/search-facets.ts +14 -0
  175. package/app/interfaces/validation.ts +14 -0
  176. package/app/layouts/default.vue +78 -0
  177. package/app/layouts/focus.vue +80 -0
  178. package/app/plugins/formkit-locale.client.ts +23 -0
  179. package/app/shared/constants.ts +1 -0
  180. package/app/validations/companyName.ts +18 -0
  181. package/app/validations/emailFormat.ts +10 -0
  182. package/app/validations/formValidationConfig.ts +50 -0
  183. package/app/validations/maxLength.ts +12 -0
  184. package/app/validations/minLength.ts +9 -0
  185. package/app/validations/optionalCompanyName.ts +23 -0
  186. package/app/validations/passwordsMatch.ts +5 -0
  187. package/app/validations/phoneNumber.ts +19 -0
  188. package/app/validations/required.ts +11 -0
  189. package/app/validations/termsRequired.ts +4 -0
  190. package/app/validations/types.ts +10 -0
  191. package/app/validations/zipCode.ts +15 -0
  192. package/i18n/locales/de/account.json +458 -0
  193. package/i18n/locales/de/auth.json +155 -0
  194. package/i18n/locales/de/cart.json +263 -0
  195. package/i18n/locales/de/checkout.json +217 -0
  196. package/i18n/locales/de/common.json +80 -0
  197. package/i18n/locales/de/cover.json +33 -0
  198. package/i18n/locales/de/order.json +1 -0
  199. package/i18n/locales/de/product.json +71 -0
  200. package/i18n/locales/de/search.json +54 -0
  201. package/i18n/locales/de/validation.json +12 -0
  202. package/i18n/locales/en/account.json +458 -0
  203. package/i18n/locales/en/auth.json +155 -0
  204. package/i18n/locales/en/cart.json +263 -0
  205. package/i18n/locales/en/checkout.json +217 -0
  206. package/i18n/locales/en/common.json +80 -0
  207. package/i18n/locales/en/cover.json +33 -0
  208. package/i18n/locales/en/order.json +1 -0
  209. package/i18n/locales/en/product.json +71 -0
  210. package/i18n/locales/en/search.json +54 -0
  211. package/i18n/locales/en/validation.json +12 -0
  212. package/nuxt.config.ts +109 -0
  213. package/package.json +65 -0
  214. package/public/img/product-placeholder.svg +8 -0
  215. package/public/templates/direct-order-template.csv +3 -0
  216. package/public/templates/direct-order-template.xlsx +0 -0
  217. package/server/api/account/address/[id].delete.ts +51 -0
  218. package/server/api/account/address/[id].put.ts +83 -0
  219. package/server/api/account/address/index.post.ts +60 -0
  220. package/server/api/account/addresses.get.ts +25 -0
  221. package/server/api/account/context.get.ts +16 -0
  222. package/server/api/account/governance/[domain]/[id].put.ts +57 -0
  223. package/server/api/account/governance/[domain].post.ts +111 -0
  224. package/server/api/account/order-lists/[id].delete.ts +22 -0
  225. package/server/api/account/order-lists/[id].get.ts +17 -0
  226. package/server/api/account/order-lists/[id].put.ts +46 -0
  227. package/server/api/account/order-lists/index.get.ts +14 -0
  228. package/server/api/account/order-lists/index.post.ts +38 -0
  229. package/server/api/account/orders/[id].get.ts +35 -0
  230. package/server/api/account/orders.get.ts +35 -0
  231. package/server/api/account/profile.get.ts +56 -0
  232. package/server/api/account/profile.put.ts +128 -0
  233. package/server/api/account/requisitions/[id].get.ts +15 -0
  234. package/server/api/account/requisitions.get.ts +23 -0
  235. package/server/api/auth/login.post.ts +37 -0
  236. package/server/api/auth/logout.post.ts +4 -0
  237. package/server/api/auth/me.get.ts +3 -0
  238. package/server/api/auth/personas.get.ts +7 -0
  239. package/server/api/auth/recovery.post.ts +32 -0
  240. package/server/api/auth/recovery.put.ts +37 -0
  241. package/server/api/auth/register.post.ts +126 -0
  242. package/server/api/cart/calculate.post.ts +25 -0
  243. package/server/api/cart/export.post.ts +81 -0
  244. package/server/api/cart/index.delete.ts +10 -0
  245. package/server/api/cart/index.get.ts +10 -0
  246. package/server/api/cart/sync.post.ts +14 -0
  247. package/server/api/carts/[id]/activate.post.ts +18 -0
  248. package/server/api/carts/[id]/index.delete.ts +18 -0
  249. package/server/api/carts/[id]/index.put.ts +19 -0
  250. package/server/api/carts/[id]/items.post.ts +21 -0
  251. package/server/api/carts/index.get.ts +14 -0
  252. package/server/api/carts/index.post.ts +14 -0
  253. package/server/api/categories/[slug].get.ts +22 -0
  254. package/server/api/categories/index.get.ts +11 -0
  255. package/server/api/checkout/profile.get.ts +21 -0
  256. package/server/api/checkout/schema/[key].get.ts +19 -0
  257. package/server/api/checkout/session.post.ts +8 -0
  258. package/server/api/images/detail/[filename].get.ts +18 -0
  259. package/server/api/images/list/[filename].get.ts +17 -0
  260. package/server/api/markets.get.ts +9 -0
  261. package/server/api/orders/index.post.ts +376 -0
  262. package/server/api/payment/methods.post.ts +65 -0
  263. package/server/api/product/[id].get.ts +29 -0
  264. package/server/api/products/[id].get.ts +30 -0
  265. package/server/api/products/index.get.ts +21 -0
  266. package/server/api/requisitions/index.post.ts +67 -0
  267. package/server/api/shipping/rates.post.ts +70 -0
  268. package/server/api/themes/index.get.ts +9 -0
  269. package/server/api/typesense/drop.get.ts +22 -0
  270. package/server/api/typesense/health.get.ts +9 -0
  271. package/server/api/typesense/search.post.ts +199 -0
  272. package/server/api/typesense/seed.get.ts +112 -0
  273. package/server/api/typesense/suggest.post.ts +69 -0
  274. package/server/config/account/organization.json +146 -0
  275. package/server/config/account/personas.json +169 -0
  276. package/server/config/account/user.json +8 -0
  277. package/server/config/account/workflows.json +19 -0
  278. package/server/data/account/address-list.json +103 -0
  279. package/server/data/account/order-list.json +491 -0
  280. package/server/data/account/order-lists.json +149 -0
  281. package/server/data/account/profile.json +9 -0
  282. package/server/data/account/registration-requests.json +3 -0
  283. package/server/data/account/requisitions.json +686 -0
  284. package/server/data/categories.json +186 -0
  285. package/server/data/forms/checkout/add-address.json +24 -0
  286. package/server/data/list/5137-1.png +0 -0
  287. package/server/data/list/5498-1.png +0 -0
  288. package/server/data/list/5498-2.png +0 -0
  289. package/server/data/list/5498-3.png +0 -0
  290. package/server/data/list/5498-4.png +0 -0
  291. package/server/data/list/5498-5.png +0 -0
  292. package/server/data/list/5498-6.png +0 -0
  293. package/server/data/list/5519-1.png +0 -0
  294. package/server/data/list/5713-1.png +0 -0
  295. package/server/data/list/5789-1.png +0 -0
  296. package/server/data/list/5930-1.png +0 -0
  297. package/server/data/list/6127-1.png +0 -0
  298. package/server/data/list/6234-1.png +0 -0
  299. package/server/data/list/6238-1.png +0 -0
  300. package/server/data/list/6246-1.png +0 -0
  301. package/server/data/list/6270-1.png +0 -0
  302. package/server/data/list/6330-1.png +0 -0
  303. package/server/data/list/6336-1.png +0 -0
  304. package/server/data/list/6360-1.png +0 -0
  305. package/server/data/list/6363-1.png +0 -0
  306. package/server/data/list/6375-1.png +0 -0
  307. package/server/data/list/6385-1.png +0 -0
  308. package/server/data/list/6413-1.png +0 -0
  309. package/server/data/list/6418-1.png +0 -0
  310. package/server/data/list/6465-1.png +0 -0
  311. package/server/data/list/6477-1.png +0 -0
  312. package/server/data/list/6509-1.png +0 -0
  313. package/server/data/list/6545-1.png +0 -0
  314. package/server/data/list/6548-1.png +0 -0
  315. package/server/data/list/6566-1.png +0 -0
  316. package/server/data/list/6581-1.png +0 -0
  317. package/server/data/list/6609-1.png +0 -0
  318. package/server/data/list/6611-1.png +0 -0
  319. package/server/data/list/6641-1.png +0 -0
  320. package/server/data/list/6659-1.png +0 -0
  321. package/server/data/list/6662-1.png +0 -0
  322. package/server/data/list/6689-1.png +0 -0
  323. package/server/data/list/6698-1.png +0 -0
  324. package/server/data/list/6701-1.png +0 -0
  325. package/server/data/list/6752-1.png +0 -0
  326. package/server/data/list/6755-1.png +0 -0
  327. package/server/data/list/6837-1.png +0 -0
  328. package/server/data/list/6841-1.png +0 -0
  329. package/server/data/list/6844-1.png +0 -0
  330. package/server/data/list/6846-1.png +0 -0
  331. package/server/data/list/6886-1.png +0 -0
  332. package/server/data/list/6895-1.png +0 -0
  333. package/server/data/list/6897-1.png +0 -0
  334. package/server/data/list/6919-1.png +0 -0
  335. package/server/data/list/6977-1.png +0 -0
  336. package/server/data/list/6983-1.png +0 -0
  337. package/server/data/list/6984-1.png +0 -0
  338. package/server/data/list/6985-1.png +0 -0
  339. package/server/data/list/6986-1.png +0 -0
  340. package/server/data/list/6989-1.png +0 -0
  341. package/server/data/list/6995-1.png +0 -0
  342. package/server/data/list/6998-1.png +0 -0
  343. package/server/data/markets.json +24 -0
  344. package/server/data/product-detail.json +2450 -0
  345. package/server/data/product-list.json +2450 -0
  346. package/server/data/themes.json +8 -0
  347. package/server/interfaces/account.ts +20 -0
  348. package/server/interfaces/auth.ts +32 -0
  349. package/server/interfaces/b2bContext.ts +23 -0
  350. package/server/interfaces/cart.ts +46 -0
  351. package/server/interfaces/cartCalculation.ts +17 -0
  352. package/server/interfaces/category.ts +12 -0
  353. package/server/interfaces/checkoutProfile.ts +5 -0
  354. package/server/interfaces/log.ts +3 -0
  355. package/server/interfaces/market.ts +10 -0
  356. package/server/interfaces/product.ts +21 -0
  357. package/server/interfaces/schema.ts +20 -0
  358. package/server/interfaces/theme.ts +10 -0
  359. package/server/services/ApiAuthService.ts +138 -0
  360. package/server/services/ApiCartService.ts +254 -0
  361. package/server/services/ApiCategoryService.ts +25 -0
  362. package/server/services/ApiMarketService.ts +71 -0
  363. package/server/services/ApiProductService.ts +53 -0
  364. package/server/services/LocalFileCategoryService.ts +23 -0
  365. package/server/services/LocalFileCheckoutProfileService.ts +117 -0
  366. package/server/services/LocalFileProductService.ts +128 -0
  367. package/server/services/LocalFileSchemaService.ts +70 -0
  368. package/server/services/LocalFileThemeService.ts +18 -0
  369. package/server/services/LogService.ts +28 -0
  370. package/server/services/MockAccountService.ts +58 -0
  371. package/server/services/MockAuthService.ts +105 -0
  372. package/server/services/MockB2BContextService.ts +149 -0
  373. package/server/services/MockCartCalculationService.ts +395 -0
  374. package/server/services/MockMarketService.ts +18 -0
  375. package/server/services/SdkAccountService.ts +56 -0
  376. package/server/services/SdkAuthService.ts +83 -0
  377. package/server/services/SessionCartService.ts +31 -0
  378. package/server/utils/accountService.ts +30 -0
  379. package/server/utils/authCookie.ts +13 -0
  380. package/server/utils/authService.ts +43 -0
  381. package/server/utils/b2bService.ts +49 -0
  382. package/server/utils/cartService.ts +59 -0
  383. package/server/utils/categoryService.ts +19 -0
  384. package/server/utils/checkoutProfileService.ts +9 -0
  385. package/server/utils/checkoutSession.ts +38 -0
  386. package/server/utils/coverData.ts +84 -0
  387. package/server/utils/governanceStore.ts +38 -0
  388. package/server/utils/i18n.ts +76 -0
  389. package/server/utils/inventoryService.ts +14 -0
  390. package/server/utils/liveCatalog.ts +234 -0
  391. package/server/utils/liveInventories.ts +76 -0
  392. package/server/utils/liveOrders.ts +139 -0
  393. package/server/utils/livePrices.ts +93 -0
  394. package/server/utils/locale.ts +39 -0
  395. package/server/utils/logService.ts +19 -0
  396. package/server/utils/marketService.ts +24 -0
  397. package/server/utils/orderService.ts +14 -0
  398. package/server/utils/paymentService.ts +14 -0
  399. package/server/utils/priceService.ts +14 -0
  400. package/server/utils/productService.ts +28 -0
  401. package/server/utils/productsSchema.ts +30 -0
  402. package/server/utils/revenexxApi.ts +136 -0
  403. package/server/utils/schemaService.ts +25 -0
  404. package/server/utils/serviceMode.ts +70 -0
  405. package/server/utils/shippingService.ts +14 -0
  406. package/server/utils/shopSdk.ts +88 -0
  407. package/server/utils/themeService.ts +16 -0
  408. package/server/utils/typesense.ts +27 -0
@@ -0,0 +1,149 @@
1
+ import type { B2BContext, ApprovalLimit, ApprovalWorkflow, CostCenter, OrganizationSettings, Requisition } from "../../app/interfaces/b2b";
2
+ import type { AuthRole } from "../../app/interfaces/auth";
3
+
4
+ import type { IB2BContextService, IRequisitionService } from "../interfaces/b2bContext";
5
+
6
+ interface OrganizationConfig {
7
+ name: string;
8
+ settings: OrganizationSettings;
9
+ costCenters: CostCenter[];
10
+ }
11
+
12
+ interface PersonaConfig {
13
+ id: string;
14
+ name: string;
15
+ email: string;
16
+ role: AuthRole;
17
+ costCenterIds?: string[];
18
+ defaultCostCenterId?: string | null;
19
+ workflowIds?: string[];
20
+ approvalLimits?: ApprovalLimit[];
21
+ }
22
+
23
+ const GUEST_ROLE = null;
24
+
25
+ /**
26
+ * Builds the B2B context from the bundled demo organization, personas and
27
+ * workflows. An override for the organization settings can be injected per
28
+ * request (demo shop dev panel) via the constructor.
29
+ */
30
+ export class MockB2BContextService implements IB2BContextService {
31
+ constructor(private readonly settingsOverride?: Partial<OrganizationSettings>) {}
32
+
33
+ async getContext(userId: string | null): Promise<B2BContext> {
34
+ const org = await readCoverConfigJson<OrganizationConfig>("account/organization.json");
35
+ const settings = this.mergeSettings(org.settings);
36
+
37
+ if (!userId) {
38
+ return {
39
+ role: GUEST_ROLE,
40
+ settings,
41
+ costCenters: [],
42
+ defaultCostCenterId: null,
43
+ approvalLimits: [],
44
+ workflows: [],
45
+ };
46
+ }
47
+
48
+ const personas = await readCoverConfigJson<PersonaConfig[]>("account/personas.json");
49
+ const persona = personas.find(p => p.id === userId);
50
+ const overrides = await readGovernanceOverrides();
51
+
52
+ const baseWorkflows = await readCoverConfigJson<ApprovalWorkflow[]>("account/workflows.json");
53
+ const allWorkflows = [
54
+ ...baseWorkflows.map(wf => ({ ...wf, ...overrides.workflowPatches[wf.id] })),
55
+ ...overrides.workflows,
56
+ ];
57
+
58
+ // Account-created cost centers belong to the whole organization;
59
+ // bundled ones follow the persona assignment. Patches overlay both.
60
+ const costCenters = settings.costCenters.enabled
61
+ ? [
62
+ ...org.costCenters.filter(cc => (persona?.costCenterIds ?? []).includes(cc.id)),
63
+ ...overrides.costCenters,
64
+ ].map(cc => ({ ...cc, ...overrides.costCenterPatches[cc.id] }))
65
+ : [];
66
+
67
+ // Approval limits only apply when actually ordering — requesters never
68
+ // order directly, so their context carries none (limits are not
69
+ // checked for requisitions).
70
+ const limitsApply = settings.approvals.enabled && persona?.role !== "requester";
71
+
72
+ const costCenterLimits = limitsApply
73
+ ? costCenters.flatMap(cc => (cc.limit ? [cc.limit] : []))
74
+ : [];
75
+
76
+ const personaLimits = limitsApply
77
+ ? [
78
+ ...(persona?.approvalLimits ?? []),
79
+ ...overrides.approvalLimits.filter(l =>
80
+ l.subject.type === "user" && l.subject.userId === userId,
81
+ ),
82
+ ]
83
+ : [];
84
+
85
+ return {
86
+ role: persona?.role ?? GUEST_ROLE,
87
+ settings,
88
+ costCenters,
89
+ defaultCostCenterId: settings.costCenters.enabled
90
+ ? (persona?.defaultCostCenterId ?? null)
91
+ : null,
92
+ approvalLimits: [...personaLimits, ...costCenterLimits].filter(l => l.active),
93
+ // Membership: listed in the persona config OR participating in a
94
+ // workflow step (account-created workflows reference users directly).
95
+ workflows: settings.workflows.enabled
96
+ ? allWorkflows.filter(wf =>
97
+ (persona?.workflowIds ?? []).includes(wf.id)
98
+ || wf.steps.some(step => step.users.some(u => u.id === userId)),
99
+ )
100
+ : [],
101
+ };
102
+ }
103
+
104
+ private mergeSettings(base: OrganizationSettings): OrganizationSettings {
105
+ const o = this.settingsOverride;
106
+ if (!o) {
107
+ return base;
108
+ }
109
+ return {
110
+ costCenters: { ...base.costCenters, ...o.costCenters },
111
+ approvals: { ...base.approvals, ...o.approvals },
112
+ workflows: { ...base.workflows, ...o.workflows },
113
+ cart: { ...base.cart, ...o.cart },
114
+ checkout: {
115
+ ...base.checkout,
116
+ ...o.checkout,
117
+ guestOrdering: { ...base.checkout.guestOrdering, ...o.checkout?.guestOrdering },
118
+ },
119
+ };
120
+ }
121
+ }
122
+
123
+ /** Serves the bundled demo requisitions, filtered by workflow membership. */
124
+ export class MockRequisitionService implements IRequisitionService {
125
+ async list(userId: string): Promise<Requisition[]> {
126
+ const personas = await readCoverConfigJson<PersonaConfig[]>("account/personas.json");
127
+ const persona = personas.find(p => p.id === userId);
128
+ if (!persona) {
129
+ return [];
130
+ }
131
+ const requisitions = await readCoverMutableJson<Requisition[]>("account/requisitions.json");
132
+ const overrides = await readGovernanceOverrides();
133
+ const memberOf = new Set([
134
+ ...(persona.workflowIds ?? []),
135
+ ...overrides.workflows
136
+ .filter(wf => wf.steps.some(step => step.users.some(u => u.id === userId)))
137
+ .map(wf => wf.id),
138
+ ]);
139
+ // Orderers see requisitions of workflows they belong to; requesters
140
+ // see their own.
141
+ return requisitions.filter(r =>
142
+ memberOf.has(r.workflowId) || r.requestedBy.id === userId,
143
+ );
144
+ }
145
+
146
+ async listOpen(userId: string): Promise<Requisition[]> {
147
+ return (await this.list(userId)).filter(r => r.status === "open");
148
+ }
149
+ }
@@ -0,0 +1,395 @@
1
+ import type { ApprovalLimitEvaluation, B2BContext } from "../../app/interfaces/b2b";
2
+ import type {
3
+ CalculatedCartLine,
4
+ CartCalculation,
5
+ CartCalculationRequest,
6
+ CartLineHint,
7
+ CartMessage,
8
+ CartTotalRow,
9
+ PriceLabelCode,
10
+ } from "../../app/interfaces/cart-calculation";
11
+ import type { CartItem } from "../../app/interfaces/cart-item";
12
+
13
+ import type { ICartCalculationService } from "../interfaces/cartCalculation";
14
+
15
+ /* ------------------------------------------------------------------ */
16
+ /* Demo rule tables — stand-ins for ERP master data */
17
+ /* ------------------------------------------------------------------ */
18
+
19
+ /** Minimum quantities / packaging steps per product. */
20
+ const QUANTITY_RULES: Record<string, { minQuantity: number; step: number }> = {
21
+ 6919: { minQuantity: 5, step: 5 }, // Nokia G42 — bulk-only demo article
22
+ };
23
+
24
+ /** Price labels overriding the price display (G/E/A semantics). */
25
+ const PRICE_LABELS: Record<string, PriceLabelCode> = {
26
+ 6841: "on-request", // Sony Xperia 1 VI — price on request
27
+ };
28
+
29
+ /** Items outside the negotiated catalog (trigger non-catalog limits). */
30
+ const NON_CATALOG_IDS = new Set<string>(["6984"]); // Dell XPS 15
31
+
32
+ /** Per-unit recycling fee demo (battery-powered devices). */
33
+ const SURCHARGE_SKU_PREFIXES = ["IP-", "RX-"];
34
+ const SURCHARGE_PER_UNIT = 2.5;
35
+
36
+ const DISCOUNT_THRESHOLD = 1000;
37
+ const DISCOUNT_RATE = 0.03;
38
+
39
+ // Free shipping from FREE_SHIPPING_THRESHOLD net (the topbar promise);
40
+ // below it standard delivery costs a flat fee. Express always costs.
41
+ const FREE_SHIPPING_THRESHOLD = 100;
42
+ const STANDARD_SHIPPING_FEE = 6.9;
43
+ const EXPRESS_SHIPPING_FEE = 9.9;
44
+
45
+ const DEFAULT_TAX_RATE = 19;
46
+
47
+ interface ProductListEntry {
48
+ product: { id: number }[];
49
+ stocks?: { quantity?: number }[];
50
+ }
51
+
52
+ const r2 = (n: number): number => Math.round(n * 100) / 100;
53
+
54
+ const normalizeRate = (rate: number | undefined): number => {
55
+ if (typeof rate !== "number" || !Number.isFinite(rate) || rate < 0) {
56
+ return DEFAULT_TAX_RATE;
57
+ }
58
+ // Round to two decimals — 0.07 * 100 must read as 7, not 7.000000000000001.
59
+ const percent = rate <= 1 ? rate * 100 : rate;
60
+ return Math.round(percent * 100) / 100;
61
+ };
62
+
63
+ const tieredUnitPrice = (item: CartItem, quantity: number): number => {
64
+ if (!item.priceTiers?.length) {
65
+ return item.price;
66
+ }
67
+ let effective = item.price;
68
+ for (const tier of item.priceTiers) {
69
+ if (quantity >= tier.minQuantity) {
70
+ effective = tier.unitPrice;
71
+ }
72
+ else {
73
+ break;
74
+ }
75
+ }
76
+ return effective;
77
+ };
78
+
79
+ /**
80
+ * Computes the full cart picture from the bundled demo data — the
81
+ * "non-ERP tenant" calculation path. ERP-backed implementations replace
82
+ * this wholesale and map their document response into the same shape.
83
+ */
84
+ export class MockCartCalculationService implements ICartCalculationService {
85
+ async calculate(
86
+ request: CartCalculationRequest,
87
+ context: B2BContext,
88
+ locale: string,
89
+ ): Promise<CartCalculation> {
90
+ await preloadLocaleNamespaces(locale, ["cart"]);
91
+ const t = (key: string, params?: Record<string, string | number>) =>
92
+ serverT(key, locale, "cart", params);
93
+
94
+ const stockById = await this.loadStock();
95
+ const { settings, role } = context;
96
+
97
+ const skuCounts = new Map<string, number>();
98
+ for (const item of request.items) {
99
+ skuCounts.set(item.sku ?? "", (skuCounts.get(item.sku ?? "") ?? 0) + 1);
100
+ }
101
+
102
+ /* ---- lines ---------------------------------------------------- */
103
+ const lines: CalculatedCartLine[] = request.items.map((item) => {
104
+ const hints: CartLineHint[] = [];
105
+
106
+ const rule = QUANTITY_RULES[item.id];
107
+ let quantity = item.quantity;
108
+ if (rule) {
109
+ const stepped = Math.max(
110
+ rule.minQuantity,
111
+ Math.ceil(quantity / rule.step) * rule.step,
112
+ );
113
+ if (stepped !== quantity) {
114
+ quantity = stepped;
115
+ hints.push({
116
+ type: "quantity-adjusted",
117
+ messageKey: "calc.hints.quantityAdjusted",
118
+ params: { quantity, min: rule.minQuantity, step: rule.step },
119
+ });
120
+ }
121
+ }
122
+
123
+ const labelCode = PRICE_LABELS[item.id];
124
+ const unitPrice = labelCode ? 0 : tieredUnitPrice(item, quantity);
125
+ const lineTotal = r2(unitPrice * quantity);
126
+
127
+ const surcharges = !labelCode && SURCHARGE_SKU_PREFIXES.some(p => (item.sku ?? "").startsWith(p))
128
+ ? [{ name: t("calc.recyclingFee"), amountPerUnit: SURCHARGE_PER_UNIT }]
129
+ : [];
130
+
131
+ const stock = stockById.get(item.id) ?? 0;
132
+ const availability = stock <= 0
133
+ ? { state: "out-of-stock" as const, availableQuantity: 0, deliveryDays: 14 }
134
+ : stock < quantity
135
+ ? { state: "partial" as const, availableQuantity: stock, deliveryDays: 7 }
136
+ : { state: "in-stock" as const, availableQuantity: stock, deliveryDays: 2 };
137
+
138
+ if ((skuCounts.get(item.sku ?? "") ?? 0) > 1) {
139
+ hints.push({ type: "duplicate", messageKey: "calc.hints.duplicate" });
140
+ }
141
+
142
+ if (settings.costCenters.enabled && settings.costCenters.required && !item.costCenterId) {
143
+ hints.push({ type: "missing-cost-center", messageKey: "calc.hints.missingCostCenter" });
144
+ }
145
+
146
+ return {
147
+ id: item.id,
148
+ quantity: item.quantity,
149
+ ...(quantity !== item.quantity ? { adjustedQuantity: quantity } : {}),
150
+ unitPrice,
151
+ lineTotal,
152
+ ...(labelCode
153
+ ? { priceLabel: { code: labelCode, label: t(`calc.priceLabels.${labelCode}`) } }
154
+ : {}),
155
+ surcharges,
156
+ availability,
157
+ hints,
158
+ };
159
+ });
160
+
161
+ const effectiveQty = (line: CalculatedCartLine) => line.adjustedQuantity ?? line.quantity;
162
+
163
+ /* ---- totals (spec order) -------------------------------------- */
164
+ const totals: CartTotalRow[] = [];
165
+
166
+ const subtotal = r2(lines.reduce((sum, l) => sum + l.lineTotal, 0));
167
+ totals.push({ key: "subtotal", label: t("calc.totals.subtotal"), amount: subtotal, alwaysVisible: true });
168
+
169
+ const surchargeSum = r2(lines.reduce(
170
+ (sum, l) => sum + l.surcharges.reduce((s, c) => s + c.amountPerUnit * effectiveQty(l), 0),
171
+ 0,
172
+ ));
173
+ if (surchargeSum > 0) {
174
+ totals.push({ key: "surcharge", label: t("calc.totals.surcharges"), amount: surchargeSum });
175
+ }
176
+
177
+ const minOrderValue = settings.cart.minOrderValue;
178
+ const minOrderCharge = minOrderValue > 0 && subtotal > 0 && subtotal < minOrderValue
179
+ ? r2(minOrderValue - subtotal)
180
+ : 0;
181
+ if (minOrderCharge > 0) {
182
+ totals.push({
183
+ key: "min-order-value",
184
+ label: t("calc.totals.minOrderValue"),
185
+ amount: minOrderCharge,
186
+ meta: { threshold: minOrderValue, missing: minOrderCharge },
187
+ });
188
+ }
189
+
190
+ const discount = subtotal >= DISCOUNT_THRESHOLD
191
+ ? r2(subtotal * DISCOUNT_RATE)
192
+ : 0;
193
+ if (discount > 0) {
194
+ totals.push({
195
+ key: "discount",
196
+ label: t("calc.totals.discount", { percent: DISCOUNT_RATE * 100 }),
197
+ amount: -discount,
198
+ });
199
+ }
200
+
201
+ const shipping = (request.deliveryMethod ?? "standard") === "express"
202
+ ? EXPRESS_SHIPPING_FEE
203
+ : subtotal >= FREE_SHIPPING_THRESHOLD ? 0 : STANDARD_SHIPPING_FEE;
204
+ totals.push({ key: "shipping", label: t("calc.totals.shipping"), amount: shipping, alwaysVisible: true });
205
+
206
+ // Taxable base per rate: line totals + line surcharges, discount applied
207
+ // proportionally; document-level charges taxed at the default rate.
208
+ const discountFactor = subtotal > 0 ? 1 - discount / subtotal : 1;
209
+ const basePerRate = new Map<number, number>();
210
+ for (let i = 0; i < lines.length; i++) {
211
+ const line = lines[i]!;
212
+ const item = request.items[i]!;
213
+ if (line.priceLabel) {
214
+ continue;
215
+ }
216
+ const rate = normalizeRate(item.taxRate);
217
+ const lineSurcharge = line.surcharges.reduce((s, c) => s + c.amountPerUnit * effectiveQty(line), 0);
218
+ const base = (line.lineTotal * discountFactor) + lineSurcharge;
219
+ basePerRate.set(rate, (basePerRate.get(rate) ?? 0) + base);
220
+ }
221
+ const docCharges = minOrderCharge + shipping;
222
+ if (docCharges > 0) {
223
+ basePerRate.set(DEFAULT_TAX_RATE, (basePerRate.get(DEFAULT_TAX_RATE) ?? 0) + docCharges);
224
+ }
225
+
226
+ let taxSum = 0;
227
+ for (const [rate, base] of [...basePerRate.entries()].sort((a, b) => a[0] - b[0])) {
228
+ const amount = r2(base * (rate / 100));
229
+ if (amount <= 0 && rate <= 0) {
230
+ continue;
231
+ }
232
+ taxSum += amount;
233
+ totals.push({
234
+ key: "tax",
235
+ label: t("calc.totals.tax", { percent: rate }),
236
+ amount,
237
+ meta: { rate },
238
+ });
239
+ }
240
+
241
+ const netTotal = r2(subtotal + surchargeSum + minOrderCharge - discount + shipping);
242
+ const total = r2(netTotal + taxSum);
243
+ totals.push({ key: "total", label: t("calc.totals.total"), amount: total, alwaysVisible: true });
244
+
245
+ /* ---- orderability ---------------------------------------------- */
246
+ const messages: CartMessage[] = [];
247
+ let orderable = true;
248
+
249
+ if (settings.costCenters.enabled && settings.costCenters.required && role) {
250
+ const missing = lines.some(l => l.hints.some(h => h.type === "missing-cost-center"));
251
+ if (context.costCenters.length === 0) {
252
+ orderable = false;
253
+ messages.push({
254
+ severity: "error",
255
+ code: "cost-centers-unassigned",
256
+ messageKey: "calc.messages.costCentersUnassigned",
257
+ });
258
+ }
259
+ else if (missing) {
260
+ orderable = false;
261
+ messages.push({
262
+ severity: "warning",
263
+ code: "cost-center-required",
264
+ messageKey: "calc.messages.costCenterRequired",
265
+ });
266
+ }
267
+ }
268
+
269
+ /* ---- approval limits -------------------------------------------- */
270
+ const approvalLimits = settings.approvals.enabled && role && role !== "requester"
271
+ ? this.evaluateApprovalLimits(context, request.items, lines, netTotal)
272
+ : [];
273
+
274
+ /* ---- spend incentives (revenue nudges for the summary) ---------- */
275
+ const incentives = [
276
+ {
277
+ key: "free-shipping" as const,
278
+ label: t("calc.incentives.freeShipping"),
279
+ threshold: FREE_SHIPPING_THRESHOLD,
280
+ missing: r2(Math.max(0, FREE_SHIPPING_THRESHOLD - subtotal)),
281
+ reached: subtotal >= FREE_SHIPPING_THRESHOLD,
282
+ },
283
+ {
284
+ key: "volume-discount" as const,
285
+ label: t("calc.incentives.volumeDiscount", { percent: DISCOUNT_RATE * 100 }),
286
+ threshold: DISCOUNT_THRESHOLD,
287
+ missing: r2(Math.max(0, DISCOUNT_THRESHOLD - subtotal)),
288
+ reached: subtotal >= DISCOUNT_THRESHOLD,
289
+ },
290
+ ];
291
+
292
+ return {
293
+ currency: "EUR",
294
+ lines,
295
+ totals,
296
+ orderable,
297
+ messages,
298
+ approvalLimits,
299
+ incentives,
300
+ };
301
+ }
302
+
303
+ private evaluateApprovalLimits(
304
+ context: B2BContext,
305
+ items: CartItem[],
306
+ lines: CalculatedCartLine[],
307
+ netTotal: number,
308
+ ): ApprovalLimitEvaluation[] {
309
+ const triggered: ApprovalLimitEvaluation[] = [];
310
+ const maxUnitPrice = Math.max(0, ...lines.filter(l => !l.priceLabel).map(l => l.unitPrice));
311
+ const hasNonCatalog = items.some(i => NON_CATALOG_IDS.has(i.id) || i.nonCatalog);
312
+
313
+ const lineTotalByCostCenter = new Map<string, number>();
314
+ for (let i = 0; i < lines.length; i++) {
315
+ const cc = items[i]!.costCenterId;
316
+ if (cc) {
317
+ lineTotalByCostCenter.set(cc, r2((lineTotalByCostCenter.get(cc) ?? 0) + lines[i]!.lineTotal));
318
+ }
319
+ }
320
+
321
+ for (const limit of context.approvalLimits) {
322
+ let entry: ApprovalLimitEvaluation | null = null;
323
+
324
+ switch (limit.type) {
325
+ case "order-value": {
326
+ const threshold = limit.maxAmount ?? 0;
327
+ entry = {
328
+ limitId: limit.id, type: limit.type, label: limit.label,
329
+ status: netTotal > threshold ? "exceeded" : "within",
330
+ threshold, actual: netTotal,
331
+ ...(netTotal > threshold ? { exceededBy: r2(netTotal - threshold) } : {}),
332
+ approval: limit.approval,
333
+ };
334
+ break;
335
+ }
336
+ case "budget": {
337
+ const remaining = limit.availableBudget ?? limit.totalBudget ?? 0;
338
+ const actual = limit.subject.type === "cost-center"
339
+ ? lineTotalByCostCenter.get(limit.subject.costCenterId) ?? 0
340
+ : netTotal;
341
+ if (limit.subject.type === "cost-center" && actual === 0) {
342
+ break; // cost center not used in this cart
343
+ }
344
+ entry = {
345
+ limitId: limit.id, type: limit.type, label: limit.label,
346
+ status: actual > remaining ? "exceeded" : "within",
347
+ threshold: remaining, actual,
348
+ ...(actual > remaining ? { exceededBy: r2(actual - remaining) } : {}),
349
+ approval: limit.approval,
350
+ };
351
+ break;
352
+ }
353
+ case "unit-price": {
354
+ const threshold = limit.maxAmount ?? 0;
355
+ entry = {
356
+ limitId: limit.id, type: limit.type, label: limit.label,
357
+ status: maxUnitPrice > threshold ? "exceeded" : "within",
358
+ threshold, actual: maxUnitPrice,
359
+ ...(maxUnitPrice > threshold ? { exceededBy: r2(maxUnitPrice - threshold) } : {}),
360
+ approval: limit.approval,
361
+ };
362
+ break;
363
+ }
364
+ case "non-catalog": {
365
+ if (hasNonCatalog) {
366
+ entry = {
367
+ limitId: limit.id, type: limit.type, label: limit.label,
368
+ status: "exceeded",
369
+ approval: limit.approval,
370
+ };
371
+ }
372
+ break;
373
+ }
374
+ }
375
+
376
+ if (entry) {
377
+ triggered.push(entry);
378
+ }
379
+ }
380
+
381
+ return triggered;
382
+ }
383
+
384
+ private async loadStock(): Promise<Map<string, number>> {
385
+ const entries = await readCoverDataJson<ProductListEntry[]>("product-list.json");
386
+ const map = new Map<string, number>();
387
+ for (const entry of entries) {
388
+ const id = entry.product?.[0]?.id;
389
+ if (id !== undefined && id !== null) {
390
+ map.set(String(id), entry.stocks?.[0]?.quantity ?? 0);
391
+ }
392
+ }
393
+ return map;
394
+ }
395
+ }
@@ -0,0 +1,18 @@
1
+ import type { ShopMarket } from "../../app/interfaces/market";
2
+ import type { IMarketService } from "../interfaces/market";
3
+
4
+ /**
5
+ * Bundled demo markets — mirrors the live markets app payload shape so the
6
+ * selector behaves identically in mock and live mode.
7
+ */
8
+ export class MockMarketService implements IMarketService {
9
+ private readonly dataKey: string;
10
+
11
+ constructor(dataKey: string) {
12
+ this.dataKey = dataKey;
13
+ }
14
+
15
+ async listMarkets(): Promise<ShopMarket[]> {
16
+ return readCoverDataJson<ShopMarket[]>(this.dataKey);
17
+ }
18
+ }
@@ -0,0 +1,56 @@
1
+ import type { H3Event } from "h3";
2
+
3
+ import type { StoredSession } from "../../app/interfaces/auth";
4
+ import type { AccountUser, IAccountService } from "../interfaces/account";
5
+
6
+ function createInitials(name: string): string {
7
+ const parts = name
8
+ .trim()
9
+ .split(/\s+/)
10
+ .filter(Boolean);
11
+ if (parts.length === 0) {
12
+ return "";
13
+ }
14
+ return parts
15
+ .slice(0, 2)
16
+ .map(part => part[0]?.toUpperCase() ?? "")
17
+ .join("");
18
+ }
19
+
20
+ /**
21
+ * Live identity implementation backed by the revenexx web SDK.
22
+ * Resolves the current user from the request's session cookie and the
23
+ * platform account endpoint. Requires the NUXT_WEB_SDK_* runtime config.
24
+ * Register via app.config → accountService: "sdk".
25
+ */
26
+ export class SdkAccountService implements IAccountService {
27
+ async getUser(event?: H3Event): Promise<AccountUser> {
28
+ if (!event) {
29
+ throw createError({ statusCode: 500, statusMessage: "SdkAccountService requires the request event" });
30
+ }
31
+
32
+ const rawSession = getCookie(event, SESSION_COOKIE_NAME);
33
+ if (!rawSession) {
34
+ throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
35
+ }
36
+
37
+ let session: StoredSession;
38
+ try {
39
+ session = JSON.parse(rawSession) as StoredSession;
40
+ }
41
+ catch {
42
+ throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
43
+ }
44
+ if (!session.fallbackCookie) {
45
+ throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
46
+ }
47
+
48
+ const accountUser = await useAuthenticatedAccount(session.fallbackCookie).accountGet();
49
+ return {
50
+ id: accountUser.$id,
51
+ name: accountUser.name,
52
+ email: accountUser.email,
53
+ initials: createInitials(accountUser.name),
54
+ };
55
+ }
56
+ }
@@ -0,0 +1,83 @@
1
+ import type { H3Event } from "h3";
2
+
3
+ import type { AuthUser, StoredSession } from "../../app/interfaces/auth";
4
+ import type { AuthLoginResult, IAuthService } from "../interfaces/auth";
5
+
6
+ /**
7
+ * Live authentication via the revenexx web SDK. Session state is carried in
8
+ * the same cookie as the mock implementation (with fallbackCookie set), so
9
+ * the client-side flow is identical. Requires NUXT_WEB_SDK_* runtime config.
10
+ */
11
+ export class SdkAuthService implements IAuthService {
12
+ async login(event: H3Event, email: string, password: string): Promise<AuthLoginResult> {
13
+ const { result: session, fallbackCookie } = await withCookieCapture(
14
+ () => useShopSdkAccount().accountCreateEmailPasswordSession({ email, password }),
15
+ );
16
+
17
+ const user = await useAuthenticatedAccount(fallbackCookie).accountGet();
18
+
19
+ const storedSession: StoredSession = {
20
+ id: session.$id,
21
+ expire: session.expire,
22
+ fallbackCookie,
23
+ };
24
+ setCookie(event, SESSION_COOKIE_NAME, JSON.stringify(storedSession), sessionCookieOptions(session.expire));
25
+
26
+ return {
27
+ user: { $id: user.$id, name: user.name, email: user.email },
28
+ session: { id: session.$id, expire: session.expire },
29
+ };
30
+ }
31
+
32
+ async me(event: H3Event): Promise<AuthUser | null> {
33
+ const raw = getCookie(event, SESSION_COOKIE_NAME);
34
+ if (!raw) {
35
+ return null;
36
+ }
37
+
38
+ let parsed: StoredSession;
39
+ try {
40
+ parsed = JSON.parse(raw) as StoredSession;
41
+ }
42
+ catch (err) {
43
+ getLogService().error("Failed to parse session cookie", toErrorContext(err));
44
+ deleteCookie(event, SESSION_COOKIE_NAME, sessionCookieOptions());
45
+ return null;
46
+ }
47
+
48
+ if (!parsed.fallbackCookie) {
49
+ deleteCookie(event, SESSION_COOKIE_NAME, sessionCookieOptions());
50
+ return null;
51
+ }
52
+
53
+ try {
54
+ const user = await useAuthenticatedAccount(parsed.fallbackCookie).accountGet();
55
+ return { $id: user.$id, name: user.name, email: user.email };
56
+ }
57
+ catch (err) {
58
+ if (!isSdkUserError(err)) {
59
+ getLogService().error("SDK request failed: me/accountGet", sdkErrorContext(err));
60
+ }
61
+ deleteCookie(event, SESSION_COOKIE_NAME, sessionCookieOptions());
62
+ return null;
63
+ }
64
+ }
65
+
66
+ async logout(event: H3Event): Promise<void> {
67
+ const raw = getCookie(event, SESSION_COOKIE_NAME);
68
+ if (raw) {
69
+ try {
70
+ const parsed = JSON.parse(raw) as StoredSession;
71
+ if (parsed.fallbackCookie) {
72
+ const sessionId = parsed.id || "current";
73
+ await useAuthenticatedAccount(parsed.fallbackCookie).accountDeleteSession({ sessionId });
74
+ }
75
+ }
76
+ catch (err) {
77
+ getLogService().error("SDK request failed: logout", sdkErrorContext(err));
78
+ // always clear the cookie regardless of SDK result
79
+ }
80
+ }
81
+ deleteCookie(event, SESSION_COOKIE_NAME, sessionCookieOptions());
82
+ }
83
+ }