@revenexx/cover 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/app/app.config.ts +1 -1
  2. package/app/components/account/AccountSidebar.vue +10 -1
  3. package/app/components/account/address/AccountAddressCard.vue +69 -88
  4. package/app/components/auth/AuthRegisterPanel.vue +122 -194
  5. package/app/formkit.config.ts +21 -0
  6. package/app/interfaces/auth.ts +0 -2
  7. package/app/interfaces/validation.ts +1 -1
  8. package/app/validations/formValidationConfig.ts +0 -30
  9. package/nuxt.config.ts +7 -3
  10. package/package.json +2 -2
  11. package/server/api/account/orders.get.ts +6 -5
  12. package/server/api/account/profile.get.ts +3 -3
  13. package/server/api/account/profile.put.ts +6 -4
  14. package/server/api/auth/login.post.ts +2 -5
  15. package/server/api/auth/recovery.post.ts +5 -13
  16. package/server/api/auth/recovery.put.ts +5 -14
  17. package/server/api/auth/register.post.ts +21 -44
  18. package/server/api/orders/index.post.ts +28 -24
  19. package/server/api/payment/methods.post.ts +5 -4
  20. package/server/api/shipping/rates.post.ts +6 -4
  21. package/server/interfaces/auth.ts +1 -1
  22. package/server/services/ApiAccountService.ts +44 -0
  23. package/server/services/ApiAuthService.ts +15 -14
  24. package/server/services/ApiCartService.ts +45 -27
  25. package/server/services/ApiMarketService.ts +11 -9
  26. package/server/utils/accountService.ts +4 -5
  27. package/server/utils/authService.ts +0 -3
  28. package/server/utils/liveCatalog.ts +30 -17
  29. package/server/utils/liveInventories.ts +4 -4
  30. package/server/utils/liveOrders.ts +5 -3
  31. package/server/utils/livePrices.ts +10 -9
  32. package/server/utils/revenexxSdk.ts +213 -0
  33. package/app/validations/companyName.ts +0 -18
  34. package/app/validations/maxLength.ts +0 -12
  35. package/app/validations/optionalCompanyName.ts +0 -23
  36. package/app/validations/phoneNumber.ts +0 -19
  37. package/app/validations/termsRequired.ts +0 -4
  38. package/app/validations/zipCode.ts +0 -15
  39. package/server/services/SdkAccountService.ts +0 -56
  40. package/server/services/SdkAuthService.ts +0 -83
  41. package/server/utils/revenexxApi.ts +0 -136
  42. package/server/utils/shopSdk.ts +0 -88
package/app/app.config.ts CHANGED
@@ -4,7 +4,7 @@ import { DEFAULT_ICON_WEIGHT, nuxtUiIcons } from "./config/icons";
4
4
  * Layer defaults for the BFF service registry. Every server data domain is
5
5
  * resolved through a registry key — `mock` serves the bundled demo data.
6
6
  * Consuming apps override individual keys in their own app.config.ts to
7
- * switch a domain to a live implementation (e.g. accountService: "sdk").
7
+ * switch a domain to a live implementation (e.g. accountService: "api").
8
8
  *
9
9
  * `ui.icons` maps Nuxt UI's built-in icons to Solar in the default weight;
10
10
  * the theme store re-applies them with the active theme's weight at runtime.
@@ -1,8 +1,17 @@
1
1
  <script setup lang="ts">
2
2
  import { accountNavigation } from "../../config/navigation";
3
+ import type { AccountNavGroup } from "../../config/navigation";
3
4
 
4
5
  const { icon } = useIcons();
5
6
 
