@revenexx/cover 0.1.13 → 0.1.15

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.
@@ -19,8 +19,6 @@ const state = reactive<ForgotPasswordFormState>({
19
19
  email: "",
20
20
  });
21
21
 
22
- const { validate } = useFormValidation<ForgotPasswordFormState>("forgotPassword");
23
-
24
22
  async function onSubmit(): Promise<void> {
25
23
  try {
26
24
  const url = new URL(localePath("/reset-password"), window.location.origin).toString();
@@ -39,25 +37,21 @@ async function onSubmit(): Promise<void> {
39
37
  {{ t('auth.forgotPassword.title') }}
40
38
  </h2>
41
39
 
42
- <UForm
43
- :state="state"
44
- :validate="validate"
40
+ <FormKit
41
+ type="form"
42
+ :actions="false"
45
43
  class="space-y-4 pt-6"
46
44
  @submit="onSubmit"
47
45
  >
48
- <UFormField
46
+ <FormKit
47
+ v-model="state.email"
48
+ type="email"
49
49
  :label="t('auth.forgotPassword.fields.email')"
50
50
  name="email"
51
- required
52
- >
53
- <UInput
54
- v-model="state.email"
55
- type="email"
56
- autocomplete="email"
57
- class="w-full"
58
- :placeholder="t('auth.forgotPassword.fields.emailPlaceholder')"
59
- />
60
- </UFormField>
51
+ validation="required|email"
52
+ autocomplete="email"
53
+ :placeholder="t('auth.forgotPassword.fields.emailPlaceholder')"
54
+ />
61
55
 
62
56
  <UAlert
63
57
  v-if="error"
@@ -83,6 +77,6 @@ async function onSubmit(): Promise<void> {
83
77
  {{ t('auth.forgotPassword.backToLogin') }}
84
78
  </NuxtLink>
85
79
  </div>
86
- </UForm>
80
+ </FormKit>
87
81
  </div>
88
82
  </template>
@@ -20,8 +20,6 @@ const state = reactive<LoginFormState>({
20
20
  password: "",
21
21
  });
22
22
 
23
- const { validate } = useFormValidation<LoginFormState>("login");
24
-
25
23
  async function onSubmit(): Promise<void> {
26
24
  try {
27
25
  await auth.login({
@@ -42,37 +40,31 @@ async function onSubmit(): Promise<void> {
42
40
  {{ t('auth.login.existingCustomer') }}
43
41
  </h2>
44
42
 
45
- <UForm
46
- :state="state"
47
- :validate="validate"
43
+ <FormKit
44
+ type="form"
45
+ :actions="false"
48
46
  class="space-y-4 pt-6"
49
47
  @submit="onSubmit"
50
48
  >
51
- <UFormField
49
+ <FormKit
50
+ v-model="state.email"
51
+ type="email"
52
52
  :label="t('auth.login.fields.email')"
53
53
  name="email"
54
- required
55
- >
56
- <UInput
57
- v-model="state.email"
58
- type="email"
59
- autocomplete="email"
60
- class="w-full"
61
- :placeholder="t('auth.login.fields.emailPlaceholder')"
62
- />
63
- </UFormField>
54
+ validation="required|email"
55
+ autocomplete="email"
56
+ :placeholder="t('auth.login.fields.emailPlaceholder')"
57
+ />
64
58
 
65
- <UFormField
59
+ <FormKit
60
+ v-model="state.password"
61
+ type="password"
66
62
  :label="t('auth.login.fields.password')"
67
63
  name="password"
68
- required
69
- >
70
- <UiFormsPasswordInput
71
- v-model="state.password"
72
- autocomplete="current-password"
73
- :placeholder="t('auth.login.fields.passwordPlaceholder')"
74
- />
75
- </UFormField>
64
+ validation="required|length:8"
65
+ autocomplete="current-password"
66
+ :placeholder="t('auth.login.fields.passwordPlaceholder')"
67
+ />
76
68
 
77
69
  <UAlert
78
70
  v-if="error"
@@ -98,6 +90,6 @@ async function onSubmit(): Promise<void> {
98
90
  {{ t('auth.login.forgotPassword') }}
99
91
  </NuxtLink>
100
92
  </div>
101
- </UForm>
93
+ </FormKit>
102
94
  </div>
103
95
  </template>
@@ -27,8 +27,6 @@ const state = reactive<ResetPasswordFormState>({
27
27
  passwordConfirm: "",
28
28
  });
29
29
 
30
- const { validate } = useFormValidation<ResetPasswordFormState>("resetPassword");
31
-
32
30
  async function onSubmit(): Promise<void> {
33
31
  try {
34
32
  await auth.confirmRecovery(props.userId, props.secret, state.password);
@@ -54,37 +52,32 @@ async function onSubmit(): Promise<void> {
54
52
  :title="t('auth.resetPassword.errors.invalidLink')"
55
53
  />
56
54
 
57
- <UForm
55
+ <FormKit
58
56
  v-else
59
- :state="state"
60
- :validate="validate"
57
+ type="form"
58
+ :actions="false"
61
59
  class="space-y-4 pt-6"
62
60
  @submit="onSubmit"
63
61
  >
64
- <UFormField
62
+ <FormKit
63
+ v-model="state.password"
64
+ type="password"
65
65
  :label="t('auth.resetPassword.fields.password')"
66
66
  name="password"
67
- required
68
- >
69
- <UiFormsPasswordInput
70
- v-model="state.password"
71
- autocomplete="new-password"
72
- :placeholder="t('auth.resetPassword.fields.passwordPlaceholder')"
73
- show-strength
74
- />
75
- </UFormField>
67
+ validation="required|length:8"
68
+ autocomplete="new-password"
69
+ :placeholder="t('auth.resetPassword.fields.passwordPlaceholder')"
70
+ />
76
71
 
77
- <UFormField
72
+ <FormKit
73
+ v-model="state.passwordConfirm"
74
+ type="password"
78
75
  :label="t('auth.resetPassword.fields.passwordConfirm')"
79
76
  name="passwordConfirm"
80
- required
81
- >
82
- <UiFormsPasswordInput
83
- v-model="state.passwordConfirm"
84
- autocomplete="new-password"
85
- :placeholder="t('auth.resetPassword.fields.passwordConfirmPlaceholder')"
86
- />
87
- </UFormField>
77
+ validation="required|confirm:password"
78
+ autocomplete="new-password"
79
+ :placeholder="t('auth.resetPassword.fields.passwordConfirmPlaceholder')"
80
+ />
88
81
 
89
82
  <UAlert
90
83
  v-if="error"
@@ -110,6 +103,6 @@ async function onSubmit(): Promise<void> {
110
103
  {{ t('auth.forgotPassword.backToLogin') }}
111
104
  </NuxtLink>
112
105
  </div>
113
- </UForm>
106
+ </FormKit>
114
107
  </div>
115
108
  </template>
@@ -10,6 +10,12 @@ export interface AuthUser {
10
10
  name: string;
11
11
  email: string;
12
12
  role?: AuthRole;
13
+ /** The customers-app contact (system of record for orders/cart ownership).
14
+ * Lets server routes attribute actions to the contact even when the session
15
+ * cookie predates the contact link. */
16
+ contactId?: string;
17
+ /** The contact's B2B organization, when one applies. */
18
+ organizationId?: string;
13
19
  }
14
20
 
15
21
  /**
@@ -0,0 +1,7 @@
1
+ export default defineNuxtPlugin(() => {
2
+ const auth = useAuthStore();
3
+ const cart = useCartStore();
4
+ // auth.init() must complete first: it detects auth-session expiry and clears
5
+ // the cart (case 2) before cart.initFromServer() tries to restore from server.
6
+ void auth.init().then(() => cart.initFromServer());
7
+ });
@@ -0,0 +1,24 @@
1
+ import { getCookie } from "h3";
2
+
3
+ import type { StoredSession } from "../interfaces/auth";
4
+
5
+ export default defineNuxtPlugin(() => {
6
+ const event = useRequestEvent();
7
+ if (!event) {
8
+ return;
9
+ }
10
+
11
+ const raw = getCookie(event, "cover-session");
12
+ if (!raw) {
13
+ return;
14
+ }
15
+
16
+ try {
17
+ const parsed = JSON.parse(raw) as StoredSession;
18
+ const auth = useAuthStore();
19
+ auth.session = parsed;
20
+ }
21
+ catch {
22
+ // Invalid cookie value — ignore
23
+ }
24
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revenexx/cover",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
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
5
  "type": "module",
6
6
  "main": "./nuxt.config.ts",
@@ -6,7 +6,15 @@ const LIVE_HISTORY_LIMIT = 20;
6
6
  export default defineEventHandler(async (event): Promise<AccountOrderListResponse> => {
7
7
  if (resolveOrderServiceKey(event) === "api") {
8
8
  const refs = sessionOrderRefs(event);
9
- if (!refs.contact_id) {
9
+ // Mirror the order-placement fallback: when the session cookie carries no
10
+ // contact link, resolve it from the authenticated user — otherwise a
11
+ // logged-in customer with a late-linked contact sees an empty history.
12
+ let contactId = refs.contact_id;
13
+ if (!contactId) {
14
+ const user = await getAuthService(event).me(event);
15
+ contactId = user?.contactId;
16
+ }
17
+ if (!contactId) {
10
18
  return { orders: [] };
11
19
  }
12
20
  try {
@@ -14,7 +22,7 @@ export default defineEventHandler(async (event): Promise<AccountOrderListRespons
14
22
  // Raw call: orders.list does not declare its query parameters in
15
23
  // the contract yet, so ordersList() cannot filter by contact.
16
24
  const { items } = await sdk.call<{ items: LiveOrder[] }>("GET", "/v1/orders", {
17
- query: { contact_id: refs.contact_id, limit: LIVE_HISTORY_LIMIT },
25
+ query: { contact_id: contactId, limit: LIVE_HISTORY_LIMIT },
18
26
  });
19
27
  // The list rows carry no positions — load the aggregates (the
20
28
  // history is capped, so this stays a bounded fan-out).
@@ -205,9 +205,17 @@ export default defineEventHandler(async (event) => {
205
205
  const shippingRow = calculation.totals.find(row => row.key === "shipping");
206
206
  try {
207
207
  const refs = sessionOrderRefs(event);
208
+ // Attribute the order to the acting contact. The session cookie is
209
+ // the fast path, but it can lack the contact link (cookie predates
210
+ // the contact, or login resolved it late) — fall back to the
211
+ // authenticated user's resolved contact so a logged-in order is
212
+ // never stored contactless (which would orphan it from the account
213
+ // order history, filtered strictly by contact_id).
214
+ const contactId = refs.contact_id ?? user?.contactId;
215
+ const organizationId = refs.organization_id ?? user?.organizationId;
208
216
  const placed = await useRevenexxSdk().orders.ordersPlace({
209
- ...(refs.contact_id ? { contactId: refs.contact_id } : {}),
210
- ...(refs.organization_id ? { organizationId: refs.organization_id } : {}),
217
+ ...(contactId ? { contactId } : {}),
218
+ ...(organizationId ? { organizationId } : {}),
211
219
  currency: calculation.currency,
212
220
  ...(body.orderNumber ? { customerOrderNumber: body.orderNumber } : {}),
213
221
  buyer: user ? { name: user.name, email: user.email } : { email: body.contactEmail ?? null },
@@ -106,6 +106,8 @@ export class ApiAuthService implements IAuthService {
106
106
  name: contactName(contact, user.name ?? user.email),
107
107
  email: user.email,
108
108
  role: toAuthRole(contact?.role),
109
+ ...(contact?.id ? { contactId: contact.id } : {}),
110
+ ...(contact?.organization_id ? { organizationId: contact.organization_id } : {}),
109
111
  };
110
112
  }
111
113
  catch (err) {
@@ -1,29 +0,0 @@
1
- import type { FormError } from "@nuxt/ui";
2
-
3
- import type { FormKey } from "../interfaces/validation";
4
- import { formValidationConfig } from "../validations/formValidationConfig";
5
-
6
- export function useFormValidation<TState extends object>(formKey: FormKey) {
7
- const { t } = useI18n();
8
- const config = formValidationConfig[formKey];
9
-
10
- function validate(state: TState): FormError[] {
11
- const errors: FormError[] = [];
12
- const stateRecord = state as Record<string, unknown>;
13
- for (const fieldConfig of config) {
14
- const value = stateRecord[fieldConfig.field];
15
- for (const rule of fieldConfig.rules) {
16
- const key = rule.kind === "cross"
17
- ? rule.fn(value, stateRecord, rule.options)
18
- : rule.fn(value, rule.options);
19
- if (key !== null) {
20
- errors.push({ name: fieldConfig.field, message: t(key, rule.options ?? {}) });
21
- break;
22
- }
23
- }
24
- }
25
- return errors;
26
- }
27
-
28
- return { validate };
29
- }
@@ -1,14 +0,0 @@
1
- import type { CrossFieldValidator, SimpleValidator } from "../validations/types";
2
-
3
- export type FormKey = "login" | "forgotPassword" | "resetPassword";
4
-
5
- export interface FieldValidationRule {
6
- readonly kind: "simple" | "cross";
7
- readonly fn: SimpleValidator | CrossFieldValidator;
8
- readonly options?: Readonly<Record<string, unknown>>;
9
- }
10
-
11
- export interface FieldConfig {
12
- readonly field: string;
13
- readonly rules: ReadonlyArray<FieldValidationRule>;
14
- }
@@ -1,10 +0,0 @@
1
- import { EMAIL_VALIDATION_REGEX } from "../shared/constants";
2
-
3
- import type { SimpleValidator } from "./types";
4
-
5
- export const emailFormat: SimpleValidator = (value) => {
6
- if (typeof value !== "string" || !EMAIL_VALIDATION_REGEX.test(value)) {
7
- return "auth.register.errors.emailFormat";
8
- }
9
- return null;
10
- };
@@ -1,20 +0,0 @@
1
- import type { FieldConfig, FormKey } from "../interfaces/validation";
2
-
3
- import { emailFormat } from "./emailFormat";
4
- import { minLength } from "./minLength";
5
- import { passwordsMatch } from "./passwordsMatch";
6
- import { required } from "./required";
7
-
8
- export const formValidationConfig: Record<FormKey, FieldConfig[]> = {
9
- login: [
10
- { field: "email", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: emailFormat }] },
11
- { field: "password", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: minLength, options: { min: 8 } }] },
12
- ],
13
- forgotPassword: [
14
- { field: "email", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: emailFormat }] },
15
- ],
16
- resetPassword: [
17
- { field: "password", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: minLength, options: { min: 8 } }] },
18
- { field: "passwordConfirm", rules: [{ kind: "cross", fn: passwordsMatch }] },
19
- ],
20
- };
@@ -1,9 +0,0 @@
1
- import type { SimpleValidator } from "./types";
2
-
3
- export const minLength: SimpleValidator = (value, options) => {
4
- const min = (options?.min as number) ?? 0;
5
- if (typeof value !== "string" || value.length < min) {
6
- return "minLength";
7
- }
8
- return null;
9
- };
@@ -1,5 +0,0 @@
1
- import type { CrossFieldValidator } from "./types";
2
-
3
- export const passwordsMatch: CrossFieldValidator = (value, state) => {
4
- return value !== state.password ? "auth.register.errors.passwordsMismatch" : null;
5
- };
@@ -1,11 +0,0 @@
1
- import type { SimpleValidator } from "./types";
2
-
3
- export const required: SimpleValidator = (value) => {
4
- if (typeof value === "boolean") {
5
- return value === false ? "required" : null;
6
- }
7
- if (typeof value === "string") {
8
- return value.trim() === "" ? "required" : null;
9
- }
10
- return value === null || value === undefined ? "required" : null;
11
- };
@@ -1,10 +0,0 @@
1
- export type SimpleValidator = (
2
- value: unknown,
3
- options?: Record<string, unknown>,
4
- ) => string | null;
5
-
6
- export type CrossFieldValidator = (
7
- value: unknown,
8
- state: Record<string, unknown>,
9
- options?: Record<string, unknown>,
10
- ) => string | null;