@gomusdev/web-components 4.17.0 → 4.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist-js/gomus-webcomponents.iife.js +157 -17
- package/dist-js/gomus-webcomponents.js +157 -17
- package/dist-js/gomus-webcomponents.min.iife.js +21 -21
- package/dist-js/gomus-webcomponents.min.js +4689 -4613
- package/dist-js/src/go/go.d.ts +10 -3
- package/dist-js/src/lib/stores/shop.customerWrites.spec.d.ts +1 -0
- package/dist-js/src/lib/stores/shop.invalidate.spec.d.ts +1 -0
- package/dist-js/src/lib/stores/shop.svelte.d.ts +47 -12
- package/dist-js/src/test-helpers/signInLocally.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.18.0 (2026-08-18)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* **go:** expose customer write endpoints on go.api
|
|
8
|
+
* **shop:** cache invalidation by endpoint prefix
|
|
9
|
+
* **shop:** createCustomerAddress
|
|
10
|
+
* **shop:** update/delete customer address
|
|
11
|
+
* **shop:** updateCustomer profile update
|
|
12
|
+
* **types:** schema types for customer write endpoints
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* **profile:** localize the not-signed-in error in go-profile-password
|
|
17
|
+
* **shop:** harden the customer writes after review
|
|
18
|
+
* **shop:** only street, zip and city are required on address create
|
|
19
|
+
* **shop:** stop asyncFetch spinning forever on uncached reads
|
|
20
|
+
* **types,docs:** match the write error types and success gates to real openapi-fetch behavior
|
|
21
|
+
* **types:** regenerate schema from the corrected v4 swagger
|
|
22
|
+
|
|
23
|
+
## 4.17.1 (2026-08-14)
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
27
|
+
* **forms:** stop coercing text-field values to numbers in formData
|
|
28
|
+
|
|
3
29
|
## 4.17.0 (2026-08-12)
|
|
4
30
|
|
|
5
31
|
### Features
|
|
@@ -6385,7 +6385,8 @@ createHTML: (html) => {
|
|
|
6385
6385
|
"cart.item.remove": "✕",
|
|
6386
6386
|
"common.table.donation": "Donation",
|
|
6387
6387
|
"cart.donation.title": "Donation",
|
|
6388
|
-
"donations.checkbox.label": "Add a {{amount}} donation to {{campaign}}"
|
|
6388
|
+
"donations.checkbox.label": "Add a {{amount}} donation to {{campaign}}",
|
|
6389
|
+
"Not signed in": "Not signed in"
|
|
6389
6390
|
},
|
|
6390
6391
|
de: {
|
|
6391
6392
|
"quantity.remove": "Artikel entfernen",
|
|
@@ -6393,7 +6394,8 @@ createHTML: (html) => {
|
|
|
6393
6394
|
"cart.item.remove": "✕",
|
|
6394
6395
|
"common.table.donation": "Spende",
|
|
6395
6396
|
"cart.donation.title": "Spende",
|
|
6396
|
-
"donations.checkbox.label": "{{amount}} für {{campaign}} spenden"
|
|
6397
|
+
"donations.checkbox.label": "{{amount}} für {{campaign}} spenden",
|
|
6398
|
+
"Not signed in": "Nicht angemeldet"
|
|
6397
6399
|
}
|
|
6398
6400
|
};
|
|
6399
6401
|
//#endregion
|
|
@@ -13720,12 +13722,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
13720
13722
|
var TICKET_AND_QUOTAS_ENDPOINT = "/api/v4/tickets/list_and_quotas";
|
|
13721
13723
|
var TICKETS_ENDPOINT = "/api/v4/tickets";
|
|
13722
13724
|
var SIGN_IN_ENDPOINT = "/api/v4/auth/sign_in";
|
|
13723
|
-
var
|
|
13725
|
+
var AUTH_ENDPOINT = "/api/v4/auth";
|
|
13724
13726
|
var WITHDRAWAL_ENDPOINT = "/api/v4/orders/withdrawals";
|
|
13725
13727
|
var MEMBERSHIP_ACTIVATION_ENDPOINT = "/api/v4/customer/memberships/activate";
|
|
13726
13728
|
var ORDERS_ENDPOINT = "/api/v4/orders";
|
|
13727
13729
|
var CUSTOMER_ADDRESSES_ENDPOINT = "/api/v4/customer/customer_addresses";
|
|
13728
13730
|
var CUSTOMER_MEMBERSHIPS_ENDPOINT = "/api/v4/customer/memberships";
|
|
13731
|
+
var CUSTOMER_ADDRESS_ENDPOINT = "/api/v4/customer/customer_addresses/{id}";
|
|
13729
13732
|
//#endregion
|
|
13730
13733
|
//#region ../../packages/gomus-api/lib/customerLevels.ts
|
|
13731
13734
|
var CustomerLevels = {
|
|
@@ -13747,6 +13750,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
13747
13750
|
locales: []
|
|
13748
13751
|
}));
|
|
13749
13752
|
#fetchStatus = {};
|
|
13753
|
+
#invalidationGens = {};
|
|
13754
|
+
#collectReads = null;
|
|
13750
13755
|
constructor(apiUrl, shopDomain, locale, type = "angular") {
|
|
13751
13756
|
this.type = type;
|
|
13752
13757
|
if (apiUrl && shopDomain && locale) this.load(apiUrl, shopDomain, locale, type);
|
|
@@ -13866,6 +13871,66 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
13866
13871
|
if (!this.auth.data.accessToken) return NOT_SIGNED_IN;
|
|
13867
13872
|
return this.fetchAndCache(CUSTOMER_MEMBERSHIPS_ENDPOINT, "customerMemberships", "", { cache: 5 });
|
|
13868
13873
|
}
|
|
13874
|
+
/**
|
|
13875
|
+
* Shared skeleton of every customer write: the no-local-token guard (a write
|
|
13876
|
+
* must never ride on a same-origin session cookie), the request itself, and
|
|
13877
|
+
* the invalidate-on-ok tail that makes the matching cached read refetch.
|
|
13878
|
+
* Pass `null` when no cached read depends on the written resource.
|
|
13879
|
+
*/
|
|
13880
|
+
async #authedWrite(invalidates, call) {
|
|
13881
|
+
if (!this.auth.data.accessToken) return NOT_SIGNED_IN;
|
|
13882
|
+
const result = await call();
|
|
13883
|
+
if (invalidates && result?.response?.ok) this.invalidate(invalidates);
|
|
13884
|
+
return result;
|
|
13885
|
+
}
|
|
13886
|
+
/**
|
|
13887
|
+
* Creates a saved address for the signed-in customer, then drops the cached
|
|
13888
|
+
* address list so the next getCustomerAddresses() refetches.
|
|
13889
|
+
* The backend body is root-keyed ({ customer_address: {...} }) — rootKey makes
|
|
13890
|
+
* apiCall validate required fields on the inner params, because the backend's
|
|
13891
|
+
* 422 carries no body and would give integrators nothing actionable.
|
|
13892
|
+
*/
|
|
13893
|
+
async createCustomerAddress(params) {
|
|
13894
|
+
return this.#authedWrite(CUSTOMER_ADDRESSES_ENDPOINT, () => this.apiPost(CUSTOMER_ADDRESSES_ENDPOINT, {
|
|
13895
|
+
body: { customer_address: params },
|
|
13896
|
+
requiredFields: [
|
|
13897
|
+
"street",
|
|
13898
|
+
"zip",
|
|
13899
|
+
"city"
|
|
13900
|
+
],
|
|
13901
|
+
rootKey: "customer_address"
|
|
13902
|
+
}));
|
|
13903
|
+
}
|
|
13904
|
+
/**
|
|
13905
|
+
* Updates a saved address (partial body — the backend applies only the sent
|
|
13906
|
+
* fields), then drops the cached address list. No client-side requiredFields:
|
|
13907
|
+
* every field is optional on update.
|
|
13908
|
+
*/
|
|
13909
|
+
async updateCustomerAddress(id, params) {
|
|
13910
|
+
return this.#authedWrite(CUSTOMER_ADDRESSES_ENDPOINT, () => this.apiPut(CUSTOMER_ADDRESS_ENDPOINT, {
|
|
13911
|
+
body: { customer_address: params },
|
|
13912
|
+
params: { path: { id } }
|
|
13913
|
+
}));
|
|
13914
|
+
}
|
|
13915
|
+
/**
|
|
13916
|
+
* Deletes a saved address, then drops the cached address list. Success is
|
|
13917
|
+
* 204 No Content — parseAs 'text' skips openapi-fetch's JSON parse (see apiCall).
|
|
13918
|
+
*/
|
|
13919
|
+
async deleteCustomerAddress(id) {
|
|
13920
|
+
return this.#authedWrite(CUSTOMER_ADDRESSES_ENDPOINT, () => this.apiDELETE(CUSTOMER_ADDRESS_ENDPOINT, {
|
|
13921
|
+
params: { path: { id } },
|
|
13922
|
+
parseAs: "text"
|
|
13923
|
+
}));
|
|
13924
|
+
}
|
|
13925
|
+
/**
|
|
13926
|
+
* Updates the signed-in customer's profile (PUT /api/v4/auth — devise
|
|
13927
|
+
* registrations#update). Profile fields only: password changes go through
|
|
13928
|
+
* updatePassword, which enforces current_password. Invalidates the cached
|
|
13929
|
+
* validate_token payload so getCustomer() refetches the fresh profile.
|
|
13930
|
+
*/
|
|
13931
|
+
async updateCustomer(params) {
|
|
13932
|
+
return this.#authedWrite(VALIDATE_TOKEN_ENDPOINT, () => this.apiPut(AUTH_ENDPOINT, { body: params }));
|
|
13933
|
+
}
|
|
13869
13934
|
ticketsCalendar(params) {
|
|
13870
13935
|
return this.fetchAndCache(TICKETS_CALENDAR_ENDPOINT, `ticketsCalendar-${JSON.stringify(params)}`, "data", {
|
|
13871
13936
|
cache: 60,
|
|
@@ -13918,7 +13983,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
13918
13983
|
"terms"
|
|
13919
13984
|
];
|
|
13920
13985
|
if (!asGuest) requiredFields.push("password", "password_confirmation");
|
|
13921
|
-
return this.apiPost(
|
|
13986
|
+
return this.apiPost(AUTH_ENDPOINT, {
|
|
13922
13987
|
body: params,
|
|
13923
13988
|
requiredFields
|
|
13924
13989
|
});
|
|
@@ -13946,15 +14011,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
13946
14011
|
cache: 60
|
|
13947
14012
|
});
|
|
13948
14013
|
}
|
|
13949
|
-
updatePassword(params) {
|
|
13950
|
-
return this.apiPut("/api/v4/auth/password", {
|
|
14014
|
+
async updatePassword(params) {
|
|
14015
|
+
return this.#authedWrite(null, () => this.apiPut("/api/v4/auth/password", {
|
|
13951
14016
|
body: params,
|
|
13952
14017
|
requiredFields: [
|
|
13953
14018
|
"current_password",
|
|
13954
14019
|
"password",
|
|
13955
14020
|
"password_confirmation"
|
|
13956
14021
|
]
|
|
13957
|
-
});
|
|
14022
|
+
}));
|
|
13958
14023
|
}
|
|
13959
14024
|
passwordReset(params) {
|
|
13960
14025
|
return this.apiPost("/api/v4/auth/password", {
|
|
@@ -14029,6 +14094,25 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14029
14094
|
}
|
|
14030
14095
|
});
|
|
14031
14096
|
}
|
|
14097
|
+
/**
|
|
14098
|
+
* Drops every cached GET slot whose fetch id starts with the endpoint prefix,
|
|
14099
|
+
* so the next fetchAndCache read refetches. #data is left intact — consumers
|
|
14100
|
+
* keep rendering the last known value until the refetch lands (no flash of
|
|
14101
|
+
* empty). Write methods call this after mutating the corresponding resource.
|
|
14102
|
+
*
|
|
14103
|
+
* In-flight slots are marked instead of deleted: their response predates the
|
|
14104
|
+
* write, but deleting them would release waitForAllFetches early AND let
|
|
14105
|
+
* apiGet resurrect the slot as fresh when the stale response lands. apiGet
|
|
14106
|
+
* drops marked slots on completion, so the next read refetches.
|
|
14107
|
+
*/
|
|
14108
|
+
invalidate(endpointPrefix) {
|
|
14109
|
+
for (const [fetchId, slot] of Object.entries(this.#fetchStatus)) {
|
|
14110
|
+
if (!fetchId.startsWith(endpointPrefix)) continue;
|
|
14111
|
+
if (slot.status === "fetching") slot.invalidated = true;
|
|
14112
|
+
else delete this.#fetchStatus[fetchId];
|
|
14113
|
+
this.#invalidationGens[fetchId] = (this.#invalidationGens[fetchId] ?? 0) + 1;
|
|
14114
|
+
}
|
|
14115
|
+
}
|
|
14032
14116
|
#fetchId(endpoint, query, path = {}) {
|
|
14033
14117
|
return endpoint + JSON.stringify(query) + JSON.stringify(path);
|
|
14034
14118
|
}
|
|
@@ -14054,6 +14138,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14054
14138
|
}
|
|
14055
14139
|
const query = options.query;
|
|
14056
14140
|
const fetchId = this.#fetchId(endpoint, query, options.path);
|
|
14141
|
+
this.#collectReads?.(fetchId);
|
|
14057
14142
|
const isNotFetchedYet = !this.#fetchStatus[fetchId];
|
|
14058
14143
|
const isCacheExpired = this.#fetchStatus[fetchId]?.fetchedAt < Date.now() - options.cache * 1e3;
|
|
14059
14144
|
if (isNotFetchedYet || isCacheExpired) this.apiGet(endpoint, query, options.path).then((ret) => {
|
|
@@ -14164,8 +14249,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14164
14249
|
}
|
|
14165
14250
|
async apiCall(path, options) {
|
|
14166
14251
|
this.#ensureApi();
|
|
14167
|
-
const { body, params = {}, requiredFields, parseAs } = options;
|
|
14168
|
-
const validationErrors = validateApiPostBody(body, requiredFields);
|
|
14252
|
+
const { body, params = {}, requiredFields, rootKey, parseAs } = options;
|
|
14253
|
+
const validationErrors = validateApiPostBody((rootKey ? body?.[rootKey] : body) ?? {}, requiredFields);
|
|
14169
14254
|
if (Object.keys(validationErrors).length > 0) return { error: { errors: validationErrors } };
|
|
14170
14255
|
const httpMethod = this.client[options.method];
|
|
14171
14256
|
return await httpMethod(path, {
|
|
@@ -14179,8 +14264,28 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14179
14264
|
* and returns the result of the method execution.
|
|
14180
14265
|
*/
|
|
14181
14266
|
async asyncFetch(method) {
|
|
14182
|
-
|
|
14183
|
-
|
|
14267
|
+
const seenGens = /* @__PURE__ */ new Map();
|
|
14268
|
+
const call = () => {
|
|
14269
|
+
const prev = this.#collectReads;
|
|
14270
|
+
this.#collectReads = (id) => {
|
|
14271
|
+
if (!seenGens.has(id)) seenGens.set(id, this.#invalidationGens[id] ?? 0);
|
|
14272
|
+
};
|
|
14273
|
+
try {
|
|
14274
|
+
return method();
|
|
14275
|
+
} finally {
|
|
14276
|
+
this.#collectReads = prev;
|
|
14277
|
+
}
|
|
14278
|
+
};
|
|
14279
|
+
const invalidatedSinceRead = () => [...seenGens].some(([id, gen]) => (this.#invalidationGens[id] ?? 0) !== gen);
|
|
14280
|
+
call();
|
|
14281
|
+
await this.waitForAllFetches();
|
|
14282
|
+
let result = call();
|
|
14283
|
+
while (invalidatedSinceRead()) {
|
|
14284
|
+
for (const id of seenGens.keys()) seenGens.set(id, this.#invalidationGens[id] ?? 0);
|
|
14285
|
+
await this.waitForAllFetches();
|
|
14286
|
+
result = call();
|
|
14287
|
+
}
|
|
14288
|
+
return result;
|
|
14184
14289
|
}
|
|
14185
14290
|
async waitForAllFetches(...variables) {
|
|
14186
14291
|
while (Object.values(this.#fetchStatus).filter((f) => f.status === "fetching").length) await wait(10);
|
|
@@ -14196,7 +14301,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14196
14301
|
if (query) params = assign(params, { params: { query } });
|
|
14197
14302
|
if (path) params = assign(params, { params: { path: pathOptions } });
|
|
14198
14303
|
const ret = await this.client.GET(path, params);
|
|
14199
|
-
this.#fetchStatus[fetchId]
|
|
14304
|
+
if (this.#fetchStatus[fetchId]?.invalidated) delete this.#fetchStatus[fetchId];
|
|
14305
|
+
else this.#fetchStatus[fetchId] = {
|
|
14200
14306
|
status: "completed",
|
|
14201
14307
|
fetchedAt: Date.now()
|
|
14202
14308
|
};
|
|
@@ -14963,14 +15069,21 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14963
15069
|
set(this.#successMessage, value, true);
|
|
14964
15070
|
}
|
|
14965
15071
|
get formData() {
|
|
14966
|
-
|
|
15072
|
+
const numericTypes = [
|
|
15073
|
+
"select",
|
|
15074
|
+
"radio",
|
|
15075
|
+
"number",
|
|
15076
|
+
"paymentMode"
|
|
15077
|
+
];
|
|
15078
|
+
function coerce(f) {
|
|
15079
|
+
const x = f.value;
|
|
14967
15080
|
if (isNumber(x) || isBoolean(x)) return x;
|
|
14968
15081
|
if (x === "true" || x === "false") return x === "true";
|
|
14969
|
-
if (!Number.isNaN(+x) && x.trim() !== "") return +x;
|
|
15082
|
+
if (numericTypes.includes(f.type) && !Number.isNaN(+x) && x.trim() !== "") return +x;
|
|
14970
15083
|
return x;
|
|
14971
15084
|
}
|
|
14972
15085
|
const validFields = this.fields.filter((f) => f !== void 0).filter((f) => f.type !== "file").filter((f) => f.value !== "");
|
|
14973
|
-
return Object.fromEntries(validFields.map((f) => [f.apiKey, coerce(f
|
|
15086
|
+
return Object.fromEntries(validFields.map((f) => [f.apiKey, coerce(f)]));
|
|
14974
15087
|
}
|
|
14975
15088
|
fieldValue(key) {
|
|
14976
15089
|
return this.fields.find((f) => f.key === key)?.value;
|
|
@@ -37558,10 +37671,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
37558
37671
|
async function submit(event) {
|
|
37559
37672
|
const details = event.target.details;
|
|
37560
37673
|
const result = await shop.updatePassword(details.formData);
|
|
37561
|
-
if (result.data) {
|
|
37674
|
+
if ("data" in result && result.data) {
|
|
37562
37675
|
details.successMessage = shop.t("user.passwordSuccess.desc.title");
|
|
37563
37676
|
details.apiErrors = [];
|
|
37564
|
-
} else
|
|
37677
|
+
} else {
|
|
37678
|
+
const errors = ("error" in result ? result.error : void 0)?.errors ?? [];
|
|
37679
|
+
details.apiErrors = Array.isArray(errors) ? errors.map((e) => shop.t(e)) : errors;
|
|
37680
|
+
}
|
|
37565
37681
|
}
|
|
37566
37682
|
init();
|
|
37567
37683
|
var go_form = root$6();
|
|
@@ -39141,6 +39257,30 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
39141
39257
|
getCustomerMemberships: async () => {
|
|
39142
39258
|
await ensureShopReady();
|
|
39143
39259
|
return shop.asyncFetch(() => shop.getCustomerMemberships());
|
|
39260
|
+
},
|
|
39261
|
+
createCustomerAddress: async (params) => {
|
|
39262
|
+
await ensureShopReady();
|
|
39263
|
+
return shop.createCustomerAddress(params);
|
|
39264
|
+
},
|
|
39265
|
+
updateCustomerAddress: async (id, params) => {
|
|
39266
|
+
await ensureShopReady();
|
|
39267
|
+
return shop.updateCustomerAddress(id, params);
|
|
39268
|
+
},
|
|
39269
|
+
deleteCustomerAddress: async (id) => {
|
|
39270
|
+
await ensureShopReady();
|
|
39271
|
+
return shop.deleteCustomerAddress(id);
|
|
39272
|
+
},
|
|
39273
|
+
updateCustomer: async (params) => {
|
|
39274
|
+
await ensureShopReady();
|
|
39275
|
+
return shop.updateCustomer(params);
|
|
39276
|
+
},
|
|
39277
|
+
updatePassword: async (params) => {
|
|
39278
|
+
await ensureShopReady();
|
|
39279
|
+
return shop.updatePassword(params);
|
|
39280
|
+
},
|
|
39281
|
+
requestPasswordReset: async (params) => {
|
|
39282
|
+
await ensureShopReady();
|
|
39283
|
+
return shop.passwordReset(params);
|
|
39144
39284
|
}
|
|
39145
39285
|
},
|
|
39146
39286
|
cart: { addItem: async (options) => {
|
|
@@ -6384,7 +6384,8 @@ var defaultTranslations_default = {
|
|
|
6384
6384
|
"cart.item.remove": "✕",
|
|
6385
6385
|
"common.table.donation": "Donation",
|
|
6386
6386
|
"cart.donation.title": "Donation",
|
|
6387
|
-
"donations.checkbox.label": "Add a {{amount}} donation to {{campaign}}"
|
|
6387
|
+
"donations.checkbox.label": "Add a {{amount}} donation to {{campaign}}",
|
|
6388
|
+
"Not signed in": "Not signed in"
|
|
6388
6389
|
},
|
|
6389
6390
|
de: {
|
|
6390
6391
|
"quantity.remove": "Artikel entfernen",
|
|
@@ -6392,7 +6393,8 @@ var defaultTranslations_default = {
|
|
|
6392
6393
|
"cart.item.remove": "✕",
|
|
6393
6394
|
"common.table.donation": "Spende",
|
|
6394
6395
|
"cart.donation.title": "Spende",
|
|
6395
|
-
"donations.checkbox.label": "{{amount}} für {{campaign}} spenden"
|
|
6396
|
+
"donations.checkbox.label": "{{amount}} für {{campaign}} spenden",
|
|
6397
|
+
"Not signed in": "Nicht angemeldet"
|
|
6396
6398
|
}
|
|
6397
6399
|
};
|
|
6398
6400
|
//#endregion
|
|
@@ -13719,12 +13721,13 @@ var VALIDATE_TOKEN_ENDPOINT = "/api/v4/auth/validate_token";
|
|
|
13719
13721
|
var TICKET_AND_QUOTAS_ENDPOINT = "/api/v4/tickets/list_and_quotas";
|
|
13720
13722
|
var TICKETS_ENDPOINT = "/api/v4/tickets";
|
|
13721
13723
|
var SIGN_IN_ENDPOINT = "/api/v4/auth/sign_in";
|
|
13722
|
-
var
|
|
13724
|
+
var AUTH_ENDPOINT = "/api/v4/auth";
|
|
13723
13725
|
var WITHDRAWAL_ENDPOINT = "/api/v4/orders/withdrawals";
|
|
13724
13726
|
var MEMBERSHIP_ACTIVATION_ENDPOINT = "/api/v4/customer/memberships/activate";
|
|
13725
13727
|
var ORDERS_ENDPOINT = "/api/v4/orders";
|
|
13726
13728
|
var CUSTOMER_ADDRESSES_ENDPOINT = "/api/v4/customer/customer_addresses";
|
|
13727
13729
|
var CUSTOMER_MEMBERSHIPS_ENDPOINT = "/api/v4/customer/memberships";
|
|
13730
|
+
var CUSTOMER_ADDRESS_ENDPOINT = "/api/v4/customer/customer_addresses/{id}";
|
|
13728
13731
|
//#endregion
|
|
13729
13732
|
//#region ../../packages/gomus-api/lib/customerLevels.ts
|
|
13730
13733
|
var CustomerLevels = {
|
|
@@ -13746,6 +13749,8 @@ var Shop = class {
|
|
|
13746
13749
|
locales: []
|
|
13747
13750
|
}));
|
|
13748
13751
|
#fetchStatus = {};
|
|
13752
|
+
#invalidationGens = {};
|
|
13753
|
+
#collectReads = null;
|
|
13749
13754
|
constructor(apiUrl, shopDomain, locale, type = "angular") {
|
|
13750
13755
|
this.type = type;
|
|
13751
13756
|
if (apiUrl && shopDomain && locale) this.load(apiUrl, shopDomain, locale, type);
|
|
@@ -13865,6 +13870,66 @@ var Shop = class {
|
|
|
13865
13870
|
if (!this.auth.data.accessToken) return NOT_SIGNED_IN;
|
|
13866
13871
|
return this.fetchAndCache(CUSTOMER_MEMBERSHIPS_ENDPOINT, "customerMemberships", "", { cache: 5 });
|
|
13867
13872
|
}
|
|
13873
|
+
/**
|
|
13874
|
+
* Shared skeleton of every customer write: the no-local-token guard (a write
|
|
13875
|
+
* must never ride on a same-origin session cookie), the request itself, and
|
|
13876
|
+
* the invalidate-on-ok tail that makes the matching cached read refetch.
|
|
13877
|
+
* Pass `null` when no cached read depends on the written resource.
|
|
13878
|
+
*/
|
|
13879
|
+
async #authedWrite(invalidates, call) {
|
|
13880
|
+
if (!this.auth.data.accessToken) return NOT_SIGNED_IN;
|
|
13881
|
+
const result = await call();
|
|
13882
|
+
if (invalidates && result?.response?.ok) this.invalidate(invalidates);
|
|
13883
|
+
return result;
|
|
13884
|
+
}
|
|
13885
|
+
/**
|
|
13886
|
+
* Creates a saved address for the signed-in customer, then drops the cached
|
|
13887
|
+
* address list so the next getCustomerAddresses() refetches.
|
|
13888
|
+
* The backend body is root-keyed ({ customer_address: {...} }) — rootKey makes
|
|
13889
|
+
* apiCall validate required fields on the inner params, because the backend's
|
|
13890
|
+
* 422 carries no body and would give integrators nothing actionable.
|
|
13891
|
+
*/
|
|
13892
|
+
async createCustomerAddress(params) {
|
|
13893
|
+
return this.#authedWrite(CUSTOMER_ADDRESSES_ENDPOINT, () => this.apiPost(CUSTOMER_ADDRESSES_ENDPOINT, {
|
|
13894
|
+
body: { customer_address: params },
|
|
13895
|
+
requiredFields: [
|
|
13896
|
+
"street",
|
|
13897
|
+
"zip",
|
|
13898
|
+
"city"
|
|
13899
|
+
],
|
|
13900
|
+
rootKey: "customer_address"
|
|
13901
|
+
}));
|
|
13902
|
+
}
|
|
13903
|
+
/**
|
|
13904
|
+
* Updates a saved address (partial body — the backend applies only the sent
|
|
13905
|
+
* fields), then drops the cached address list. No client-side requiredFields:
|
|
13906
|
+
* every field is optional on update.
|
|
13907
|
+
*/
|
|
13908
|
+
async updateCustomerAddress(id, params) {
|
|
13909
|
+
return this.#authedWrite(CUSTOMER_ADDRESSES_ENDPOINT, () => this.apiPut(CUSTOMER_ADDRESS_ENDPOINT, {
|
|
13910
|
+
body: { customer_address: params },
|
|
13911
|
+
params: { path: { id } }
|
|
13912
|
+
}));
|
|
13913
|
+
}
|
|
13914
|
+
/**
|
|
13915
|
+
* Deletes a saved address, then drops the cached address list. Success is
|
|
13916
|
+
* 204 No Content — parseAs 'text' skips openapi-fetch's JSON parse (see apiCall).
|
|
13917
|
+
*/
|
|
13918
|
+
async deleteCustomerAddress(id) {
|
|
13919
|
+
return this.#authedWrite(CUSTOMER_ADDRESSES_ENDPOINT, () => this.apiDELETE(CUSTOMER_ADDRESS_ENDPOINT, {
|
|
13920
|
+
params: { path: { id } },
|
|
13921
|
+
parseAs: "text"
|
|
13922
|
+
}));
|
|
13923
|
+
}
|
|
13924
|
+
/**
|
|
13925
|
+
* Updates the signed-in customer's profile (PUT /api/v4/auth — devise
|
|
13926
|
+
* registrations#update). Profile fields only: password changes go through
|
|
13927
|
+
* updatePassword, which enforces current_password. Invalidates the cached
|
|
13928
|
+
* validate_token payload so getCustomer() refetches the fresh profile.
|
|
13929
|
+
*/
|
|
13930
|
+
async updateCustomer(params) {
|
|
13931
|
+
return this.#authedWrite(VALIDATE_TOKEN_ENDPOINT, () => this.apiPut(AUTH_ENDPOINT, { body: params }));
|
|
13932
|
+
}
|
|
13868
13933
|
ticketsCalendar(params) {
|
|
13869
13934
|
return this.fetchAndCache(TICKETS_CALENDAR_ENDPOINT, `ticketsCalendar-${JSON.stringify(params)}`, "data", {
|
|
13870
13935
|
cache: 60,
|
|
@@ -13917,7 +13982,7 @@ var Shop = class {
|
|
|
13917
13982
|
"terms"
|
|
13918
13983
|
];
|
|
13919
13984
|
if (!asGuest) requiredFields.push("password", "password_confirmation");
|
|
13920
|
-
return this.apiPost(
|
|
13985
|
+
return this.apiPost(AUTH_ENDPOINT, {
|
|
13921
13986
|
body: params,
|
|
13922
13987
|
requiredFields
|
|
13923
13988
|
});
|
|
@@ -13945,15 +14010,15 @@ var Shop = class {
|
|
|
13945
14010
|
cache: 60
|
|
13946
14011
|
});
|
|
13947
14012
|
}
|
|
13948
|
-
updatePassword(params) {
|
|
13949
|
-
return this.apiPut("/api/v4/auth/password", {
|
|
14013
|
+
async updatePassword(params) {
|
|
14014
|
+
return this.#authedWrite(null, () => this.apiPut("/api/v4/auth/password", {
|
|
13950
14015
|
body: params,
|
|
13951
14016
|
requiredFields: [
|
|
13952
14017
|
"current_password",
|
|
13953
14018
|
"password",
|
|
13954
14019
|
"password_confirmation"
|
|
13955
14020
|
]
|
|
13956
|
-
});
|
|
14021
|
+
}));
|
|
13957
14022
|
}
|
|
13958
14023
|
passwordReset(params) {
|
|
13959
14024
|
return this.apiPost("/api/v4/auth/password", {
|
|
@@ -14028,6 +14093,25 @@ var Shop = class {
|
|
|
14028
14093
|
}
|
|
14029
14094
|
});
|
|
14030
14095
|
}
|
|
14096
|
+
/**
|
|
14097
|
+
* Drops every cached GET slot whose fetch id starts with the endpoint prefix,
|
|
14098
|
+
* so the next fetchAndCache read refetches. #data is left intact — consumers
|
|
14099
|
+
* keep rendering the last known value until the refetch lands (no flash of
|
|
14100
|
+
* empty). Write methods call this after mutating the corresponding resource.
|
|
14101
|
+
*
|
|
14102
|
+
* In-flight slots are marked instead of deleted: their response predates the
|
|
14103
|
+
* write, but deleting them would release waitForAllFetches early AND let
|
|
14104
|
+
* apiGet resurrect the slot as fresh when the stale response lands. apiGet
|
|
14105
|
+
* drops marked slots on completion, so the next read refetches.
|
|
14106
|
+
*/
|
|
14107
|
+
invalidate(endpointPrefix) {
|
|
14108
|
+
for (const [fetchId, slot] of Object.entries(this.#fetchStatus)) {
|
|
14109
|
+
if (!fetchId.startsWith(endpointPrefix)) continue;
|
|
14110
|
+
if (slot.status === "fetching") slot.invalidated = true;
|
|
14111
|
+
else delete this.#fetchStatus[fetchId];
|
|
14112
|
+
this.#invalidationGens[fetchId] = (this.#invalidationGens[fetchId] ?? 0) + 1;
|
|
14113
|
+
}
|
|
14114
|
+
}
|
|
14031
14115
|
#fetchId(endpoint, query, path = {}) {
|
|
14032
14116
|
return endpoint + JSON.stringify(query) + JSON.stringify(path);
|
|
14033
14117
|
}
|
|
@@ -14053,6 +14137,7 @@ var Shop = class {
|
|
|
14053
14137
|
}
|
|
14054
14138
|
const query = options.query;
|
|
14055
14139
|
const fetchId = this.#fetchId(endpoint, query, options.path);
|
|
14140
|
+
this.#collectReads?.(fetchId);
|
|
14056
14141
|
const isNotFetchedYet = !this.#fetchStatus[fetchId];
|
|
14057
14142
|
const isCacheExpired = this.#fetchStatus[fetchId]?.fetchedAt < Date.now() - options.cache * 1e3;
|
|
14058
14143
|
if (isNotFetchedYet || isCacheExpired) this.apiGet(endpoint, query, options.path).then((ret) => {
|
|
@@ -14163,8 +14248,8 @@ var Shop = class {
|
|
|
14163
14248
|
}
|
|
14164
14249
|
async apiCall(path, options) {
|
|
14165
14250
|
this.#ensureApi();
|
|
14166
|
-
const { body, params = {}, requiredFields, parseAs } = options;
|
|
14167
|
-
const validationErrors = validateApiPostBody(body, requiredFields);
|
|
14251
|
+
const { body, params = {}, requiredFields, rootKey, parseAs } = options;
|
|
14252
|
+
const validationErrors = validateApiPostBody((rootKey ? body?.[rootKey] : body) ?? {}, requiredFields);
|
|
14168
14253
|
if (Object.keys(validationErrors).length > 0) return { error: { errors: validationErrors } };
|
|
14169
14254
|
const httpMethod = this.client[options.method];
|
|
14170
14255
|
return await httpMethod(path, {
|
|
@@ -14178,8 +14263,28 @@ var Shop = class {
|
|
|
14178
14263
|
* and returns the result of the method execution.
|
|
14179
14264
|
*/
|
|
14180
14265
|
async asyncFetch(method) {
|
|
14181
|
-
|
|
14182
|
-
|
|
14266
|
+
const seenGens = /* @__PURE__ */ new Map();
|
|
14267
|
+
const call = () => {
|
|
14268
|
+
const prev = this.#collectReads;
|
|
14269
|
+
this.#collectReads = (id) => {
|
|
14270
|
+
if (!seenGens.has(id)) seenGens.set(id, this.#invalidationGens[id] ?? 0);
|
|
14271
|
+
};
|
|
14272
|
+
try {
|
|
14273
|
+
return method();
|
|
14274
|
+
} finally {
|
|
14275
|
+
this.#collectReads = prev;
|
|
14276
|
+
}
|
|
14277
|
+
};
|
|
14278
|
+
const invalidatedSinceRead = () => [...seenGens].some(([id, gen]) => (this.#invalidationGens[id] ?? 0) !== gen);
|
|
14279
|
+
call();
|
|
14280
|
+
await this.waitForAllFetches();
|
|
14281
|
+
let result = call();
|
|
14282
|
+
while (invalidatedSinceRead()) {
|
|
14283
|
+
for (const id of seenGens.keys()) seenGens.set(id, this.#invalidationGens[id] ?? 0);
|
|
14284
|
+
await this.waitForAllFetches();
|
|
14285
|
+
result = call();
|
|
14286
|
+
}
|
|
14287
|
+
return result;
|
|
14183
14288
|
}
|
|
14184
14289
|
async waitForAllFetches(...variables) {
|
|
14185
14290
|
while (Object.values(this.#fetchStatus).filter((f) => f.status === "fetching").length) await wait(10);
|
|
@@ -14195,7 +14300,8 @@ var Shop = class {
|
|
|
14195
14300
|
if (query) params = assign(params, { params: { query } });
|
|
14196
14301
|
if (path) params = assign(params, { params: { path: pathOptions } });
|
|
14197
14302
|
const ret = await this.client.GET(path, params);
|
|
14198
|
-
this.#fetchStatus[fetchId]
|
|
14303
|
+
if (this.#fetchStatus[fetchId]?.invalidated) delete this.#fetchStatus[fetchId];
|
|
14304
|
+
else this.#fetchStatus[fetchId] = {
|
|
14199
14305
|
status: "completed",
|
|
14200
14306
|
fetchedAt: Date.now()
|
|
14201
14307
|
};
|
|
@@ -14962,14 +15068,21 @@ var FormDetails = class {
|
|
|
14962
15068
|
set(this.#successMessage, value, true);
|
|
14963
15069
|
}
|
|
14964
15070
|
get formData() {
|
|
14965
|
-
|
|
15071
|
+
const numericTypes = [
|
|
15072
|
+
"select",
|
|
15073
|
+
"radio",
|
|
15074
|
+
"number",
|
|
15075
|
+
"paymentMode"
|
|
15076
|
+
];
|
|
15077
|
+
function coerce(f) {
|
|
15078
|
+
const x = f.value;
|
|
14966
15079
|
if (isNumber(x) || isBoolean(x)) return x;
|
|
14967
15080
|
if (x === "true" || x === "false") return x === "true";
|
|
14968
|
-
if (!Number.isNaN(+x) && x.trim() !== "") return +x;
|
|
15081
|
+
if (numericTypes.includes(f.type) && !Number.isNaN(+x) && x.trim() !== "") return +x;
|
|
14969
15082
|
return x;
|
|
14970
15083
|
}
|
|
14971
15084
|
const validFields = this.fields.filter((f) => f !== void 0).filter((f) => f.type !== "file").filter((f) => f.value !== "");
|
|
14972
|
-
return Object.fromEntries(validFields.map((f) => [f.apiKey, coerce(f
|
|
15085
|
+
return Object.fromEntries(validFields.map((f) => [f.apiKey, coerce(f)]));
|
|
14973
15086
|
}
|
|
14974
15087
|
fieldValue(key) {
|
|
14975
15088
|
return this.fields.find((f) => f.key === key)?.value;
|
|
@@ -37557,10 +37670,13 @@ function Password($$anchor, $$props) {
|
|
|
37557
37670
|
async function submit(event) {
|
|
37558
37671
|
const details = event.target.details;
|
|
37559
37672
|
const result = await shop.updatePassword(details.formData);
|
|
37560
|
-
if (result.data) {
|
|
37673
|
+
if ("data" in result && result.data) {
|
|
37561
37674
|
details.successMessage = shop.t("user.passwordSuccess.desc.title");
|
|
37562
37675
|
details.apiErrors = [];
|
|
37563
|
-
} else
|
|
37676
|
+
} else {
|
|
37677
|
+
const errors = ("error" in result ? result.error : void 0)?.errors ?? [];
|
|
37678
|
+
details.apiErrors = Array.isArray(errors) ? errors.map((e) => shop.t(e)) : errors;
|
|
37679
|
+
}
|
|
37564
37680
|
}
|
|
37565
37681
|
init();
|
|
37566
37682
|
var go_form = root$6();
|
|
@@ -39140,6 +39256,30 @@ var go = {
|
|
|
39140
39256
|
getCustomerMemberships: async () => {
|
|
39141
39257
|
await ensureShopReady();
|
|
39142
39258
|
return shop.asyncFetch(() => shop.getCustomerMemberships());
|
|
39259
|
+
},
|
|
39260
|
+
createCustomerAddress: async (params) => {
|
|
39261
|
+
await ensureShopReady();
|
|
39262
|
+
return shop.createCustomerAddress(params);
|
|
39263
|
+
},
|
|
39264
|
+
updateCustomerAddress: async (id, params) => {
|
|
39265
|
+
await ensureShopReady();
|
|
39266
|
+
return shop.updateCustomerAddress(id, params);
|
|
39267
|
+
},
|
|
39268
|
+
deleteCustomerAddress: async (id) => {
|
|
39269
|
+
await ensureShopReady();
|
|
39270
|
+
return shop.deleteCustomerAddress(id);
|
|
39271
|
+
},
|
|
39272
|
+
updateCustomer: async (params) => {
|
|
39273
|
+
await ensureShopReady();
|
|
39274
|
+
return shop.updateCustomer(params);
|
|
39275
|
+
},
|
|
39276
|
+
updatePassword: async (params) => {
|
|
39277
|
+
await ensureShopReady();
|
|
39278
|
+
return shop.updatePassword(params);
|
|
39279
|
+
},
|
|
39280
|
+
requestPasswordReset: async (params) => {
|
|
39281
|
+
await ensureShopReady();
|
|
39282
|
+
return shop.passwordReset(params);
|
|
39143
39283
|
}
|
|
39144
39284
|
},
|
|
39145
39285
|
cart: { addItem: async (options) => {
|