7
+ // Consumers can replace the full B2B navigation tree with their own
8
+ // (e.g. a lean storefront theme exposing only orders + profile) via
9
+ // app.config — `accountNavigation: AccountNavGroup[]`.
10
+ const appConfig = useAppConfig();
11
+ const navigation = computed<AccountNavGroup[]>(() =>
12
+ (appConfig.accountNavigation as AccountNavGroup[] | undefined) ?? accountNavigation,
13
+ );
14
+
6
15
  /**
7
16
  * The full B2B account navigation tree (structure from the relation
8
17
  * prototype). Implemented entries route to real pages, the rest leads to the
@@ -41,7 +50,7 @@ function isActive(to: string): boolean {
41
50
  >
42
51
  <ul class="divide-y divide-default">
43
52
  <li
44
- v-for="group in accountNavigation"
53
+ v-for="group in navigation"
45
54
  :key="group.labelKey"
46
55
  class="py-4"
47
56
  >
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import type { AccountAddress, AccountAddressFormState } from "../../../interfaces/account/address-list";
3
+ import { COUNTRIES } from "../../../config/countries";
3
4
 
4
5
  const { icon } = useIcons();
5
6
 
@@ -23,7 +24,6 @@ const emit = defineEmits<{
23
24
  }>();
24
25
 
25
26
  const { t } = useI18n();
26
- const { validate: validateAddress } = useFormValidation<AccountAddressFormState>("accountAddress");
27
27
 
28
28
  const confirmDeleteOpen = ref(false);
29
29
 
@@ -120,116 +120,97 @@ function confirmDelete() {
120
120
  <div>{{ address.country }}</div>
121
121
  </address>
122
122
 
123
- <UForm
123
+ <FormKit
124
124
  v-else
125
- :state="formState"
126
- :validate="validateAddress"
127
- class="space-y-4"
125
+ type="form"
126
+ :actions="false"
128
127
  @submit="emit('submit')"
129
128
  >
130
129
  <div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
131
- <UFormField
130
+ <FormKit
131
+ v-model="formState.label"
132
+ type="text"
132
133
  :label="t('addresses.fields.label')"
133
134
  name="label"
134
- class="sm:col-span-2"
135
- >
136
- <UInput
137
- v-model="formState.label"
138
- class="w-full"
139
- />
140
- </UFormField>
141
- <UFormField
135
+ validation="required|length:2,200"
136
+ outer-class="sm:col-span-2"
137
+ />
138
+ <FormKit
139
+ v-model="formState.company"
140
+ type="text"
142
141
  :label="t('addresses.fields.company')"
143
142
  name="company"
144
- class="sm:col-span-2"
145
- >
146
- <UInput
147
- v-model="formState.company"
148
- autocomplete="organization"
149
- class="w-full"
150
- />
151
- </UFormField>
152
- <UFormField
143
+ validation="companyName"
144
+ :validation-messages="{ companyName: t('validation.invalidCompanyName') }"
145
+ autocomplete="organization"
146
+ outer-class="sm:col-span-2"
147
+ />
148
+ <FormKit
149
+ v-model="formState.firstName"
150
+ type="text"
153
151
  :label="t('addresses.fields.firstName')"
154
152
  name="firstName"
155
- >
156
- <UInput
157
- v-model="formState.firstName"
158
- autocomplete="given-name"
159
- class="w-full"
160
- />
161
- </UFormField>
162
- <UFormField
153
+ validation="required|length:2,100"
154
+ autocomplete="given-name"
155
+ />
156
+ <FormKit
157
+ v-model="formState.lastName"
158
+ type="text"
163
159
  :label="t('addresses.fields.lastName')"
164
160
  name="lastName"
165
- >
166
- <UInput
167
- v-model="formState.lastName"
168
- autocomplete="family-name"
169
- class="w-full"
170
- />
171
- </UFormField>
172
- <UFormField
161
+ validation="required|length:2,100"
162
+ autocomplete="family-name"
163
+ />
164
+ <FormKit
165
+ v-model="formState.street"
166
+ type="text"
173
167
  :label="t('addresses.fields.street')"
174
168
  name="street"
175
- >
176
- <UInput
177
- v-model="formState.street"
178
- autocomplete="street-address"
179
- class="w-full"
180
- />
181
- </UFormField>
182
- <UFormField
169
+ validation="required|length:2,200"
170
+ autocomplete="street-address"
171
+ />
172
+ <FormKit
173
+ v-model="formState.streetNumber"
174
+ type="text"
183
175
  :label="t('addresses.fields.streetNumber')"
184
176
  name="streetNumber"
185
- >
186
- <UInput
187
- v-model="formState.streetNumber"
188
- class="w-full"
189
- />
190
- </UFormField>
191
- <UFormField
177
+ validation="required"
178
+ />
179
+ <FormKit
180
+ v-model="formState.zip"
181
+ type="text"
192
182
  :label="t('addresses.fields.zip')"
193
183
  name="zip"
194
- >
195
- <UInput
196
- v-model="formState.zip"
197
- autocomplete="postal-code"
198
- class="w-full"
199
- />
200
- </UFormField>
201
- <UFormField
184
+ validation="required|zip"
185
+ :validation-messages="{ zip: t('validation.invalidZipCode') }"
186
+ autocomplete="postal-code"
187
+ />
188
+ <FormKit
189
+ v-model="formState.city"
190
+ type="text"
202
191
  :label="t('addresses.fields.city')"
203
192
  name="city"
204
- >
205
- <UInput
206
- v-model="formState.city"
207
- autocomplete="address-level2"
208
- class="w-full"
209
- />
210
- </UFormField>
211
- <UFormField
193
+ validation="required|length:2,100"
194
+ autocomplete="address-level2"
195
+ />
196
+ <FormKit
197
+ v-model="formState.country"
198
+ type="select"
212
199
  :label="t('addresses.fields.country')"
213
200
  name="country"
214
- class="sm:col-span-2"
215
- >
216
- <UiFormsCountrySelection
217
- v-model="formState.country"
218
- class="w-full"
219
- />
220
- </UFormField>
221
- <UFormField
201
+ validation="required"
202
+ :options="COUNTRIES"
203
+ outer-class="sm:col-span-2"
204
+ />
205
+ <FormKit
206
+ v-model="formState.type"
207
+ type="select"
222
208
  :label="t('addresses.fields.type')"
223
209
  name="type"
224
- class="sm:col-span-2"
225
- >
226
- <USelect
227
- v-model="formState.type"
228
- :items="typeOptions"
229
- value-key="value"
230
- class="w-full"
231
- />
232
- </UFormField>
210
+ validation="required"
211
+ :options="typeOptions"
212
+ outer-class="sm:col-span-2"
213
+ />
233
214
  <div class="sm:col-span-2">
234
215
  <UCheckbox
235
216
  :model-value="formState.isDefault"
@@ -256,7 +237,7 @@ function confirmDelete() {
256
237
  :label="t('addresses.actions.save')"
257
238
  />
258
239
  </div>
259
- </UForm>
240
+ </FormKit>
260
241
  </UCard>
261
242
 
262
243
  <UModal
@@ -1,9 +1,5 @@
1
1
  <script setup lang="ts">
2
2
  import { COUNTRIES } from "../../config/countries";
3
- import { required } from "../../validations/required";
4
- import { zipCode } from "../../validations/zipCode";
5
- import type { FormError } from "@nuxt/ui";
6
-
7
3
  import type { RegisterFormState, RegistrationMode, RegistrationProfile } from "../../interfaces/auth";
8
4
 
9
5
  /**
@@ -47,36 +43,13 @@ const modes: { value: RegistrationMode; icon: string }[] = [
47
43
  { value: "existing", icon: "key" },
48
44
  ];
49
45
 
50
- const salutationItems = computed(() =>
51
- (["mr", "ms", "none"] as const).map(value => ({
46
+ const salutationItems = computed(() => [
47
+ { value: "", label: "" },
48
+ ...(["mr", "ms", "none"] as const).map(value => ({
52
49
  value,
53
50
  label: t(`auth.register.fields2.salutations.${value}`),
54
- })));
55
-
56
- const countryItems = COUNTRIES;
57
-
58
- /* ---- validation: static base + mode-dependent fields ------------------- */
59
- const { validate: validateBase } = useFormValidation<RegisterFormState>("register");
60
-
61
- function validate(formState: RegisterFormState): FormError[] {
62
- const errors = validateBase(formState);
63
- const push = (name: string, key: string | null) => {
64
- if (key !== null) {
65
- errors.push({ name, message: t(key) });
66
- }
67
- };
68
- if (formState.mode === "new") {
69
- push("street", required(formState.street));
70
- push("streetNumber", required(formState.streetNumber));
71
- push("zip", required(formState.zip) ?? zipCode(formState.zip));
72
- push("city", required(formState.city));
73
- push("country", required(formState.country));
74
- }
75
- else {
76
- push("customerNumber", required(formState.customerNumber));
77
- }
78
- return errors;
79
- }
51
+ })),
52
+ ]);
80
53
 
