@marianmeres/stuic 3.21.3 → 3.23.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 (42) hide show
  1. package/dist/components/Checkout/CheckoutCompleteStep.svelte +9 -0
  2. package/dist/components/Checkout/CheckoutCompleteStep.svelte.d.ts +3 -0
  3. package/dist/components/Checkout/CheckoutConfirmStep.svelte +15 -0
  4. package/dist/components/Checkout/CheckoutConfirmStep.svelte.d.ts +3 -0
  5. package/dist/components/Checkout/CheckoutGuestForm.svelte +5 -0
  6. package/dist/components/Checkout/CheckoutGuestForm.svelte.d.ts +3 -0
  7. package/dist/components/Checkout/CheckoutGuestOrLoginForm.svelte +96 -7
  8. package/dist/components/Checkout/CheckoutGuestOrLoginForm.svelte.d.ts +10 -0
  9. package/dist/components/Checkout/CheckoutLoginForm.svelte +33 -137
  10. package/dist/components/Checkout/CheckoutLoginForm.svelte.d.ts +3 -0
  11. package/dist/components/Checkout/CheckoutReviewStep.svelte +10 -0
  12. package/dist/components/Checkout/CheckoutReviewStep.svelte.d.ts +3 -0
  13. package/dist/components/Checkout/CheckoutShippingStep.svelte +9 -0
  14. package/dist/components/Checkout/CheckoutShippingStep.svelte.d.ts +3 -0
  15. package/dist/components/Checkout/_internal/checkout-i18n-defaults.js +3 -0
  16. package/dist/components/Checkout/_internal/checkout-types.d.ts +1 -0
  17. package/dist/components/Checkout/_internal/checkout-utils.js +1 -1
  18. package/dist/components/Checkout/_login-form.css +22 -61
  19. package/dist/components/Input/FieldObject.svelte +316 -0
  20. package/dist/components/Input/FieldObject.svelte.d.ts +35 -0
  21. package/dist/components/Input/index.d.ts +1 -0
  22. package/dist/components/Input/index.js +1 -0
  23. package/dist/components/LoginForm/LoginForm.svelte +295 -0
  24. package/dist/components/LoginForm/LoginForm.svelte.d.ts +64 -0
  25. package/dist/components/LoginForm/LoginFormModal.svelte +200 -0
  26. package/dist/components/LoginForm/LoginFormModal.svelte.d.ts +76 -0
  27. package/dist/components/LoginForm/_internal/login-form-i18n-defaults.d.ts +1 -0
  28. package/dist/components/LoginForm/_internal/login-form-i18n-defaults.js +26 -0
  29. package/dist/components/LoginForm/_internal/login-form-types.d.ts +9 -0
  30. package/dist/components/LoginForm/_internal/login-form-types.js +1 -0
  31. package/dist/components/LoginForm/_internal/login-form-utils.d.ts +4 -0
  32. package/dist/components/LoginForm/_internal/login-form-utils.js +18 -0
  33. package/dist/components/LoginForm/index.css +83 -0
  34. package/dist/components/LoginForm/index.d.ts +3 -0
  35. package/dist/components/LoginForm/index.js +2 -0
  36. package/dist/components/Modal/Modal.svelte +6 -1
  37. package/dist/components/Modal/Modal.svelte.d.ts +1 -0
  38. package/dist/components/Modal/index.css +1 -8
  39. package/dist/index.css +1 -0
  40. package/dist/index.d.ts +1 -0
  41. package/dist/index.js +1 -0
  42. package/package.json +1 -1
@@ -3,6 +3,7 @@
3
3
  import type { HTMLAttributes } from "svelte/elements";
4
4
  import type { TranslateFn } from "../../types.js";
5
5
  import type { CheckoutOrderData, CheckoutStep } from "./_internal/checkout-types.js";
6
+ import type { NotificationsStack } from "../Notifications/notifications-stack.svelte.js";
6
7
 
7
8
  export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
8
9
  /** Completed order data */
@@ -57,6 +58,9 @@
57
58
  /** Additional content after the confirmation */
58
59
  confirmationFooter?: Snippet<[{ orderId: string; order: CheckoutOrderData }]>;
