@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,211 @@
1
+ <script setup lang="ts">
2
+ import CartApprovalsPanel from "../../cart/view/CartApprovalsPanel.vue";
3
+
4
+ const { icon } = useIcons();
5
+
6
+ const { t } = useI18n();
7
+ const checkout = useCheckoutOnePage();
8
+ const {
9
+ form, fieldErrors, canSubmit, submitting, submitError,
10
+ validateField, submitOrder, submitRequisition,
11
+ } = checkout;
12
+ const { role, workflows } = useB2BContext();
13
+
14
+ const isRequester = computed(() => role.value === "requester");
15
+ const selectedWorkflowId = useState<string | null>("cart-selected-workflow", () => null);
16
+ const { to } = useAppUrl();
17
+ const cart = useCartStore();
18
+ const { formatCurrency } = useCartSummaryFormatting();
19
+
20
+ // The checkout shows the same server-calculated document as the cart,
21
+ // recalculated when the delivery method changes (spec: shipping can flip
22
+ // limits). The cart itself is read-only inside the checkout.
23
+ const deliveryMethod = computed(() => form.value.deliveryMethod);
24
+ const { calculation, pending: calculating } = useCartCalculation({
25
+ orchestrate: true,
26
+ deliveryMethod,
27
+ });
28
+
29
+ const totals = computed(() =>
30
+ (calculation.value?.totals ?? []).filter(row => row.alwaysVisible || row.amount !== 0),
31
+ );
32
+
33
+ async function handlePlaceOrder() {
34
+ // Requesters submit a requisition into their workflow — the CTA stays
35
+ // active without the order terms (the orderer accepts them later).
36
+ if (isRequester.value) {
37
+ const workflowId = selectedWorkflowId.value ?? workflows.value[0]?.id;
38
+ if (!workflowId) {
39
+ return;
40
+ }
41
+ const requisitionId = await submitRequisition(workflowId);
42
+ if (requisitionId) {
43
+ await navigateTo(to("/checkout/confirmation", { id: requisitionId, type: "requisition" }));
44
+ }
45
+ return;
46
+ }
47
+
48
+ validateField("acceptedTerms");
49
+ const result = await submitOrder();
50
+ if (result) {
51
+ await navigateTo(to("/checkout/confirmation", {
52
+ id: result.orderId,
53
+ ...(result.approvalRequired
54
+ ? { type: "approval", approvers: result.approvers.join(",") }
55
+ : {}),
56
+ }));
57
+ }
58
+ }
59
+ </script>
60
+
61
+ <template>
62
+ <div class="bg-(--ui-bg) border border-(--ui-border) rounded-xl p-6 space-y-5">
63
+ <h2 class="text-lg font-semibold text-highlighted">
64
+ {{ t('onepage.sections.summary') }}
65
+ </h2>
66
+ <USeparator />
67
+
68
+ <!-- Submit error -->
69
+ <UAlert
70
+ v-if="submitError"
71
+ color="error"
72
+ variant="soft"
73
+ :description="submitError"
74
+ :icon="icon('alert')"
75
+ />
76
+
77
+ <!-- Read-only cart (spec: Warenkorb im Checkout) -->
78
+ <ul class="space-y-4 divide-y divide-(--ui-border)">
79
+ <li
80
+ v-for="(item, index) in cart.items"
81
+ :key="cart.keyOf(item)"
82
+ class="flex items-start gap-3 pt-4 first:pt-0"
83
+ >
84
+ <img
85
+ v-if="item.image"
86
+ :src="item.image"
87
+ :alt="item.name"
88
+ class="w-12 h-12 object-cover rounded-md shrink-0 border border-(--ui-border)"
89
+ >
90
+ <div class="flex-1 min-w-0 space-y-0.5">
91
+ <p class="text-sm font-medium text-highlighted truncate">
92
+ {{ item.name }}
93
+ </p>
94
+ <p class="text-xs text-muted">
95
+ {{ item.sku }}
96
+ <template v-if="item.customSku">
97
+ · {{ item.customSku }}
98
+ </template>
99
+ </p>
100
+ <p
101
+ v-if="item.positionTexts?.length"
102
+ class="text-xs text-muted italic truncate"
103
+ >
104
+ {{ item.positionTexts.join(' · ') }}
105
+ </p>
106
+ <div class="flex items-center justify-between gap-2 pt-0.5">
107
+ <span class="text-sm text-muted">
108
+ {{ calculation?.lines[index]?.adjustedQuantity ?? item.quantity }}×
109
+ </span>
110
+ <template v-if="calculation?.lines[index]?.priceLabel">
111
+ <UBadge
112
+ color="neutral"
113
+ variant="subtle"
114
+ size="sm"
115
+ :label="calculation.lines[index]!.priceLabel!.label"
116
+ />
117
+ </template>
118
+ <p
119
+ v-else
120
+ class="text-sm font-medium text-highlighted shrink-0"
121
+ >
122
+ {{ formatCurrency(calculation?.lines[index]?.lineTotal
123
+ ?? cart.getEffectivePrice(item.id) * item.quantity) }}
124
+ </p>
125
+ </div>
126
+ </div>
127
+ </li>
128
+ </ul>
129
+
130
+ <div
131
+ class="border-t border-(--ui-border) pt-4 space-y-2"
132
+ :class="calculating ? 'opacity-60' : ''"
133
+ >
134
+ <div
135
+ v-for="(row, index) in totals"
136
+ :key="`${row.key}-${index}`"
137
+ class="flex justify-between text-sm"
138
+ :class="row.key === 'total'
139
+ ? 'text-base font-semibold border-t border-(--ui-border) pt-2 mt-2 text-highlighted'
140
+ : ''"
141
+ >
142
+ <span :class="row.key === 'total' ? 'text-highlighted' : 'text-muted'">{{ row.label }}</span>
143
+ <span class="text-highlighted tabular-nums">{{ formatCurrency(row.amount) }}</span>
144
+ </div>
145
+ </div>
146
+
147
+ <!-- Approval limits in the checkout summary -->
148
+ <CartApprovalsPanel :limits="calculation?.approvalLimits ?? []" />
149
+
150
+ <!-- Promo code -->
151
+ <div class="flex gap-2">
152
+ <UInput
153
+ v-model="form.promoCode"
154
+ :placeholder="t('onepage.summary.promoCodePlaceholder')"
155
+ class="flex-1"
156
+ />
157
+ <UButton variant="outline">
158
+ {{ t('onepage.summary.applyPromo') }}
159
+ </UButton>
160
+ </div>
161
+
162
+ <div class="border-t border-(--ui-border) pt-4 space-y-4">
163
+ <!-- Terms checkbox (orders only — requisitions are accepted by the orderer) -->
164
+ <div v-if="!isRequester">
165
+ <UCheckbox
166
+ :model-value="form.acceptedTerms"
167
+ @update:model-value="form.acceptedTerms = $event as boolean;
168
+ validateField('acceptedTerms')"
169
+ >
170
+ <template #label>
171
+ <i18n-t keypath="onepage.summary.termsText">
172
+ <template #link>
173
+ <a
174
+ href="#"
175
+ class="underline hover:no-underline"
176
+ >{{ t('onepage.summary.termsLink') }}</a>
177
+ </template>
178
+ </i18n-t>
179
+ </template>
180
+ </UCheckbox>
181
+ <p
182
+ v-if="fieldErrors.acceptedTerms"
183
+ class="text-sm text-error-500 mt-1 ml-6"
184
+ >
185
+ {{ fieldErrors.acceptedTerms }}
186
+ </p>
187
+ </div>
188
+
189
+ <!-- Place order / submit requisition -->
190
+ <UButton
191
+ block
192
+ :disabled="isRequester ? submitting : !canSubmit"
193
+ :loading="submitting"
194
+ @click="handlePlaceOrder"
195
+ >
196
+ {{ isRequester ? t('onepage.summary.submitRequisition') : t('onepage.summary.placeOrder') }}
197
+ </UButton>
198
+
199
+ <!-- Back to cart -->
200
+ <UButton
201
+ block
202
+ variant="ghost"
203
+ color="neutral"
204
+ :to="to('/cart')"
205
+ :icon="icon('arrow-left')"
206
+ >
207
+ {{ t('onepage.summary.backToCart') }}
208
+ </UButton>
209
+ </div>
210
+ </div>
211
+ </template>
@@ -0,0 +1,38 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Requester checkout: shows which workflow the requisition enters and who
4
+ * acts next (all users of the following workflow step).
5
+ */
6
+ const props = defineProps<{
7
+ workflowId: string | null;
8
+ }>();
9
+
10
+ const { icon } = useIcons();
11
+ const { t } = useI18n();
12
+ const { workflows } = useB2BContext();
13
+
14
+ const workflow = computed(() =>
15
+ workflows.value.find(wf => wf.id === props.workflowId) ?? workflows.value[0] ?? null,
16
+ );
17
+
18
+ const nextActors = computed(() => {
19
+ // The requisition starts in step 1 — the next actors are step 2's users
20
+ // (or the orderers when the workflow has a single request step).
21
+ const steps = workflow.value?.steps ?? [];
22
+ const next = steps[1] ?? steps[steps.length - 1];
23
+ return next?.users.map(u => u.name) ?? [];
24
+ });
25
+ </script>
26
+
27
+ <template>
28
+ <UAlert
29
+ v-if="workflow"
30
+ color="info"
31
+ variant="soft"
32
+ :icon="icon('route')"
33
+ :title="t('onepage.workflow.title', { name: workflow.name })"
34
+ :description="nextActors.length
35
+ ? t('onepage.workflow.nextActors', { names: nextActors.join(', ') })
36
+ : undefined"
37
+ />
38
+ </template>
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ const { appName, localizeAppName, logo } = useAppConfig();
3
+ const { t } = useI18n();
4
+ const { to: appUrl } = useAppUrl();
5
+
6
+ const displayName = computed(() => localizeAppName ? t("common.appName") : appName);
7
+ </script>
8
+
9
+ <template>
10
+ <NuxtLink
11
+ :to="appUrl('/')"
12
+ tabindex="0"
13
+ :aria-label="t('nav.homeAriaLabel')"
14
+ >
15
+ <img
16
+ :src="logo"
17
+ :alt="displayName"
18
+ class="w-auto h-6"
19
+ >
20
+ </NuxtLink>
21
+ </template>
@@ -0,0 +1,148 @@
1
+ <script setup lang="ts">
2
+ import { footerLinks, legalLinks, shopContact } from "../../../config/navigation";
3
+
4
+ const { icon } = useIcons();
5
+
6
+ const { t } = useI18n();
7
+ const year = new Date().getFullYear();
8
+ </script>
9
+
10
+ <template>
11
+ <footer class="bg-(--ui-color-primary-50) dark:bg-(--ui-color-primary-500)">
12
+ <!-- Main body: logo/tagline + link columns -->
13
+ <div class="max-w-(--ui-container) mx-auto w-full px-4 sm:px-6 lg:px-8 py-16">
14
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-x-8 gap-y-12">
15
+ <!-- Logo + tagline + contact -->
16
+ <div class="flex flex-col gap-3">
17
+ <LayoutAppLogo />
18
+ <p class="text-sm/6 text-muted dark:text-white/90">
19
+ {{ t('footer.tagline') }}
20
+ </p>
21
+ <address class="not-italic flex flex-col gap-2.5 text-sm">
22
+ <span class="flex items-start gap-2">
23
+ <UIcon
24
+ :name="icon('location')"
25
+ class="size-4 mt-0.5 shrink-0 text-(--ui-primary)"
26
+ />
27
+ <span class="text-muted dark:text-white/90">{{ shopContact.address }}</span>
28
+ </span>
29
+ <a
30
+ :href="`tel:${shopContact.phone.replace(/\s/g, '')}`"
31
+ class="flex items-center gap-2 text-sm text-muted dark:text-white/90
32
+ hover:text-default dark:hover:text-white transition-colors"
33
+ >
34
+ <UIcon
35
+ :name="icon('phone')"
36
+ class="size-4 shrink-0 text-(--ui-primary)"
37
+ />
38
+ {{ shopContact.phone }}
39
+ </a>
40
+ <a
41
+ :href="`mailto:${shopContact.email}`"
42
+ class="flex items-center gap-2 text-sm text-muted dark:text-white/90
43
+ hover:text-default dark:hover:text-white transition-colors"
44
+ >
45
+ <UIcon
46
+ :name="icon('mail')"
47
+ class="size-4 shrink-0 text-(--ui-primary)"
48
+ />
49
+ {{ shopContact.email }}
50
+ </a>
51
+ </address>
52
+ </div>
53
+
54
+ <!-- Link columns -->
55
+ <div
56
+ v-for="group in footerLinks"
57
+ :key="group.heading"
58
+ >
59
+ <h3
60
+ class="text-xs font-semibold uppercase tracking-wider
61
+ text-dimmed dark:text-white/70 mb-4"
62
+ >
63
+ {{ group.heading }}
64
+ </h3>
65
+ <ul
66
+ role="list"
67
+ class="flex flex-col gap-2.5"
68
+ >
69
+ <li
70
+ v-for="link in group.links"
71
+ :key="link.to"
72
+ >
73
+ <NuxtLink
74
+ :to="link.to"
75
+ tabindex="0"
76
+ class="text-sm text-muted dark:text-white/90
77
+ hover:text-default dark:hover:text-white transition-colors"
78
+ >
79
+ {{ link.label }}
80
+ </NuxtLink>
81
+ </li>
82
+ </ul>
83
+ </div>
84
+ </div>
85
+ </div>
86
+
87
+ <!-- Bottom bar -->
88
+ <div class="border-t border-(--ui-border) dark:border-white/20">
89
+ <div
90
+ class="max-w-(--ui-container) mx-auto w-full px-4 sm:px-6 lg:px-8 py-5
91
+ flex flex-col md:flex-row items-start md:items-center justify-between gap-4"
92
+ >
93
+ <!-- Copyright + locale selector (discoverable in header AND footer) -->
94
+ <div class="flex flex-col sm:flex-row sm:items-center gap-x-6 gap-y-2">
95
+ <p class="text-xs text-dimmed dark:text-white/70">
96
+ {{ t('footer.copyright', { year }) }}
97
+ </p>
98
+ <LayoutHeaderLocaleSwitcher
99
+ class="text-xs text-dimmed dark:text-white/70 hover:text-default dark:hover:text-white"
100
+ />
101
+ </div>
102
+
103
+ <!-- Legal links -->
104
+ <nav
105
+ class="flex flex-wrap gap-x-5 gap-y-2"
106
+ aria-label="Legal"
107
+ >
108
+ <NuxtLink
109
+ v-for="link in legalLinks"
110
+ :key="link.to"
111
+ :to="link.to"
112
+ tabindex="0"
113
+ class="text-xs text-dimmed dark:text-white/70
114
+ hover:text-default dark:hover:text-white transition-colors"
115
+ >
116
+ {{ link.label }}
117
+ </NuxtLink>
118
+ </nav>
119
+
120
+ <!-- Social icons -->
121
+ <div class="flex items-center gap-1">
122
+ <UButton
123
+ to="https://linkedin.com"
124
+ target="_blank"
125
+ icon="i-simple-icons-linkedin"
126
+ aria-label="LinkedIn"
127
+ color="primary"
128
+ variant="ghost"
129
+ size="sm"
130
+ tabindex="0"
131
+ class="dark:text-white/90 dark:hover:text-white"
132
+ />
133
+ <UButton
134
+ to="https://x.com"
135
+ target="_blank"
136
+ icon="i-simple-icons-x"
137
+ aria-label="X / Twitter"
138
+ color="primary"
139
+ variant="ghost"
140
+ size="sm"
141
+ tabindex="0"
142
+ class="dark:text-white/90 dark:hover:text-white"
143
+ />
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </footer>
148
+ </template>
@@ -0,0 +1,75 @@
1
+ <script setup lang="ts">
2
+ const { icon } = useIcons();
3
+
4
+ const { t } = useI18n();
5
+ const localePath = useLocalePath();
6
+ const isMenuOpen = ref(false);
7
+
8
+ // Symmetric grid: flexible side columns keep the wide search column truly
9
+ // centered in the header, independent of logo/action widths.
10
+ const headerUi = {
11
+ container: [
12
+ "grid grid-cols-[1fr_minmax(0,46rem)_1fr] items-center gap-4 lg:gap-6",
13
+ "h-(--ui-header-height)",
14
+ "px-4 sm:px-6 lg:px-8",
15
+ "max-w-(--ui-container) mx-auto w-full",
16
+ ].join(" "),
17
+ left: "flex items-center gap-2 justify-self-start",
18
+ center: "w-full min-w-0 flex items-center",
19
+ // -mr-2.5 cancels the action items' own px-2.5 hover padding so the last
20
+ // item's text ends flush with the topbar/main-nav edge below and above.
21
+ right: "flex items-center justify-end gap-1.5 -mr-2.5",
22
+ };
23
+
24
+ let mq: MediaQueryList | null = null;
25
+ const closeMenu = (e: MediaQueryListEvent) => {
26
+ if (e.matches) {
27
+ isMenuOpen.value = false;
28
+ }
29
+ };
30
+
31
+ onMounted(() => {
32
+ mq = window.matchMedia("(min-width: 768px)");
33
+ mq.addEventListener("change", closeMenu);
34
+ });
35
+
36
+ onUnmounted(() => mq?.removeEventListener("change", closeMenu));
37
+
38
+ const { navigateToSearch } = useSearch();
39
+ </script>
40
+
41
+ <template>
42
+ <UHeader
43
+ v-model:open="isMenuOpen"
44
+ :toggle="{ color: 'primary', class: 'md:hidden' }"
45
+ :ui="headerUi"
46
+ >
47
+ <template #left>
48
+ <LayoutAppLogo class="shrink-0" />
49
+ </template>
50
+
51
+ <template #default>
52
+ <div class="hidden md:flex w-full">
53
+ <UiFormsSearchInput
54
+ class="w-full"
55
+ @search="navigateToSearch"
56
+ />
57
+ </div>
58
+ </template>
59
+
60
+ <template #right>
61
+ <LayoutHeaderActionItem
62
+ :icon="icon('direct-order')"
63
+ :label="t('topbar.directOrder')"
64
+ :to="localePath('/account/direct-order')"
65
+ class="hidden md:flex"
66
+ />
67
+ <AccountMenu />
68
+ <CartButton />
69
+ </template>
70
+
71
+ <template #body>
72
+ <LayoutHeaderMobileMenu />
73
+ </template>
74
+ </UHeader>
75
+ </template>
@@ -0,0 +1,93 @@
1
+ <script setup lang="ts">
2
+ const { categoryNavItems } = useCategories();
3
+ const navEl = ref<HTMLElement | null>(null);
4
+
5
+ let blockedTrigger: HTMLElement | null = null;
6
+
7
+ function blockMoveCapture(e: PointerEvent) {
8
+ if (e.pointerType === "mouse") {
9
+ e.stopImmediatePropagation();
10
+ }
11
+ }
12
+
13
+ function clearBlock() {
14
+ if (!blockedTrigger) {
15
+ return;
16
+ }
17
+ blockedTrigger.removeEventListener("pointermove", blockMoveCapture, true);
18
+ blockedTrigger.dispatchEvent(
19
+ new PointerEvent("pointerleave", { bubbles: false, pointerType: "mouse" }),
20
+ );
21
+ blockedTrigger = null;
22
+ document.removeEventListener("mousemove", unblockIfLeft);
23
+ }
24
+
25
+ function unblockIfLeft(e: MouseEvent) {
26
+ if (!blockedTrigger) {
27
+ return;
28
+ }
29
+ const rect = blockedTrigger.getBoundingClientRect();
30
+ const outside = e.clientX < rect.left || e.clientX > rect.right
31
+ || e.clientY < rect.top || e.clientY > rect.bottom;
32
+ if (outside) {
33
+ clearBlock();
34
+ }
35
+ }
36
+
37
+ function onPointerDownCapture(e: PointerEvent) {
38
+ if (e.pointerType !== "mouse") {
39
+ return;
40
+ }
41
+ const trigger = (e.target as Element)
42
+ .closest("[data-navigation-menu-trigger]") as HTMLElement | null;
43
+ if (!trigger || trigger.getAttribute("data-state") !== "open") {
44
+ return;
45
+ }
46
+ if (blockedTrigger && blockedTrigger !== trigger) {
47
+ clearBlock();
48
+ }
49
+ if (blockedTrigger === trigger) {
50
+ return;
51
+ }
52
+ blockedTrigger = trigger;
53
+ trigger.addEventListener("pointermove", blockMoveCapture, true);
54
+ trigger.dispatchEvent(
55
+ new PointerEvent("pointerleave", { bubbles: false, pointerType: "mouse" }),
56
+ );
57
+ document.addEventListener("mousemove", unblockIfLeft);
58
+ }
59
+
60
+ // Trailing icon clicks must never trigger link navigation regardless of menu state.
61
+ function onTrailingClickCapture(e: MouseEvent) {
62
+ const trailing = (e.target as Element).closest("[data-slot='linkTrailing']");
63
+ if (trailing) {
64
+ e.preventDefault();
65
+ }
66
+ }
67
+
68
+ onMounted(() => {
69
+ navEl.value?.addEventListener("pointerdown", onPointerDownCapture, true);
70
+ navEl.value?.addEventListener("click", onTrailingClickCapture, true);
71
+ });
72
+ onUnmounted(() => {
73
+ navEl.value?.removeEventListener("pointerdown", onPointerDownCapture, true);
74
+ navEl.value?.removeEventListener("click", onTrailingClickCapture, true);
75
+ clearBlock();
76
+ });
77
+ </script>
78
+
79
+ <template>
80
+ <div class="border-b border-(--ui-border) hidden md:block">
81
+ <div
82
+ ref="navEl"
83
+ class="px-4 sm:px-6 lg:px-8 xl:max-w-screen-xl xl:mx-auto"
84
+ >
85
+ <UNavigationMenu
86
+ :items="categoryNavItems"
87
+ orientation="horizontal"
88
+ aria-label="Product categories"
89
+ :ui="{ linkLeadingIcon: 'text-(--ui-primary)', childLinkIcon: 'text-(--ui-primary)' }"
90
+ />
91
+ </div>
92
+ </div>
93
+ </template>
@@ -0,0 +1,81 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Two-line header action (icon + title + subline), as introduced by the
4
+ * relation prototype header. Renders as a link when `to` is set, otherwise
5
+ * as a button (e.g. the cart trigger). Pass a custom icon via the #icon
6
+ * slot or a cover icon token via the `icon` prop. The text collapses on
7
+ * small screens, leaving the icon.
8
+ */
9
+ const props = defineProps<{
10
+ icon?: string;
11
+ label: string;
12
+ sublabel?: string;
13
+ to?: string;
14
+ }>();
15
+
16
+ defineEmits<{ click: [] }>();
17
+
18
+ defineSlots<{ icon?(): unknown; trailing?(): unknown }>();
19
+
20
+ const { icon: resolveIcon } = useIcons();
21
+ const resolvedIcon = computed(() => (props.icon ? resolveIcon(props.icon) : undefined));
22
+
23
+ const itemClasses = `group flex items-center gap-2 px-2.5 py-1.5 rounded-lg text-left cursor-pointer
24
+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--ui-primary)`;
25
+ </script>
26
+
27
+ <template>
28
+ <NuxtLink
29
+ v-if="to"
30
+ :to="to"
31
+ tabindex="0"
32
+ :class="itemClasses"
33
+ >
34
+ <span class="flex items-center shrink-0 text-primary">
35
+ <slot name="icon">
36
+ <UIcon
37
+ v-if="resolvedIcon"
38
+ :name="resolvedIcon"
39
+ class="size-6"
40
+ />
41
+ </slot>
42
+ </span>
43
+ <span class="hidden xl:flex items-center gap-1.5 text-sm/5 whitespace-nowrap">
44
+ <span class="flex flex-col">
45
+ <span class="text-highlighted group-hover:underline underline-offset-4 decoration-2">{{ label }}</span>
46
+ <span
47
+ v-if="sublabel"
48
+ class="text-muted"
49
+ >{{ sublabel }}</span>
50
+ </span>
51
+ <slot name="trailing" />
52
+ </span>
53
+ </NuxtLink>
54
+ <button
55
+ v-else
56
+ type="button"
57
+ tabindex="0"
58
+ :class="itemClasses"
59
+ @click="$emit('click')"
60
+ >
61
+ <span class="flex items-center shrink-0 text-primary">
62
+ <slot name="icon">
63
+ <UIcon
64
+ v-if="resolvedIcon"
65
+ :name="resolvedIcon"
66
+ class="size-6"
67
+ />
68
+ </slot>
69
+ </span>
70
+ <span class="hidden xl:flex items-center gap-1.5 text-sm/5 whitespace-nowrap">
71
+ <span class="flex flex-col">
72
+ <span class="text-highlighted group-hover:underline underline-offset-4 decoration-2">{{ label }}</span>
73
+ <span
74
+ v-if="sublabel"
75
+ class="text-muted"
76
+ >{{ sublabel }}</span>
77
+ </span>
78
+ <slot name="trailing" />
79
+ </span>
80
+ </button>
81
+ </template>