81
54
  function buildProfile(): RegistrationProfile {
82
55
  return {
@@ -164,9 +137,9 @@ async function onSubmit(): Promise<void> {
164
137
  </div>
165
138
  </fieldset>
166
139
 
167
- <UForm
168
- :state="state"
169
- :validate="validate"
140
+ <FormKit
141
+ type="form"
142
+ :actions="false"
170
143
  class="space-y-7 pt-7"
171
144
  @submit="onSubmit"
172
145
  >
@@ -175,46 +148,38 @@ async function onSubmit(): Promise<void> {
175
148
  <h3 class="text-xs font-semibold uppercase tracking-wide text-muted">
176
149
  {{ t('auth.register.sections.company') }}
177
150
  </h3>
178
- <UFormField
151
+ <FormKit
152
+ v-model="state.company"
153
+ type="text"
179
154
  :label="t('auth.register.fields.company')"
180
155
  name="company"
181
- required
182
- :hint="t('auth.register.companyRequiredHint')"
183
- >
184
- <UInput
185
- v-model="state.company"
186
- autocomplete="organization"
187
- class="w-full"
188
- />
189
- </UFormField>
156
+ validation="required|companyName"
157
+ :validation-messages="{ companyName: t('validation.invalidCompanyName') }"
158
+ :help="t('auth.register.companyRequiredHint')"
159
+ autocomplete="organization"
160
+ />
190
161
  <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
191
- <UFormField
162
+ <FormKit
192
163
  v-if="state.mode === 'existing'"
164
+ v-model="state.customerNumber"
165
+ type="text"
193
166
  :label="t('auth.register.fields2.customerNumber')"
194
167
  name="customerNumber"
195
- required
196
- :hint="t('auth.register.fields2.customerNumberHint')"
197
- >
198
- <UInput
199
- v-model="state.customerNumber"
200
- class="w-full"
201
- />
202
- </UFormField>
203
- <UFormField
168
+ validation="required"
169
+ :help="t('auth.register.fields2.customerNumberHint')"
170
+ />
171
+ <FormKit
172
+ v-model="state.vatId"
173
+ type="text"
204
174
  :label="t('auth.register.fields2.vatId')"
205
175
  name="vatId"
206
- :hint="t('auth.register.fields2.vatIdHint')"
207
- >
208
- <UInput
209
- v-model="state.vatId"
210
- class="w-full"
211
- placeholder="DE123456789"
212
- />
213
- </UFormField>
176
+ :help="t('auth.register.fields2.vatIdHint')"
177
+ placeholder="DE123456789"
178
+ />
214
179
  </div>
215
180
  </section>
216
181
 
217
- <!-- Billing address (new customers) -->
182
+ <!-- Billing address (new customers only) -->
218
183
  <section
219
184
  v-if="state.mode === 'new'"
220
185
  class="space-y-4"
@@ -223,66 +188,52 @@ async function onSubmit(): Promise<void> {
223
188
  {{ t('auth.register.sections.address') }}
224
189
  </h3>
225
190
  <div class="grid grid-cols-3 gap-4">
226
- <UFormField
191
+ <FormKit
192
+ v-model="state.street"
193
+ type="text"
227
194
  :label="t('auth.register.fields2.street')"
228
195
  name="street"
229
- required
230
- class="col-span-2"
231
- >
232
- <UInput
233
- v-model="state.street"
234
- autocomplete="address-line1"
235
- class="w-full"
236
- />
237
- </UFormField>
238
- <UFormField
196
+ validation="required"
197
+ autocomplete="address-line1"
198
+ outer-class="col-span-2"
199
+ />
200
+ <FormKit
201
+ v-model="state.streetNumber"
202
+ type="text"
239
203
  :label="t('auth.register.fields2.streetNumber')"
240
204
  name="streetNumber"
241
- required
242
- >
243
- <UInput
244
- v-model="state.streetNumber"
245
- autocomplete="address-line2"
246
- class="w-full"
247
- />
248
- </UFormField>
205
+ validation="required"
206
+ autocomplete="address-line2"
207
+ />
249
208
  </div>
250
209
  <div class="grid grid-cols-3 gap-4">
251
- <UFormField
210
+ <FormKit
211
+ v-model="state.zip"
212
+ type="text"
252
213
  :label="t('auth.register.fields2.zip')"
253
214
  name="zip"
254
- required
255
- >
256
- <UInput
257
- v-model="state.zip"
258
- autocomplete="postal-code"
259
- class="w-full"
260
- />
261
- </UFormField>
262
- <UFormField
215
+ validation="required|zip"
216
+ :validation-messages="{ zip: t('validation.invalidZipCode') }"
217
+ autocomplete="postal-code"
218
+ />
219
+ <FormKit
220
+ v-model="state.city"
221
+ type="text"
263
222
  :label="t('auth.register.fields2.city')"
264
223
  name="city"
265
- required
266
- class="col-span-2"
267
- >
268
- <UInput
269
- v-model="state.city"
270
- autocomplete="address-level2"
271
- class="w-full"
272
- />
273
- </UFormField>
224
+ validation="required"
225
+ autocomplete="address-level2"
226
+ outer-class="col-span-2"
227
+ />
274
228
  </div>
275
- <UFormField
229
+ <FormKit
230
+ v-model="state.country"
231
+ type="select"
276
232
  :label="t('auth.register.fields2.country')"
277
233
  name="country"
278
- required
279
- >
280
- <USelect
281
- v-model="state.country"
282
- :items="countryItems"
283
- class="w-full"
284
- />
285
- </UFormField>
234
+ validation="required"
235
+ :options="COUNTRIES"
236
+ />
286
237
  </section>
287
238
 
288
239
  <!-- Contact person -->
@@ -291,63 +242,47 @@ async function onSubmit(): Promise<void> {
291
242
  {{ t('auth.register.sections.contact') }}
292
243
  </h3>
293
244
  <div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
294
- <UFormField
245
+ <FormKit
246
+ v-model="state.salutation"
247
+ type="select"
295
248
  :label="t('auth.register.fields2.salutation')"
296
249
  name="salutation"
297
- >
298
- <USelect
299
- v-model="state.salutation"
300
- :items="salutationItems"
301
- class="w-full"
302
- />
303
- </UFormField>
304
- <UFormField
250
+ :options="salutationItems"
251
+ />
252
+ <FormKit
253
+ v-model="state.firstName"
254
+ type="text"
305
255
  :label="t('auth.register.fields.firstName')"
306
256
  name="firstName"
307
- required
308
- >
309
- <UInput
310
- v-model="state.firstName"
311
- autocomplete="given-name"
312
- class="w-full"
313
- />
314
- </UFormField>
315
- <UFormField
257
+ validation="required"
258
+ autocomplete="given-name"
259
+ />
260
+ <FormKit
261
+ v-model="state.lastName"
262
+ type="text"
316
263
  :label="t('auth.register.fields.lastName')"
317
264
  name="lastName"
318
- required
319
- >
320
- <UInput
321
- v-model="state.lastName"
322
- autocomplete="family-name"
323
- class="w-full"
324
- />
325
- </UFormField>
265
+ validation="required"
266
+ autocomplete="family-name"
267
+ />
326
268
  </div>
327
269
  <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
328
- <UFormField
270
+ <FormKit
271
+ v-model="state.jobTitle"
272
+ type="text"
329
273
  :label="t('auth.register.fields2.jobTitle')"
330
274
  name="jobTitle"
331
- :hint="t('auth.register.fields2.jobTitleHint')"
332
- >
333
- <UInput
334
- v-model="state.jobTitle"
335
- autocomplete="organization-title"
336
- class="w-full"
337
- />
338
- </UFormField>
339
- <UFormField
275
+ :help="t('auth.register.fields2.jobTitleHint')"
276
+ autocomplete="organization-title"
277
+ />
278
+ <FormKit
279
+ v-model="state.phone"
280
+ type="tel"
340
281
  :label="t('auth.register.fields2.phone')"
341
282
  name="phone"
342
- :hint="t('auth.register.fields2.phoneHint')"
343
- >
344
- <UInput
345
- v-model="state.phone"
346
- type="tel"
347
- autocomplete="tel"
348
- class="w-full"
349
- />
350
- </UFormField>
283
+ :help="t('auth.register.fields2.phoneHint')"
284
+ autocomplete="tel"
285
+ />
351
286
  </div>
352
287
  </section>
353
288
 
@@ -356,53 +291,46 @@ async function onSubmit(): Promise<void> {
356
291
  <h3 class="text-xs font-semibold uppercase tracking-wide text-muted">
357
292
  {{ t('auth.register.sections.account') }}
358
293
  </h3>
359
- <UFormField
294
+ <FormKit
295
+ v-model="state.email"
296
+ type="email"
360
297
  :label="t('auth.register.fields.email')"
361
298
  name="email"
362
- required
363
- >
364
- <UInput
365
- v-model="state.email"
366
- type="email"
367
- autocomplete="email"
368
- class="w-full"
369
- :placeholder="t('auth.register.fields.emailPlaceholder')"
370
- />
371
- </UFormField>
299
+ validation="required|email"
300
+ autocomplete="email"
301
+ :placeholder="t('auth.register.fields.emailPlaceholder')"
302
+ />
372
303
  <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
373
- <UFormField
304
+ <FormKit
305
+ v-model="state.password"
306
+ type="password"
374
307
  :label="t('auth.register.fields.password')"
375
308
  name="password"
376
- required
377
- >
378
- <UiFormsPasswordInput
379
- v-model="state.password"
380
- autocomplete="new-password"
381
- :placeholder="t('auth.register.fields.passwordPlaceholder')"
382
- show-strength
383
- />
384
- </UFormField>
385
- <UFormField
309
+ validation="required|length:8"
310
+ autocomplete="new-password"
311
+ :placeholder="t('auth.register.fields.passwordPlaceholder')"
312
+ />
313
+ <FormKit
314
+ v-model="state.passwordConfirm"
315
+ type="password"
386
316
  :label="t('auth.register.fields.passwordConfirm')"
387
317
  name="passwordConfirm"
388
- required
389
- >
390
- <UiFormsPasswordInput
391
- v-model="state.passwordConfirm"
392
- autocomplete="new-password"
393
- :placeholder="t('auth.register.fields.passwordConfirmPlaceholder')"
394
- />
395
- </UFormField>
318
+ validation="required|confirm:password"
319
+ autocomplete="new-password"
320
+ :placeholder="t('auth.register.fields.passwordConfirmPlaceholder')"
321
+ />
396
322
  </div>
397
323
  </section>
398
324
 
399
- <UFormField name="acceptTerms">
400
- <UCheckbox
401
- v-model="state.acceptTerms"
402
- :label="t('auth.register.fields.acceptTerms')"
403
- :description="t('auth.register.fields.acceptTermsDescription')"
404
- />
405
- </UFormField>
325
+ <FormKit
326
+ v-model="state.acceptTerms"
327
+ type="checkbox"
328
+ :label="t('auth.register.fields.acceptTerms')"
329
+ name="acceptTerms"
330
+ validation="accepted"
331
+ :validation-messages="{ accepted: t('validation.termsRequired') }"
332
+ :help="t('auth.register.fields.acceptTermsDescription')"
333
+ />
406
334
 
407
335
  <UAlert
408
336
  color="info"
@@ -426,6 +354,6 @@ async function onSubmit(): Promise<void> {
426
354
  :loading="loading"
427
355
  :label="t('auth.register.submit')"
428
356
  />
429
- </UForm>
357
+ </FormKit>
430
358
  </div>
431
359
  </template>