59
60
 
61
+ /** Optional notifications instance — errors will be sent via notifications.error() */
62
+ notifications?: NotificationsStack;
63
+
60
64
  t?: TranslateFn;
61
65
  unstyled?: boolean;
62
66
  class?: string;
@@ -79,6 +83,7 @@
79
83
  emailSent,
80
84
  isLoading = false,
81
85
  error,
86
+ notifications,
82
87
  hideProgress = false,
83
88
  currentStep = "complete",
84
89
  steps,
@@ -99,6 +104,10 @@
99
104
 
100
105
  let t = $derived(tProp ?? t_default);
101
106
 
107
+ $effect(() => {
108
+ if (error && notifications) notifications.error(error);
109
+ });
110
+
102
111
  let _class = $derived(
103
112
  unstyled ? classProp : twMerge("stuic-checkout-complete-step", classProp)
104
113
  );
@@ -2,6 +2,7 @@ import type { Snippet } from "svelte";
2
2
  import type { HTMLAttributes } from "svelte/elements";
3
3
  import type { TranslateFn } from "../../types.js";
4
4
  import type { CheckoutOrderData, CheckoutStep } from "./_internal/checkout-types.js";
5
+ import type { NotificationsStack } from "../Notifications/notifications-stack.svelte.js";
5
6
  export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
6
7
  /** Completed order data */
7
8
  order: CheckoutOrderData;
@@ -38,6 +39,8 @@ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children">
38
39
  orderId: string;
39
40
  order: CheckoutOrderData;
40
41
  }]>;
42
+ /** Optional notifications instance — errors will be sent via notifications.error() */
43
+ notifications?: NotificationsStack;
41
44
  t?: TranslateFn;
42
45
  unstyled?: boolean;
43
46
  class?: string;
@@ -7,6 +7,7 @@
7
7
  CheckoutStep,
8
8
  CheckoutValidationError,
9
9
  } from "./_internal/checkout-types.js";
10
+ import type { NotificationsStack } from "../Notifications/notifications-stack.svelte.js";
10
11
 
11
12
  export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
12
13
  /** Order data to review and display totals */
@@ -73,6 +74,9 @@
73
74
  /** Override entire right column */
74
75
  rightColumn?: Snippet;
75
76
 
77
+ /** Optional notifications instance — errors will be sent via notifications.error() */
78
+ notifications?: NotificationsStack;
79
+
76
80
  t?: TranslateFn;
77
81
  unstyled?: boolean;
78
82
  class?: string;
@@ -100,6 +104,7 @@
100
104
  isLoading = false,
101
105
  error,
102
106
  validationErrors,
107
+ notifications,
103
108
  isValid = true,
104
109
  isSubmitting = false,
105
110
  hideProgress = false,
@@ -129,6 +134,16 @@
129
134
 
130
135
  let t = $derived(tProp ?? t_default);
131
136
 
137
+ $effect(() => {
138
+ if (error && notifications) notifications.error(error);
139
+ });
140
+
141
+ $effect(() => {
142
+ if (validationErrors && validationErrors.length > 0 && notifications) {
143
+ notifications.error(validationErrors.map((e) => e.message).join(", "));
144
+ }
145
+ });
146
+
132
147
  let _class = $derived(
133
148
  unstyled ? classProp : twMerge("stuic-checkout-confirm-step", classProp)
134
149
  );
@@ -2,6 +2,7 @@ import type { Snippet } from "svelte";
2
2
  import type { HTMLAttributes } from "svelte/elements";
3
3
  import type { TranslateFn } from "../../types.js";
4
4
  import type { CheckoutOrderData, CheckoutStep, CheckoutValidationError } from "./_internal/checkout-types.js";
5
+ import type { NotificationsStack } from "../Notifications/notifications-stack.svelte.js";
5
6
  export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
6
7
  /** Order data to review and display totals */
7
8
  order: CheckoutOrderData;
@@ -38,6 +39,8 @@ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children">
38
39
  leftColumn?: Snippet;
39
40
  /** Override entire right column */
40
41
  rightColumn?: Snippet;
