@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.
- package/LICENSE +21 -0
- package/README.md +39 -0
- package/app/api/account.ts +8 -0
- package/app/api/categories.ts +3 -0
- package/app/api/checkout.ts +6 -0
- package/app/api/images.ts +4 -0
- package/app/api/markets.ts +3 -0
- package/app/api/product.ts +3 -0
- package/app/api/products.ts +4 -0
- package/app/api/search.ts +4 -0
- package/app/app.config.ts +29 -0
- package/app/assets/css/cover.css +25 -0
- package/app/components/account/AccountMenu.vue +30 -0
- package/app/components/account/AccountShell.vue +18 -0
- package/app/components/account/AccountSidebar.vue +90 -0
- package/app/components/account/address/AccountAddressCard.vue +287 -0
- package/app/components/account/carts/AccountCartsTable.vue +284 -0
- package/app/components/account/dashboard/AccountDashboard.vue +351 -0
- package/app/components/account/directorder/AccountDirectOrder.vue +512 -0
- package/app/components/account/governance/AccountApprovalLimitsTable.vue +221 -0
- package/app/components/account/governance/AccountCostCenterDetail.vue +276 -0
- package/app/components/account/governance/AccountCostCentersTable.vue +252 -0
- package/app/components/account/governance/AccountRequisitionDetail.vue +295 -0
- package/app/components/account/governance/AccountRequisitionsTable.vue +255 -0
- package/app/components/account/governance/AccountWorkflowDetail.vue +215 -0
- package/app/components/account/governance/AccountWorkflowsTable.vue +168 -0
- package/app/components/account/governance/GovernanceApprovalLimitModal.vue +183 -0
- package/app/components/account/governance/GovernanceCostCenterModal.vue +188 -0
- package/app/components/account/governance/GovernanceWorkflowModal.vue +191 -0
- package/app/components/account/orderlists/AccountOrderListDetail.vue +349 -0
- package/app/components/account/orderlists/AccountOrderListsTable.vue +352 -0
- package/app/components/account/orders/AccountOrderDetail.vue +376 -0
- package/app/components/account/orders/AccountOrdersTable.vue +281 -0
- package/app/components/account/preferences/AccountPreferences.vue +50 -0
- package/app/components/auth/AuthForgotPasswordPanel.vue +88 -0
- package/app/components/auth/AuthLoginPanel.vue +103 -0
- package/app/components/auth/AuthLoginTeaser.vue +24 -0
- package/app/components/auth/AuthPageHeader.vue +21 -0
- package/app/components/auth/AuthRegisterPanel.vue +431 -0
- package/app/components/auth/AuthRegisterTeaser.vue +24 -0
- package/app/components/auth/AuthResetPasswordPanel.vue +115 -0
- package/app/components/cart/CartButton.vue +42 -0
- package/app/components/cart/actions/CartCostCenterModal.vue +110 -0
- package/app/components/cart/actions/CartExportModal.vue +82 -0
- package/app/components/cart/actions/CartPositionTextsModal.vue +92 -0
- package/app/components/cart/actions/CartSaveToListModal.vue +177 -0
- package/app/components/cart/actions/CartWorkflowPickerModal.vue +64 -0
- package/app/components/cart/drawer/CartDrawer.vue +49 -0
- package/app/components/cart/item/CartItem.vue +318 -0
- package/app/components/cart/manage/CartNameModal.vue +68 -0
- package/app/components/cart/position/CartPosition.vue +369 -0
- package/app/components/cart/quickadd/CartQuickAdd.vue +145 -0
- package/app/components/cart/recommend/CartCrossSell.vue +102 -0
- package/app/components/cart/recommend/CartRecommendCard.vue +59 -0
- package/app/components/cart/recommend/CartReorderStrip.vue +108 -0
- package/app/components/cart/requisition/CartRequisitionGroup.vue +74 -0
- package/app/components/cart/skeleton/CartSkeleton.vue +18 -0
- package/app/components/cart/summary/CartSummary.vue +25 -0
- package/app/components/cart/summary/CartSummaryBreakdown.vue +35 -0
- package/app/components/cart/switcher/CartSwitcher.vue +147 -0
- package/app/components/cart/target/CartTargetModal.vue +129 -0
- package/app/components/cart/view/CartApprovalsPanel.vue +89 -0
- package/app/components/cart/view/CartListHeader.vue +86 -0
- package/app/components/cart/view/CartPositionList.vue +94 -0
- package/app/components/cart/view/CartSummaryPanel.vue +254 -0
- package/app/components/cart/view/CartView.vue +310 -0
- package/app/components/category/info/CategoryPrice.vue +47 -0
- package/app/components/category/info/CategorySku.vue +15 -0
- package/app/components/category/info/CategoryStock.vue +25 -0
- package/app/components/category/list/CategoryItemCount.vue +27 -0
- package/app/components/category/list/CategoryListPagination.vue +81 -0
- package/app/components/category/skeleton/CategoryDetailSkeleton.vue +12 -0
- package/app/components/category/skeleton/CategoryListSkeleton.vue +65 -0
- package/app/components/category/view/CategoryViewToggle.vue +33 -0
- package/app/components/category/view/GridView.vue +78 -0
- package/app/components/category/view/ListView.vue +80 -0
- package/app/components/checkout/confirmation/CheckoutConfirmationView.vue +289 -0
- package/app/components/checkout/form/CheckoutSchemaForm.vue +72 -0
- package/app/components/checkout/onepage/CheckoutAddressPickerModal.vue +260 -0
- package/app/components/checkout/onepage/CheckoutAddressSection.vue +186 -0
- package/app/components/checkout/onepage/CheckoutDeliverySection.vue +158 -0
- package/app/components/checkout/onepage/CheckoutGate.vue +67 -0
- package/app/components/checkout/onepage/CheckoutGuestDetailsSection.vue +154 -0
- package/app/components/checkout/onepage/CheckoutNotesSection.vue +62 -0
- package/app/components/checkout/onepage/CheckoutOnePageView.vue +77 -0
- package/app/components/checkout/onepage/CheckoutPaymentSection.vue +181 -0
- package/app/components/checkout/onepage/CheckoutSectionTitle.vue +18 -0
- package/app/components/checkout/onepage/CheckoutSummarySection.vue +211 -0
- package/app/components/checkout/onepage/CheckoutWorkflowPanel.vue +38 -0
- package/app/components/layout/AppLogo.vue +21 -0
- package/app/components/layout/footer/Footer.vue +148 -0
- package/app/components/layout/header/AppBar.vue +75 -0
- package/app/components/layout/header/CategoryNav.vue +93 -0
- package/app/components/layout/header/HeaderActionItem.vue +81 -0
- package/app/components/layout/header/LocaleSwitcher.vue +123 -0
- package/app/components/layout/header/MainNav.vue +61 -0
- package/app/components/layout/header/MobileMenu.vue +81 -0
- package/app/components/layout/header/TopBar.vue +63 -0
- package/app/components/listing/ListingView.vue +227 -0
- package/app/components/product/detail/ProductDetailAddToCart.vue +188 -0
- package/app/components/product/detail/ProductDetailDescription.vue +20 -0
- package/app/components/product/detail/ProductDetailImageGallery.vue +58 -0
- package/app/components/product/detail/ProductDetailPricing.vue +108 -0
- package/app/components/product/detail/ProductDetailSaveToList.vue +291 -0
- package/app/components/product/detail/ProductDetailSpecifications.vue +33 -0
- package/app/components/product/detail/ProductDetailStock.vue +36 -0
- package/app/components/search/filter/SearchFacets.vue +256 -0
- package/app/components/search/filter/SearchFacetsDrawer.vue +58 -0
- package/app/components/ui/AppShell.vue +7 -0
- package/app/components/ui/debug/PiniaStateCard.vue +106 -0
- package/app/components/ui/feedback/EmptyState.vue +30 -0
- package/app/components/ui/feedback/ErrorBoundary.vue +44 -0
- package/app/components/ui/feedback/NotFound.vue +34 -0
- package/app/components/ui/forms/CountrySelection.vue +14 -0
- package/app/components/ui/forms/FormKitDatePicker.vue +124 -0
- package/app/components/ui/forms/PasswordInput.vue +136 -0
- package/app/components/ui/forms/SearchInput.vue +213 -0
- package/app/components/ui/table/TableSortButton.vue +51 -0
- package/app/components/ui/table/TableToolbar.vue +66 -0
- package/app/composables/useAccount.ts +27 -0
- package/app/composables/useAccountAddresses.ts +116 -0
- package/app/composables/useAccountOrders.ts +18 -0
- package/app/composables/useAppUrl.ts +38 -0
- package/app/composables/useAuthStore.ts +202 -0
- package/app/composables/useB2BContext.ts +23 -0
- package/app/composables/useCartActions.ts +25 -0
- package/app/composables/useCartCalculation.ts +90 -0
- package/app/composables/useCartSelection.ts +46 -0
- package/app/composables/useCartStore.ts +837 -0
- package/app/composables/useCartSummaryFormatting.ts +28 -0
- package/app/composables/useCategories.ts +29 -0
- package/app/composables/useCheckoutOnePage.ts +438 -0
- package/app/composables/useCheckoutProfileSource.ts +8 -0
- package/app/composables/useCheckoutSchema.ts +28 -0
- package/app/composables/useDataTable.ts +14 -0
- package/app/composables/useFormValidation.ts +29 -0
- package/app/composables/useIcons.ts +17 -0
- package/app/composables/useLocalePreferences.ts +45 -0
- package/app/composables/useMarkets.ts +15 -0
- package/app/composables/useProduct.ts +89 -0
- package/app/composables/useProductCard.ts +8 -0
- package/app/composables/useProductCardActions.ts +106 -0
- package/app/composables/useProductImage.ts +12 -0
- package/app/composables/useProductListing.ts +210 -0
- package/app/composables/useProductUrl.ts +17 -0
- package/app/composables/useProducts.ts +130 -0
- package/app/composables/useSearch.ts +12 -0
- package/app/composables/useThemeStore.ts +81 -0
- package/app/composables/useViewMode.ts +18 -0
- package/app/config/countries.ts +74 -0
- package/app/config/icons.ts +283 -0
- package/app/config/navigation.ts +191 -0
- package/app/config/palette.ts +13 -0
- package/app/config/themes.ts +20 -0
- package/app/formkit.config.ts +50 -0
- package/app/interfaces/account/address-list.ts +34 -0
- package/app/interfaces/account/order-list.ts +47 -0
- package/app/interfaces/account/order-lists.ts +35 -0
- package/app/interfaces/account/profile.ts +17 -0
- package/app/interfaces/account.ts +3 -0
- package/app/interfaces/address.ts +9 -0
- package/app/interfaces/auth.ts +104 -0
- package/app/interfaces/b2b.ts +234 -0
- package/app/interfaces/cart-calculation.ts +131 -0
- package/app/interfaces/cart-item.ts +45 -0
- package/app/interfaces/checkout-draft.ts +4 -0
- package/app/interfaces/checkout.ts +43 -0
- package/app/interfaces/delivery.ts +13 -0
- package/app/interfaces/market.ts +24 -0
- package/app/interfaces/payment.ts +19 -0
- package/app/interfaces/persisted-cart.ts +10 -0
- package/app/interfaces/product-detail.ts +54 -0
- package/app/interfaces/product-list.ts +33 -0
- package/app/interfaces/search-facets.ts +14 -0
- package/app/interfaces/validation.ts +14 -0
- package/app/layouts/default.vue +78 -0
- package/app/layouts/focus.vue +80 -0
- package/app/plugins/formkit-locale.client.ts +23 -0
- package/app/shared/constants.ts +1 -0
- package/app/validations/companyName.ts +18 -0
- package/app/validations/emailFormat.ts +10 -0
- package/app/validations/formValidationConfig.ts +50 -0
- package/app/validations/maxLength.ts +12 -0
- package/app/validations/minLength.ts +9 -0
- package/app/validations/optionalCompanyName.ts +23 -0
- package/app/validations/passwordsMatch.ts +5 -0
- package/app/validations/phoneNumber.ts +19 -0
- package/app/validations/required.ts +11 -0
- package/app/validations/termsRequired.ts +4 -0
- package/app/validations/types.ts +10 -0
- package/app/validations/zipCode.ts +15 -0
- package/i18n/locales/de/account.json +458 -0
- package/i18n/locales/de/auth.json +155 -0
- package/i18n/locales/de/cart.json +263 -0
- package/i18n/locales/de/checkout.json +217 -0
- package/i18n/locales/de/common.json +80 -0
- package/i18n/locales/de/cover.json +33 -0
- package/i18n/locales/de/order.json +1 -0
- package/i18n/locales/de/product.json +71 -0
- package/i18n/locales/de/search.json +54 -0
- package/i18n/locales/de/validation.json +12 -0
- package/i18n/locales/en/account.json +458 -0
- package/i18n/locales/en/auth.json +155 -0
- package/i18n/locales/en/cart.json +263 -0
- package/i18n/locales/en/checkout.json +217 -0
- package/i18n/locales/en/common.json +80 -0
- package/i18n/locales/en/cover.json +33 -0
- package/i18n/locales/en/order.json +1 -0
- package/i18n/locales/en/product.json +71 -0
- package/i18n/locales/en/search.json +54 -0
- package/i18n/locales/en/validation.json +12 -0
- package/nuxt.config.ts +109 -0
- package/package.json +65 -0
- package/public/img/product-placeholder.svg +8 -0
- package/public/templates/direct-order-template.csv +3 -0
- package/public/templates/direct-order-template.xlsx +0 -0
- package/server/api/account/address/[id].delete.ts +51 -0
- package/server/api/account/address/[id].put.ts +83 -0
- package/server/api/account/address/index.post.ts +60 -0
- package/server/api/account/addresses.get.ts +25 -0
- package/server/api/account/context.get.ts +16 -0
- package/server/api/account/governance/[domain]/[id].put.ts +57 -0
- package/server/api/account/governance/[domain].post.ts +111 -0
- package/server/api/account/order-lists/[id].delete.ts +22 -0
- package/server/api/account/order-lists/[id].get.ts +17 -0
- package/server/api/account/order-lists/[id].put.ts +46 -0
- package/server/api/account/order-lists/index.get.ts +14 -0
- package/server/api/account/order-lists/index.post.ts +38 -0
- package/server/api/account/orders/[id].get.ts +35 -0
- package/server/api/account/orders.get.ts +35 -0
- package/server/api/account/profile.get.ts +56 -0
- package/server/api/account/profile.put.ts +128 -0
- package/server/api/account/requisitions/[id].get.ts +15 -0
- package/server/api/account/requisitions.get.ts +23 -0
- package/server/api/auth/login.post.ts +37 -0
- package/server/api/auth/logout.post.ts +4 -0
- package/server/api/auth/me.get.ts +3 -0
- package/server/api/auth/personas.get.ts +7 -0
- package/server/api/auth/recovery.post.ts +32 -0
- package/server/api/auth/recovery.put.ts +37 -0
- package/server/api/auth/register.post.ts +126 -0
- package/server/api/cart/calculate.post.ts +25 -0
- package/server/api/cart/export.post.ts +81 -0
- package/server/api/cart/index.delete.ts +10 -0
- package/server/api/cart/index.get.ts +10 -0
- package/server/api/cart/sync.post.ts +14 -0
- package/server/api/carts/[id]/activate.post.ts +18 -0
- package/server/api/carts/[id]/index.delete.ts +18 -0
- package/server/api/carts/[id]/index.put.ts +19 -0
- package/server/api/carts/[id]/items.post.ts +21 -0
- package/server/api/carts/index.get.ts +14 -0
- package/server/api/carts/index.post.ts +14 -0
- package/server/api/categories/[slug].get.ts +22 -0
- package/server/api/categories/index.get.ts +11 -0
- package/server/api/checkout/profile.get.ts +21 -0
- package/server/api/checkout/schema/[key].get.ts +19 -0
- package/server/api/checkout/session.post.ts +8 -0
- package/server/api/images/detail/[filename].get.ts +18 -0
- package/server/api/images/list/[filename].get.ts +17 -0
- package/server/api/markets.get.ts +9 -0
- package/server/api/orders/index.post.ts +376 -0
- package/server/api/payment/methods.post.ts +65 -0
- package/server/api/product/[id].get.ts +29 -0
- package/server/api/products/[id].get.ts +30 -0
- package/server/api/products/index.get.ts +21 -0
- package/server/api/requisitions/index.post.ts +67 -0
- package/server/api/shipping/rates.post.ts +70 -0
- package/server/api/themes/index.get.ts +9 -0
- package/server/api/typesense/drop.get.ts +22 -0
- package/server/api/typesense/health.get.ts +9 -0
- package/server/api/typesense/search.post.ts +199 -0
- package/server/api/typesense/seed.get.ts +112 -0
- package/server/api/typesense/suggest.post.ts +69 -0
- package/server/config/account/organization.json +146 -0
- package/server/config/account/personas.json +169 -0
- package/server/config/account/user.json +8 -0
- package/server/config/account/workflows.json +19 -0
- package/server/data/account/address-list.json +103 -0
- package/server/data/account/order-list.json +491 -0
- package/server/data/account/order-lists.json +149 -0
- package/server/data/account/profile.json +9 -0
- package/server/data/account/registration-requests.json +3 -0
- package/server/data/account/requisitions.json +686 -0
- package/server/data/categories.json +186 -0
- package/server/data/forms/checkout/add-address.json +24 -0
- package/server/data/list/5137-1.png +0 -0
- package/server/data/list/5498-1.png +0 -0
- package/server/data/list/5498-2.png +0 -0
- package/server/data/list/5498-3.png +0 -0
- package/server/data/list/5498-4.png +0 -0
- package/server/data/list/5498-5.png +0 -0
- package/server/data/list/5498-6.png +0 -0
- package/server/data/list/5519-1.png +0 -0
- package/server/data/list/5713-1.png +0 -0
- package/server/data/list/5789-1.png +0 -0
- package/server/data/list/5930-1.png +0 -0
- package/server/data/list/6127-1.png +0 -0
- package/server/data/list/6234-1.png +0 -0
- package/server/data/list/6238-1.png +0 -0
- package/server/data/list/6246-1.png +0 -0
- package/server/data/list/6270-1.png +0 -0
- package/server/data/list/6330-1.png +0 -0
- package/server/data/list/6336-1.png +0 -0
- package/server/data/list/6360-1.png +0 -0
- package/server/data/list/6363-1.png +0 -0
- package/server/data/list/6375-1.png +0 -0
- package/server/data/list/6385-1.png +0 -0
- package/server/data/list/6413-1.png +0 -0
- package/server/data/list/6418-1.png +0 -0
- package/server/data/list/6465-1.png +0 -0
- package/server/data/list/6477-1.png +0 -0
- package/server/data/list/6509-1.png +0 -0
- package/server/data/list/6545-1.png +0 -0
- package/server/data/list/6548-1.png +0 -0
- package/server/data/list/6566-1.png +0 -0
- package/server/data/list/6581-1.png +0 -0
- package/server/data/list/6609-1.png +0 -0
- package/server/data/list/6611-1.png +0 -0
- package/server/data/list/6641-1.png +0 -0
- package/server/data/list/6659-1.png +0 -0
- package/server/data/list/6662-1.png +0 -0
- package/server/data/list/6689-1.png +0 -0
- package/server/data/list/6698-1.png +0 -0
- package/server/data/list/6701-1.png +0 -0
- package/server/data/list/6752-1.png +0 -0
- package/server/data/list/6755-1.png +0 -0
- package/server/data/list/6837-1.png +0 -0
- package/server/data/list/6841-1.png +0 -0
- package/server/data/list/6844-1.png +0 -0
- package/server/data/list/6846-1.png +0 -0
- package/server/data/list/6886-1.png +0 -0
- package/server/data/list/6895-1.png +0 -0
- package/server/data/list/6897-1.png +0 -0
- package/server/data/list/6919-1.png +0 -0
- package/server/data/list/6977-1.png +0 -0
- package/server/data/list/6983-1.png +0 -0
- package/server/data/list/6984-1.png +0 -0
- package/server/data/list/6985-1.png +0 -0
- package/server/data/list/6986-1.png +0 -0
- package/server/data/list/6989-1.png +0 -0
- package/server/data/list/6995-1.png +0 -0
- package/server/data/list/6998-1.png +0 -0
- package/server/data/markets.json +24 -0
- package/server/data/product-detail.json +2450 -0
- package/server/data/product-list.json +2450 -0
- package/server/data/themes.json +8 -0
- package/server/interfaces/account.ts +20 -0
- package/server/interfaces/auth.ts +32 -0
- package/server/interfaces/b2bContext.ts +23 -0
- package/server/interfaces/cart.ts +46 -0
- package/server/interfaces/cartCalculation.ts +17 -0
- package/server/interfaces/category.ts +12 -0
- package/server/interfaces/checkoutProfile.ts +5 -0
- package/server/interfaces/log.ts +3 -0
- package/server/interfaces/market.ts +10 -0
- package/server/interfaces/product.ts +21 -0
- package/server/interfaces/schema.ts +20 -0
- package/server/interfaces/theme.ts +10 -0
- package/server/services/ApiAuthService.ts +138 -0
- package/server/services/ApiCartService.ts +254 -0
- package/server/services/ApiCategoryService.ts +25 -0
- package/server/services/ApiMarketService.ts +71 -0
- package/server/services/ApiProductService.ts +53 -0
- package/server/services/LocalFileCategoryService.ts +23 -0
- package/server/services/LocalFileCheckoutProfileService.ts +117 -0
- package/server/services/LocalFileProductService.ts +128 -0
- package/server/services/LocalFileSchemaService.ts +70 -0
- package/server/services/LocalFileThemeService.ts +18 -0
- package/server/services/LogService.ts +28 -0
- package/server/services/MockAccountService.ts +58 -0
- package/server/services/MockAuthService.ts +105 -0
- package/server/services/MockB2BContextService.ts +149 -0
- package/server/services/MockCartCalculationService.ts +395 -0
- package/server/services/MockMarketService.ts +18 -0
- package/server/services/SdkAccountService.ts +56 -0
- package/server/services/SdkAuthService.ts +83 -0
- package/server/services/SessionCartService.ts +31 -0
- package/server/utils/accountService.ts +30 -0
- package/server/utils/authCookie.ts +13 -0
- package/server/utils/authService.ts +43 -0
- package/server/utils/b2bService.ts +49 -0
- package/server/utils/cartService.ts +59 -0
- package/server/utils/categoryService.ts +19 -0
- package/server/utils/checkoutProfileService.ts +9 -0
- package/server/utils/checkoutSession.ts +38 -0
- package/server/utils/coverData.ts +84 -0
- package/server/utils/governanceStore.ts +38 -0
- package/server/utils/i18n.ts +76 -0
- package/server/utils/inventoryService.ts +14 -0
- package/server/utils/liveCatalog.ts +234 -0
- package/server/utils/liveInventories.ts +76 -0
- package/server/utils/liveOrders.ts +139 -0
- package/server/utils/livePrices.ts +93 -0
- package/server/utils/locale.ts +39 -0
- package/server/utils/logService.ts +19 -0
- package/server/utils/marketService.ts +24 -0
- package/server/utils/orderService.ts +14 -0
- package/server/utils/paymentService.ts +14 -0
- package/server/utils/priceService.ts +14 -0
- package/server/utils/productService.ts +28 -0
- package/server/utils/productsSchema.ts +30 -0
- package/server/utils/revenexxApi.ts +136 -0
- package/server/utils/schemaService.ts +25 -0
- package/server/utils/serviceMode.ts +70 -0
- package/server/utils/shippingService.ts +14 -0
- package/server/utils/shopSdk.ts +88 -0
- package/server/utils/themeService.ts +16 -0
- package/server/utils/typesense.ts +27 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"search": {
|
|
3
|
+
"input": {
|
|
4
|
+
"placeholder": "Search products, SKUs...",
|
|
5
|
+
"scan": "Scan code",
|
|
6
|
+
"scanTitle": "Scan barcode / QR code",
|
|
7
|
+
"scanHint": "Point the camera at a barcode or QR code — the result becomes your search query."
|
|
8
|
+
},
|
|
9
|
+
"results": {
|
|
10
|
+
"title": "Search results for \"{query}\"",
|
|
11
|
+
"count": "{count} result | {count} results",
|
|
12
|
+
"error": "Search failed",
|
|
13
|
+
"emptyTitle": "No matches",
|
|
14
|
+
"emptyDescription": "Adjust your search term or filters — or browse the catalog.",
|
|
15
|
+
"initialTitle": "Search the catalog",
|
|
16
|
+
"initialDescription": "Enter a product name, article number or keyword above."
|
|
17
|
+
},
|
|
18
|
+
"sort": {
|
|
19
|
+
"label": "Sort",
|
|
20
|
+
"relevance": "Relevance",
|
|
21
|
+
"priceAsc": "Price: Low to High",
|
|
22
|
+
"priceDesc": "Price: High to Low",
|
|
23
|
+
"nameAsc": "Name A–Z",
|
|
24
|
+
"nameDesc": "Name Z–A"
|
|
25
|
+
},
|
|
26
|
+
"filters": {
|
|
27
|
+
"title": "Filters",
|
|
28
|
+
"clearAll": "Clear all",
|
|
29
|
+
"noFilters": "No filters available.",
|
|
30
|
+
"openDrawer": "Filter",
|
|
31
|
+
"facets": {
|
|
32
|
+
"category": "Category",
|
|
33
|
+
"subcategory": "Subcategory",
|
|
34
|
+
"available": "Availability",
|
|
35
|
+
"manufacturer": "Manufacturer"
|
|
36
|
+
},
|
|
37
|
+
"values": {
|
|
38
|
+
"inStock": "In Stock",
|
|
39
|
+
"outOfStock": "Out of Stock"
|
|
40
|
+
},
|
|
41
|
+
"price": {
|
|
42
|
+
"label": "Price (€)",
|
|
43
|
+
"min": "Min",
|
|
44
|
+
"max": "Max"
|
|
45
|
+
},
|
|
46
|
+
"removeFilter": "Remove filter {value}",
|
|
47
|
+
"showResults": "Show {count} result | Show {count} results"
|
|
48
|
+
},
|
|
49
|
+
"category": {
|
|
50
|
+
"searchPlaceholder": "Search within this category…",
|
|
51
|
+
"allProducts": "All products"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"required": "This field is required",
|
|
3
|
+
"maxLength": "Value must be at most {max} characters",
|
|
4
|
+
"minLength": "Value must be at least {min} characters",
|
|
5
|
+
"pattern": "Value does not match the required format",
|
|
6
|
+
"postalCodeFormat": "Invalid postal code format",
|
|
7
|
+
"ibanFormat": "Invalid IBAN format",
|
|
8
|
+
"termsRequired": "Please accept the Terms & Conditions",
|
|
9
|
+
"invalidPhoneNumber": "Phone number contains invalid characters",
|
|
10
|
+
"invalidCompanyName": "Company name contains invalid characters",
|
|
11
|
+
"invalidZipCode": "Invalid zip code (digits only, 3–5 characters)"
|
|
12
|
+
}
|
package/nuxt.config.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { readdirSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
import { defineNuxtConfig } from "nuxt/config";
|
|
7
|
+
|
|
8
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const require_ = createRequire(import.meta.url);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Auto-discovers the layer's locale files under i18n/locales/{code}/.
|
|
13
|
+
* Paths are layer-relative; @nuxtjs/i18n resolves them against this layer.
|
|
14
|
+
*/
|
|
15
|
+
function layerLocaleFiles(code: string): string[] {
|
|
16
|
+
return readdirSync(resolve(currentDir, "i18n/locales", code))
|
|
17
|
+
.filter(f => f.endsWith(".json"))
|
|
18
|
+
.sort()
|
|
19
|
+
.map(f => `${code}/${f}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Cover design system — Nuxt layer.
|
|
24
|
+
*
|
|
25
|
+
* Consumers add this layer via `extends: ["@revenexx/cover"]`.
|
|
26
|
+
* The layer contributes:
|
|
27
|
+
* - the storefront component library (app/components/**: ui, layout, product,
|
|
28
|
+
* cart, category, checkout, search, account) — auto-imported with the same
|
|
29
|
+
* names as before the extraction
|
|
30
|
+
* - storefront composables and Pinia stores (app/composables/**, including
|
|
31
|
+
* useAuthStore/useCartStore/useThemeStore — store files
|
|
32
|
+
* live in composables/ so core Nuxt auto-imports them from the layer)
|
|
33
|
+
* - the BFF API contract used by the composables (app/api/** path helpers);
|
|
34
|
+
* consuming apps implement these endpoints (the demo shop serves them from
|
|
35
|
+
* mock services, a production storefront from live platform data)
|
|
36
|
+
* - theming (app/config/themes.ts, palette.ts), shared config, validations,
|
|
37
|
+
* interfaces (importable via `@revenexx/cover/interfaces/*`)
|
|
38
|
+
* - the default shop layout (app/layouts/default.vue)
|
|
39
|
+
* - its i18n messages (i18n/locales/{de,en}/*.json), merged into the
|
|
40
|
+
* consuming app's locales by @nuxtjs/i18n layer support
|
|
41
|
+
*
|
|
42
|
+
* The consuming app is expected to provide the modules this layer relies on:
|
|
43
|
+
* @nuxt/ui, @pinia/nuxt, @nuxtjs/i18n and @nuxt/image.
|
|
44
|
+
*/
|
|
45
|
+
export default defineNuxtConfig({
|
|
46
|
+
$meta: {
|
|
47
|
+
name: "@revenexx/cover",
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
// Forms: FormKit renders the BFF-served form schemas (SSR-safe). The
|
|
51
|
+
// module and config ship with the layer so every consumer gets them.
|
|
52
|
+
modules: ["@formkit/nuxt"],
|
|
53
|
+
|
|
54
|
+
formkit: {
|
|
55
|
+
// autoImport's build-time transform produces conflicting sourcemaps in
|
|
56
|
+
// production builds (rollup "Multiple conflicting contents") — use the
|
|
57
|
+
// classic global plugin registration instead.
|
|
58
|
+
autoImport: false,
|
|
59
|
+
configFile: resolve(currentDir, "app/formkit.config.ts"),
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
// Defaults for the BFF's live backends — consuming apps override these
|
|
63
|
+
// via NUXT_* environment variables (web SDK identity, Typesense search).
|
|
64
|
+
runtimeConfig: {
|
|
65
|
+
webSdkApiUrl: "",
|
|
66
|
+
webSdkProject: "",
|
|
67
|
+
webSdkDevKey: "",
|
|
68
|
+
// Public revenexx API (gateway) — the live "api" service registry
|
|
69
|
+
// implementations. Stand-in for the generated web SDKs; the key is a
|
|
70
|
+
// tenant API key and stays server-side only.
|
|
71
|
+
revenexxApiUrl: "https://api.revenexx.com",
|
|
72
|
+
revenexxTenant: "",
|
|
73
|
+
revenexxApiKey: "",
|
|
74
|
+
typesenseHost: "localhost",
|
|
75
|
+
typesensePort: 8108,
|
|
76
|
+
typesenseProtocol: "http",
|
|
77
|
+
typesenseApiKey: "xyz",
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
css: [
|
|
81
|
+
resolve(currentDir, "app/assets/css/cover.css"),
|
|
82
|
+
// Camera overlay styles for the FormKit barcode input, resolved through
|
|
83
|
+
// the package's exports ("./genesis" IS the CSS asset) — works in any
|
|
84
|
+
// node_modules layout (pnpm workspace, npm-hoisted vendored split).
|
|
85
|
+
require_.resolve("@formkit/barcode/genesis"),
|
|
86
|
+
],
|
|
87
|
+
|
|
88
|
+
nitro: {
|
|
89
|
+
// Demo data, config and locale files ship as server assets so the BFF
|
|
90
|
+
// mock implementations work in every consuming app, in dev and build.
|
|
91
|
+
serverAssets: [
|
|
92
|
+
{ baseName: "cover-data", dir: resolve(currentDir, "server/data") },
|
|
93
|
+
{ baseName: "cover-config", dir: resolve(currentDir, "server/config") },
|
|
94
|
+
{ baseName: "cover-locales", dir: resolve(currentDir, "i18n/locales") },
|
|
95
|
+
],
|
|
96
|
+
// Static files the layer serves at the web root (e.g. the direct
|
|
97
|
+
// order templates) — layer public/ dirs are not merged automatically.
|
|
98
|
+
publicAssets: [
|
|
99
|
+
{ dir: resolve(currentDir, "public") },
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
i18n: {
|
|
104
|
+
locales: [
|
|
105
|
+
{ code: "de", language: "de-DE", files: layerLocaleFiles("de") },
|
|
106
|
+
{ code: "en", language: "en-US", files: layerLocaleFiles("en") },
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@revenexx/cover",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Cover \u2014 revenexx design system for Nuxt. Distributed as a Nuxt layer: generic UI components, theming tokens and stores shared by the demo shop, custom storefronts and the Blokkli theme.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./nuxt.config.ts",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "Revenexx",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/revenexx/cover.git",
|
|
12
|
+
"directory": "packages/cover"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"nuxt",
|
|
16
|
+
"nuxt-layer",
|
|
17
|
+
"vue",
|
|
18
|
+
"design-system",
|
|
19
|
+
"revenexx",
|
|
20
|
+
"cover"
|
|
21
|
+
],
|
|
22
|
+
"exports": {
|
|
23
|
+
".": "./nuxt.config.ts",
|
|
24
|
+
"./api/*": "./app/api/*.ts",
|
|
25
|
+
"./components/*": "./app/components/*",
|
|
26
|
+
"./composables/*": "./app/composables/*.ts",
|
|
27
|
+
"./config/*": "./app/config/*.ts",
|
|
28
|
+
"./interfaces/*": "./app/interfaces/*.ts",
|
|
29
|
+
"./shared/*": "./app/shared/*.ts",
|
|
30
|
+
"./validations/*": "./app/validations/*.ts"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@formkit/barcode": "^1.0.5",
|
|
34
|
+
"@formkit/core": "^1.6.0",
|
|
35
|
+
"@formkit/i18n": "^1.6.0",
|
|
36
|
+
"@formkit/nuxt": "^1.6.0",
|
|
37
|
+
"@formkit/themes": "^1.6.0",
|
|
38
|
+
"@formkit/vue": "^1.6.0",
|
|
39
|
+
"@iconify-json/lucide": "^1.2.111",
|
|
40
|
+
"@internationalized/date": "^3.12.0",
|
|
41
|
+
"@revenexx/sdk": "^0.0.2",
|
|
42
|
+
"@vueuse/core": "^14.3.0",
|
|
43
|
+
"typesense": "^3.0.5"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@nuxt/ui": "^4.5.1",
|
|
47
|
+
"pinia": "^3.0.4",
|
|
48
|
+
"vue": "^3.5.0",
|
|
49
|
+
"vue-i18n": "^11.0.0 || ^10.0.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"nuxt": "^4.4.2"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"registry": "https://registry.npmjs.org",
|
|
56
|
+
"access": "public"
|
|
57
|
+
},
|
|
58
|
+
"files": [
|
|
59
|
+
"app",
|
|
60
|
+
"i18n",
|
|
61
|
+
"server",
|
|
62
|
+
"public",
|
|
63
|
+
"nuxt.config.ts"
|
|
64
|
+
]
|
|
65
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" role="img" aria-label="No product image">
|
|
2
|
+
<rect width="400" height="400" fill="#f3f4f6"/>
|
|
3
|
+
<g fill="none" stroke="#9ca3af" stroke-width="10" stroke-linejoin="round" stroke-linecap="round">
|
|
4
|
+
<path d="M200 110 L300 160 L300 260 L200 310 L100 260 L100 160 Z"/>
|
|
5
|
+
<path d="M100 160 L200 210 L300 160"/>
|
|
6
|
+
<path d="M200 210 L200 310"/>
|
|
7
|
+
</g>
|
|
8
|
+
</svg>
|
|
Binary file
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { AccountAddress, AccountAddressListResponse } from "../../../../app/interfaces/account/address-list";
|
|
2
|
+
|
|
3
|
+
interface AddressListRaw {
|
|
4
|
+
userId: string;
|
|
5
|
+
addresses: AccountAddress[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default defineEventHandler(async (event): Promise<AccountAddressListResponse> => {
|
|
9
|
+
const id = getRouterParam(event, "id");
|
|
10
|
+
if (!id) {
|
|
11
|
+
throw createError({ statusCode: 400, statusMessage: "Missing address id" });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let data: AddressListRaw;
|
|
15
|
+
try {
|
|
16
|
+
data = await readCoverMutableJson<AddressListRaw>("account/address-list.json");
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw createError({ statusCode: 500, statusMessage: "Address store unavailable" });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const address = data.addresses.find(a => a.id === id);
|
|
23
|
+
if (!address) {
|
|
24
|
+
throw createError({ statusCode: 404, statusMessage: "Address not found" });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const billingCount = data.addresses.filter(a => a.type === "billing").length;
|
|
28
|
+
if (address.type === "billing" && billingCount <= 1) {
|
|
29
|
+
throw createError({ statusCode: 400, statusMessage: "Cannot delete the last billing address" });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let remaining = data.addresses.filter(a => a.id !== id);
|
|
33
|
+
|
|
34
|
+
// If deletion leaves no billing address, promote first shipping to billing + isDefault
|
|
35
|
+
const hasBilling = remaining.some(a => a.type === "billing");
|
|
36
|
+
if (!hasBilling && remaining.length > 0) {
|
|
37
|
+
const firstShippingIdx = remaining.findIndex(a => a.type === "shipping");
|
|
38
|
+
if (firstShippingIdx !== -1) {
|
|
39
|
+
remaining = remaining.map((a, i) => {
|
|
40
|
+
if (i === firstShippingIdx) {
|
|
41
|
+
return { ...a, type: "billing" as const, isDefault: true };
|
|
42
|
+
}
|
|
43
|
+
return a;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await writeCoverMutableJson("account/address-list.json", { ...data, addresses: remaining });
|
|
49
|
+
|
|
50
|
+
return { addresses: remaining };
|
|
51
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { AccountAddress, AccountAddressType } from "../../../../app/interfaces/account/address-list";
|
|
2
|
+
|
|
3
|
+
interface AddressListRaw {
|
|
4
|
+
userId: string;
|
|
5
|
+
addresses: AccountAddress[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface AddressUpdateBody {
|
|
9
|
+
readonly company?: unknown;
|
|
10
|
+
readonly label?: unknown;
|
|
11
|
+
readonly firstName?: unknown;
|
|
12
|
+
readonly lastName?: unknown;
|
|
13
|
+
readonly street?: unknown;
|
|
14
|
+
readonly streetNumber?: unknown;
|
|
15
|
+
readonly city?: unknown;
|
|
16
|
+
readonly zip?: unknown;
|
|
17
|
+
readonly country?: unknown;
|
|
18
|
+
readonly type?: unknown;
|
|
19
|
+
readonly isDefault?: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function requireString(value: unknown, field: string): string {
|
|
23
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
24
|
+
throw createError({ statusCode: 422, statusMessage: `${field} is required` });
|
|
25
|
+
}
|
|
26
|
+
return value.trim();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default defineEventHandler(async (event): Promise<AccountAddress> => {
|
|
30
|
+
const id = getRouterParam(event, "id");
|
|
31
|
+
if (!id) {
|
|
32
|
+
throw createError({ statusCode: 400, statusMessage: "Missing address id" });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const body = await readBody<AddressUpdateBody>(event);
|
|
36
|
+
|
|
37
|
+
const company = typeof body.company === "string" ? body.company.trim() : "";
|
|
38
|
+
const label = requireString(body.label, "label");
|
|
39
|
+
const firstName = requireString(body.firstName, "firstName");
|
|
40
|
+
const lastName = requireString(body.lastName, "lastName");
|
|
41
|
+
const street = requireString(body.street, "street");
|
|
42
|
+
const streetNumber = requireString(body.streetNumber, "streetNumber");
|
|
43
|
+
const city = requireString(body.city, "city");
|
|
44
|
+
const zip = requireString(body.zip, "zip");
|
|
45
|
+
const country = requireString(body.country, "country");
|
|
46
|
+
|
|
47
|
+
if (body.type !== "billing" && body.type !== "shipping") {
|
|
48
|
+
throw createError({ statusCode: 422, statusMessage: "Invalid address type" });
|
|
49
|
+
}
|
|
50
|
+
const type = body.type as AccountAddressType;
|
|
51
|
+
const isDefault = body.isDefault === true;
|
|
52
|
+
|
|
53
|
+
let data: AddressListRaw;
|
|
54
|
+
try {
|
|
55
|
+
data = await readCoverMutableJson<AddressListRaw>("account/address-list.json");
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
throw createError({ statusCode: 500, statusMessage: "Address store unavailable" });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const index = data.addresses.findIndex(a => a.id === id);
|
|
62
|
+
if (index === -1) {
|
|
63
|
+
throw createError({ statusCode: 404, statusMessage: "Address not found" });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// When setting isDefault, clear it from other addresses of the same type
|
|
67
|
+
const updatedAddresses: AccountAddress[] = data.addresses.map((a, i) => {
|
|
68
|
+
if (i === index) {
|
|
69
|
+
return {
|
|
70
|
+
...a, company, label, firstName, lastName,
|
|
71
|
+
street, streetNumber, city, zip, country, type, isDefault,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (isDefault && a.type === type) {
|
|
75
|
+
return { ...a, isDefault: false };
|
|
76
|
+
}
|
|
77
|
+
return a;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await writeCoverMutableJson("account/address-list.json", { ...data, addresses: updatedAddresses });
|
|
81
|
+
|
|
82
|
+
return updatedAddresses[index]!;
|
|
83
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AccountAddress,
|
|
3
|
+
AccountAddressFormState,
|
|
4
|
+
} from "../../../../app/interfaces/account/address-list";
|
|
5
|
+
|
|
6
|
+
interface AddressListRaw {
|
|
7
|
+
userId: string;
|
|
8
|
+
addresses: AccountAddress[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default defineEventHandler(async (event): Promise<AccountAddress> => {
|
|
12
|
+
const body = await readBody<AccountAddressFormState>(event);
|
|
13
|
+
|
|
14
|
+
const required = [
|
|
15
|
+
"label", "firstName", "lastName", "street", "streetNumber", "city", "zip", "country", "type",
|
|
16
|
+
] as const;
|
|
17
|
+
for (const field of required) {
|
|
18
|
+
if (!body[field] || String(body[field]).trim().length === 0) {
|
|
19
|
+
throw createError({ statusCode: 400, statusMessage: `Missing required field: ${field}` });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let data: AddressListRaw;
|
|
24
|
+
try {
|
|
25
|
+
data = await readCoverMutableJson<AddressListRaw>("account/address-list.json");
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
throw createError({ statusCode: 500, statusMessage: "Address store unavailable" });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const sameType = data.addresses.filter(a => a.type === body.type);
|
|
32
|
+
const isDefault = body.isDefault || sameType.length === 0;
|
|
33
|
+
|
|
34
|
+
const newAddress: AccountAddress = {
|
|
35
|
+
id: crypto.randomUUID(),
|
|
36
|
+
company: body.company ?? "",
|
|
37
|
+
label: body.label.trim(),
|
|
38
|
+
firstName: body.firstName.trim(),
|
|
39
|
+
lastName: body.lastName.trim(),
|
|
40
|
+
street: body.street.trim(),
|
|
41
|
+
streetNumber: body.streetNumber.trim(),
|
|
42
|
+
city: body.city.trim(),
|
|
43
|
+
zip: body.zip.trim(),
|
|
44
|
+
country: body.country,
|
|
45
|
+
type: body.type,
|
|
46
|
+
isDefault,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
let addresses = [...data.addresses];
|
|
50
|
+
if (isDefault) {
|
|
51
|
+
addresses = addresses.map(a =>
|
|
52
|
+
a.type === newAddress.type ? { ...a, isDefault: false } : a,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
addresses.push(newAddress);
|
|
56
|
+
|
|
57
|
+
await writeCoverMutableJson("account/address-list.json", { ...data, addresses });
|
|
58
|
+
|
|
59
|
+
return newAddress;
|
|
60
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
|
|
2
|
+
interface AccountAddress {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly company: string;
|
|
5
|
+
readonly label: string;
|
|
6
|
+
readonly firstName: string;
|
|
7
|
+
readonly lastName: string;
|
|
8
|
+
readonly street: string;
|
|
9
|
+
readonly streetNumber: string;
|
|
10
|
+
readonly city: string;
|
|
11
|
+
readonly zip: string;
|
|
12
|
+
readonly country: string;
|
|
13
|
+
readonly isDefault: boolean;
|
|
14
|
+
readonly type: "billing" | "shipping";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface AccountAddressListResponse {
|
|
18
|
+
readonly addresses: AccountAddress[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default defineEventHandler((): AccountAddressListResponse => {
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
return readCoverMutableJson<AccountAddressListResponse>("account/address-list.json");
|
|
25
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-user B2B context: organization settings, assigned cost centers,
|
|
3
|
+
* spending limits and workflow memberships. Guests receive the
|
|
4
|
+
* organization defaults with empty assignments.
|
|
5
|
+
*/
|
|
6
|
+
export default defineEventHandler(async (event) => {
|
|
7
|
+
const user = await getAuthService(event).me(event);
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
return await getB2BContextService(event).getContext(user?.$id ?? null);
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
getLogService().error("Service error: account/context", toErrorContext(err));
|
|
14
|
+
throw createError({ statusCode: 500, message: "Internal server error" });
|
|
15
|
+
}
|
|
16
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
type Domain = "cost-centers" | "workflows";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Updates a governance record: created records are edited in place,
|
|
5
|
+
* bundled base records receive an overlay patch by id.
|
|
6
|
+
*/
|
|
7
|
+
export default defineEventHandler(async (event) => {
|
|
8
|
+
const user = await getAuthService(event).me(event);
|
|
9
|
+
if (!user) {
|
|
10
|
+
throw createError({ statusCode: 401, message: "Unauthorized" });
|
|
11
|
+
}
|
|
12
|
+
if (user.role === "requester") {
|
|
13
|
+
throw createError({ statusCode: 403, message: "Requesters cannot manage governance" });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const domain = getRouterParam(event, "domain") as Domain;
|
|
17
|
+
const id = getRouterParam(event, "id") ?? "";
|
|
18
|
+
if (!["cost-centers", "workflows"].includes(domain) || !id) {
|
|
19
|
+
throw createError({ statusCode: 404, message: "Unknown governance record" });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const patch = await readBody<Record<string, unknown>>(event);
|
|
23
|
+
const overrides = await readGovernanceOverrides();
|
|
24
|
+
|
|
25
|
+
if (domain === "cost-centers") {
|
|
26
|
+
const own = overrides.costCenters.find(cc => cc.id === id);
|
|
27
|
+
if (own) {
|
|
28
|
+
Object.assign(own, { label: patch.label ?? own.label, code: patch.code ?? own.code });
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
overrides.costCenterPatches[id] = {
|
|
32
|
+
...overrides.costCenterPatches[id],
|
|
33
|
+
...(patch.label ? { label: String(patch.label) } : {}),
|
|
34
|
+
...(patch.code ? { code: String(patch.code) } : {}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const own = overrides.workflows.find(wf => wf.id === id);
|
|
40
|
+
if (own) {
|
|
41
|
+
Object.assign(own, {
|
|
42
|
+
name: patch.name ?? own.name,
|
|
43
|
+
steps: patch.steps ?? own.steps,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
overrides.workflowPatches[id] = {
|
|
48
|
+
...overrides.workflowPatches[id],
|
|
49
|
+
...(patch.name ? { name: String(patch.name) } : {}),
|
|
50
|
+
...(patch.steps ? { steps: patch.steps as never } : {}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await writeGovernanceOverrides(overrides);
|
|
56
|
+
return { ok: true };
|
|
57
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { ApprovalLimit, ApprovalWorkflow, CostCenter } from "../../../../app/interfaces/b2b";
|
|
2
|
+
|
|
3
|
+
type Domain = "cost-centers" | "approval-limits" | "workflows";
|
|
4
|
+
|
|
5
|
+
const DOMAINS: Domain[] = ["cost-centers", "approval-limits", "workflows"];
|
|
6
|
+
|
|
7
|
+
const uid = (prefix: string) => `${prefix}-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 5)}`;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a governance record from the account area (demo persistence in
|
|
11
|
+
* the mutable overrides store). Admins and approvers manage governance;
|
|
12
|
+
* buyers may create cost centers.
|
|
13
|
+
*/
|
|
14
|
+
export default defineEventHandler(async (event) => {
|
|
15
|
+
const user = await getAuthService(event).me(event);
|
|
16
|
+
if (!user) {
|
|
17
|
+
throw createError({ statusCode: 401, message: "Unauthorized" });
|
|
18
|
+
}
|
|
19
|
+
if (user.role === "requester") {
|
|
20
|
+
throw createError({ statusCode: 403, message: "Requesters cannot manage governance" });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const domain = getRouterParam(event, "domain") as Domain;
|
|
24
|
+
if (!DOMAINS.includes(domain)) {
|
|
25
|
+
throw createError({ statusCode: 404, message: "Unknown governance domain" });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const body = await readBody<Record<string, unknown>>(event);
|
|
29
|
+
const overrides = await readGovernanceOverrides();
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
if (domain === "cost-centers") {
|
|
33
|
+
const record: CostCenter = {
|
|
34
|
+
id: uid("cc"),
|
|
35
|
+
code: String(body.code ?? "").trim(),
|
|
36
|
+
label: String(body.label ?? "").trim(),
|
|
37
|
+
};
|
|
38
|
+
if (!record.code || !record.label) {
|
|
39
|
+
throw createError({ statusCode: 400, message: "code and label required" });
|
|
40
|
+
}
|
|
41
|
+
if (typeof body.totalBudget === "number" && body.totalBudget > 0) {
|
|
42
|
+
record.limit = {
|
|
43
|
+
id: uid("lim"),
|
|
44
|
+
type: "budget",
|
|
45
|
+
subject: { type: "cost-center", costCenterId: record.id },
|
|
46
|
+
label: String(body.limitLabel ?? `${record.label} Budget`),
|
|
47
|
+
totalBudget: body.totalBudget,
|
|
48
|
+
availableBudget: typeof body.availableBudget === "number"
|
|
49
|
+
? body.availableBudget
|
|
50
|
+
: body.totalBudget,
|
|
51
|
+
currency: "EUR",
|
|
52
|
+
active: true,
|
|
53
|
+
approval: (body.approval ?? { required: false }) as ApprovalLimit["approval"],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
overrides.costCenters.push(record);
|
|
57
|
+
await writeGovernanceOverrides(overrides);
|
|
58
|
+
return record;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (domain === "approval-limits") {
|
|
62
|
+
const type = String(body.type ?? "");
|
|
63
|
+
if (!["budget", "order-value", "unit-price", "non-catalog"].includes(type)) {
|
|
64
|
+
throw createError({ statusCode: 400, message: "invalid limit type" });
|
|
65
|
+
}
|
|
66
|
+
const record: ApprovalLimit = {
|
|
67
|
+
id: uid("lim"),
|
|
68
|
+
type: type as ApprovalLimit["type"],
|
|
69
|
+
subject: { type: "user", userId: String(body.userId ?? user.$id) },
|
|
70
|
+
label: String(body.label ?? "").trim(),
|
|
71
|
+
...(typeof body.maxAmount === "number" ? { maxAmount: body.maxAmount } : {}),
|
|
72
|
+
...(typeof body.totalBudget === "number"
|
|
73
|
+
? { totalBudget: body.totalBudget, availableBudget: body.totalBudget }
|
|
74
|
+
: {}),
|
|
75
|
+
currency: "EUR",
|
|
76
|
+
active: true,
|
|
77
|
+
approval: (body.approval ?? { required: false }) as ApprovalLimit["approval"],
|
|
78
|
+
};
|
|
79
|
+
if (!record.label) {
|
|
80
|
+
throw createError({ statusCode: 400, message: "label required" });
|
|
81
|
+
}
|
|
82
|
+
overrides.approvalLimits.push(record);
|
|
83
|
+
await writeGovernanceOverrides(overrides);
|
|
84
|
+
return record;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// workflows
|
|
88
|
+
const steps = (body.steps as ApprovalWorkflow["steps"]) ?? [];
|
|
89
|
+
const record: ApprovalWorkflow = {
|
|
90
|
+
id: uid("wf"),
|
|
91
|
+
name: String(body.name ?? "").trim(),
|
|
92
|
+
steps,
|
|
93
|
+
};
|
|
94
|
+
if (!record.name || steps.length < 2 || steps[steps.length - 1]?.type !== "order") {
|
|
95
|
+
throw createError({
|
|
96
|
+
statusCode: 400,
|
|
97
|
+
message: "name and at least one request step ending in an order step required",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
overrides.workflows.push(record);
|
|
101
|
+
await writeGovernanceOverrides(overrides);
|
|
102
|
+
return record;
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
if (err !== null && typeof err === "object" && "statusCode" in err) {
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
getLogService().error("Service error: governance/create", toErrorContext(err));
|
|
109
|
+
throw createError({ statusCode: 500, message: "Internal server error" });
|
|
110
|
+
}
|
|
111
|
+
});
|