@gomusdev/web-components 1.12.0 → 1.13.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.
@@ -13,13 +13,13 @@ export declare class Forms {
13
13
  static defineForm(options: defineFormOptions): void;
14
14
  static defineFields(fields: Record<string, FieldInit>): void;
15
15
  static setRequiredApiKeys(formId: string, requiredApiKeys: string[]): void;
16
- static getRequiredApiKeys(formId: string): string[];
17
16
  static createField(key: string, required: boolean): Field;
18
17
  static getFormFields(formId: string): any;
19
18
  static getFieldInit(key: string): any;
20
19
  }
21
20
  /**
22
21
  * Calculates the number of fields that are mounted and have errors.
22
+ * Also validates that confirmation fields match their corresponding fields.
23
23
  *
24
24
  * @param {Field[] | undefined} fields - The list of fields to evaluate. Each field should have a `mounted` and an `errors` property.
25
25
  * @return {number} The count of fields that are mounted and contain errors. Returns 0 if no fields are provided.
@@ -5,8 +5,6 @@ export declare class FormDetails {
5
5
  form: HTMLFormElement;
6
6
  isValid: boolean;
7
7
  get formData(): any;
8
- ensureApiRequiredFields(): boolean;
9
- get requiredApiKeys(): string[];
10
8
  get apiErrors(): string[] | Record<string, string[]>;
11
9
  /**
12
10
  * Sets API errors for the current instance.
@@ -14,6 +12,8 @@ export declare class FormDetails {
14
12
  * @param {string[] | Record<string, string[]>} errors - Errors passed as an array or an object where keys are API field keys and values are arrays of error messages. If an array is provided, it assigns the errors directly. If an object is provided, it maps the errors to the respective fields based on their API keys.
15
13
  */
16
14
  set apiErrors(errors: string[] | Record<string, string[]>);
15
+ validateForm(): void;
16
+ validateField(field: Field): void;
17
17
  addField(field: Field): void;