42
+ /** Optional notifications instance — errors will be sent via notifications.error() */
43
+ notifications?: NotificationsStack;
41
44
  t?: TranslateFn;
42
45
  unstyled?: boolean;
43
46
  class?: string;
@@ -6,6 +6,7 @@
6
6
  CheckoutCustomerFormData,
7
7
  CheckoutValidationError,
8
8
  } from "./_internal/checkout-types.js";
9
+ import type { NotificationsStack } from "../Notifications/notifications-stack.svelte.js";
9
10
 
10
11
  export interface Props extends Omit<HTMLAttributes<HTMLFormElement>, "children"> {
11
12
  /** Bindable form data. Default: createEmptyCustomerFormData() */
@@ -54,6 +55,9 @@
54
55
  */
55
56
  validate?: (data: CheckoutCustomerFormData) => CheckoutValidationError[];
56
57
 
58
+ /** Optional notifications instance */
59
+ notifications?: NotificationsStack;
60
+
57
61
  t?: TranslateFn;
58
62
  unstyled?: boolean;
59
63
  class?: string;
@@ -76,6 +80,7 @@
76
80
  onSubmit,
77
81
  isSubmitting = false,
78
82
  errors: externalErrors = [],
83
+ notifications,
79
84
  showB2bFields = true,
80
85
  b2bExpanded = false,
81
86
  fields,
@@ -2,6 +2,7 @@ import type { Snippet } from "svelte";
2
2
  import type { HTMLAttributes } from "svelte/elements";
3
3
  import type { TranslateFn } from "../../types.js";
4
4
  import type { CheckoutCustomerFormData, CheckoutValidationError } from "./_internal/checkout-types.js";
5
+ import type { NotificationsStack } from "../Notifications/notifications-stack.svelte.js";
5
6
  export interface Props extends Omit<HTMLAttributes<HTMLFormElement>, "children"> {
6
7
  /** Bindable form data. Default: createEmptyCustomerFormData() */
7
8
  formData?: CheckoutCustomerFormData;
@@ -41,6 +42,8 @@ export interface Props extends Omit<HTMLAttributes<HTMLFormElement>, "children">
41
42
  * Return empty array = valid. When provided, replaces (not extends) built-in validation.
42
43
  */
43
44
  validate?: (data: CheckoutCustomerFormData) => CheckoutValidationError[];
45
+ /** Optional notifications instance */
46
+ notifications?: NotificationsStack;
44
47
  t?: TranslateFn;
45
48
  unstyled?: boolean;
46
49
  class?: string;
@@ -4,6 +4,8 @@
4
4
  import type { TranslateFn } from "../../types.js";
5
5
  import type { Props as GuestFormProps } from "./CheckoutGuestForm.svelte";
6
6
  import type { Props as LoginFormProps } from "./CheckoutLoginForm.svelte";
7
+ import type { Props as LoginFormModalProps } from "../LoginForm/LoginFormModal.svelte";
8
+ import type { NotificationsStack } from "../Notifications/notifications-stack.svelte.js";
7
9
 
8
10
  export type FormMode = "guest-only" | "login-only" | "tabbed" | "stacked";
9
11
 
@@ -23,6 +25,22 @@
23
25
  */
24
26
  formMode?: FormMode;
25
27
 
28
+ /**
29
+ * When provided, clicking the login tab opens a LoginFormModal
30
+ * instead of rendering the form inline (tabbed mode only).
31
+ * Form-related props are taken from loginForm.
32
+ */
33
+ loginModal?: Pick<
34
+ LoginFormModalProps,
35
+ | "title"
36
+ | "classModal"
37
+ | "classInner"
38
+ | "classForm"
39
+ | "noXClose"
40
+ | "onClose"
41
+ | "showRememberMe"
42
+ >;
43
+
26
44
  /** Tab label for the guest form tab. Default from i18n. */
27
45
  guestTabLabel?: string;
28
46
 
@@ -35,6 +53,9 @@
35
53
  /** Optional heading rendered above the switcher/forms */
36
54
  heading?: Snippet | string;
37
55
 
56
+ /** Optional notifications instance — forwarded to child forms */
57
+ notifications?: NotificationsStack;
58
+
38
59
  t?: TranslateFn;
39
60
  unstyled?: boolean;
40
61
  class?: string;
@@ -51,14 +72,35 @@
51
72
  import { t_default } from "./_internal/checkout-i18n-defaults.js";
52
73
  import CheckoutGuestForm from "./CheckoutGuestForm.svelte";
53
74
  import CheckoutLoginForm from "./CheckoutLoginForm.svelte";
75
+ import LoginFormModal from "../LoginForm/LoginFormModal.svelte";
54
76
  import TabbedMenu from "../TabbedMenu/TabbedMenu.svelte";
55
77
  import { H, type HLevel } from "../H/index.js";
56
78
  import CheckoutSectionHeader from "./CheckoutSectionHeader.svelte";
57
79
 
80
+ // Map login_form.* keys → checkout.login.* keys (same as CheckoutLoginForm)
81
+ const LOGIN_FORM_KEY_MAP: Record<string, string> = {
82
+ "login_form.email_label": "checkout.login.email_label",
83
+ "login_form.email_placeholder": "checkout.login.email_placeholder",
84
+ "login_form.password_label": "checkout.login.password_label",
85
+ "login_form.password_placeholder": "checkout.login.password_placeholder",
86
+ "login_form.submit": "checkout.login.submit",
87
+ "login_form.submitting": "checkout.login.submitting",
88
+ "login_form.forgot_password": "checkout.login.forgot_password",
89
+ "login_form.email_required": "checkout.login.email_required",
90
+ "login_form.email_invalid": "checkout.login.email_invalid",
91
+ "login_form.password_required": "checkout.login.password_required",
92
+ "login_form.social_divider": "checkout.login.social_divider",
93
+ "login_form.remember_me": "checkout.login.remember_me",
94
+ "login_form.remember_me_tooltip": "checkout.login.remember_me_tooltip",
95
+ "login_form.modal_title": "checkout.login.modal_title",
96
+ };
97
+
58
98
  let {
59
99
  guestForm,
60
100
  loginForm,
61
101
  formMode = "tabbed",
102
+ loginModal,
103
+ notifications,
62
104
  guestTabLabel,
63
105
  loginTabLabel,
64
106
  activeTab = $bindable("guest"),
@@ -74,9 +116,33 @@
74
116
 
75
117
  let t = $derived(tProp ?? t_default);
76
118
 
119
+ let loginModalRef: LoginFormModal = $state()!;
120
+
121
+ // Adapted t for LoginFormModal (maps login_form.* → checkout.login.*)
122
+ let modalT = $derived(
123
+ loginModal
124
+ ? (
125
+ key: string,
126
+ values?: false | null | undefined | Record<string, string | number>,
127
+ fallback?: string | boolean
128
+ ) => t(LOGIN_FORM_KEY_MAP[key] ?? key, values, fallback)
129
+ : undefined
130
+ );
131
+
77
132
  let tabItems = $derived([
78
133
  { id: "guest", label: guestTabLabel ?? t("checkout.guest_or_login.guest_tab") },
79
- { id: "login", label: loginTabLabel ?? t("checkout.guest_or_login.login_tab") },
134
+ {
135
+ id: "login",
136
+ label: loginTabLabel ?? t("checkout.guest_or_login.login_tab"),
137
+ ...(loginModal
138
+ ? {
139
+ onSelect: () => {
140
+ loginModalRef?.open();
141
+ return false;
142
+ },
143
+ }
144
+ : {}),
145
+ },
80
146
  ]);
81
147
 
82
148
  let _class = $derived(
@@ -100,11 +166,11 @@
100
166
 
101
167
  {#if formMode === "guest-only"}
102
168
  {#if guestForm}
103
- <CheckoutGuestForm {...guestForm} t={tProp} {unstyled} />
169
+ <CheckoutGuestForm {...guestForm} {notifications} t={tProp} {unstyled} />
104
170
  {/if}
105
171
  {:else if formMode === "login-only"}
106
172
  {#if loginForm}
107
- <CheckoutLoginForm {...loginForm} t={tProp} {unstyled} />
173
+ <CheckoutLoginForm {...loginForm} {notifications} t={tProp} {unstyled} />
108
174
  {/if}
109
175
  {:else if formMode === "tabbed"}
110
176
  <TabbedMenu
@@ -118,22 +184,45 @@
118
184
  />
119
185
  {#if activeTab === "guest" && guestForm}
120
186
  <div role="tabpanel">
121
- <CheckoutGuestForm {...guestForm} t={tProp} {unstyled} />
187
+ <CheckoutGuestForm {...guestForm} {notifications} t={tProp} {unstyled} />
122
188
  </div>
123
189
  {:else if activeTab === "login" && loginForm}
124
190
  <div role="tabpanel">
125
- <CheckoutLoginForm {...loginForm} t={tProp} {unstyled} />
191
+ <CheckoutLoginForm {...loginForm} {notifications} t={tProp} {unstyled} />
126
192
  </div>
127
193
  {/if}
128
194
  {:else if formMode === "stacked"}
129
195
  {#if loginForm}
130
- <CheckoutLoginForm {...loginForm} t={tProp} {unstyled} />
196
+ <CheckoutLoginForm {...loginForm} {notifications} t={tProp} {unstyled} />
131
197
  {/if}
132
198
  <div class={unstyled ? undefined : "stuic-checkout-guest-or-login-divider"}>
133
199
  <span>{t("checkout.step.or_divider")}</span>
134
200
  </div>
135
201
  {#if guestForm}
136
- <CheckoutGuestForm {...guestForm} t={tProp} {unstyled} />
202
+ <CheckoutGuestForm {...guestForm} {notifications} t={tProp} {unstyled} />
137
203
  {/if}
138
204
  {/if}
205
+
206
+ {#if loginModal && loginForm}
207
+ <LoginFormModal
208
+ bind:this={loginModalRef}
209
+ formData={loginForm.formData}
210
+ onSubmit={loginForm.onSubmit}
211
+ isSubmitting={loginForm.isSubmitting}
212
+ errors={loginForm.errors}
213
+ error={loginForm.error}
214
+ onForgotPassword={loginForm.onForgotPassword}
215
+ submitLabel={loginForm.submitLabel}
216
+ submittingLabel={loginForm.submittingLabel}
217
+ submitButton={loginForm.submitButton}
218
+ socialLogins={loginForm.socialLogins}
219
+ socialDividerLabel={loginForm.socialDividerLabel}
220
+ footer={loginForm.footer}
221
+ {notifications}
222
+ title={loginTabLabel ?? t("checkout.guest_or_login.login_tab")}
223
+ t={modalT}
224
+ {unstyled}
225
+ {...loginModal}
226
+ />
227
+ {/if}
139
228
  </div>
@@ -3,6 +3,8 @@ import type { HTMLAttributes } from "svelte/elements";
3
3
  import type { TranslateFn } from "../../types.js";
4
4
  import type { Props as GuestFormProps } from "./CheckoutGuestForm.svelte";
5
5
  import type { Props as LoginFormProps } from "./CheckoutLoginForm.svelte";
6
+ import type { Props as LoginFormModalProps } from "../LoginForm/LoginFormModal.svelte";
7
+ import type { NotificationsStack } from "../Notifications/notifications-stack.svelte.js";
6
8
  export type FormMode = "guest-only" | "login-only" | "tabbed" | "stacked";
7
9
  export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
8
10
  /** Guest form configuration. Required for "guest-only", "tabbed", "stacked" modes. */
@@ -17,6 +19,12 @@ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children">
17
19
  * - "stacked": Show both forms stacked vertically with a divider
18
20
  */
19
21
  formMode?: FormMode;
22
+ /**
23
+ * When provided, clicking the login tab opens a LoginFormModal
24
+ * instead of rendering the form inline (tabbed mode only).
25
+ * Form-related props are taken from loginForm.
26
+ */
27
+ loginModal?: Pick<LoginFormModalProps, "title" | "classModal" | "classInner" | "classForm" | "noXClose" | "onClose" | "showRememberMe">;
20
28
  /** Tab label for the guest form tab. Default from i18n. */
21
29
  guestTabLabel?: string;
22
30
  /** Tab label for the login form tab. Default from i18n. */
@@ -25,6 +33,8 @@ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children">
25
33
  activeTab?: "guest" | "login";
26
34
  /** Optional heading rendered above the switcher/forms */
27
35
  heading?: Snippet | string;
36
+ /** Optional notifications instance — forwarded to child forms */
37
+ notifications?: NotificationsStack;
28
38
  t?: TranslateFn;
29
39
  unstyled?: boolean;
30
40
  class?: string;
@@ -6,6 +6,7 @@
6
6
  CheckoutLoginFormData,
7
7
  CheckoutValidationError,
8
8
  } from "./_internal/checkout-types.js";
9
+ import type { NotificationsStack } from "../Notifications/notifications-stack.svelte.js";
9
10
 
10
11
  export interface Props extends Omit<HTMLAttributes<HTMLFormElement>, "children"> {
11
12
  /** Bindable login data. Default: createEmptyLoginFormData() */
@@ -62,6 +63,9 @@
62
63
  */
63
64
  footer?: Snippet;
64
65
 
66
+ /** Optional notifications instance — errors will be sent via notifications.error() */
67
+ notifications?: NotificationsStack;
68
+
65
69
  t?: TranslateFn;
66
70
  unstyled?: boolean;
67
71
  class?: string;
@@ -72,27 +76,28 @@
72
76
  <script lang="ts">
73
77
  import { twMerge } from "../../utils/tw-merge.js";
74
78
  import { t_default } from "./_internal/checkout-i18n-defaults.js";
75
- import {
76
- createEmptyLoginFormData,
77
- validateLoginForm,
78
- } from "./_internal/checkout-utils.js";
79
- import Button from "../Button/Button.svelte";
80
- import DismissibleMessage from "../DismissibleMessage/DismissibleMessage.svelte";
81
- import FieldInput from "../Input/FieldInput.svelte";
79
+ import { createEmptyLoginFormData } from "./_internal/checkout-utils.js";
80
+ import LoginForm from "../LoginForm/LoginForm.svelte";
81
+
82
+ // Map login_form.* keys → checkout.login.* keys for backwards compatibility
83
+ const KEY_MAP: Record<string, string> = {
84
+ "login_form.email_label": "checkout.login.email_label",
85
+ "login_form.email_placeholder": "checkout.login.email_placeholder",
86
+ "login_form.password_label": "checkout.login.password_label",
87
+ "login_form.password_placeholder": "checkout.login.password_placeholder",
88
+ "login_form.submit": "checkout.login.submit",
89
+ "login_form.submitting": "checkout.login.submitting",
90
+ "login_form.forgot_password": "checkout.login.forgot_password",
91
+ "login_form.email_required": "checkout.login.email_required",
92
+ "login_form.email_invalid": "checkout.login.email_invalid",
93
+ "login_form.password_required": "checkout.login.password_required",
94
+ "login_form.social_divider": "checkout.login.social_divider",
95
+ "login_form.remember_me": "checkout.login.remember_me",
96
+ "login_form.remember_me_tooltip": "checkout.login.remember_me_tooltip",
97
+ };
82
98
 
83
99
  let {
84
100
  formData = $bindable(createEmptyLoginFormData()),
85
- onSubmit,
86
- isSubmitting = false,
87
- errors: externalErrors = [],
88
- error,
89
- onForgotPassword,
90
- submitLabel,
91
- submittingLabel,
92
- submitButton,
93
- socialLogins,
94
- socialDividerLabel,
95
- footer,
96
101
  t: tProp,
97
102
  unstyled = false,
98
103
  class: classProp,
@@ -102,128 +107,19 @@
102
107
 
103
108
  let t = $derived(tProp ?? t_default);
104
109
 
105
- // Internal validation errors (set on submit)
106
- let internalErrors = $state<CheckoutValidationError[]>([]);
107
-
108
- // Merge internal + external errors; external takes precedence per field
109
- let allErrors = $derived.by(() => {
110
- const map = new Map<string, string>();
111
- for (const e of internalErrors) map.set(e.field, e.message);
112
- for (const e of externalErrors) map.set(e.field, e.message);
113
- return [...map.entries()].map(([field, message]) => ({ field, message }));
114
- });
115
-
116
- function fieldError(field: string): string | undefined {
117
- return allErrors.find((e) => e.field === field)?.message;
118
- }
119
-
120
- function handleSubmit(e: SubmitEvent) {
121
- e.preventDefault();
122
-
123
- const validationErrors = validateLoginForm(formData, t);
124
- internalErrors = validationErrors;
125
-
126
- if (validationErrors.length === 0 && externalErrors.length === 0) {
127
- onSubmit(formData);
128
- }
129
- }
110
+ // Adapt the t function: LoginForm uses login_form.* keys,
111
+ // but checkout consumers provide checkout.login.* translations
112
+ let adaptedT = $derived(
113
+ (
114
+ key: string,
115
+ values?: false | null | undefined | Record<string, string | number>,
116
+ fallback?: string | boolean
117
+ ) => t(KEY_MAP[key] ?? key, values, fallback)
118
+ );
130
119
 
131
120
  let _class = $derived(
132
121
  unstyled ? classProp : twMerge("stuic-checkout-login-form", classProp)
133
122
  );
134
123
  </script>
135
124
 
136
- <form bind:this={el} class={_class} onsubmit={handleSubmit} novalidate {...rest}>
137
- <!-- General error alert -->
138
- <DismissibleMessage message={error} intent="destructive" onDismiss={false} />
139
-
140
- <!--
141
- svelte-ignore binding_property_non_reactive:
142
- formData is a $bindable prop — deep reactivity depends on the consumer
143
- passing a $state() object. The bindings work correctly regardless.
144
- -->
145
- <!-- Email -->
146
- <!-- svelte-ignore binding_property_non_reactive -->
147
- <FieldInput
148
- bind:value={formData.email}
149
- label={t("checkout.login.email_label")}
150
- type="email"
151
- placeholder={t("checkout.login.email_placeholder")}
152
- required
153
- name="checkout-login-email"
154
- labelLeftBreakpoint={0}
155
- validate={{
156
- customValidator(val) {
157
- return fieldError("email") || "";
158
- },
159
- }}
160
- />
161
-
162
- <!-- Password -->
163
- <!-- svelte-ignore binding_property_non_reactive -->
164
- <FieldInput
165
- bind:value={formData.password}
166
- label={t("checkout.login.password_label")}
167
- type="password"
168
- placeholder={t("checkout.login.password_placeholder")}
169
- required
170
- name="checkout-login-password"
171
- labelLeftBreakpoint={0}
172
- validate={{
173
- customValidator(val) {
174
- return fieldError("password") || "";
175
- },
176
- }}
177
- />
178
-
179
- <!-- CTA -->
180
- {#if submitButton}
181
- {@render submitButton({ isSubmitting, disabled: isSubmitting })}
182
- {:else}
183
- <div class={unstyled ? undefined : "stuic-checkout-login-submit"}>
184
- <Button intent="primary" type="submit" disabled={isSubmitting} class="w-full">
185
- {isSubmitting
186
- ? (submittingLabel ?? t("checkout.login.submitting"))
187
- : (submitLabel ?? t("checkout.login.submit"))}
188
- </Button>
189
- </div>
190
- {/if}
191
-
192
- <!-- Forgot password -->
193
- {#if onForgotPassword}
194
- <div class={unstyled ? undefined : "stuic-checkout-login-forgot"}>
195
- <Button
196
- variant="link"
197
- type="button"
198
- class="text-muted-foreground"
199
- size="sm"
200
- onclick={onForgotPassword}
201
- >
202
- {t("checkout.login.forgot_password")}
203
- </Button>
204
- </div>
205
- {/if}
206
-
207
- <!-- Social logins -->
208
- {#if socialLogins}
209
- <div class={unstyled ? undefined : "stuic-checkout-login-social"}>
210
- {#if socialDividerLabel !== false}
211
- <div class={unstyled ? undefined : "stuic-checkout-login-social-divider"}>
212
- <span>
213
- {typeof socialDividerLabel === "string"
214
- ? socialDividerLabel
215
- : t("checkout.login.social_divider")}
216
- </span>
217
- </div>
218
- {/if}
219
- <div class={unstyled ? undefined : "stuic-checkout-login-social-buttons"}>
220
- {@render socialLogins()}
221
- </div>
222
- </div>
223
- {/if}
224
-
225
- <!-- Footer -->
226
- {#if footer}
227
- {@render footer()}
228
- {/if}
229
- </form>
125
+ <LoginForm bind:formData bind:el t={adaptedT} {unstyled} class={_class} {...rest} />
@@ -2,6 +2,7 @@ import type { Snippet } from "svelte";
2
2
  import type { HTMLAttributes } from "svelte/elements";
3
3
  import type { TranslateFn } from "../../types.js";
4
4
  import type { CheckoutLoginFormData, CheckoutValidationError } from "./_internal/checkout-types.js";
5
+ import type { NotificationsStack } from "../Notifications/notifications-stack.svelte.js";
5
6
  export interface Props extends Omit<HTMLAttributes<HTMLFormElement>, "children"> {
6
7
  /** Bindable login data. Default: createEmptyLoginFormData() */
7
8
  formData?: CheckoutLoginFormData;
@@ -48,6 +49,8 @@ export interface Props extends Omit<HTMLAttributes<HTMLFormElement>, "children">
48
49
  * Use for "Or continue as guest" links, sign-up links, etc.
49
50
  */
50
51
  footer?: Snippet;
52
+ /** Optional notifications instance — errors will be sent via notifications.error() */
53
+ notifications?: NotificationsStack;
51
54
  t?: TranslateFn;
52
55
  unstyled?: boolean;
53
56
  class?: string;
@@ -9,6 +9,7 @@
9
9
  CheckoutStep,
10
10
  CheckoutValidationError,
11
11
  } from "./_internal/checkout-types.js";
12
+ import type { NotificationsStack } from "../Notifications/notifications-stack.svelte.js";
12
13
 
13
14
  export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
14
15
  /** Cart items to display in the cart review */
@@ -94,6 +95,9 @@
94
95
  /** Override the right column entirely */
95
96
  rightColumn?: Snippet;
96
97
 
98
+ /** Optional notifications instance — errors will be sent via notifications.error() */
99
+ notifications?: NotificationsStack;
100
+
97
101
  t?: TranslateFn;
98
102
  unstyled?: boolean;
99
103
  class?: string;
@@ -114,6 +118,7 @@
114
118
  items,
115
119
  isLoading = false,
116
120
  error,
121
+ notifications,
117
122
  hideProgress = false,
118
123
  currentStep = "review",
119
124
  steps,
@@ -137,6 +142,10 @@
137
142
 
138
143
  let t = $derived(tProp ?? t_default);
139
144
 
145
+ $effect(() => {
146
+ if (error && notifications) notifications.error(error);
147
+ });
148
+
140
149
  let _class = $derived(
141
150
  unstyled ? classProp : twMerge("stuic-checkout-review-step", classProp)
142
151
  );
@@ -202,6 +211,7 @@
202
211
  {guestTabLabel}
203
212
  {loginTabLabel}
204
213
  heading={t("checkout.step.contact_title")}
214
+ {notifications}
205
215
  t={tProp}
206
216
  {unstyled}
207
217
  />
@@ -3,6 +3,7 @@ import type { HTMLAttributes } from "svelte/elements";
3
3
  import type { TranslateFn } from "../../types.js";
4
4
  import type { CartComponentItem } from "../Cart/Cart.svelte";
5
5
  import type { CheckoutCustomerFormData, CheckoutLoginFormData, CheckoutStep, CheckoutValidationError } from "./_internal/checkout-types.js";
6
+ import type { NotificationsStack } from "../Notifications/notifications-stack.svelte.js";
6
7
  export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
7
8
  /** Cart items to display in the cart review */
8
9
  items: CartComponentItem[];
@@ -63,6 +64,8 @@ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children">
63
64
  leftColumn?: Snippet;
64
65
  /** Override the right column entirely */
65
66
  rightColumn?: Snippet;
67
+ /** Optional notifications instance — errors will be sent via notifications.error() */
68
+ notifications?: NotificationsStack;
66
69
  t?: TranslateFn;
67
70
  unstyled?: boolean;
68
71
  class?: string;