@asteby/storefront-react 0.25.0 → 0.26.1
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 +35 -2
- package/dist/account-server.cjs +128 -19
- package/dist/account-server.cjs.map +1 -1
- package/dist/account-server.d.cts +67 -4
- package/dist/account-server.d.ts +67 -4
- package/dist/account-server.js +125 -20
- package/dist/account-server.js.map +1 -1
- package/dist/account.cjs +252 -7
- package/dist/account.cjs.map +1 -1
- package/dist/account.css +91 -0
- package/dist/account.d.cts +130 -6
- package/dist/account.d.ts +130 -6
- package/dist/account.js +247 -8
- package/dist/account.js.map +1 -1
- package/dist/checkout.cjs +162 -8
- package/dist/checkout.cjs.map +1 -1
- package/dist/checkout.d.cts +104 -4
- package/dist/checkout.d.ts +104 -4
- package/dist/checkout.js +158 -10
- package/dist/checkout.js.map +1 -1
- package/dist/chunk-Z4C4LKXF.js +25 -0
- package/dist/chunk-Z4C4LKXF.js.map +1 -0
- package/dist/fiscal-server.cjs +27 -2
- package/dist/fiscal-server.cjs.map +1 -1
- package/dist/fiscal-server.d.cts +10 -1
- package/dist/fiscal-server.d.ts +10 -1
- package/dist/fiscal-server.js +26 -3
- package/dist/fiscal-server.js.map +1 -1
- package/dist/fit-key-B9sYQVwG.d.cts +49 -0
- package/dist/fit-key-B9sYQVwG.d.ts +49 -0
- package/dist/fitment.cjs +54 -9
- package/dist/fitment.cjs.map +1 -1
- package/dist/fitment.d.cts +20 -2
- package/dist/fitment.d.ts +20 -2
- package/dist/fitment.js +54 -10
- package/dist/fitment.js.map +1 -1
- package/dist/settings-types-jxFQFFqP.d.cts +77 -0
- package/dist/settings-types-jxFQFFqP.d.ts +77 -0
- package/dist/subjects-server.cjs +248 -0
- package/dist/subjects-server.cjs.map +1 -0
- package/dist/subjects-server.d.cts +130 -0
- package/dist/subjects-server.d.ts +130 -0
- package/dist/subjects-server.js +217 -0
- package/dist/subjects-server.js.map +1 -0
- package/dist/subjects.cjs +501 -0
- package/dist/subjects.cjs.map +1 -0
- package/dist/subjects.css +166 -0
- package/dist/subjects.d.cts +178 -0
- package/dist/subjects.d.ts +178 -0
- package/dist/subjects.js +465 -0
- package/dist/subjects.js.map +1 -0
- package/package.json +23 -2
- package/dist/types-D7pVK0GK.d.cts +0 -39
- package/dist/types-D7pVK0GK.d.ts +0 -39
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var storefront = require('@asteby/storefront');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/subjects/subject-picker.tsx
|
|
8
|
+
var StorefrontContext = react.createContext(null);
|
|
9
|
+
function useOptionalStorefront() {
|
|
10
|
+
return react.useContext(StorefrontContext);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/locale.ts
|
|
14
|
+
function useResolvedLocale(locale) {
|
|
15
|
+
const sf = useOptionalStorefront();
|
|
16
|
+
return locale ?? sf?.locale ?? "es-MX";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/subjects/messages.ts
|
|
20
|
+
var ES = {
|
|
21
|
+
garage: {
|
|
22
|
+
empty: "A\xFAn no guardas ning\xFAn {kind}.",
|
|
23
|
+
add: "Agregar {kind}",
|
|
24
|
+
active: "Activo",
|
|
25
|
+
makeActive: "Hacer activo",
|
|
26
|
+
edit: "Editar",
|
|
27
|
+
remove: "Eliminar",
|
|
28
|
+
removeConfirm: "S\xED, eliminar {name}",
|
|
29
|
+
removeHint: "Se borra de verdad: no queda nada guardado.",
|
|
30
|
+
viewParts: "Ver piezas compatibles",
|
|
31
|
+
limit: "Llegaste al l\xEDmite ({max}). Elimina uno para agregar otro.",
|
|
32
|
+
loadError: "No pudimos cargar esto.",
|
|
33
|
+
retry: "Reintentar",
|
|
34
|
+
saved: "Guardado.",
|
|
35
|
+
removed: "Eliminado.",
|
|
36
|
+
privateLabel: "{field}: {value}"
|
|
37
|
+
},
|
|
38
|
+
picker: {
|
|
39
|
+
choose: "Elige\u2026",
|
|
40
|
+
chooseFirst: "Primero elige el campo anterior",
|
|
41
|
+
nickname: "Apodo (opcional)",
|
|
42
|
+
nicknameHint: "Para reconocerlo, por ejemplo \xABEl de diario\xBB.",
|
|
43
|
+
privateNote: "Solo t\xFA lo ves. No se usa para buscar piezas ni se comparte.",
|
|
44
|
+
privateKeep: "D\xE9jalo vac\xEDo para conservar el guardado.",
|
|
45
|
+
privateClear: "Quitar el guardado",
|
|
46
|
+
makeActive: "Usar como activo",
|
|
47
|
+
save: "Guardar",
|
|
48
|
+
saving: "Guardando\u2026",
|
|
49
|
+
cancel: "Cancelar",
|
|
50
|
+
refine: "Continuar",
|
|
51
|
+
errors: {
|
|
52
|
+
required: "Completa este campo.",
|
|
53
|
+
unknown: "Ese campo no existe.",
|
|
54
|
+
invalid: "El valor no es v\xE1lido.",
|
|
55
|
+
not_in_catalog: "Elige una opci\xF3n de la lista.",
|
|
56
|
+
year_range: "Ese a\xF1o no corresponde a lo que elegiste.",
|
|
57
|
+
catalog_unavailable: "El cat\xE1logo no est\xE1 disponible por ahora.",
|
|
58
|
+
validation: "Revisa los datos.",
|
|
59
|
+
duplicate: "Ya lo tienes guardado.",
|
|
60
|
+
limit: "Llegaste al l\xEDmite. Elimina uno para agregar otro.",
|
|
61
|
+
rate_limited: "Demasiados intentos. Espera un momento.",
|
|
62
|
+
unauthorized: "Inicia sesi\xF3n para continuar.",
|
|
63
|
+
not_found: "Ya no existe.",
|
|
64
|
+
unsupported: "Esta tienda no ofrece esto.",
|
|
65
|
+
store_misconfigured: "La tienda no est\xE1 configurada para esto. Av\xEDsale al negocio.",
|
|
66
|
+
unavailable: "No pudimos guardar. Intenta de nuevo."
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
filter: {
|
|
70
|
+
forItem: "Para: {name}",
|
|
71
|
+
change: "Cambiar",
|
|
72
|
+
choose: "Elige tu {kind} para ver solo lo que le queda",
|
|
73
|
+
onlyFits: "Solo lo compatible",
|
|
74
|
+
showAll: "Ver todo",
|
|
75
|
+
save: "Guardar en mi cuenta"
|
|
76
|
+
},
|
|
77
|
+
fit: { fits: "Compatible con tu {kind}", universal: "Universal", no: "No es para tu {kind}", unknown: "Sin datos de compatibilidad" }
|
|
78
|
+
};
|
|
79
|
+
var EN = {
|
|
80
|
+
garage: {
|
|
81
|
+
empty: "You have not saved any {kind} yet.",
|
|
82
|
+
add: "Add {kind}",
|
|
83
|
+
active: "Active",
|
|
84
|
+
makeActive: "Make active",
|
|
85
|
+
edit: "Edit",
|
|
86
|
+
remove: "Delete",
|
|
87
|
+
removeConfirm: "Yes, delete {name}",
|
|
88
|
+
removeHint: "It is really deleted: nothing is kept.",
|
|
89
|
+
viewParts: "See compatible parts",
|
|
90
|
+
limit: "You reached the limit ({max}). Delete one to add another.",
|
|
91
|
+
loadError: "We could not load this.",
|
|
92
|
+
retry: "Try again",
|
|
93
|
+
saved: "Saved.",
|
|
94
|
+
removed: "Deleted.",
|
|
95
|
+
privateLabel: "{field}: {value}"
|
|
96
|
+
},
|
|
97
|
+
picker: {
|
|
98
|
+
choose: "Choose\u2026",
|
|
99
|
+
chooseFirst: "Choose the previous field first",
|
|
100
|
+
nickname: "Nickname (optional)",
|
|
101
|
+
nicknameHint: "To recognize it, e.g. \u201CThe everyday one\u201D.",
|
|
102
|
+
privateNote: "Only you see it. It is not used to search parts and it is never shared.",
|
|
103
|
+
privateKeep: "Leave it empty to keep the saved one.",
|
|
104
|
+
privateClear: "Remove the saved one",
|
|
105
|
+
makeActive: "Use as active",
|
|
106
|
+
save: "Save",
|
|
107
|
+
saving: "Saving\u2026",
|
|
108
|
+
cancel: "Cancel",
|
|
109
|
+
refine: "Continue",
|
|
110
|
+
errors: {
|
|
111
|
+
required: "Fill in this field.",
|
|
112
|
+
unknown: "That field does not exist.",
|
|
113
|
+
invalid: "The value is not valid.",
|
|
114
|
+
not_in_catalog: "Choose an option from the list.",
|
|
115
|
+
year_range: "That year does not match what you chose.",
|
|
116
|
+
catalog_unavailable: "The catalog is not available right now.",
|
|
117
|
+
validation: "Check the details.",
|
|
118
|
+
duplicate: "You already saved this.",
|
|
119
|
+
limit: "You reached the limit. Delete one to add another.",
|
|
120
|
+
rate_limited: "Too many attempts. Wait a moment.",
|
|
121
|
+
unauthorized: "Sign in to continue.",
|
|
122
|
+
not_found: "It no longer exists.",
|
|
123
|
+
unsupported: "This store does not offer this.",
|
|
124
|
+
store_misconfigured: "The store is not set up for this. Let the business know.",
|
|
125
|
+
unavailable: "We could not save. Try again."
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
filter: {
|
|
129
|
+
forItem: "For: {name}",
|
|
130
|
+
change: "Change",
|
|
131
|
+
choose: "Choose your {kind} to see only what fits",
|
|
132
|
+
onlyFits: "Only what fits",
|
|
133
|
+
showAll: "Show all",
|
|
134
|
+
save: "Save to my account"
|
|
135
|
+
},
|
|
136
|
+
fit: { fits: "Fits your {kind}", universal: "Universal", no: "Not for your {kind}", unknown: "No compatibility data" }
|
|
137
|
+
};
|
|
138
|
+
function merge(base, over) {
|
|
139
|
+
if (!over) return base;
|
|
140
|
+
const out = { ...base };
|
|
141
|
+
for (const [k, v] of Object.entries(over)) {
|
|
142
|
+
if (v === void 0) continue;
|
|
143
|
+
const cur = out[k];
|
|
144
|
+
out[k] = cur && typeof cur === "object" && typeof v === "object" && v ? merge(cur, v) : v;
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
function subjectMessages(locale, overrides) {
|
|
149
|
+
return merge(locale?.toLowerCase().startsWith("en") ? EN : ES, overrides);
|
|
150
|
+
}
|
|
151
|
+
function fill(template, values) {
|
|
152
|
+
return template.replace(/\{(\w+)\}/g, (m, k) => k in values ? String(values[k]) : m);
|
|
153
|
+
}
|
|
154
|
+
var isYearField = (f) => f.type === "year";
|
|
155
|
+
var initialValues = (kind, subject, values) => {
|
|
156
|
+
const out = {};
|
|
157
|
+
for (const f of kind.fields) {
|
|
158
|
+
if (f.private) continue;
|
|
159
|
+
const v = values?.[f.key] ?? subject?.attributes[f.key];
|
|
160
|
+
if (v !== void 0 && v !== null) out[f.key] = v;
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
};
|
|
164
|
+
function SubjectPicker(props) {
|
|
165
|
+
const { kind, subject, data, action, namePrefix = "subject", onSubmit, onCancel, pending, classNames: cx = {} } = props;
|
|
166
|
+
const locale = useResolvedLocale(props.locale);
|
|
167
|
+
const m = subjectMessages(locale, props.messages).picker;
|
|
168
|
+
const uid = react.useId().replace(/:/g, "");
|
|
169
|
+
const [values, setValues] = react.useState(() => initialValues(kind, subject, props.values ?? data?.values));
|
|
170
|
+
const [nickname, setNickname] = react.useState(subject?.nickname ?? "");
|
|
171
|
+
const [makeDefault, setMakeDefault] = react.useState(false);
|
|
172
|
+
const [clientProblems, setClientProblems] = react.useState(null);
|
|
173
|
+
const [clear, setClear] = react.useState({});
|
|
174
|
+
const [privates, setPrivates] = react.useState({});
|
|
175
|
+
const serverErrors = data?.fieldErrors ?? {};
|
|
176
|
+
const problemOf = (key) => clientProblems?.[key] ?? serverErrors[key];
|
|
177
|
+
const errorText = (code) => code ? m.errors[code] ?? m.errors.invalid : void 0;
|
|
178
|
+
const topError = !data?.ok && data?.error && !data.error.field ? errorText(data.error.code) : void 0;
|
|
179
|
+
const optionsOf = react.useMemo(() => (f) => f.source === "catalog" ? storefront.subjectOptions(kind, f.key, values) : [], [kind, values]);
|
|
180
|
+
const set = (key, value) => {
|
|
181
|
+
setClientProblems(null);
|
|
182
|
+
setValues((cur) => storefront.reconcileSubjectValues(kind, { ...cur, [key]: value }));
|
|
183
|
+
};
|
|
184
|
+
const submit = (e) => {
|
|
185
|
+
if (!onSubmit) return;
|
|
186
|
+
e.preventDefault();
|
|
187
|
+
const sent = { ...values };
|
|
188
|
+
for (const f of kind.fields) {
|
|
189
|
+
if (!f.private) continue;
|
|
190
|
+
if (clear[f.key]) sent[f.key] = null;
|
|
191
|
+
else if (privates[f.key]) sent[f.key] = privates[f.key];
|
|
192
|
+
}
|
|
193
|
+
const r = storefront.resolveSubject(kind, sent);
|
|
194
|
+
if (!r.ok) {
|
|
195
|
+
const privateKeys = new Set(kind.fields.filter((f) => f.private).map((f) => f.key));
|
|
196
|
+
const blocking = Object.fromEntries(Object.entries(r.problems).filter(([key, code]) => !(subject && code === "required" && privateKeys.has(key))));
|
|
197
|
+
if (Object.keys(blocking).length > 0) {
|
|
198
|
+
setClientProblems(blocking);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const attributes = {};
|
|
203
|
+
for (const f of kind.fields) {
|
|
204
|
+
const v = sent[f.key];
|
|
205
|
+
if (v === null && f.private) attributes[f.key] = null;
|
|
206
|
+
else if (v !== void 0 && v !== null && v !== "") attributes[f.key] = v;
|
|
207
|
+
}
|
|
208
|
+
void onSubmit({ kind: kind.key, attributes, nickname: nickname.trim() || null, ...makeDefault ? { is_default: true } : {} }, subject);
|
|
209
|
+
};
|
|
210
|
+
const name = (key) => `${namePrefix}.${key}`;
|
|
211
|
+
const levelKeys = kind.fields.filter((f) => f.source === "catalog" && !isYearField(f)).map((f) => f.key);
|
|
212
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("form", { method: "post", action, onSubmit: submit, className: cx.form, "data-sf-subject-picker": true, "data-sf-subject-kind": kind.key, noValidate: !!onSubmit, children: [
|
|
213
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name: "intent", value: "subject-save" }),
|
|
214
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name: "kind", value: kind.key }),
|
|
215
|
+
subject ? /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name: "id", value: subject.id }) : null,
|
|
216
|
+
topError ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: cx.error, "data-sf-subject-error": true, children: topError }) : null,
|
|
217
|
+
kind.fields.map((f) => {
|
|
218
|
+
const id = `sf-subject-${uid}-${f.key}`;
|
|
219
|
+
const err = errorText(problemOf(f.key));
|
|
220
|
+
const opts = optionsOf(f);
|
|
221
|
+
const waiting = f.source === "catalog" && !isYearField(f) && levelKeys.indexOf(f.key) > 0 && opts.length === 0;
|
|
222
|
+
const describedBy = [err ? `${id}-err` : "", f.private ? `${id}-priv` : ""].filter(Boolean).join(" ") || void 0;
|
|
223
|
+
let control;
|
|
224
|
+
if (f.private) {
|
|
225
|
+
control = /* @__PURE__ */ jsxRuntime.jsx(
|
|
226
|
+
"input",
|
|
227
|
+
{
|
|
228
|
+
id,
|
|
229
|
+
name: name(f.key),
|
|
230
|
+
type: "text",
|
|
231
|
+
autoComplete: "off",
|
|
232
|
+
autoCapitalize: "characters",
|
|
233
|
+
spellCheck: false,
|
|
234
|
+
maxLength: f.max,
|
|
235
|
+
className: cx.control,
|
|
236
|
+
value: privates[f.key] ?? "",
|
|
237
|
+
onChange: (e) => setPrivates((cur) => ({ ...cur, [f.key]: e.target.value })),
|
|
238
|
+
"aria-describedby": describedBy,
|
|
239
|
+
"aria-invalid": err ? true : void 0,
|
|
240
|
+
placeholder: subject && typeof subject.attributes[f.key] === "string" ? String(subject.attributes[f.key]) : void 0,
|
|
241
|
+
"data-sf-private": true
|
|
242
|
+
}
|
|
243
|
+
);
|
|
244
|
+
} else if (f.source === "catalog" && opts.length > 0) {
|
|
245
|
+
control = /* @__PURE__ */ jsxRuntime.jsxs("select", { id, name: name(f.key), className: cx.control, value: String(values[f.key] ?? ""), onChange: (e) => set(f.key, e.target.value), required: f.required, "aria-describedby": describedBy, "aria-invalid": err ? true : void 0, children: [
|
|
246
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: m.choose }),
|
|
247
|
+
opts.map((o) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: o.value, children: o.label }, o.value))
|
|
248
|
+
] });
|
|
249
|
+
} else if (f.source === "catalog" && !isYearField(f)) {
|
|
250
|
+
control = /* @__PURE__ */ jsxRuntime.jsx("select", { id, name: name(f.key), className: cx.control, value: "", disabled: true, "aria-describedby": describedBy, children: /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: waiting ? m.chooseFirst : m.choose }) });
|
|
251
|
+
} else {
|
|
252
|
+
control = /* @__PURE__ */ jsxRuntime.jsx(
|
|
253
|
+
"input",
|
|
254
|
+
{
|
|
255
|
+
id,
|
|
256
|
+
name: name(f.key),
|
|
257
|
+
type: isYearField(f) ? "number" : "text",
|
|
258
|
+
inputMode: isYearField(f) ? "numeric" : void 0,
|
|
259
|
+
min: isYearField(f) ? 1900 : void 0,
|
|
260
|
+
max: isYearField(f) ? 2100 : void 0,
|
|
261
|
+
maxLength: f.max,
|
|
262
|
+
className: cx.control,
|
|
263
|
+
value: String(values[f.key] ?? ""),
|
|
264
|
+
onChange: (e) => set(f.key, e.target.value),
|
|
265
|
+
required: f.required,
|
|
266
|
+
"aria-describedby": describedBy,
|
|
267
|
+
"aria-invalid": err ? true : void 0
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.field, "data-sf-subject-field": f.key, "data-sf-subject-year": isYearField(f) ? "" : void 0, children: [
|
|
272
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: id, className: cx.label, children: [
|
|
273
|
+
f.label,
|
|
274
|
+
f.required ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: " *" }) : null
|
|
275
|
+
] }),
|
|
276
|
+
control,
|
|
277
|
+
f.private ? /* @__PURE__ */ jsxRuntime.jsxs("p", { id: `${id}-priv`, className: cx.privateNote ?? cx.hint, "data-sf-subject-private-note": true, children: [
|
|
278
|
+
m.privateNote,
|
|
279
|
+
subject && subject.attributes[f.key] !== void 0 ? ` ${m.privateKeep}` : ""
|
|
280
|
+
] }) : null,
|
|
281
|
+
f.private && subject && subject.attributes[f.key] !== void 0 ? /* @__PURE__ */ jsxRuntime.jsxs("label", { className: cx.hint, "data-sf-subject-clear": true, children: [
|
|
282
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", name: `${name(f.key)}.clear`, checked: !!clear[f.key], onChange: (e) => setClear((cur) => ({ ...cur, [f.key]: e.target.checked })) }),
|
|
283
|
+
" ",
|
|
284
|
+
m.privateClear
|
|
285
|
+
] }) : null,
|
|
286
|
+
err ? /* @__PURE__ */ jsxRuntime.jsx("p", { id: `${id}-err`, role: "alert", className: cx.error, "data-sf-subject-error": true, children: err }) : null
|
|
287
|
+
] }, f.key);
|
|
288
|
+
}),
|
|
289
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.field, "data-sf-subject-field": "nickname", children: [
|
|
290
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: `sf-subject-${uid}-nick`, className: cx.label, children: m.nickname }),
|
|
291
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { id: `sf-subject-${uid}-nick`, name: "nickname", type: "text", maxLength: 40, className: cx.control, value: nickname, onChange: (e) => setNickname(e.target.value), "aria-describedby": `sf-subject-${uid}-nick-h` }),
|
|
292
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { id: `sf-subject-${uid}-nick-h`, className: cx.hint, children: m.nicknameHint })
|
|
293
|
+
] }),
|
|
294
|
+
props.offerDefault && !subject?.is_default ? /* @__PURE__ */ jsxRuntime.jsxs("label", { className: cx.hint, "data-sf-subject-default": true, children: [
|
|
295
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", name: "is_default", value: "1", checked: makeDefault, onChange: (e) => setMakeDefault(e.target.checked) }),
|
|
296
|
+
" ",
|
|
297
|
+
m.makeActive
|
|
298
|
+
] }) : null,
|
|
299
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.actions, children: [
|
|
300
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: cx.submit, disabled: pending, "data-sf-subject-submit": true, children: pending ? m.saving : m.save }),
|
|
301
|
+
/* @__PURE__ */ jsxRuntime.jsx("noscript", { children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", name: "intent", value: "subject-refine", formNoValidate: true, "data-sf-subject-refine": true, children: m.refine }) }),
|
|
302
|
+
onCancel ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: cx.cancel, onClick: onCancel, "data-sf-subject-cancel": true, children: m.cancel }) : null
|
|
303
|
+
] })
|
|
304
|
+
] });
|
|
305
|
+
}
|
|
306
|
+
function SubjectGarage(props) {
|
|
307
|
+
const { kind, status = "ready", action, partsHref, data, onSave, onDelete, onMakeDefault, pending, classNames: cx = {} } = props;
|
|
308
|
+
const locale = useResolvedLocale(props.locale);
|
|
309
|
+
const msgs = subjectMessages(locale, props.messages);
|
|
310
|
+
const m = msgs.garage;
|
|
311
|
+
const Heading = props.headingLevel === 3 ? "h3" : "h2";
|
|
312
|
+
const singular = kind.label.toLowerCase();
|
|
313
|
+
const mine = props.subjects.filter((s) => s.kind === kind.key);
|
|
314
|
+
const atLimit = mine.length >= kind.max_per_account;
|
|
315
|
+
const openId = props.openId ?? (data && !data.ok && (data.intent === "subject-save" || data.intent === "subject-refine") ? data.id ?? "new" : null);
|
|
316
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: cx.root, "data-sf-subject-garage": true, "data-sf-subject-kind": kind.key, "aria-labelledby": `sf-garage-${kind.key}`, children: [
|
|
317
|
+
/* @__PURE__ */ jsxRuntime.jsx("header", { className: cx.header, children: /* @__PURE__ */ jsxRuntime.jsx(Heading, { id: `sf-garage-${kind.key}`, className: cx.title, children: kind.label_plural }) }),
|
|
318
|
+
data?.ok && data.intent && data.intent !== "subject-default" ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", className: cx.notice, "data-sf-subject-notice": true, children: data.intent === "subject-delete" ? m.removed : m.saved }) : null,
|
|
319
|
+
status === "error" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "alert", className: cx.notice, "data-sf-subject-degraded": true, children: [
|
|
320
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: m.loadError }),
|
|
321
|
+
props.onRetry ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: cx.button, onClick: props.onRetry, children: m.retry }) : /* @__PURE__ */ jsxRuntime.jsx("a", { href: "", className: cx.button, children: m.retry })
|
|
322
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
323
|
+
mine.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.empty, "data-sf-subject-empty": true, children: fill(m.empty, { kind: singular }) }) : /* @__PURE__ */ jsxRuntime.jsx("ul", { className: cx.list, "data-sf-subject-list": true, children: mine.map((s) => {
|
|
324
|
+
const parts = partsHref?.(s);
|
|
325
|
+
const privates = kind.fields.filter((f) => f.private && s.attributes[f.key] !== void 0);
|
|
326
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: cx.card, "data-sf-subject": s.id, "data-sf-subject-active": s.is_default ? "" : void 0, children: [
|
|
327
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: cx.cardTitle, "data-sf-subject-label": true, children: [
|
|
328
|
+
s.label,
|
|
329
|
+
s.is_default ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cx.badge, "data-sf-subject-badge": true, children: m.active }) : null
|
|
330
|
+
] }),
|
|
331
|
+
s.nickname ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.nickname, children: s.nickname }) : null,
|
|
332
|
+
privates.map((f) => /* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.meta, "data-sf-subject-private-value": true, children: fill(m.privateLabel, { field: f.label, value: String(s.attributes[f.key]) }) }, f.key)),
|
|
333
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.cardActions, children: [
|
|
334
|
+
parts ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: parts, className: `${cx.button ?? ""} ${cx.primary ?? ""}`.trim() || void 0, "data-sf-subject-parts": true, children: m.viewParts }) : null,
|
|
335
|
+
!s.is_default ? /* @__PURE__ */ jsxRuntime.jsxs("form", { method: "post", action, onSubmit: onMakeDefault ? (e) => {
|
|
336
|
+
e.preventDefault();
|
|
337
|
+
void onMakeDefault(s);
|
|
338
|
+
} : void 0, "data-sf-subject-default-form": true, children: [
|
|
339
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name: "intent", value: "subject-default" }),
|
|
340
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name: "id", value: s.id }),
|
|
341
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: cx.button, disabled: pending, children: m.makeActive })
|
|
342
|
+
] }) : null,
|
|
343
|
+
/* @__PURE__ */ jsxRuntime.jsxs("details", { className: cx.details, open: openId === s.id || void 0, "data-sf-subject-edit": true, children: [
|
|
344
|
+
/* @__PURE__ */ jsxRuntime.jsx("summary", { className: cx.summary, children: m.edit }),
|
|
345
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
346
|
+
SubjectPicker,
|
|
347
|
+
{
|
|
348
|
+
kind,
|
|
349
|
+
subject: s,
|
|
350
|
+
...action ? { action } : {},
|
|
351
|
+
data: openId === s.id ? data : null,
|
|
352
|
+
...onSave ? { onSubmit: onSave } : {},
|
|
353
|
+
pending,
|
|
354
|
+
locale,
|
|
355
|
+
...props.messages ? { messages: props.messages } : {},
|
|
356
|
+
classNames: cx
|
|
357
|
+
}
|
|
358
|
+
)
|
|
359
|
+
] }),
|
|
360
|
+
/* @__PURE__ */ jsxRuntime.jsxs("details", { className: cx.details, "data-sf-subject-remove": true, children: [
|
|
361
|
+
/* @__PURE__ */ jsxRuntime.jsx("summary", { className: cx.summary, children: m.remove }),
|
|
362
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.meta, children: m.removeHint }),
|
|
363
|
+
/* @__PURE__ */ jsxRuntime.jsxs("form", { method: "post", action, onSubmit: onDelete ? (e) => {
|
|
364
|
+
e.preventDefault();
|
|
365
|
+
void onDelete(s);
|
|
366
|
+
} : void 0, children: [
|
|
367
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name: "intent", value: "subject-delete" }),
|
|
368
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name: "id", value: s.id }),
|
|
369
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: `${cx.button ?? ""} ${cx.danger ?? ""}`.trim() || void 0, disabled: pending, children: fill(m.removeConfirm, { name: s.label }) })
|
|
370
|
+
] })
|
|
371
|
+
] })
|
|
372
|
+
] })
|
|
373
|
+
] }, s.id);
|
|
374
|
+
}) }),
|
|
375
|
+
atLimit ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.limit, "data-sf-subject-limit": true, children: fill(m.limit, { max: kind.max_per_account }) }) : /* @__PURE__ */ jsxRuntime.jsxs("details", { className: cx.details, open: openId === "new" || void 0, "data-sf-subject-add": true, children: [
|
|
376
|
+
/* @__PURE__ */ jsxRuntime.jsx("summary", { className: cx.summary, children: fill(m.add, { kind: singular }) }),
|
|
377
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
378
|
+
SubjectPicker,
|
|
379
|
+
{
|
|
380
|
+
kind,
|
|
381
|
+
...action ? { action } : {},
|
|
382
|
+
data: openId === "new" ? data : null,
|
|
383
|
+
...onSave ? { onSubmit: onSave } : {},
|
|
384
|
+
offerDefault: mine.length > 0,
|
|
385
|
+
pending,
|
|
386
|
+
locale,
|
|
387
|
+
...props.messages ? { messages: props.messages } : {},
|
|
388
|
+
classNames: cx
|
|
389
|
+
}
|
|
390
|
+
)
|
|
391
|
+
] })
|
|
392
|
+
] })
|
|
393
|
+
] });
|
|
394
|
+
}
|
|
395
|
+
function SubjectFilter(props) {
|
|
396
|
+
const { kind, active, showingAll, classNames: cx = {} } = props;
|
|
397
|
+
const locale = useResolvedLocale(props.locale);
|
|
398
|
+
const m = subjectMessages(locale, props.messages).filter;
|
|
399
|
+
const singular = kind.label.toLowerCase();
|
|
400
|
+
if (!active) {
|
|
401
|
+
return props.chooseHref ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: cx.root, "data-sf-subject-filter": true, "data-sf-subject-filter-empty": true, children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: props.chooseHref, className: cx.link, children: fill(m.choose, { kind: singular }) }) }) : null;
|
|
402
|
+
}
|
|
403
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cx.root, "data-sf-subject-filter": true, "data-sf-subject-showing": showingAll ? "all" : "fits", children: [
|
|
404
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: cx.label, children: fill(m.forItem, { name: active.label }) }),
|
|
405
|
+
props.changeHref ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: props.changeHref, className: cx.link, children: m.change }) : null,
|
|
406
|
+
showingAll && props.onlyFitsHref ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: props.onlyFitsHref, className: cx.toggle ?? cx.link, children: m.onlyFits }) : null,
|
|
407
|
+
!showingAll && props.showAllHref ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: props.showAllHref, className: cx.toggle ?? cx.link, children: m.showAll }) : null
|
|
408
|
+
] });
|
|
409
|
+
}
|
|
410
|
+
function SubjectFitBadge(props) {
|
|
411
|
+
const locale = useResolvedLocale(props.locale);
|
|
412
|
+
const m = subjectMessages(locale, props.messages).fit;
|
|
413
|
+
if (props.state === "unknown" && !props.showUnknown) return null;
|
|
414
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: props.className, "data-sf-subject-fit": props.state, children: [
|
|
415
|
+
fill(m[props.state], { kind: props.kind.label.toLowerCase() }),
|
|
416
|
+
props.children
|
|
417
|
+
] });
|
|
418
|
+
}
|
|
419
|
+
function useSubjects(client) {
|
|
420
|
+
const ctx = useOptionalStorefront();
|
|
421
|
+
const sf = client ?? ctx?.client ?? null;
|
|
422
|
+
const [state, setState] = react.useState({ status: "loading", kinds: [], subjects: [], error: null });
|
|
423
|
+
const alive = react.useRef(null);
|
|
424
|
+
const load = react.useCallback(() => {
|
|
425
|
+
alive.current?.abort();
|
|
426
|
+
if (!sf) {
|
|
427
|
+
setState({ status: "loading", kinds: [], subjects: [], error: null });
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const ctl = new AbortController();
|
|
431
|
+
alive.current = ctl;
|
|
432
|
+
const opt = { signal: ctl.signal };
|
|
433
|
+
setState((s) => s.status === "ready" ? s : { status: "loading", kinds: [], subjects: [], error: null });
|
|
434
|
+
sf.content.subjectKinds(opt).then(
|
|
435
|
+
async (kinds) => {
|
|
436
|
+
if (ctl.signal.aborted) return;
|
|
437
|
+
if (kinds.length === 0) return setState({ status: "unavailable", kinds: [], subjects: [], error: null });
|
|
438
|
+
try {
|
|
439
|
+
const subjects = await sf.customer.subjects.list(void 0, opt);
|
|
440
|
+
if (!ctl.signal.aborted) setState({ status: "ready", kinds, subjects, error: null });
|
|
441
|
+
} catch (e) {
|
|
442
|
+
if (!ctl.signal.aborted) setState({ status: "error", kinds, subjects: [], error: e instanceof Error ? e : new Error("Request failed") });
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
(e) => !ctl.signal.aborted && setState({ status: "error", kinds: [], subjects: [], error: e instanceof Error ? e : new Error("Request failed") })
|
|
446
|
+
);
|
|
447
|
+
}, [sf]);
|
|
448
|
+
react.useEffect(() => {
|
|
449
|
+
load();
|
|
450
|
+
return () => alive.current?.abort();
|
|
451
|
+
}, [load]);
|
|
452
|
+
const need = () => {
|
|
453
|
+
if (!sf) throw new Error("useSubjects: no storefront client");
|
|
454
|
+
return sf;
|
|
455
|
+
};
|
|
456
|
+
const create = react.useCallback(async (input) => {
|
|
457
|
+
const saved = await need().customer.subjects.create(input);
|
|
458
|
+
load();
|
|
459
|
+
return saved;
|
|
460
|
+
}, [sf, load]);
|
|
461
|
+
const update = react.useCallback(async (id, patch) => {
|
|
462
|
+
const saved = await need().customer.subjects.update(id, patch);
|
|
463
|
+
load();
|
|
464
|
+
return saved;
|
|
465
|
+
}, [sf, load]);
|
|
466
|
+
const remove = react.useCallback(async (id) => {
|
|
467
|
+
await need().customer.subjects.delete(id);
|
|
468
|
+
load();
|
|
469
|
+
}, [sf, load]);
|
|
470
|
+
const makeDefault = react.useCallback((id) => update(id, { is_default: true }), [update]);
|
|
471
|
+
return { ...state, reload: load, create, update, remove, makeDefault };
|
|
472
|
+
}
|
|
473
|
+
var KEY_RE = /^[a-z0-9._+/-]{1,100}$/;
|
|
474
|
+
var escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
475
|
+
function subjectFromFitKey(kind, fitKey) {
|
|
476
|
+
if (!fitKey || !KEY_RE.test(fitKey) || !kind.fit_key_template) return null;
|
|
477
|
+
const names = [];
|
|
478
|
+
const pattern = kind.fit_key_template.split(/(\{[a-z][a-z0-9_]*\})/).map((part) => {
|
|
479
|
+
const m = /^\{([a-z][a-z0-9_]*)\}$/.exec(part);
|
|
480
|
+
if (!m) return escape(part);
|
|
481
|
+
names.push(m[1]);
|
|
482
|
+
return "([^/]+)";
|
|
483
|
+
}).join("");
|
|
484
|
+
const match = new RegExp(`^${pattern}$`).exec(fitKey);
|
|
485
|
+
if (!match) return null;
|
|
486
|
+
const values = {};
|
|
487
|
+
names.forEach((n, i) => values[n] = match[i + 1]);
|
|
488
|
+
const r = storefront.resolveSubject(kind, values);
|
|
489
|
+
return r.ok && r.subject.fit_key === fitKey ? r.subject : null;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
exports.SubjectFilter = SubjectFilter;
|
|
493
|
+
exports.SubjectFitBadge = SubjectFitBadge;
|
|
494
|
+
exports.SubjectGarage = SubjectGarage;
|
|
495
|
+
exports.SubjectPicker = SubjectPicker;
|
|
496
|
+
exports.fillSubjectMessage = fill;
|
|
497
|
+
exports.subjectFromFitKey = subjectFromFitKey;
|
|
498
|
+
exports.subjectMessages = subjectMessages;
|
|
499
|
+
exports.useSubjects = useSubjects;
|
|
500
|
+
//# sourceMappingURL=subjects.cjs.map
|
|
501
|
+
//# sourceMappingURL=subjects.cjs.map
|