18
18
  /**
19
19
  * The list of fields associated with form in the config.
@@ -5447,6 +5447,10 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
5447
5447
  return function(host) {
5448
5448
  if (host) {
5449
5449
  const container = proxy({ data: void 0 });
5450
+ if (!host.isConnected) {
5451
+ console.error("(createGetDetails) Host is not mounted");
5452
+ return void 0;
5453
+ }
5450
5454
  host.dispatchEvent(new CustomEvent(KEY2, { detail: container, bubbles: true }));
5451
5455
  return container.data;
5452
5456
  } else {
@@ -6579,6 +6583,24 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
6579
6583
  throw new Error("Unhandled shop type: " + never);
6580
6584
  }
6581
6585
  };
6586
+ function isEmpty(value) {
6587
+ return value === null || value === void 0 || typeof value === "string" && value.trim() === "";
6588
+ }
6589
+ function validateRequiredFields(body, requiredFields) {
6590
+ const errors2 = {};
6591
+ if (!requiredFields || requiredFields.length === 0) {
6592
+ return errors2;
6593
+ }
6594
+ for (const field of requiredFields) {
6595
+ if (!(field in body) || isEmpty(body[field])) {
6596
+ errors2[field] = [`Field '${field}' is required and cannot be empty`];
6597
+ }
6598
+ }
6599
+ return errors2;
6600
+ }
6601
+ function validateApiPostBody(body, requiredFields) {
6602
+ return validateRequiredFields(body, requiredFields);
6603
+ }
6582
6604
  var util;
6583
6605
  (function(util2) {
6584
6606
  util2.assertEqual = (val) => val;
@@ -11854,7 +11876,7 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
11854
11876
  return this.fetchAndCache(TICKETS_ENDPOINT, `tickets-${JSON.stringify(params)}`, "tickets", { cache: 60, query: params });
11855
11877
  }
11856
11878
  signIn(params) {
11857
- return this.apiPost(SIGN_IN_ENDPOINT, { body: params });
11879
+ return this.apiPost(SIGN_IN_ENDPOINT, { body: params, requiredFields: ["email", "password"] });
11858
11880
  }
11859
11881
  signUp(params, asGuest = false) {
11860
11882
  const defaultParams = {
@@ -11865,10 +11887,19 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
11865
11887
  level: asGuest ? CustomerLevels.SHOP_GUEST : CustomerLevels.SHOP
11866
11888
  };
11867
11889
  params = assign(defaultParams, params);
11868
- return this.apiPost(SIGN_UP_ENDPOINT, { body: params });
11890
+ let requiredFields = [
11891
+ "name",
11892
+ //first name
11893
+ "surname",
11894
+ "email",
11895
+ "email_confirmation",
11896
+ "terms"
11897
+ ];
11898
+ if (!asGuest) requiredFields.push("password", "password_confirmation");
11899
+ return this.apiPost(SIGN_UP_ENDPOINT, { body: params, requiredFields });
11869
11900
  }
11870
11901
  checkout(params) {
11871
- return this.apiPost(`/api/v4/orders`, { body: params });
11902
+ return this.apiPost(`/api/v4/orders`, { body: params, requiredFields: ["items", "total"] });
11872
11903
  }
11873
11904
  order(token) {
11874
11905
  return this.fetchAndCache("/api/v4/orders/{id}", `order-${token}`, "order", { path: { id: token } });
@@ -11877,7 +11908,7 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
11877
11908
  return this.fetchAndCache("/api/v4/annual/order", `annual-order-${token}`, "order", { query: { token } });
11878
11909
  }
11879
11910
  finalizePersonalizations(token, params) {
11880
- return this.apiPost("/api/v4/annual/personalization/finalize", { body: params }, { query: { token } });
11911
+ return this.apiPost("/api/v4/annual/personalization/finalize", { body: params, params: { query: { token } } });
11881
11912
  }
11882
11913
  /**
11883
11914
  * Returns a reactive value that will contain the fetched data, no need to await.
@@ -11951,8 +11982,24 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
11951
11982
  }
11952
11983
  };
11953
11984
  }
11954
- async apiPost(path, { body }, params = {}) {
11985
+ /**
11986
+ * Sends a POST request to the specified API endpoint.
11987
+ *
11988
+ * @param {string} path - The API endpoint path to make the POST request.
11989
+ * @param {Object} options - The options for the POST request.
11990
+ * @param {Record<string, any>} options.body - The body of the POST request, containing the data to be sent.
11991
+ * @param {Record<string, unknown>} [options.params] - Optional query parameters to include in the request.
11992
+ * @param {string[]} [options.requiredFields] - Optional list of required field names to validate in the body.
11993
+ * @return {Promise<T>} A promise that resolves with the typed response of the POST request.
11994
+ */
11995
+ async apiPost(path, options) {
11955
11996
  this.#ensureApi();
11997
+ const { body, params = {}, requiredFields } = options;
11998
+ const validationErrors = validateApiPostBody(body, requiredFields);
11999
+ if (Object.keys(validationErrors).length > 0) {
12000
+ console.log("shop.apiPost: validationErrors", { validationErrors, body });
12001
+ return { error: { errors: validationErrors } };
12002
+ }
11956
12003
  const ret = await this.client.POST(path, { body, params: assign(this.#defaultApiParams, params) });
11957
12004
  return ret;
11958
12005
  }
@@ -14704,7 +14751,6 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
14704
14751
  const _Forms = class _Forms {
14705
14752
  static defineForm(options) {
14706
14753
  go.defineConfig({ forms: { [options.id]: { fields: options.fields } } });
14707
- __privateGet(_Forms, _requiredApiKeysMap)[options.id] = options.requiredApiKeys || [];
14708
14754
  }
14709
14755
  static defineFields(fields) {
14710
14756
  go.defineConfig({ fields });
@@ -14712,9 +14758,6 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
14712
14758
  static setRequiredApiKeys(formId, requiredApiKeys) {
14713
14759
  __privateGet(_Forms, _requiredApiKeysMap)[formId] = requiredApiKeys;
14714
14760
  }
14715
- static getRequiredApiKeys(formId) {
14716
- return __privateGet(_Forms, _requiredApiKeysMap)[formId];
14717
- }
14718
14761
  static createField(key, required) {
14719
14762
  const init2 = _Forms.getFieldInit(key);
14720
14763
  if (!init2) {
@@ -14779,9 +14822,9 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
14779
14822
  },
14780
14823
  []
14781
14824
  );
14782
- personalizationForms.forEach((detail) => {
14783
- detail.fields.forEach((f) => {
14784
- f.validate();
14825
+ personalizationForms.forEach((details2) => {
14826
+ details2.fields.forEach((f) => {
14827
+ details2.validateField(f);
14785
14828
  });
14786
14829
  });
14787
14830
  const isValid2 = iterate(
@@ -14929,18 +14972,6 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
14929
14972
  const ret = Object.fromEntries(validFields.map((f) => [f.apiKey, coerce2(f.value)]));
14930
14973
  return ret;
14931
14974
  }
14932
- ensureApiRequiredFields() {
14933
- const formData = this.formData;
14934
- const missingKeys = this.requiredApiKeys.filter((k) => !(k in formData));
14935
- if (missingKeys.length > 0) {
14936
- console.error("(ensureApiRequiredFields) Missing required API keys: " + missingKeys.join(","));
14937
- return false;
14938
- }
14939
- return true;
14940
- }
14941
- get requiredApiKeys() {
14942
- return Forms.getRequiredApiKeys(this.formId);
14943
- }
14944
14975
  get apiErrors() {
14945
14976
  return get$2(this.#apiErrors);
14946
14977
  }
@@ -14967,6 +14998,23 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
14967
14998
  field.apiErrors = value;
14968
14999
  }
14969
15000
  }
15001
+ validateForm() {
15002
+ get$2(this.#fields).forEach((f) => this.validateField(f));
15003
+ }
15004
+ validateField(field) {
15005
+ field.validate();
15006
+ if (field.apiKey === "password_confirmation") {
15007
+ const passwordField = this.fields.find((f) => f.apiKey === "password");
15008
+ if (!passwordField || passwordField && field.value !== passwordField.value) {
15009
+ field.errors.push("Passwords do not match");
15010
+ }
15011
+ } else if (field.apiKey === "email_confirmation") {
15012
+ const emailField = this.fields.find((f) => f.apiKey === "email");
15013
+ if (!emailField || emailField && field.value !== emailField.value) {
15014
+ field.errors.push("Emails do not match");
15015
+ }
15016
+ }
15017
+ }
14970
15018
  addField(field) {
14971
15019
  get$2(this.#fields).push(field);
14972
15020
  }
@@ -15011,9 +15059,7 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
15011
15059
  setDetails($$props.$$host, details);
15012
15060
  async function handleSubmit(event) {
15013
15061
  event.preventDefault();
15014
- details.fields.forEach((f) => {
15015
- f.validate();
15016
- });
15062
+ details.validateForm();
15017
15063
  details?.form?.dispatchEvent(new Event("after-validation", event));
15018
15064
  if (details.isValid) {
15019
15065
  details?.form?.dispatchEvent(new Event("go-submit", event));
@@ -15074,7 +15120,6 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
15074
15120
  let form;
15075
15121
  Forms.defineForm({
15076
15122
  id: "signIn",
15077
- requiredApiKeys: ["email", "password"],
15078
15123
  fields: [
15079
15124
  { key: "email", required: true },
15080
15125
  { key: "password", required: true }
@@ -15085,7 +15130,6 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
15085
15130
  throw new Error("(go-sign-in: form not found");
15086
15131
  }
15087
15132
  const result = await shop.signIn(form.details.formData);
15088
- form.details.ensureApiRequiredFields();
15089
15133
  if (result.data) {
15090
15134
  $$props.$$host.dispatchEvent(new Event("go-success", { bubbles: true, composed: true }));
15091
15135
  } else {
@@ -15297,6 +15341,10 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
15297
15341
  let lastLS = "";
15298
15342
  setInterval(
15299
15343
  () => {
15344
+ if (!localStorage) {
15345
+ console.warn("(syncCartToLocalStorage) localStorage is not available");
15346
+ return;
15347
+ }
15300
15348
  let newLS = localStorage.getItem("go-cart");
15301
15349
  if (lastLS && lastLS !== newLS) {
15302
15350
  loadFromLocalStorage(cart2);
@@ -15560,13 +15608,6 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
15560
15608
  { key: "email", required: true },
15561
15609
  { key: "confirmEmail", required: true },
15562
15610
  { key: "acceptTerms", required: true }
15563
- ],
15564
- requiredApiKeys: [
15565
- "firstName",
15566
- "lastName",
15567
- "email",
15568
- "confirmEmail",
15569
- "acceptTerms"
15570
15611
  ]
15571
15612
  });
15572
15613
  wrapInElement($$props.$$host, "go-form", { "form-id": "checkoutGuest", custom: custom2() });
@@ -27571,7 +27612,7 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
27571
27612
  getSegmentProps() {
27572
27613
  const segmentValues = this.root.segmentValues;
27573
27614
  const placeholder = this.root.placeholder.current;
27574
- const isEmpty = segmentValues[this.part] === null;
27615
+ const isEmpty2 = segmentValues[this.part] === null;
27575
27616
  let date2 = placeholder;
27576
27617
  if (segmentValues[this.part]) {
27577
27618
  date2 = placeholder.set({ [this.part]: Number.parseInt(segmentValues[this.part]) });
@@ -27579,9 +27620,9 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
27579
27620
  const valueNow = date2[this.part];
27580
27621
  const valueMin = this.#getMin();
27581
27622
  const valueMax = this.#getMax();
27582
- let valueText = isEmpty ? "Empty" : `${valueNow}`;
27623
+ let valueText = isEmpty2 ? "Empty" : `${valueNow}`;
27583
27624
  if (this.part === "hour" && "dayPeriod" in segmentValues && segmentValues.dayPeriod) {
27584
- valueText = isEmpty ? "Empty" : `${valueNow} ${segmentValues.dayPeriod}`;
27625
+ valueText = isEmpty2 ? "Empty" : `${valueNow} ${segmentValues.dayPeriod}`;
27585
27626
  }
27586
27627
  return {
27587
27628
  "aria-label": `${this.part}, `,
@@ -29822,7 +29863,7 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
29822
29863
  reset(fieldset);
29823
29864
  append($$anchor2, fieldset);
29824
29865
  };
29825
- let field = prop($$props, "field", 15), describedById = prop($$props, "describedById", 7), labelClass = prop($$props, "labelClass", 7), inputClass = prop($$props, "inputClass", 7), restProps = /* @__PURE__ */ rest_props($$props, [
29866
+ let field = prop($$props, "field", 15), describedById = prop($$props, "describedById", 7), labelClass = prop($$props, "labelClass", 7), inputClass = prop($$props, "inputClass", 7), host = prop($$props, "host", 7), restProps = /* @__PURE__ */ rest_props($$props, [
29826
29867
  "$$slots",
29827
29868
  "$$events",
29828
29869
  "$$legacy",
@@ -29830,8 +29871,13 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
29830
29871
  "field",
29831
29872
  "describedById",
29832
29873
  "labelClass",
29833
- "inputClass"
29874
+ "inputClass",
29875
+ "host"
29834
29876
  ]);
29877
+ let details = /* @__PURE__ */ state(void 0);
29878
+ onMount(async () => {
29879
+ set(details, await pollUntilTruthy(() => getDetails(host())), true);
29880
+ });
29835
29881
  const map = {
29836
29882
  input,
29837
29883
  text: input,
@@ -29851,7 +29897,9 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
29851
29897
  let inputId = /* @__PURE__ */ user_derived(() => $$props.id || `go-field-${Math.random().toString(36).substring(2, 9)}`);
29852
29898
  function onblur() {
29853
29899
  if (field().type == "checkbox") return;
29854
- if (field().value) field().validate();
29900
+ if (field().value) if (get$2(details))
29901
+ get$2(details).validateField(field());
29902
+ else field().validate();
29855
29903
  }
29856
29904
  const fieldAttributes = /* @__PURE__ */ user_derived(() => ({
29857
29905
  id: get$2(inputId),
@@ -29889,6 +29937,13 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
29889
29937
  set inputClass($$value) {
29890
29938
  inputClass($$value);
29891
29939
  flushSync();
29940
+ },
29941
+ get host() {
29942
+ return host();
29943
+ },
29944
+ set host($$value) {
29945
+ host($$value);
29946
+ flushSync();
29892
29947
  }
29893
29948
  };
29894
29949
  var fragment_6 = comment();
@@ -29907,7 +29962,19 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
29907
29962
  append($$anchor, fragment_6);
29908
29963
  return pop($$exports);
29909
29964
  }
29910
- create_custom_element(InputAndLabel, { field: {}, describedById: {}, labelClass: {}, inputClass: {} }, [], [], true);
29965
+ create_custom_element(
29966
+ InputAndLabel,
29967
+ {
29968
+ field: {},
29969
+ describedById: {},
29970
+ labelClass: {},
29971
+ inputClass: {},
29972
+ host: {}
29973
+ },
29974
+ [],
29975
+ [],
29976
+ true
29977
+ );
29911
29978
  var root_1$6 = /* @__PURE__ */ from_html(`<span> </span>`);
29912
29979
  var root_3$3 = /* @__PURE__ */ from_html(`<li> </li>`);
29913
29980
  var root_2$5 = /* @__PURE__ */ from_html(`<ul class="go-field-errors" role="alert"></ul>`);
@@ -29939,7 +30006,7 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
29939
30006
  // Track the value
29940
30007
  ).value;
29941
30008
  untrack(() => {
29942
- if (get$2(field)?.errors.length) get$2(field).validate();
30009
+ if (get$2(field)?.errors.length) get$2(details).validateField(get$2(field));
29943
30010
  });
29944
30011
  });
29945
30012
  var $$exports = {
@@ -29985,6 +30052,7 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
29985
30052
  get inputClass() {
29986
30053
  return inputClass();
29987
30054
  },
30055
+ host: $$props.$$host,
29988
30056
  get field() {
29989
30057
  return get$2(field);
29990
30058
  },
@@ -30069,15 +30137,18 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
30069
30137
  var root_1$4 = /* @__PURE__ */ from_html(`<div><!> <!> <!></div>`);
30070
30138
  function ErrorsFeedback($$anchor, $$props) {
30071
30139
  push($$props, true);
30072
- let details = getDetails($$props.$$host);
30140
+ let details = /* @__PURE__ */ state(void 0);
30141
+ onMount(async () => {
30142
+ set(details, await pollUntilTruthy(() => getDetails($$props.$$host)), true);
30143
+ });
30073
30144
  user_effect(() => {
30074
- details?.form?.addEventListener("after-validation", () => {
30075
- set(errorsOnSubmit, errors(details?.fields), true);
30145
+ get$2(details)?.form?.addEventListener("after-validation", () => {
30146
+ set(errorsOnSubmit, errors(get$2(details)?.fields), true);
30076
30147
  });
30077
30148
  });
30078
- let errorsRealtime = /* @__PURE__ */ user_derived(() => errors(details?.fields));
30149
+ let errorsRealtime = /* @__PURE__ */ user_derived(() => errors(get$2(details)?.fields));
30079
30150
  let errorsOnSubmit = /* @__PURE__ */ state(-1);
30080
- let generalApiErrors = /* @__PURE__ */ user_derived(() => details && isArray(details.apiErrors) && details.apiErrors.length > 0 ? details.apiErrors : []);
30151
+ let generalApiErrors = /* @__PURE__ */ user_derived(() => get$2(details) && isArray(get$2(details).apiErrors) && get$2(details).apiErrors.length > 0 ? get$2(details).apiErrors : []);
30081
30152
  var fragment = comment();
30082
30153
  var node = first_child(fragment);
30083
30154
  {
@@ -30101,7 +30172,7 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
30101
30172
  {
30102
30173
  var consequent_1 = ($$anchor3) => {
30103
30174
  var ul = root_3$2();
30104
- each(ul, 21, () => details?.apiErrors, index$1, ($$anchor4, error) => {
30175
+ each(ul, 21, () => get$2(details)?.apiErrors, index$1, ($$anchor4, error) => {
30105
30176
  var li = root_4$1();
30106
30177
  var text_1 = child(li, true);
30107
30178
  reset(li);
@@ -30112,7 +30183,7 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
30112
30183
  append($$anchor3, ul);
30113
30184
  };
30114
30185
  if_block(node_2, ($$render) => {
30115
- if (details && isArray(details.apiErrors) && details.apiErrors.length > 0) $$render(consequent_1);
30186
+ if (get$2(details) && isArray(get$2(details).apiErrors) && get$2(details).apiErrors.length > 0) $$render(consequent_1);
30116
30187
  });
30117
30188
  }
30118
30189
  var node_3 = sibling(node_2, 2);
@@ -30369,7 +30440,12 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
30369
30440
  }
30370
30441
  });
30371
30442
  onMount(async () => {
30372
- set(formDetails, await pollUntilTruthy(() => getDetails($$props.$$host)), true);
30443
+ try {
30444
+ set(formDetails, await pollUntilTruthy(() => getDetails($$props.$$host)), true);
30445
+ } catch (error) {
30446
+ console.error("(IF) Error fetching form details:", error);
30447
+ console.error("Stack trace:", error instanceof Error ? error.stack : "No stack trace available");
30448
+ }
30373
30449
  });
30374
30450
  var $$exports = {
30375
30451
  get data() {
@@ -5447,6 +5447,10 @@ function createGetDetails(KEY2) {
5447
5447
  return function(host) {
5448
5448
  if (host) {
5449
5449
  const container = proxy({ data: void 0 });
5450
+ if (!host.isConnected) {
5451
+ console.error("(createGetDetails) Host is not mounted");
5452
+ return void 0;
5453
+ }
5450
5454
  host.dispatchEvent(new CustomEvent(KEY2, { detail: container, bubbles: true }));
5451
5455
  return container.data;
5452
5456
  } else {
@@ -6579,6 +6583,24 @@ const getUserProvider = (type) => {
6579
6583
  throw new Error("Unhandled shop type: " + never);
6580
6584
  }
6581
6585
  };
6586
+ function isEmpty(value) {
6587
+ return value === null || value === void 0 || typeof value === "string" && value.trim() === "";
6588
+ }
6589
+ function validateRequiredFields(body, requiredFields) {
6590
+ const errors2 = {};
6591
+ if (!requiredFields || requiredFields.length === 0) {
6592
+ return errors2;
6593
+ }
6594
+ for (const field of requiredFields) {
6595
+ if (!(field in body) || isEmpty(body[field])) {
6596
+ errors2[field] = [`Field '${field}' is required and cannot be empty`];
6597
+ }
6598
+ }
6599
+ return errors2;
6600
+ }
6601
+ function validateApiPostBody(body, requiredFields) {
6602
+ return validateRequiredFields(body, requiredFields);
6603
+ }
6582
6604
  var util;
6583
6605
  (function(util2) {
6584
6606
  util2.assertEqual = (val) => val;
@@ -11854,7 +11876,7 @@ class Shop {
11854
11876
  return this.fetchAndCache(TICKETS_ENDPOINT, `tickets-${JSON.stringify(params)}`, "tickets", { cache: 60, query: params });
11855
11877
  }
11856
11878
  signIn(params) {
11857
- return this.apiPost(SIGN_IN_ENDPOINT, { body: params });
11879
+ return this.apiPost(SIGN_IN_ENDPOINT, { body: params, requiredFields: ["email", "password"] });
11858
11880
  }
11859
11881
  signUp(params, asGuest = false) {
11860
11882
  const defaultParams = {
@@ -11865,10 +11887,19 @@ class Shop {
11865
11887
  level: asGuest ? CustomerLevels.SHOP_GUEST : CustomerLevels.SHOP
11866
11888
  };
11867
11889
  params = assign(defaultParams, params);
11868
- return this.apiPost(SIGN_UP_ENDPOINT, { body: params });
11890
+ let requiredFields = [
11891
+ "name",
11892
+ //first name
11893
+ "surname",
11894
+ "email",
11895
+ "email_confirmation",
11896
+ "terms"
11897
+ ];
11898
+ if (!asGuest) requiredFields.push("password", "password_confirmation");
11899
+ return this.apiPost(SIGN_UP_ENDPOINT, { body: params, requiredFields });
11869
11900
  }
11870
11901
  checkout(params) {
11871
- return this.apiPost(`/api/v4/orders`, { body: params });
11902
+ return this.apiPost(`/api/v4/orders`, { body: params, requiredFields: ["items", "total"] });
11872
11903
  }
11873
11904
  order(token) {
11874
11905
  return this.fetchAndCache("/api/v4/orders/{id}", `order-${token}`, "order", { path: { id: token } });
@@ -11877,7 +11908,7 @@ class Shop {
11877
11908
  return this.fetchAndCache("/api/v4/annual/order", `annual-order-${token}`, "order", { query: { token } });
11878
11909
  }
11879
11910
  finalizePersonalizations(token, params) {
11880
- return this.apiPost("/api/v4/annual/personalization/finalize", { body: params }, { query: { token } });
11911
+ return this.apiPost("/api/v4/annual/personalization/finalize", { body: params, params: { query: { token } } });
11881
11912
  }
11882
11913
  /**
11883
11914
  * Returns a reactive value that will contain the fetched data, no need to await.
@@ -11951,8 +11982,24 @@ class Shop {
11951
11982
  }
11952
11983
  };
11953
11984
  }
11954
- async apiPost(path, { body }, params = {}) {
11985
+ /**
11986
+ * Sends a POST request to the specified API endpoint.
11987
+ *
11988
+ * @param {string} path - The API endpoint path to make the POST request.
11989
+ * @param {Object} options - The options for the POST request.
11990
+ * @param {Record<string, any>} options.body - The body of the POST request, containing the data to be sent.
11991
+ * @param {Record<string, unknown>} [options.params] - Optional query parameters to include in the request.
11992
+ * @param {string[]} [options.requiredFields] - Optional list of required field names to validate in the body.
11993
+ * @return {Promise<T>} A promise that resolves with the typed response of the POST request.
11994
+ */
11995
+ async apiPost(path, options) {
11955
11996
  this.#ensureApi();
11997
+ const { body, params = {}, requiredFields } = options;
11998
+ const validationErrors = validateApiPostBody(body, requiredFields);
11999
+ if (Object.keys(validationErrors).length > 0) {
12000
+ console.log("shop.apiPost: validationErrors", { validationErrors, body });
12001
+ return { error: { errors: validationErrors } };
12002
+ }
11956
12003
  const ret = await this.client.POST(path, { body, params: assign(this.#defaultApiParams, params) });
11957
12004
  return ret;
11958
12005
  }
@@ -14704,7 +14751,6 @@ function validateField(field) {
14704
14751
  const _Forms = class _Forms {
14705
14752
  static defineForm(options) {
14706
14753
  go.defineConfig({ forms: { [options.id]: { fields: options.fields } } });
14707
- __privateGet(_Forms, _requiredApiKeysMap)[options.id] = options.requiredApiKeys || [];
14708
14754
  }
14709
14755
  static defineFields(fields) {
14710
14756
  go.defineConfig({ fields });
@@ -14712,9 +14758,6 @@ const _Forms = class _Forms {
14712
14758
  static setRequiredApiKeys(formId, requiredApiKeys) {
14713
14759
  __privateGet(_Forms, _requiredApiKeysMap)[formId] = requiredApiKeys;
14714
14760
  }
14715
- static getRequiredApiKeys(formId) {
14716
- return __privateGet(_Forms, _requiredApiKeysMap)[formId];
14717
- }
14718
14761
  static createField(key, required) {
14719
14762
  const init2 = _Forms.getFieldInit(key);
14720
14763
  if (!init2) {
@@ -14779,9 +14822,9 @@ function AnnualTicketPersonalizationForm($$anchor, $$props) {
14779
14822
  },
14780
14823
  []
14781
14824
  );
14782
- personalizationForms.forEach((detail) => {
14783
- detail.fields.forEach((f) => {
14784
- f.validate();
14825
+ personalizationForms.forEach((details2) => {
14826
+ details2.fields.forEach((f) => {
14827
+ details2.validateField(f);
14785
14828
  });
14786
14829
  });
14787
14830
  const isValid2 = iterate(
@@ -14929,18 +14972,6 @@ class FormDetails {
14929
14972
  const ret = Object.fromEntries(validFields.map((f) => [f.apiKey, coerce2(f.value)]));
14930
14973
  return ret;
14931
14974
  }
14932
- ensureApiRequiredFields() {
14933
- const formData = this.formData;
14934
- const missingKeys = this.requiredApiKeys.filter((k) => !(k in formData));
14935
- if (missingKeys.length > 0) {
14936
- console.error("(ensureApiRequiredFields) Missing required API keys: " + missingKeys.join(","));
14937
- return false;
14938
- }
14939
- return true;
14940
- }
14941
- get requiredApiKeys() {
14942
- return Forms.getRequiredApiKeys(this.formId);
14943
- }
14944
14975
  get apiErrors() {
14945
14976
  return get$2(this.#apiErrors);
14946
14977
  }
@@ -14967,6 +14998,23 @@ class FormDetails {
14967
14998
  field.apiErrors = value;
14968
14999
  }
14969
15000
  }
15001
+ validateForm() {
15002
+ get$2(this.#fields).forEach((f) => this.validateField(f));
15003
+ }
15004
+ validateField(field) {
15005
+ field.validate();
15006
+ if (field.apiKey === "password_confirmation") {
15007
+ const passwordField = this.fields.find((f) => f.apiKey === "password");
15008
+ if (!passwordField || passwordField && field.value !== passwordField.value) {
15009
+ field.errors.push("Passwords do not match");
15010
+ }
15011
+ } else if (field.apiKey === "email_confirmation") {
15012
+ const emailField = this.fields.find((f) => f.apiKey === "email");
15013
+ if (!emailField || emailField && field.value !== emailField.value) {
15014
+ field.errors.push("Emails do not match");
15015
+ }
15016
+ }
15017
+ }
14970
15018
  addField(field) {
14971
15019
  get$2(this.#fields).push(field);
14972
15020
  }
@@ -15011,9 +15059,7 @@ function Form($$anchor, $$props) {
15011
15059
  setDetails($$props.$$host, details);
15012
15060
  async function handleSubmit(event) {
15013
15061
  event.preventDefault();
15014
- details.fields.forEach((f) => {
15015
- f.validate();
15016
- });
15062
+ details.validateForm();
15017
15063
  details?.form?.dispatchEvent(new Event("after-validation", event));
15018
15064
  if (details.isValid) {
15019
15065
  details?.form?.dispatchEvent(new Event("go-submit", event));
@@ -15074,7 +15120,6 @@ function SignIn($$anchor, $$props) {
15074
15120
  let form;
15075
15121
  Forms.defineForm({
15076
15122
  id: "signIn",
15077
- requiredApiKeys: ["email", "password"],
15078
15123
  fields: [
15079
15124
  { key: "email", required: true },
15080
15125
  { key: "password", required: true }
@@ -15085,7 +15130,6 @@ function SignIn($$anchor, $$props) {
15085
15130
  throw new Error("(go-sign-in: form not found");
15086
15131
  }
15087
15132
  const result = await shop.signIn(form.details.formData);
15088
- form.details.ensureApiRequiredFields();
15089
15133
  if (result.data) {
15090
15134
  $$props.$$host.dispatchEvent(new Event("go-success", { bubbles: true, composed: true }));
15091
15135
  } else {
@@ -15297,6 +15341,10 @@ function syncCartToLocalStorage(cart2) {
15297
15341
  let lastLS = "";
15298
15342
  setInterval(
15299
15343
  () => {
15344
+ if (!localStorage) {
15345
+ console.warn("(syncCartToLocalStorage) localStorage is not available");
15346
+ return;
15347
+ }
15300
15348
  let newLS = localStorage.getItem("go-cart");
15301
15349
  if (lastLS && lastLS !== newLS) {
15302
15350
  loadFromLocalStorage(cart2);
@@ -15560,13 +15608,6 @@ function CheckoutForm($$anchor, $$props) {
15560
15608
  { key: "email", required: true },
15561
15609
  { key: "confirmEmail", required: true },
15562
15610
  { key: "acceptTerms", required: true }
15563
- ],
15564
- requiredApiKeys: [
15565
- "firstName",
15566
- "lastName",
15567
- "email",
15568
- "confirmEmail",
15569
- "acceptTerms"
15570
15611
  ]
15571
15612
  });
15572
15613
  wrapInElement($$props.$$host, "go-form", { "form-id": "checkoutGuest", custom: custom2() });
@@ -27571,7 +27612,7 @@ class BaseNumericSegmentState {
27571
27612
  getSegmentProps() {
27572
27613
  const segmentValues = this.root.segmentValues;
27573
27614
  const placeholder = this.root.placeholder.current;
27574
- const isEmpty = segmentValues[this.part] === null;
27615
+ const isEmpty2 = segmentValues[this.part] === null;
27575
27616
  let date2 = placeholder;
27576
27617
  if (segmentValues[this.part]) {
27577
27618
  date2 = placeholder.set({ [this.part]: Number.parseInt(segmentValues[this.part]) });
@@ -27579,9 +27620,9 @@ class BaseNumericSegmentState {
27579
27620
  const valueNow = date2[this.part];
27580
27621
  const valueMin = this.#getMin();
27581
27622
  const valueMax = this.#getMax();
27582
- let valueText = isEmpty ? "Empty" : `${valueNow}`;
27623
+ let valueText = isEmpty2 ? "Empty" : `${valueNow}`;
27583
27624
  if (this.part === "hour" && "dayPeriod" in segmentValues && segmentValues.dayPeriod) {
27584
- valueText = isEmpty ? "Empty" : `${valueNow} ${segmentValues.dayPeriod}`;
27625
+ valueText = isEmpty2 ? "Empty" : `${valueNow} ${segmentValues.dayPeriod}`;
27585
27626
  }
27586
27627
  return {
27587
27628
  "aria-label": `${this.part}, `,
@@ -29822,7 +29863,7 @@ function InputAndLabel($$anchor, $$props) {
29822
29863
  reset(fieldset);
29823
29864
  append($$anchor2, fieldset);
29824
29865
  };
29825
- let field = prop($$props, "field", 15), describedById = prop($$props, "describedById", 7), labelClass = prop($$props, "labelClass", 7), inputClass = prop($$props, "inputClass", 7), restProps = /* @__PURE__ */ rest_props($$props, [
29866
+ let field = prop($$props, "field", 15), describedById = prop($$props, "describedById", 7), labelClass = prop($$props, "labelClass", 7), inputClass = prop($$props, "inputClass", 7), host = prop($$props, "host", 7), restProps = /* @__PURE__ */ rest_props($$props, [
29826
29867
  "$$slots",
29827
29868
  "$$events",
29828
29869
  "$$legacy",
@@ -29830,8 +29871,13 @@ function InputAndLabel($$anchor, $$props) {
29830
29871
  "field",
29831
29872
  "describedById",
29832
29873
  "labelClass",
29833
- "inputClass"
29874
+ "inputClass",
29875
+ "host"
29834
29876
  ]);
29877
+ let details = /* @__PURE__ */ state(void 0);
29878
+ onMount(async () => {
29879
+ set(details, await pollUntilTruthy(() => getDetails(host())), true);
29880
+ });
29835
29881
  const map = {
29836
29882
  input,
29837
29883
  text: input,
@@ -29851,7 +29897,9 @@ function InputAndLabel($$anchor, $$props) {
29851
29897
  let inputId = /* @__PURE__ */ user_derived(() => $$props.id || `go-field-${Math.random().toString(36).substring(2, 9)}`);
29852
29898
  function onblur() {
29853
29899
  if (field().type == "checkbox") return;
29854
- if (field().value) field().validate();
29900
+ if (field().value) if (get$2(details))
29901
+ get$2(details).validateField(field());
29902
+ else field().validate();
29855
29903
  }
29856
29904
  const fieldAttributes = /* @__PURE__ */ user_derived(() => ({
29857
29905
  id: get$2(inputId),
@@ -29889,6 +29937,13 @@ function InputAndLabel($$anchor, $$props) {
29889
29937
  set inputClass($$value) {
29890
29938
  inputClass($$value);
29891
29939
  flushSync();
29940
+ },
29941
+ get host() {
29942
+ return host();
29943
+ },
29944
+ set host($$value) {
29945
+ host($$value);
29946
+ flushSync();
29892
29947
  }
29893
29948
  };
29894
29949
  var fragment_6 = comment();
@@ -29907,7 +29962,19 @@ function InputAndLabel($$anchor, $$props) {
29907
29962
  append($$anchor, fragment_6);
29908
29963
  return pop($$exports);
29909
29964
  }
29910
- create_custom_element(InputAndLabel, { field: {}, describedById: {}, labelClass: {}, inputClass: {} }, [], [], true);
29965
+ create_custom_element(
29966
+ InputAndLabel,
29967
+ {
29968
+ field: {},
29969
+ describedById: {},
29970
+ labelClass: {},
29971
+ inputClass: {},
29972
+ host: {}
29973
+ },
29974
+ [],
29975
+ [],
29976
+ true
29977
+ );
29911
29978
  var root_1$6 = /* @__PURE__ */ from_html(`<span> </span>`);
29912
29979
  var root_3$3 = /* @__PURE__ */ from_html(`<li> </li>`);
29913
29980
  var root_2$5 = /* @__PURE__ */ from_html(`<ul class="go-field-errors" role="alert"></ul>`);
@@ -29939,7 +30006,7 @@ function Field($$anchor, $$props) {
29939
30006
  // Track the value
29940
30007
  ).value;
29941
30008
  untrack(() => {
29942
- if (get$2(field)?.errors.length) get$2(field).validate();
30009
+ if (get$2(field)?.errors.length) get$2(details).validateField(get$2(field));
29943
30010
  });
29944
30011
  });
29945
30012
  var $$exports = {
@@ -29985,6 +30052,7 @@ function Field($$anchor, $$props) {
29985
30052
  get inputClass() {
29986
30053
  return inputClass();
29987
30054
  },
30055
+ host: $$props.$$host,
29988
30056
  get field() {
29989
30057
  return get$2(field);
29990
30058
  },
@@ -30069,15 +30137,18 @@ var root_5 = /* @__PURE__ */ from_html(`<p aria-hidden="true"> </p>`);
30069
30137
  var root_1$4 = /* @__PURE__ */ from_html(`<div><!> <!> <!></div>`);
30070
30138
  function ErrorsFeedback($$anchor, $$props) {
30071
30139
  push($$props, true);
30072
- let details = getDetails($$props.$$host);
30140
+ let details = /* @__PURE__ */ state(void 0);
30141
+ onMount(async () => {
30142
+ set(details, await pollUntilTruthy(() => getDetails($$props.$$host)), true);
30143
+ });
30073
30144
  user_effect(() => {
30074
- details?.form?.addEventListener("after-validation", () => {
30075
- set(errorsOnSubmit, errors(details?.fields), true);
30145
+ get$2(details)?.form?.addEventListener("after-validation", () => {
30146
+ set(errorsOnSubmit, errors(get$2(details)?.fields), true);
30076
30147
  });
30077
30148
  });
30078
- let errorsRealtime = /* @__PURE__ */ user_derived(() => errors(details?.fields));
30149
+ let errorsRealtime = /* @__PURE__ */ user_derived(() => errors(get$2(details)?.fields));
30079
30150
  let errorsOnSubmit = /* @__PURE__ */ state(-1);
30080
- let generalApiErrors = /* @__PURE__ */ user_derived(() => details && isArray(details.apiErrors) && details.apiErrors.length > 0 ? details.apiErrors : []);
30151
+ let generalApiErrors = /* @__PURE__ */ user_derived(() => get$2(details) && isArray(get$2(details).apiErrors) && get$2(details).apiErrors.length > 0 ? get$2(details).apiErrors : []);
30081
30152
  var fragment = comment();
30082
30153
  var node = first_child(fragment);
30083
30154
  {
@@ -30101,7 +30172,7 @@ function ErrorsFeedback($$anchor, $$props) {
30101
30172
  {
30102
30173
  var consequent_1 = ($$anchor3) => {
30103
30174
  var ul = root_3$2();
30104
- each(ul, 21, () => details?.apiErrors, index$1, ($$anchor4, error) => {
30175
+ each(ul, 21, () => get$2(details)?.apiErrors, index$1, ($$anchor4, error) => {
30105
30176
  var li = root_4$1();
30106
30177
  var text_1 = child(li, true);
30107
30178
  reset(li);
@@ -30112,7 +30183,7 @@ function ErrorsFeedback($$anchor, $$props) {
30112
30183
  append($$anchor3, ul);
30113
30184
  };
30114
30185
  if_block(node_2, ($$render) => {
30115
- if (details && isArray(details.apiErrors) && details.apiErrors.length > 0) $$render(consequent_1);
30186
+ if (get$2(details) && isArray(get$2(details).apiErrors) && get$2(details).apiErrors.length > 0) $$render(consequent_1);
30116
30187
  });
30117
30188
  }
30118
30189
  var node_3 = sibling(node_2, 2);
@@ -30369,7 +30440,12 @@ function If($$anchor, $$props) {
30369
30440
  }
30370
30441
  });
30371
30442
  onMount(async () => {
30372
- set(formDetails, await pollUntilTruthy(() => getDetails($$props.$$host)), true);
30443
+ try {
30444
+ set(formDetails, await pollUntilTruthy(() => getDetails($$props.$$host)), true);
30445
+ } catch (error) {
30446
+ console.error("(IF) Error fetching form details:", error);
30447
+ console.error("Stack trace:", error instanceof Error ? error.stack : "No stack trace available");
30448
+ }
30373
30449
  });
30374
30450
  var $$exports = {
30375
30451
  get data() {
@@ -478,9 +478,21 @@ export declare class Shop {
478
478
  annualTicketPersonalizationList: (token: string) => string;
479
479
  annualTicketPersonalizationFormSubmit: (token: string) => string;
480
480
  } | undefined;
481
- apiPost<T>(path: string, { body }: {
481
+ /**
482
+ * Sends a POST request to the specified API endpoint.
483
+ *
484
+ * @param {string} path - The API endpoint path to make the POST request.
485
+ * @param {Object} options - The options for the POST request.
486
+ * @param {Record<string, any>} options.body - The body of the POST request, containing the data to be sent.
487
+ * @param {Record<string, unknown>} [options.params] - Optional query parameters to include in the request.
488
+ * @param {string[]} [options.requiredFields] - Optional list of required field names to validate in the body.
489
+ * @return {Promise<T>} A promise that resolves with the typed response of the POST request.
490
+ */
491
+ apiPost<T>(path: string, options: {
482
492
  body: Record<string, any>;
483
- }, params?: Record<string, unknown>): Promise<T>;
493
+ params?: Record<string, unknown>;
494
+ requiredFields?: string[];
495
+ }): Promise<T>;
484
496
  waitForAllFetches(...variables: unknown[]): Promise<void>;
485
497
  apiGet(path: `/api${string}`, query?: Record<string, unknown>, pathOptions?: Record<string, unknown>): Promise<unknown>;
486
498
  }
@@ -0,0 +1,18 @@
1
+ export type ValidationErrors = Record<string, string[]>;
2
+ /**
3
+ * Checks if a value is empty (null, undefined, empty string, or whitespace-only string)
4
+ */
5
+ export declare function isEmpty(value: unknown): boolean;
6
+ /**
7
+ * Validates that all required fields are present and non-empty in the body
8
+ * @returns Record of field names to error messages, empty if all valid
9
+ */
10
+ export declare function validateRequiredFields(body: Record<string, any>, requiredFields?: string[]): ValidationErrors;
11
+ /**
12
+ * Validates the entire request body for API POST requests
13
+ * - Validates required fields
14
+ * - Validates email fields
15
+ * - Validates password fields
16
+ * @returns Record of field names to error messages, empty if all valid
17
+ */
18
+ export declare function validateApiPostBody(body: Record<string, any>, requiredFields?: string[]): ValidationErrors;
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "name": "Giantmonkey GmbH"
5
5
  },
6
6
  "license": "MIT",
7
- "version": "1.12.0",
7
+ "version": "1.13.0",
8
8
  "type": "module",
9
9
  "main": "./dist-js/gomus-webcomponents.iife.js",
10
10
  "module": "./dist-js/gomus-webcomponents.iife.js",
@@ -92,7 +92,7 @@
92
92
  "vite": "^7.1.12",
93
93
  "vite-plugin-dts": "^4.5.4",
94
94
  "vite-plugin-string": "^1.2.3",
95
- "vitest": "^4.0.5",
95
+ "vitest": "^3.2.4",
96
96
  "wait-for-expect": "^4.0.0",
97
97
  "eslint-plugin-storybook": "10.0.1"
98
98
  },