@infuro/cms-core 1.0.36 → 1.0.39
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/dist/admin.cjs +459 -496
- package/dist/admin.js +459 -496
- package/dist/api.cjs +36 -36
- package/dist/api.js +4 -4
- package/dist/auth.cjs +51 -51
- package/dist/auth.js +3 -3
- package/dist/{chunk-COBORIJH.cjs → chunk-22FFHLTO.cjs} +39 -56
- package/dist/{chunk-X6H5VBQ2.cjs → chunk-E6WVP7BK.cjs} +6 -6
- package/dist/{chunk-2AEVFOQP.cjs → chunk-ERJK5QXN.cjs} +418 -139
- package/dist/{chunk-VMY2S32H.js → chunk-NNQBWDCI.js} +39 -56
- package/dist/{chunk-WO2DW4AU.cjs → chunk-OIGCAGH7.cjs} +7 -7
- package/dist/{chunk-UF3BYUXY.js → chunk-P7QCLE5W.js} +4 -3
- package/dist/{chunk-FG6I64HP.js → chunk-SBRBR3VO.js} +1 -1
- package/dist/{chunk-JS2W7XZ4.js → chunk-SFDZMGHD.js} +351 -72
- package/dist/{chunk-LI2IXVAY.js → chunk-UXMDSNIG.js} +1 -1
- package/dist/{chunk-KPKFNLQQ.cjs → chunk-WMXPKKXU.cjs} +4 -3
- package/dist/index.cjs +260 -260
- package/dist/index.d.cts +12 -5
- package/dist/index.d.ts +12 -5
- package/dist/index.js +8 -8
- package/dist/migrations/1775500000000-RssFeedsAndArticles.ts +0 -1
- package/dist/migrations/1775700000000-JobSchedules.ts +0 -1
- package/dist/migrations/1781260000000-RenameWhatsappTriggerBindingsToOrderNotificationBindings.ts +92 -0
- package/dist/migrations/1781270000000-ComboVendorId.ts +58 -0
- package/dist/{whatsapp-trigger-dispatcher-ONHXI7UY.cjs → order-notification-dispatcher-RU7BTGGX.cjs} +3 -4
- package/dist/{whatsapp-trigger-dispatcher-PWFI4FKP.js → order-notification-dispatcher-XWIJYDGA.js} +1 -2
- package/dist/{rbac-debug-db-HWP65A4O.cjs → rbac-debug-db-KHG4FJC3.cjs} +2 -2
- package/dist/{rbac-debug-db-QJH3AD34.js → rbac-debug-db-QWHOENBR.js} +1 -1
- package/package.json +147 -147
package/dist/admin.js
CHANGED
|
@@ -419,7 +419,8 @@ var init_vendor_scope = __esm({
|
|
|
419
419
|
"vendor_customers",
|
|
420
420
|
"discounts",
|
|
421
421
|
"events",
|
|
422
|
-
"refund_policies"
|
|
422
|
+
"refund_policies",
|
|
423
|
+
"combos"
|
|
423
424
|
]);
|
|
424
425
|
VENDOR_STORE_RBAC_ENTITIES = /* @__PURE__ */ new Set([
|
|
425
426
|
...VENDOR_SCOPED_STORE_ENTITIES,
|
|
@@ -456,7 +457,7 @@ var init_admin_config_context = __esm({
|
|
|
456
457
|
var CMS_VERSION;
|
|
457
458
|
var init_cms_version = __esm({
|
|
458
459
|
"src/lib/cms-version.ts"() {
|
|
459
|
-
CMS_VERSION = "1.0.
|
|
460
|
+
CMS_VERSION = "1.0.39" ;
|
|
460
461
|
}
|
|
461
462
|
});
|
|
462
463
|
function useCatalogCategories(enabled = true) {
|
|
@@ -14268,203 +14269,149 @@ var init_email_recipients = __esm({
|
|
|
14268
14269
|
__name(serializeEmailRecipients, "serializeEmailRecipients");
|
|
14269
14270
|
}
|
|
14270
14271
|
});
|
|
14271
|
-
|
|
14272
|
-
|
|
14273
|
-
|
|
14274
|
-
|
|
14275
|
-
|
|
14276
|
-
|
|
14277
|
-
|
|
14278
|
-
|
|
14279
|
-
|
|
14280
|
-
|
|
14281
|
-
|
|
14282
|
-
|
|
14283
|
-
|
|
14284
|
-
|
|
14285
|
-
|
|
14286
|
-
|
|
14287
|
-
eventName: "Event title",
|
|
14288
|
-
eventSlug: "Event URL slug",
|
|
14289
|
-
venue: "Venue name",
|
|
14290
|
-
startDate: "Formatted event start",
|
|
14291
|
-
orderNumber: "Order number",
|
|
14292
|
-
orderTotal: "Order total amount",
|
|
14293
|
-
currency: "Currency code",
|
|
14294
|
-
customerName: "Buyer name",
|
|
14295
|
-
customerEmail: "Buyer email",
|
|
14296
|
-
customerPhone: "Buyer phone",
|
|
14297
|
-
productNames: "Comma-separated ticket names",
|
|
14298
|
-
trackUrl: "Public order tracking link (QR destination)",
|
|
14299
|
-
qrImageUrl: 'QR code image URL for email (use in <img src="\u2026">)',
|
|
14300
|
-
orderDetails: "Plain-text order summary with line items",
|
|
14301
|
-
orderDetailsHtml: "HTML order summary block",
|
|
14302
|
-
trackUrlWhatsApp: "Same as trackUrl \u2014 include in Meta template as last param"
|
|
14303
|
-
};
|
|
14272
|
+
function getEmailTriggerDefaults(triggerKey) {
|
|
14273
|
+
return EMAIL_TRIGGER_DEFAULTS[triggerKey] ?? {
|
|
14274
|
+
subject: "",
|
|
14275
|
+
body: "",
|
|
14276
|
+
subjectPlaceholder: "e.g. Order update for {{orderNumber}}",
|
|
14277
|
+
bodyPlaceholder: "Hi {{customerName}},\n\nYour order {{orderNumber}} has been updated."
|
|
14278
|
+
};
|
|
14279
|
+
}
|
|
14280
|
+
async function jsonFetch(url, init) {
|
|
14281
|
+
const res = await fetch(url, {
|
|
14282
|
+
credentials: "include",
|
|
14283
|
+
...init
|
|
14284
|
+
});
|
|
14285
|
+
const data = await res.json().catch(() => ({}));
|
|
14286
|
+
if (!res.ok) {
|
|
14287
|
+
throw new Error(data?.error || `Request failed (${res.status})`);
|
|
14304
14288
|
}
|
|
14305
|
-
|
|
14306
|
-
function mergeChannelUpdate(allItems, channel, updatedChannelItems) {
|
|
14307
|
-
return [
|
|
14308
|
-
...allItems.filter((item) => item.channel !== channel),
|
|
14309
|
-
...updatedChannelItems
|
|
14310
|
-
];
|
|
14289
|
+
return data;
|
|
14311
14290
|
}
|
|
14312
|
-
function
|
|
14313
|
-
const [
|
|
14314
|
-
const
|
|
14315
|
-
const
|
|
14316
|
-
|
|
14291
|
+
function useOrderNotificationTriggerBindings(channel) {
|
|
14292
|
+
const [triggers, setTriggers] = useState([]);
|
|
14293
|
+
const [bindings, setBindings] = useState([]);
|
|
14294
|
+
const [loading, setLoading] = useState(true);
|
|
14295
|
+
const load = useCallback(async () => {
|
|
14296
|
+
setLoading(true);
|
|
14297
|
+
try {
|
|
14298
|
+
const [triggersRes, bindingsRes] = await Promise.all([
|
|
14299
|
+
jsonFetch("/api/order_notification_triggers?limit=100&sortField=sortOrder&sortOrder=asc"),
|
|
14300
|
+
// Bindings for all channels are fetched and filtered client-side rather
|
|
14301
|
+
// than relying on server-side ?channel= filtering, since that's not
|
|
14302
|
+
// confirmed to be supported by the generic CRUD layer.
|
|
14303
|
+
jsonFetch("/api/order_notification_bindings?limit=200")
|
|
14304
|
+
]);
|
|
14305
|
+
setTriggers((triggersRes.data ?? []).filter((t) => t.enabled));
|
|
14306
|
+
setBindings((bindingsRes.data ?? []).filter((b) => b.channel === channel));
|
|
14307
|
+
} catch (e) {
|
|
14308
|
+
toast.error(e instanceof Error ? e.message : "Failed to load trigger bindings");
|
|
14309
|
+
} finally {
|
|
14310
|
+
setLoading(false);
|
|
14311
|
+
}
|
|
14312
|
+
}, [
|
|
14317
14313
|
channel
|
|
14318
14314
|
]);
|
|
14319
|
-
|
|
14320
|
-
|
|
14321
|
-
return [
|
|
14322
|
-
...EVENT_ORDER_WHATSAPP_PARAM_KEYS
|
|
14323
|
-
];
|
|
14315
|
+
useEffect(() => {
|
|
14316
|
+
void load();
|
|
14324
14317
|
}, [
|
|
14318
|
+
load
|
|
14319
|
+
]);
|
|
14320
|
+
const rebind = useCallback(async (triggerKey, messageTemplateId, templateLabel) => {
|
|
14321
|
+
const existing = bindings.find((b) => b.triggerKey === triggerKey);
|
|
14322
|
+
try {
|
|
14323
|
+
if (existing) {
|
|
14324
|
+
const updated = await jsonFetch(`/api/order_notification_bindings/${existing.id}`, {
|
|
14325
|
+
method: "PUT",
|
|
14326
|
+
headers: {
|
|
14327
|
+
"Content-Type": "application/json"
|
|
14328
|
+
},
|
|
14329
|
+
body: JSON.stringify({
|
|
14330
|
+
messageTemplateId
|
|
14331
|
+
})
|
|
14332
|
+
});
|
|
14333
|
+
setBindings((prev) => prev.map((b) => b.id === updated.id ? updated : b));
|
|
14334
|
+
} else {
|
|
14335
|
+
const created = await jsonFetch("/api/order_notification_bindings", {
|
|
14336
|
+
method: "POST",
|
|
14337
|
+
headers: {
|
|
14338
|
+
"Content-Type": "application/json"
|
|
14339
|
+
},
|
|
14340
|
+
body: JSON.stringify({
|
|
14341
|
+
triggerKey,
|
|
14342
|
+
channel,
|
|
14343
|
+
messageTemplateId,
|
|
14344
|
+
enabled: true
|
|
14345
|
+
})
|
|
14346
|
+
});
|
|
14347
|
+
setBindings((prev) => [
|
|
14348
|
+
...prev,
|
|
14349
|
+
created
|
|
14350
|
+
]);
|
|
14351
|
+
}
|
|
14352
|
+
toast.success(`"${triggerKey}" bound to "${templateLabel}"`);
|
|
14353
|
+
return true;
|
|
14354
|
+
} catch (e) {
|
|
14355
|
+
toast.error(e instanceof Error ? e.message : "Failed to update binding");
|
|
14356
|
+
return false;
|
|
14357
|
+
}
|
|
14358
|
+
}, [
|
|
14359
|
+
bindings,
|
|
14325
14360
|
channel
|
|
14326
14361
|
]);
|
|
14327
|
-
|
|
14328
|
-
|
|
14329
|
-
|
|
14330
|
-
|
|
14331
|
-
|
|
14332
|
-
|
|
14333
|
-
|
|
14334
|
-
}, "patchVisible");
|
|
14335
|
-
if (!visibleItems.length) return null;
|
|
14336
|
-
return /* @__PURE__ */ React.createElement("div", {
|
|
14337
|
-
className: "space-y-3 rounded-md border border-gray-200 dark:border-gray-600 p-3"
|
|
14338
|
-
}, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", {
|
|
14339
|
-
className: "text-sm font-medium text-gray-900 dark:text-white"
|
|
14340
|
-
}, copy.title), /* @__PURE__ */ React.createElement("p", {
|
|
14341
|
-
className: "text-xs text-gray-500 dark:text-gray-400 mt-1"
|
|
14342
|
-
}, copy.description)), /* @__PURE__ */ React.createElement("div", {
|
|
14343
|
-
className: "rounded-md bg-gray-50 dark:bg-gray-800/50 p-2"
|
|
14344
|
-
}, /* @__PURE__ */ React.createElement("p", {
|
|
14345
|
-
className: "text-[11px] font-medium text-gray-600 dark:text-gray-400 mb-1"
|
|
14346
|
-
}, "Variables"), /* @__PURE__ */ React.createElement("div", {
|
|
14347
|
-
className: "flex flex-wrap gap-1"
|
|
14348
|
-
}, (displayVariables.length ? displayVariables : templateVariables).map((v) => /* @__PURE__ */ React.createElement("span", {
|
|
14349
|
-
key: v,
|
|
14350
|
-
className: "inline-flex rounded border border-gray-200 bg-white px-1.5 py-0.5 font-mono text-[10px] text-gray-700 dark:border-gray-600 dark:bg-gray-900 dark:text-gray-300",
|
|
14351
|
-
title: EVENT_ORDER_TEMPLATE_VARIABLE_HINTS[v] ?? v
|
|
14352
|
-
}, `{{${v}}}`)))), visibleItems.map((t) => /* @__PURE__ */ React.createElement("div", {
|
|
14353
|
-
key: t.channel,
|
|
14354
|
-
className: "space-y-3 border-t border-gray-100 pt-3 dark:border-gray-700"
|
|
14355
|
-
}, /* @__PURE__ */ React.createElement("div", {
|
|
14356
|
-
className: "flex flex-wrap items-center justify-between gap-2"
|
|
14357
|
-
}, /* @__PURE__ */ React.createElement("div", {
|
|
14358
|
-
className: "text-sm font-medium"
|
|
14359
|
-
}, t.name), /* @__PURE__ */ React.createElement("div", {
|
|
14360
|
-
className: "flex items-center gap-2"
|
|
14361
|
-
}, /* @__PURE__ */ React.createElement(Label3, {
|
|
14362
|
-
htmlFor: `${settingsGroup}-evt-${t.channel}-enabled`,
|
|
14363
|
-
className: "text-xs text-gray-600"
|
|
14364
|
-
}, "Enabled"), /* @__PURE__ */ React.createElement(Switch, {
|
|
14365
|
-
id: `${settingsGroup}-evt-${t.channel}-enabled`,
|
|
14366
|
-
checked: t.enabled,
|
|
14367
|
-
onCheckedChange: /* @__PURE__ */ __name((v) => patchVisible(visibleItems.map((x) => x.channel === t.channel ? {
|
|
14368
|
-
...x,
|
|
14369
|
-
enabled: v
|
|
14370
|
-
} : x)), "onCheckedChange")
|
|
14371
|
-
}))), t.channel === "email" ? /* @__PURE__ */ React.createElement("div", {
|
|
14372
|
-
className: "space-y-1"
|
|
14373
|
-
}, /* @__PURE__ */ React.createElement(Label3, {
|
|
14374
|
-
className: "text-xs"
|
|
14375
|
-
}, "Subject"), /* @__PURE__ */ React.createElement(Input, {
|
|
14376
|
-
value: t.subject,
|
|
14377
|
-
onChange: /* @__PURE__ */ __name((e) => patchVisible(visibleItems.map((x) => x.channel === t.channel ? {
|
|
14378
|
-
...x,
|
|
14379
|
-
subject: e.target.value
|
|
14380
|
-
} : x)), "onChange"),
|
|
14381
|
-
className: "h-8 text-sm"
|
|
14382
|
-
})) : null, /* @__PURE__ */ React.createElement("div", {
|
|
14383
|
-
className: "space-y-1"
|
|
14384
|
-
}, /* @__PURE__ */ React.createElement(Label3, {
|
|
14385
|
-
className: "text-xs"
|
|
14386
|
-
}, t.channel === "email" ? "Body (HTML)" : "Body (reference only)"), /* @__PURE__ */ React.createElement(Textarea, {
|
|
14387
|
-
value: t.body,
|
|
14388
|
-
onChange: /* @__PURE__ */ __name((e) => patchVisible(visibleItems.map((x) => x.channel === t.channel ? {
|
|
14389
|
-
...x,
|
|
14390
|
-
body: e.target.value
|
|
14391
|
-
} : x)), "onChange"),
|
|
14392
|
-
rows: t.channel === "email" ? 8 : 3,
|
|
14393
|
-
className: "text-sm font-mono"
|
|
14394
|
-
})), t.channel === "whatsapp" ? /* @__PURE__ */ React.createElement("div", {
|
|
14395
|
-
className: "space-y-1"
|
|
14396
|
-
}, /* @__PURE__ */ React.createElement(Label3, {
|
|
14397
|
-
className: "text-xs"
|
|
14398
|
-
}, "Meta approved template name"), /* @__PURE__ */ React.createElement(Input, {
|
|
14399
|
-
value: t.externalTemplateRef,
|
|
14400
|
-
onChange: /* @__PURE__ */ __name((e) => patchVisible(visibleItems.map((x) => x.channel === t.channel ? {
|
|
14401
|
-
...x,
|
|
14402
|
-
externalTemplateRef: e.target.value
|
|
14403
|
-
} : x)), "onChange"),
|
|
14404
|
-
placeholder: "event_order_placed",
|
|
14405
|
-
className: "h-8 text-sm"
|
|
14406
|
-
})) : null)));
|
|
14407
|
-
}
|
|
14408
|
-
async function fetchEventOrderTemplateRows() {
|
|
14409
|
-
const res = await fetch("/api/message-templates/event-order");
|
|
14410
|
-
if (!res.ok) return [];
|
|
14411
|
-
const data = await res.json();
|
|
14412
|
-
return data.items ?? [];
|
|
14413
|
-
}
|
|
14414
|
-
async function saveEventOrderTemplateRows(items) {
|
|
14415
|
-
const res = await fetch("/api/message-templates/event-order", {
|
|
14416
|
-
method: "PUT",
|
|
14417
|
-
headers: {
|
|
14418
|
-
"Content-Type": "application/json"
|
|
14419
|
-
},
|
|
14420
|
-
body: JSON.stringify({
|
|
14421
|
-
items: items.map((t) => ({
|
|
14422
|
-
channel: t.channel,
|
|
14423
|
-
subject: t.subject,
|
|
14424
|
-
body: t.body,
|
|
14425
|
-
externalTemplateRef: t.externalTemplateRef,
|
|
14426
|
-
enabled: t.enabled
|
|
14427
|
-
}))
|
|
14428
|
-
})
|
|
14429
|
-
});
|
|
14430
|
-
return res.ok;
|
|
14362
|
+
return {
|
|
14363
|
+
triggers,
|
|
14364
|
+
bindings,
|
|
14365
|
+
loading,
|
|
14366
|
+
reload: load,
|
|
14367
|
+
rebind
|
|
14368
|
+
};
|
|
14431
14369
|
}
|
|
14432
|
-
var
|
|
14433
|
-
var
|
|
14434
|
-
"src/admin/components/
|
|
14370
|
+
var KNOWN_VARS, EMAIL_TRIGGER_DEFAULTS;
|
|
14371
|
+
var init_order_notification_bindings_shared = __esm({
|
|
14372
|
+
"src/admin/components/order-notification-bindings-shared.ts"() {
|
|
14435
14373
|
"use client";
|
|
14436
|
-
|
|
14437
|
-
|
|
14438
|
-
init_textarea();
|
|
14439
|
-
init_switch();
|
|
14440
|
-
init_event_order_defaults();
|
|
14441
|
-
CHANNEL_COPY = {
|
|
14442
|
-
email: {
|
|
14443
|
-
title: "Email template",
|
|
14444
|
-
description: "HTML email with order details and QR code. Use {{qrImageUrl}} for the QR image and {{orderDetailsHtml}} for line items."
|
|
14445
|
-
},
|
|
14446
|
-
whatsapp: {
|
|
14447
|
-
title: "WhatsApp template",
|
|
14448
|
-
description: "Must match a Meta-approved template. Body params are sent in order: " + EVENT_ORDER_WHATSAPP_PARAM_KEYS.join(", ") + "."
|
|
14449
|
-
}
|
|
14450
|
-
};
|
|
14451
|
-
EMAIL_VARIABLES = [
|
|
14452
|
-
"customerName",
|
|
14453
|
-
"eventName",
|
|
14374
|
+
KNOWN_VARS = [
|
|
14375
|
+
"orderId",
|
|
14454
14376
|
"orderNumber",
|
|
14455
|
-
"
|
|
14377
|
+
"customerName",
|
|
14378
|
+
"customerPhone",
|
|
14379
|
+
"total",
|
|
14456
14380
|
"currency",
|
|
14457
|
-
"
|
|
14458
|
-
"
|
|
14459
|
-
"venue",
|
|
14460
|
-
"startDate",
|
|
14461
|
-
"qrImageUrl",
|
|
14462
|
-
"trackUrl"
|
|
14381
|
+
"vendorId",
|
|
14382
|
+
"refundAmount"
|
|
14463
14383
|
];
|
|
14464
|
-
|
|
14465
|
-
|
|
14466
|
-
|
|
14467
|
-
|
|
14384
|
+
EMAIL_TRIGGER_DEFAULTS = {
|
|
14385
|
+
order_placed: {
|
|
14386
|
+
subject: "Your order {{orderNumber}} is confirmed",
|
|
14387
|
+
subjectPlaceholder: "e.g. Your order {{orderNumber}} is confirmed",
|
|
14388
|
+
body: `Hi {{customerName}},
|
|
14389
|
+
|
|
14390
|
+
Thank you for your order! Your order {{orderNumber}} for {{total}} {{currency}} has been confirmed.
|
|
14391
|
+
|
|
14392
|
+
We'll keep you updated on any changes.
|
|
14393
|
+
|
|
14394
|
+
Thank you!`,
|
|
14395
|
+
bodyPlaceholder: "Hi {{customerName}},\n\nYour order {{orderNumber}} for {{total}} {{currency}} is confirmed.\n\nThank you!"
|
|
14396
|
+
},
|
|
14397
|
+
order_cancelled: {
|
|
14398
|
+
subject: "Your order {{orderNumber}} has been cancelled",
|
|
14399
|
+
subjectPlaceholder: "e.g. Your order {{orderNumber}} has been cancelled",
|
|
14400
|
+
body: `Hi {{customerName}},
|
|
14401
|
+
|
|
14402
|
+
Your order {{orderNumber}} for {{total}} {{currency}} has been cancelled.
|
|
14403
|
+
|
|
14404
|
+
If a refund is due, you will receive {{refundAmount}} {{currency}} back to your original payment method.
|
|
14405
|
+
|
|
14406
|
+
If you have any questions, please contact us.
|
|
14407
|
+
|
|
14408
|
+
Thank you!`,
|
|
14409
|
+
bodyPlaceholder: "Hi {{customerName}},\n\nYour order {{orderNumber}} has been cancelled.\n\nIf a refund is due, it will be returned to your original payment method."
|
|
14410
|
+
}
|
|
14411
|
+
};
|
|
14412
|
+
__name(getEmailTriggerDefaults, "getEmailTriggerDefaults");
|
|
14413
|
+
__name(jsonFetch, "jsonFetch");
|
|
14414
|
+
__name(useOrderNotificationTriggerBindings, "useOrderNotificationTriggerBindings");
|
|
14468
14415
|
}
|
|
14469
14416
|
});
|
|
14470
14417
|
function varCount(t) {
|
|
@@ -14510,22 +14457,10 @@ function contextBeforePlaceholder(bodyText, n) {
|
|
|
14510
14457
|
if (idx === -1) return "";
|
|
14511
14458
|
return bodyText.slice(Math.max(0, idx - 25), idx);
|
|
14512
14459
|
}
|
|
14513
|
-
async function jsonFetch(url, init) {
|
|
14514
|
-
const res = await fetch(url, {
|
|
14515
|
-
credentials: "include",
|
|
14516
|
-
...init
|
|
14517
|
-
});
|
|
14518
|
-
const data = await res.json().catch(() => ({}));
|
|
14519
|
-
if (!res.ok) {
|
|
14520
|
-
throw new Error(data?.error || `Request failed (${res.status})`);
|
|
14521
|
-
}
|
|
14522
|
-
return data;
|
|
14523
|
-
}
|
|
14524
14460
|
function WhatsAppTriggerBindings() {
|
|
14525
|
-
const
|
|
14526
|
-
const [bindings, setBindings] = useState([]);
|
|
14461
|
+
const { triggers, bindings, loading, reload, rebind } = useOrderNotificationTriggerBindings("whatsapp");
|
|
14527
14462
|
const [templates, setTemplates] = useState([]);
|
|
14528
|
-
const [
|
|
14463
|
+
const [templatesLoaded, setTemplatesLoaded] = useState(false);
|
|
14529
14464
|
const [syncing, setSyncing] = useState(false);
|
|
14530
14465
|
const [editingTemplateId, setEditingTemplateId] = useState(null);
|
|
14531
14466
|
const templatesById = useMemo(() => {
|
|
@@ -14535,27 +14470,20 @@ function WhatsAppTriggerBindings() {
|
|
|
14535
14470
|
}, [
|
|
14536
14471
|
templates
|
|
14537
14472
|
]);
|
|
14538
|
-
const
|
|
14539
|
-
setLoading(true);
|
|
14473
|
+
const loadTemplates = /* @__PURE__ */ __name(async () => {
|
|
14540
14474
|
try {
|
|
14541
|
-
const
|
|
14542
|
-
|
|
14543
|
-
jsonFetch("/api/whatsapp_trigger_bindings?limit=100"),
|
|
14544
|
-
jsonFetch("/api/message-templates/whatsapp")
|
|
14545
|
-
]);
|
|
14546
|
-
setTriggers((triggersRes.data ?? []).filter((t) => t.enabled));
|
|
14547
|
-
setBindings(bindingsRes.data ?? []);
|
|
14548
|
-
setTemplates(templatesRes.items ?? []);
|
|
14475
|
+
const res = await jsonFetch("/api/message-templates/whatsapp");
|
|
14476
|
+
setTemplates(res.items ?? []);
|
|
14549
14477
|
} catch (e) {
|
|
14550
|
-
toast.error(e instanceof Error ? e.message : "Failed to load WhatsApp
|
|
14478
|
+
toast.error(e instanceof Error ? e.message : "Failed to load WhatsApp templates");
|
|
14551
14479
|
} finally {
|
|
14552
|
-
|
|
14480
|
+
setTemplatesLoaded(true);
|
|
14553
14481
|
}
|
|
14554
|
-
},
|
|
14555
|
-
|
|
14556
|
-
void
|
|
14482
|
+
}, "loadTemplates");
|
|
14483
|
+
useMemo(() => {
|
|
14484
|
+
if (!templatesLoaded) void loadTemplates();
|
|
14557
14485
|
}, [
|
|
14558
|
-
|
|
14486
|
+
templatesLoaded
|
|
14559
14487
|
]);
|
|
14560
14488
|
const handleSync = /* @__PURE__ */ __name(async () => {
|
|
14561
14489
|
setSyncing(true);
|
|
@@ -14563,7 +14491,10 @@ function WhatsAppTriggerBindings() {
|
|
|
14563
14491
|
await jsonFetch("/api/message-templates/whatsapp/sync", {
|
|
14564
14492
|
method: "POST"
|
|
14565
14493
|
});
|
|
14566
|
-
await
|
|
14494
|
+
await Promise.all([
|
|
14495
|
+
loadTemplates(),
|
|
14496
|
+
reload()
|
|
14497
|
+
]);
|
|
14567
14498
|
toast.success("Synced templates from Meta");
|
|
14568
14499
|
} catch (e) {
|
|
14569
14500
|
toast.error(e instanceof Error ? e.message : "Sync failed");
|
|
@@ -14572,40 +14503,8 @@ function WhatsAppTriggerBindings() {
|
|
|
14572
14503
|
}
|
|
14573
14504
|
}, "handleSync");
|
|
14574
14505
|
const handleRebind = /* @__PURE__ */ __name(async (triggerKey, messageTemplateId) => {
|
|
14575
|
-
const
|
|
14576
|
-
|
|
14577
|
-
if (existing) {
|
|
14578
|
-
const updated = await jsonFetch(`/api/whatsapp_trigger_bindings/${existing.id}`, {
|
|
14579
|
-
method: "PUT",
|
|
14580
|
-
headers: {
|
|
14581
|
-
"Content-Type": "application/json"
|
|
14582
|
-
},
|
|
14583
|
-
body: JSON.stringify({
|
|
14584
|
-
messageTemplateId
|
|
14585
|
-
})
|
|
14586
|
-
});
|
|
14587
|
-
setBindings((prev) => prev.map((b) => b.id === updated.id ? updated : b));
|
|
14588
|
-
} else {
|
|
14589
|
-
const created = await jsonFetch("/api/whatsapp_trigger_bindings", {
|
|
14590
|
-
method: "POST",
|
|
14591
|
-
headers: {
|
|
14592
|
-
"Content-Type": "application/json"
|
|
14593
|
-
},
|
|
14594
|
-
body: JSON.stringify({
|
|
14595
|
-
triggerKey,
|
|
14596
|
-
messageTemplateId,
|
|
14597
|
-
enabled: true
|
|
14598
|
-
})
|
|
14599
|
-
});
|
|
14600
|
-
setBindings((prev) => [
|
|
14601
|
-
...prev,
|
|
14602
|
-
created
|
|
14603
|
-
]);
|
|
14604
|
-
}
|
|
14605
|
-
toast.success(`"${triggerKey}" bound to "${templatesById.get(messageTemplateId)?.name ?? messageTemplateId}"`);
|
|
14606
|
-
} catch (e) {
|
|
14607
|
-
toast.error(e instanceof Error ? e.message : "Failed to update binding");
|
|
14608
|
-
}
|
|
14506
|
+
const label = templatesById.get(messageTemplateId)?.name ?? String(messageTemplateId);
|
|
14507
|
+
await rebind(triggerKey, messageTemplateId, label);
|
|
14609
14508
|
}, "handleRebind");
|
|
14610
14509
|
const handleSaveParamOrder = /* @__PURE__ */ __name(async (templateId, paramOrder) => {
|
|
14611
14510
|
try {
|
|
@@ -14625,7 +14524,7 @@ function WhatsAppTriggerBindings() {
|
|
|
14625
14524
|
toast.error(e instanceof Error ? e.message : "Failed to save param order");
|
|
14626
14525
|
}
|
|
14627
14526
|
}, "handleSaveParamOrder");
|
|
14628
|
-
if (loading) {
|
|
14527
|
+
if (loading || !templatesLoaded) {
|
|
14629
14528
|
return /* @__PURE__ */ React.createElement("div", {
|
|
14630
14529
|
className: "flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400"
|
|
14631
14530
|
}, /* @__PURE__ */ React.createElement(Loader2, {
|
|
@@ -14640,7 +14539,7 @@ function WhatsAppTriggerBindings() {
|
|
|
14640
14539
|
className: "text-sm font-medium text-gray-900 dark:text-white"
|
|
14641
14540
|
}, "Order trigger templates"), /* @__PURE__ */ React.createElement("p", {
|
|
14642
14541
|
className: "text-xs text-gray-500 dark:text-gray-400 mt-1"
|
|
14643
|
-
}, "When
|
|
14542
|
+
}, "When a trigger fires, the matching template is sent on WhatsApp. Sync templates from Meta, then pick one for each trigger below.")), /* @__PURE__ */ React.createElement(Button, {
|
|
14644
14543
|
type: "button",
|
|
14645
14544
|
variant: "outline",
|
|
14646
14545
|
size: "sm",
|
|
@@ -14817,24 +14716,15 @@ function ParamOrderEditor({ template, onCancel, onSave }) {
|
|
|
14817
14716
|
onClick: /* @__PURE__ */ __name(() => onSave(values), "onClick")
|
|
14818
14717
|
}, "Save"))));
|
|
14819
14718
|
}
|
|
14820
|
-
var
|
|
14719
|
+
var CONTEXT_HINTS;
|
|
14821
14720
|
var init_whatsappTriggerBindingsPage = __esm({
|
|
14822
14721
|
"src/admin/pages/whatsappTriggerBindingsPage.tsx"() {
|
|
14823
14722
|
"use client";
|
|
14824
14723
|
init_button();
|
|
14825
14724
|
init_select();
|
|
14725
|
+
init_order_notification_bindings_shared();
|
|
14826
14726
|
__name(varCount, "varCount");
|
|
14827
14727
|
__name(bodyExample, "bodyExample");
|
|
14828
|
-
KNOWN_VARS = [
|
|
14829
|
-
"orderId",
|
|
14830
|
-
"orderNumber",
|
|
14831
|
-
"customerName",
|
|
14832
|
-
"customerPhone",
|
|
14833
|
-
"total",
|
|
14834
|
-
"currency",
|
|
14835
|
-
"vendorId",
|
|
14836
|
-
"refundAmount"
|
|
14837
|
-
];
|
|
14838
14728
|
CONTEXT_HINTS = [
|
|
14839
14729
|
{
|
|
14840
14730
|
pattern: /refund/,
|
|
@@ -14873,42 +14763,280 @@ var init_whatsappTriggerBindingsPage = __esm({
|
|
|
14873
14763
|
__name(guessFromExampleShape, "guessFromExampleShape");
|
|
14874
14764
|
__name(guessKnownVar, "guessKnownVar");
|
|
14875
14765
|
__name(contextBeforePlaceholder, "contextBeforePlaceholder");
|
|
14876
|
-
__name(jsonFetch, "jsonFetch");
|
|
14877
14766
|
__name(WhatsAppTriggerBindings, "WhatsAppTriggerBindings");
|
|
14878
14767
|
__name(ParamOrderEditor, "ParamOrderEditor");
|
|
14879
14768
|
}
|
|
14880
14769
|
});
|
|
14881
|
-
function
|
|
14882
|
-
const
|
|
14883
|
-
const [
|
|
14884
|
-
const [
|
|
14770
|
+
function EmailTriggerBindings() {
|
|
14771
|
+
const { triggers, bindings, loading, rebind } = useOrderNotificationTriggerBindings("email");
|
|
14772
|
+
const [templates, setTemplates] = useState([]);
|
|
14773
|
+
const [templatesLoaded, setTemplatesLoaded] = useState(false);
|
|
14774
|
+
const templatesById = useMemo(() => {
|
|
14775
|
+
const m = /* @__PURE__ */ new Map();
|
|
14776
|
+
for (const t of templates) m.set(t.id, t);
|
|
14777
|
+
return m;
|
|
14778
|
+
}, [
|
|
14779
|
+
templates
|
|
14780
|
+
]);
|
|
14885
14781
|
useEffect(() => {
|
|
14886
14782
|
let cancelled = false;
|
|
14887
|
-
|
|
14888
|
-
|
|
14889
|
-
|
|
14890
|
-
|
|
14891
|
-
|
|
14892
|
-
|
|
14783
|
+
(async () => {
|
|
14784
|
+
try {
|
|
14785
|
+
const res = await jsonFetch("/api/message-templates/email");
|
|
14786
|
+
if (!cancelled) setTemplates(res.items ?? []);
|
|
14787
|
+
} catch (e) {
|
|
14788
|
+
if (!cancelled) toast.error(e instanceof Error ? e.message : "Failed to load email templates");
|
|
14789
|
+
} finally {
|
|
14790
|
+
if (!cancelled) setTemplatesLoaded(true);
|
|
14791
|
+
}
|
|
14792
|
+
})();
|
|
14893
14793
|
return () => {
|
|
14894
14794
|
cancelled = true;
|
|
14895
14795
|
};
|
|
14896
14796
|
}, []);
|
|
14897
|
-
const
|
|
14898
|
-
|
|
14899
|
-
|
|
14900
|
-
|
|
14901
|
-
|
|
14797
|
+
const handleTemplateSaved = /* @__PURE__ */ __name((template) => {
|
|
14798
|
+
setTemplates((prev) => {
|
|
14799
|
+
const exists = prev.some((t) => t.id === template.id);
|
|
14800
|
+
return exists ? prev.map((t) => t.id === template.id ? template : t) : [
|
|
14801
|
+
...prev,
|
|
14802
|
+
template
|
|
14803
|
+
].sort((a, b) => a.name.localeCompare(b.name));
|
|
14804
|
+
});
|
|
14805
|
+
}, "handleTemplateSaved");
|
|
14806
|
+
if (loading || !templatesLoaded) {
|
|
14807
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
14808
|
+
className: "flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400"
|
|
14809
|
+
}, /* @__PURE__ */ React.createElement(Loader2, {
|
|
14810
|
+
className: "h-4 w-4 animate-spin"
|
|
14811
|
+
}), "Loading trigger bindings\u2026");
|
|
14812
|
+
}
|
|
14813
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
14814
|
+
className: "space-y-3 rounded-md border border-gray-200 dark:border-gray-600 p-3"
|
|
14815
|
+
}, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", {
|
|
14816
|
+
className: "text-sm font-medium text-gray-900 dark:text-white"
|
|
14817
|
+
}, "Order trigger emails"), /* @__PURE__ */ React.createElement("p", {
|
|
14818
|
+
className: "text-xs text-gray-500 dark:text-gray-400 mt-1"
|
|
14819
|
+
}, "Write the subject and body for each trigger below. Use", " ", /* @__PURE__ */ React.createElement("code", {
|
|
14820
|
+
className: "text-[11px]"
|
|
14821
|
+
}, "{{variable}}"), " tokens \u2014 click a variable chip to insert it at your cursor.")), /* @__PURE__ */ React.createElement("div", {
|
|
14822
|
+
className: "space-y-4"
|
|
14823
|
+
}, triggers.length === 0 ? /* @__PURE__ */ React.createElement("p", {
|
|
14824
|
+
className: "text-xs text-amber-600 dark:text-amber-400"
|
|
14825
|
+
}, "No order triggers found. Run database migrations to seed default triggers.") : null, triggers.map(({ triggerKey, label, description }) => {
|
|
14826
|
+
const binding = bindings.find((b) => b.triggerKey === triggerKey) ?? null;
|
|
14827
|
+
const template = binding?.messageTemplateId != null ? templatesById.get(binding.messageTemplateId) : void 0;
|
|
14828
|
+
return /* @__PURE__ */ React.createElement(TriggerEmailEditor, {
|
|
14829
|
+
key: triggerKey,
|
|
14830
|
+
triggerKey,
|
|
14831
|
+
label,
|
|
14832
|
+
description,
|
|
14833
|
+
template,
|
|
14834
|
+
onSaved: handleTemplateSaved,
|
|
14835
|
+
onBind: /* @__PURE__ */ __name((templateId, templateLabel) => rebind(triggerKey, templateId, templateLabel), "onBind")
|
|
14836
|
+
});
|
|
14837
|
+
})));
|
|
14838
|
+
}
|
|
14839
|
+
function TriggerEmailEditor({ triggerKey, label, description, template, onSaved, onBind }) {
|
|
14840
|
+
const defaults = useMemo(() => getEmailTriggerDefaults(triggerKey), [
|
|
14841
|
+
triggerKey
|
|
14842
|
+
]);
|
|
14843
|
+
const [subject, setSubject] = useState(() => template?.subject ?? defaults.subject);
|
|
14844
|
+
const [body, setBody] = useState(() => template?.body ?? defaults.body);
|
|
14845
|
+
const [saving, setSaving] = useState(false);
|
|
14846
|
+
const [focusedField, setFocusedField] = useState("body");
|
|
14847
|
+
const subjectRef = useRef(null);
|
|
14848
|
+
const bodyRef = useRef(null);
|
|
14849
|
+
useEffect(() => {
|
|
14850
|
+
if (template) {
|
|
14851
|
+
setSubject(template.subject ?? "");
|
|
14852
|
+
setBody(template.body ?? "");
|
|
14853
|
+
} else {
|
|
14854
|
+
setSubject(defaults.subject);
|
|
14855
|
+
setBody(defaults.body);
|
|
14902
14856
|
}
|
|
14903
|
-
|
|
14904
|
-
|
|
14905
|
-
|
|
14906
|
-
|
|
14857
|
+
}, [
|
|
14858
|
+
template,
|
|
14859
|
+
defaults.subject,
|
|
14860
|
+
defaults.body
|
|
14861
|
+
]);
|
|
14862
|
+
const savedSubject = template?.subject ?? defaults.subject;
|
|
14863
|
+
const savedBody = template?.body ?? defaults.body;
|
|
14864
|
+
const isDirty = subject !== savedSubject || body !== savedBody;
|
|
14865
|
+
function insertVariable(v) {
|
|
14866
|
+
const token = `{{${v}}}`;
|
|
14867
|
+
if (focusedField === "subject") {
|
|
14868
|
+
const el = subjectRef.current;
|
|
14869
|
+
const start = el?.selectionStart ?? subject.length;
|
|
14870
|
+
const end = el?.selectionEnd ?? subject.length;
|
|
14871
|
+
const next = subject.slice(0, start) + token + subject.slice(end);
|
|
14872
|
+
setSubject(next);
|
|
14873
|
+
requestAnimationFrame(() => {
|
|
14874
|
+
el?.focus();
|
|
14875
|
+
el?.setSelectionRange(start + token.length, start + token.length);
|
|
14876
|
+
});
|
|
14877
|
+
} else {
|
|
14878
|
+
const el = bodyRef.current;
|
|
14879
|
+
const start = el?.selectionStart ?? body.length;
|
|
14880
|
+
const end = el?.selectionEnd ?? body.length;
|
|
14881
|
+
const next = body.slice(0, start) + token + body.slice(end);
|
|
14882
|
+
setBody(next);
|
|
14883
|
+
requestAnimationFrame(() => {
|
|
14884
|
+
el?.focus();
|
|
14885
|
+
el?.setSelectionRange(start + token.length, start + token.length);
|
|
14886
|
+
});
|
|
14887
|
+
}
|
|
14888
|
+
}
|
|
14889
|
+
__name(insertVariable, "insertVariable");
|
|
14890
|
+
const canSave = body.trim() !== "";
|
|
14891
|
+
const handleSave = /* @__PURE__ */ __name(async () => {
|
|
14892
|
+
if (!canSave) return;
|
|
14893
|
+
setSaving(true);
|
|
14894
|
+
try {
|
|
14895
|
+
const templateName = template?.name ?? `${label} email`;
|
|
14896
|
+
let saved;
|
|
14897
|
+
if (template) {
|
|
14898
|
+
const res = await jsonFetch(`/api/message-templates/email/${template.id}`, {
|
|
14899
|
+
method: "PATCH",
|
|
14900
|
+
headers: {
|
|
14901
|
+
"Content-Type": "application/json"
|
|
14902
|
+
},
|
|
14903
|
+
body: JSON.stringify({
|
|
14904
|
+
name: templateName,
|
|
14905
|
+
subject,
|
|
14906
|
+
body
|
|
14907
|
+
})
|
|
14908
|
+
});
|
|
14909
|
+
saved = res.item;
|
|
14910
|
+
toast.success(`"${label}" email template saved`);
|
|
14911
|
+
} else {
|
|
14912
|
+
const res = await jsonFetch("/api/message-templates/email", {
|
|
14913
|
+
method: "POST",
|
|
14914
|
+
headers: {
|
|
14915
|
+
"Content-Type": "application/json"
|
|
14916
|
+
},
|
|
14917
|
+
body: JSON.stringify({
|
|
14918
|
+
name: templateName,
|
|
14919
|
+
subject,
|
|
14920
|
+
body
|
|
14921
|
+
})
|
|
14922
|
+
});
|
|
14923
|
+
saved = res.item;
|
|
14924
|
+
onSaved(saved);
|
|
14925
|
+
const bound = await onBind(saved.id, saved.name);
|
|
14926
|
+
if (!bound) {
|
|
14927
|
+
toast.success(`"${label}" email template created`);
|
|
14928
|
+
}
|
|
14907
14929
|
return;
|
|
14908
14930
|
}
|
|
14931
|
+
onSaved(saved);
|
|
14932
|
+
} catch (e) {
|
|
14933
|
+
toast.error(e instanceof Error ? e.message : "Failed to save template");
|
|
14934
|
+
} finally {
|
|
14935
|
+
setSaving(false);
|
|
14909
14936
|
}
|
|
14910
|
-
|
|
14911
|
-
|
|
14937
|
+
}, "handleSave");
|
|
14938
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
14939
|
+
className: "space-y-3 border-t border-gray-100 pt-4 first:border-t-0 first:pt-0 dark:border-gray-700"
|
|
14940
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
14941
|
+
className: "flex items-start justify-between gap-2"
|
|
14942
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
14943
|
+
className: "min-w-0"
|
|
14944
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
14945
|
+
className: "flex items-center gap-2"
|
|
14946
|
+
}, /* @__PURE__ */ React.createElement(Mail, {
|
|
14947
|
+
className: "h-3.5 w-3.5 text-gray-400 shrink-0"
|
|
14948
|
+
}), /* @__PURE__ */ React.createElement("span", {
|
|
14949
|
+
className: "text-sm font-medium text-gray-900 dark:text-gray-100"
|
|
14950
|
+
}, label), /* @__PURE__ */ React.createElement("span", {
|
|
14951
|
+
className: "text-[11px] font-mono text-gray-400"
|
|
14952
|
+
}, triggerKey)), description ? /* @__PURE__ */ React.createElement("p", {
|
|
14953
|
+
className: "text-[11px] text-gray-500 dark:text-gray-400 mt-0.5 ml-5"
|
|
14954
|
+
}, description) : null), /* @__PURE__ */ React.createElement(Button, {
|
|
14955
|
+
type: "button",
|
|
14956
|
+
size: "sm",
|
|
14957
|
+
className: "h-8 gap-1.5 shrink-0",
|
|
14958
|
+
disabled: !canSave || saving || !isDirty && Boolean(template),
|
|
14959
|
+
onClick: /* @__PURE__ */ __name(() => void handleSave(), "onClick")
|
|
14960
|
+
}, saving ? /* @__PURE__ */ React.createElement(Loader2, {
|
|
14961
|
+
className: "h-3.5 w-3.5 animate-spin"
|
|
14962
|
+
}) : /* @__PURE__ */ React.createElement(Save, {
|
|
14963
|
+
className: "h-3.5 w-3.5"
|
|
14964
|
+
}), template ? "Save" : "Create")), !template ? /* @__PURE__ */ React.createElement("p", {
|
|
14965
|
+
className: "text-xs text-amber-600 dark:text-amber-400 ml-5"
|
|
14966
|
+
}, "No email saved for this trigger yet \u2014 review the draft below and click Create.") : null, /* @__PURE__ */ React.createElement("div", {
|
|
14967
|
+
className: "ml-5 space-y-3 rounded-md border border-gray-100 bg-gray-50/50 p-3 dark:border-gray-700 dark:bg-gray-800/30"
|
|
14968
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
14969
|
+
className: "space-y-1"
|
|
14970
|
+
}, /* @__PURE__ */ React.createElement(Label3, {
|
|
14971
|
+
className: "text-xs text-gray-600 dark:text-gray-400"
|
|
14972
|
+
}, "Subject"), /* @__PURE__ */ React.createElement(Input, {
|
|
14973
|
+
ref: subjectRef,
|
|
14974
|
+
value: subject,
|
|
14975
|
+
onChange: /* @__PURE__ */ __name((e) => setSubject(e.target.value), "onChange"),
|
|
14976
|
+
onFocus: /* @__PURE__ */ __name(() => setFocusedField("subject"), "onFocus"),
|
|
14977
|
+
placeholder: defaults.subjectPlaceholder,
|
|
14978
|
+
className: "h-8 text-sm bg-white dark:bg-gray-900"
|
|
14979
|
+
})), /* @__PURE__ */ React.createElement("div", {
|
|
14980
|
+
className: "space-y-1"
|
|
14981
|
+
}, /* @__PURE__ */ React.createElement(Label3, {
|
|
14982
|
+
className: "text-xs text-gray-600 dark:text-gray-400"
|
|
14983
|
+
}, "Body"), /* @__PURE__ */ React.createElement(Textarea, {
|
|
14984
|
+
ref: bodyRef,
|
|
14985
|
+
value: body,
|
|
14986
|
+
onChange: /* @__PURE__ */ __name((e) => setBody(e.target.value), "onChange"),
|
|
14987
|
+
onFocus: /* @__PURE__ */ __name(() => setFocusedField("body"), "onFocus"),
|
|
14988
|
+
rows: 6,
|
|
14989
|
+
placeholder: defaults.bodyPlaceholder,
|
|
14990
|
+
className: "text-sm font-mono bg-white dark:bg-gray-900 resize-y min-h-[120px]"
|
|
14991
|
+
})), /* @__PURE__ */ React.createElement("div", {
|
|
14992
|
+
className: "space-y-1.5"
|
|
14993
|
+
}, /* @__PURE__ */ React.createElement(Label3, {
|
|
14994
|
+
className: "text-xs text-gray-500 dark:text-gray-400"
|
|
14995
|
+
}, "Variables \u2014 inserts into ", focusedField === "subject" ? "subject" : "body", ":"), /* @__PURE__ */ React.createElement("div", {
|
|
14996
|
+
className: "flex flex-wrap gap-1.5"
|
|
14997
|
+
}, KNOWN_VARS.map((v) => /* @__PURE__ */ React.createElement("button", {
|
|
14998
|
+
key: v,
|
|
14999
|
+
type: "button",
|
|
15000
|
+
title: VAR_HINTS[v],
|
|
15001
|
+
onClick: /* @__PURE__ */ __name(() => insertVariable(v), "onClick"),
|
|
15002
|
+
className: "rounded border border-gray-200 bg-white px-2 py-1 font-mono text-[11px] text-gray-700 hover:border-blue-300 hover:bg-blue-50 dark:border-gray-600 dark:bg-gray-900 dark:text-gray-300 dark:hover:border-blue-700 dark:hover:bg-blue-950/40"
|
|
15003
|
+
}, `{{${v}}}`))), /* @__PURE__ */ React.createElement("p", {
|
|
15004
|
+
className: "text-[10px] text-gray-400 dark:text-gray-500"
|
|
15005
|
+
}, "Hover a chip to see what each variable contains at send time."))));
|
|
15006
|
+
}
|
|
15007
|
+
var VAR_HINTS;
|
|
15008
|
+
var init_emailTriggersBindingsPage = __esm({
|
|
15009
|
+
"src/admin/pages/emailTriggersBindingsPage.tsx"() {
|
|
15010
|
+
"use client";
|
|
15011
|
+
init_button();
|
|
15012
|
+
init_input();
|
|
15013
|
+
init_textarea();
|
|
15014
|
+
init_label();
|
|
15015
|
+
init_order_notification_bindings_shared();
|
|
15016
|
+
VAR_HINTS = {
|
|
15017
|
+
orderId: "Internal order ID",
|
|
15018
|
+
orderNumber: "Display order number",
|
|
15019
|
+
customerName: "Customer full name",
|
|
15020
|
+
customerPhone: "Customer phone",
|
|
15021
|
+
total: "Order total amount",
|
|
15022
|
+
currency: "Currency code (e.g. INR)",
|
|
15023
|
+
vendorId: "Vendor / store ID",
|
|
15024
|
+
refundAmount: "Refund amount (refund triggers)"
|
|
15025
|
+
};
|
|
15026
|
+
__name(EmailTriggerBindings, "EmailTriggerBindings");
|
|
15027
|
+
__name(TriggerEmailEditor, "TriggerEmailEditor");
|
|
15028
|
+
}
|
|
15029
|
+
});
|
|
15030
|
+
function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledChange, onSaveSettings, saving }) {
|
|
15031
|
+
const [tab, setTab] = useState("email");
|
|
15032
|
+
const handleSaveEnabled = /* @__PURE__ */ __name(async () => {
|
|
15033
|
+
const ok = await onSaveSettings();
|
|
15034
|
+
if (!ok) {
|
|
15035
|
+
toast.error("Failed to save plugin settings");
|
|
15036
|
+
return;
|
|
15037
|
+
}
|
|
15038
|
+
toast.success("Settings saved");
|
|
15039
|
+
}, "handleSaveEnabled");
|
|
14912
15040
|
return /* @__PURE__ */ React.createElement("div", {
|
|
14913
15041
|
className: "space-y-5"
|
|
14914
15042
|
}, /* @__PURE__ */ React.createElement("div", {
|
|
@@ -14923,11 +15051,13 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
|
|
|
14923
15051
|
className: "font-medium text-gray-900 dark:text-white"
|
|
14924
15052
|
}, "Order notifications"), /* @__PURE__ */ React.createElement("ol", {
|
|
14925
15053
|
className: "list-decimal pl-4 space-y-1 text-xs leading-relaxed"
|
|
14926
|
-
}, /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement("strong", null, "Email"), " tab:
|
|
15054
|
+
}, /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement("strong", null, "Email"), " tab: each trigger has its own subject and body \u2014 use", " ", /* @__PURE__ */ React.createElement("code", {
|
|
15055
|
+
className: "text-[11px]"
|
|
15056
|
+
}, "{{variable}}"), " tokens and click Create or Save."), /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement("strong", null, "WhatsApp"), " tab: sync Meta-approved templates, then bind a trigger to one."), /* @__PURE__ */ React.createElement("li", null, "Both tabs bind against the same trigger catalog (", /* @__PURE__ */ React.createElement("code", {
|
|
14927
15057
|
className: "text-[11px]"
|
|
14928
|
-
}, "order_notification_triggers"), "
|
|
15058
|
+
}, "order_notification_triggers"), ") and share", " ", /* @__PURE__ */ React.createElement("code", {
|
|
14929
15059
|
className: "text-[11px]"
|
|
14930
|
-
}, "
|
|
15060
|
+
}, "order_notification_bindings"), ", keyed by trigger + channel \u2014 so the same trigger (e.g. order placed) can have its own email template and its own WhatsApp template at the same time.")), /* @__PURE__ */ React.createElement("p", {
|
|
14931
15061
|
className: "text-xs text-gray-500 dark:text-gray-400"
|
|
14932
15062
|
}, "Requires ", /* @__PURE__ */ React.createElement("strong", null, "Email"), " plugin (SMTP) for email and ", /* @__PURE__ */ React.createElement("strong", null, "WhatsApp"), " plugin (Meta credentials) for WhatsApp delivery.")))), /* @__PURE__ */ React.createElement("div", {
|
|
14933
15063
|
className: "flex items-center justify-between gap-2"
|
|
@@ -14954,16 +15084,9 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
|
|
|
14954
15084
|
className: `inline-flex items-center gap-2 px-4 py-2.5 text-sm font-medium border-b-2 ${tab === "whatsapp" ? "border-gray-800 text-gray-800 dark:border-gray-200 dark:text-gray-100" : "border-transparent text-gray-500 hover:text-gray-700"}`
|
|
14955
15085
|
}, /* @__PURE__ */ React.createElement(MessageCircle, {
|
|
14956
15086
|
className: "h-4 w-4"
|
|
14957
|
-
}), "WhatsApp template"))), tab === "whatsapp" ? /* @__PURE__ */ React.createElement(WhatsAppTriggerBindings, null) :
|
|
14958
|
-
className: "text-sm text-gray-500"
|
|
14959
|
-
}, "Loading templates\u2026") : /* @__PURE__ */ React.createElement(EventOrderGlobalTemplates, {
|
|
14960
|
-
items: templateItems,
|
|
14961
|
-
onChange: setTemplateItems,
|
|
14962
|
-
channel: tab,
|
|
14963
|
-
settingsGroup
|
|
14964
|
-
}), /* @__PURE__ */ React.createElement(Button, {
|
|
15087
|
+
}), "WhatsApp template"))), tab === "whatsapp" ? /* @__PURE__ */ React.createElement(WhatsAppTriggerBindings, null) : /* @__PURE__ */ React.createElement(EmailTriggerBindings, null), /* @__PURE__ */ React.createElement(Button, {
|
|
14965
15088
|
size: "sm",
|
|
14966
|
-
onClick: /* @__PURE__ */ __name(() => void
|
|
15089
|
+
onClick: /* @__PURE__ */ __name(() => void handleSaveEnabled(), "onClick"),
|
|
14967
15090
|
disabled: saving,
|
|
14968
15091
|
className: "gap-1"
|
|
14969
15092
|
}, /* @__PURE__ */ React.createElement(Save, {
|
|
@@ -14976,8 +15099,8 @@ var init_EventNotificationsPluginSettings = __esm({
|
|
|
14976
15099
|
init_button();
|
|
14977
15100
|
init_label();
|
|
14978
15101
|
init_switch();
|
|
14979
|
-
init_EventOrderGlobalTemplates();
|
|
14980
15102
|
init_whatsappTriggerBindingsPage();
|
|
15103
|
+
init_emailTriggersBindingsPage();
|
|
14981
15104
|
__name(EventNotificationsPluginSettings, "EventNotificationsPluginSettings");
|
|
14982
15105
|
}
|
|
14983
15106
|
});
|
|
@@ -26116,47 +26239,6 @@ var init_ProductIncomeCalculatorDialog = __esm({
|
|
|
26116
26239
|
});
|
|
26117
26240
|
|
|
26118
26241
|
// src/lib/currency-prices.ts
|
|
26119
|
-
function normalizeCurrencyCode(code) {
|
|
26120
|
-
return code.trim().toUpperCase();
|
|
26121
|
-
}
|
|
26122
|
-
function parseCurrencyPrices(raw) {
|
|
26123
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
26124
|
-
const out = {};
|
|
26125
|
-
for (const [key, value] of Object.entries(raw)) {
|
|
26126
|
-
const code = normalizeCurrencyCode(key);
|
|
26127
|
-
if (!code) continue;
|
|
26128
|
-
const n = Number(value);
|
|
26129
|
-
if (Number.isFinite(n) && n >= 0) out[code] = n;
|
|
26130
|
-
}
|
|
26131
|
-
return out;
|
|
26132
|
-
}
|
|
26133
|
-
function serializeCurrencyPrices(defaultCurrency, defaultPrice, additional) {
|
|
26134
|
-
const base = normalizeCurrencyCode(defaultCurrency) || "INR";
|
|
26135
|
-
const out = {
|
|
26136
|
-
[base]: defaultPrice
|
|
26137
|
-
};
|
|
26138
|
-
for (const [code, amount] of Object.entries(additional)) {
|
|
26139
|
-
const normalized = normalizeCurrencyCode(code);
|
|
26140
|
-
if (!normalized || normalized === base) continue;
|
|
26141
|
-
if (Number.isFinite(amount) && amount >= 0) out[normalized] = amount;
|
|
26142
|
-
}
|
|
26143
|
-
return Object.keys(out).length ? out : null;
|
|
26144
|
-
}
|
|
26145
|
-
function currencyPricesToForm(defaultCurrency, storedPrice, currencyPrices) {
|
|
26146
|
-
const base = normalizeCurrencyCode(defaultCurrency) || "INR";
|
|
26147
|
-
const map = parseCurrencyPrices(currencyPrices);
|
|
26148
|
-
const defaultFromMap = map[base];
|
|
26149
|
-
const defaultPrice = defaultFromMap != null ? String(defaultFromMap) : storedPrice != null && Number.isFinite(Number(storedPrice)) ? String(storedPrice) : "";
|
|
26150
|
-
const additional = {};
|
|
26151
|
-
for (const [code, amount] of Object.entries(map)) {
|
|
26152
|
-
if (code === base) continue;
|
|
26153
|
-
additional[code] = String(amount);
|
|
26154
|
-
}
|
|
26155
|
-
return {
|
|
26156
|
-
defaultPrice,
|
|
26157
|
-
additional
|
|
26158
|
-
};
|
|
26159
|
-
}
|
|
26160
26242
|
function parseDefaultProductPrice(defaultPriceRaw) {
|
|
26161
26243
|
const trimmed = defaultPriceRaw.trim();
|
|
26162
26244
|
if (!trimmed) return null;
|
|
@@ -26166,31 +26248,9 @@ function parseDefaultProductPrice(defaultPriceRaw) {
|
|
|
26166
26248
|
price: Math.round(price * 100) / 100
|
|
26167
26249
|
};
|
|
26168
26250
|
}
|
|
26169
|
-
function formToCurrencyPricesPayload(defaultCurrency, defaultPriceRaw, additionalForm) {
|
|
26170
|
-
const base = normalizeCurrencyCode(defaultCurrency) || "INR";
|
|
26171
|
-
const defaultPrice = Number(defaultPriceRaw.trim());
|
|
26172
|
-
if (!Number.isFinite(defaultPrice) || defaultPrice < 0) return null;
|
|
26173
|
-
const additional = {};
|
|
26174
|
-
for (const [code, raw] of Object.entries(additionalForm)) {
|
|
26175
|
-
const trimmed = raw.trim();
|
|
26176
|
-
if (!trimmed) continue;
|
|
26177
|
-
const n = Number(trimmed);
|
|
26178
|
-
if (!Number.isFinite(n) || n < 0) continue;
|
|
26179
|
-
additional[normalizeCurrencyCode(code)] = n;
|
|
26180
|
-
}
|
|
26181
|
-
return {
|
|
26182
|
-
price: defaultPrice,
|
|
26183
|
-
currencyPrices: serializeCurrencyPrices(base, defaultPrice, additional)
|
|
26184
|
-
};
|
|
26185
|
-
}
|
|
26186
26251
|
var init_currency_prices = __esm({
|
|
26187
26252
|
"src/lib/currency-prices.ts"() {
|
|
26188
|
-
__name(normalizeCurrencyCode, "normalizeCurrencyCode");
|
|
26189
|
-
__name(parseCurrencyPrices, "parseCurrencyPrices");
|
|
26190
|
-
__name(serializeCurrencyPrices, "serializeCurrencyPrices");
|
|
26191
|
-
__name(currencyPricesToForm, "currencyPricesToForm");
|
|
26192
26253
|
__name(parseDefaultProductPrice, "parseDefaultProductPrice");
|
|
26193
|
-
__name(formToCurrencyPricesPayload, "formToCurrencyPricesPayload");
|
|
26194
26254
|
}
|
|
26195
26255
|
});
|
|
26196
26256
|
|
|
@@ -29628,44 +29688,6 @@ function SupportedCurrenciesPicker({ currencies, defaultCurrency, supported, onD
|
|
|
29628
29688
|
className: "mt-1 text-xs text-gray-500"
|
|
29629
29689
|
}, "Default currency is always included.")));
|
|
29630
29690
|
}
|
|
29631
|
-
function MultiCurrencyPriceFields({ defaultCurrency, currencies, defaultPrice, additionalPrices, onDefaultPriceChange, onAdditionalPriceChange, labelCls: labelClassName = "block text-xs font-medium text-gray-600 mb-1", inputCls: inputClassName = "w-full rounded-md border border-gray-300 px-2 py-1.5 text-sm" }) {
|
|
29632
|
-
const base = defaultCurrency.trim().toUpperCase() || "INR";
|
|
29633
|
-
const others = currencies.filter((c) => c.code.toUpperCase() !== base);
|
|
29634
|
-
return /* @__PURE__ */ React.createElement("div", {
|
|
29635
|
-
className: "space-y-4"
|
|
29636
|
-
}, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
|
|
29637
|
-
className: labelClassName
|
|
29638
|
-
}, base, " price ", /* @__PURE__ */ React.createElement("span", {
|
|
29639
|
-
className: "text-red-600"
|
|
29640
|
-
}, "*")), /* @__PURE__ */ React.createElement("input", {
|
|
29641
|
-
type: "number",
|
|
29642
|
-
min: 0,
|
|
29643
|
-
step: "any",
|
|
29644
|
-
value: defaultPrice,
|
|
29645
|
-
onChange: /* @__PURE__ */ __name((e) => onDefaultPriceChange(e.target.value), "onChange"),
|
|
29646
|
-
className: inputClassName,
|
|
29647
|
-
required: true
|
|
29648
|
-
})), others.length > 0 ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", {
|
|
29649
|
-
className: `${labelClassName} mb-2`
|
|
29650
|
-
}, "Additional currencies (optional)"), /* @__PURE__ */ React.createElement("div", {
|
|
29651
|
-
className: "grid gap-3 sm:grid-cols-2"
|
|
29652
|
-
}, others.map((c) => {
|
|
29653
|
-
const code = c.code.toUpperCase();
|
|
29654
|
-
return /* @__PURE__ */ React.createElement("div", {
|
|
29655
|
-
key: code
|
|
29656
|
-
}, /* @__PURE__ */ React.createElement("label", {
|
|
29657
|
-
className: labelClassName
|
|
29658
|
-
}, code), /* @__PURE__ */ React.createElement("input", {
|
|
29659
|
-
type: "number",
|
|
29660
|
-
min: 0,
|
|
29661
|
-
step: "any",
|
|
29662
|
-
value: additionalPrices[code] ?? "",
|
|
29663
|
-
onChange: /* @__PURE__ */ __name((e) => onAdditionalPriceChange(code, e.target.value), "onChange"),
|
|
29664
|
-
className: inputClassName,
|
|
29665
|
-
placeholder: "Optional"
|
|
29666
|
-
}));
|
|
29667
|
-
}))) : null);
|
|
29668
|
-
}
|
|
29669
29691
|
var inputCls5, labelCls5, ICONS;
|
|
29670
29692
|
var init_EventManagementFields = __esm({
|
|
29671
29693
|
"src/components/Admin/EventManagementFields.tsx"() {
|
|
@@ -29687,7 +29709,6 @@ var init_EventManagementFields = __esm({
|
|
|
29687
29709
|
__name(SocialMediaLinksDisplay, "SocialMediaLinksDisplay");
|
|
29688
29710
|
__name(TimezoneSelect, "TimezoneSelect");
|
|
29689
29711
|
__name(SupportedCurrenciesPicker, "SupportedCurrenciesPicker");
|
|
29690
|
-
__name(MultiCurrencyPriceFields, "MultiCurrencyPriceFields");
|
|
29691
29712
|
}
|
|
29692
29713
|
});
|
|
29693
29714
|
|
|
@@ -29839,28 +29860,11 @@ function intersectSupportedCurrencies(supported, defaultCurrency, options) {
|
|
|
29839
29860
|
])
|
|
29840
29861
|
];
|
|
29841
29862
|
}
|
|
29842
|
-
function currencyOptionsIncluding(options, codes) {
|
|
29843
|
-
const map = new Map(options.map((o) => [
|
|
29844
|
-
o.code.toUpperCase(),
|
|
29845
|
-
o
|
|
29846
|
-
]));
|
|
29847
|
-
for (const code of codes) {
|
|
29848
|
-
const normalized = code.trim().toUpperCase();
|
|
29849
|
-
if (!normalized || map.has(normalized)) continue;
|
|
29850
|
-
map.set(normalized, {
|
|
29851
|
-
code: normalized
|
|
29852
|
-
});
|
|
29853
|
-
}
|
|
29854
|
-
return [
|
|
29855
|
-
...map.values()
|
|
29856
|
-
].sort((a, b) => a.code.localeCompare(b.code));
|
|
29857
|
-
}
|
|
29858
29863
|
var init_currency_options = __esm({
|
|
29859
29864
|
"src/lib/currency-options.ts"() {
|
|
29860
29865
|
__name(parseCurrencyApiRows, "parseCurrencyApiRows");
|
|
29861
29866
|
__name(fetchActiveCurrencyOptions, "fetchActiveCurrencyOptions");
|
|
29862
29867
|
__name(intersectSupportedCurrencies, "intersectSupportedCurrencies");
|
|
29863
|
-
__name(currencyOptionsIncluding, "currencyOptionsIncluding");
|
|
29864
29868
|
}
|
|
29865
29869
|
});
|
|
29866
29870
|
|
|
@@ -30840,25 +30844,14 @@ function formatDateTimeLocal(value) {
|
|
|
30840
30844
|
if (isNaN(date.getTime())) return "";
|
|
30841
30845
|
return date.toISOString().slice(0, 16);
|
|
30842
30846
|
}
|
|
30843
|
-
async function
|
|
30847
|
+
async function fetchEventDefaultCurrency(eventId) {
|
|
30844
30848
|
try {
|
|
30845
30849
|
const res = await fetch(`/api/events/${eventId}`);
|
|
30846
|
-
if (!res.ok) return
|
|
30850
|
+
if (!res.ok) return "INR";
|
|
30847
30851
|
const data = await res.json();
|
|
30848
|
-
|
|
30849
|
-
const supported = Array.isArray(data.supportedCurrencies) ? data.supportedCurrencies.map((c) => String(c).trim().toUpperCase()).filter((c) => Boolean(c)) : [
|
|
30850
|
-
defaultCurrency
|
|
30851
|
-
];
|
|
30852
|
-
const set = new Set(supported);
|
|
30853
|
-
set.add(defaultCurrency);
|
|
30854
|
-
return {
|
|
30855
|
-
defaultCurrency,
|
|
30856
|
-
supportedCurrencies: [
|
|
30857
|
-
...set
|
|
30858
|
-
]
|
|
30859
|
-
};
|
|
30852
|
+
return String(data.defaultCurrency ?? "INR").trim().toUpperCase() || "INR";
|
|
30860
30853
|
} catch {
|
|
30861
|
-
return
|
|
30854
|
+
return "INR";
|
|
30862
30855
|
}
|
|
30863
30856
|
}
|
|
30864
30857
|
async function validateComboProductInventory(productIds) {
|
|
@@ -30900,10 +30893,8 @@ function ComboEditPage({ comboId }) {
|
|
|
30900
30893
|
const [name, setName] = useState("");
|
|
30901
30894
|
const [desc, setDesc] = useState("");
|
|
30902
30895
|
const [eventId, setEventId] = useState("");
|
|
30903
|
-
const [
|
|
30904
|
-
const [
|
|
30905
|
-
const [pricingConfig, setPricingConfig] = useState(DEFAULT_PRICING);
|
|
30906
|
-
const [currencySchemaOptions, setCurrencySchemaOptions] = useState([]);
|
|
30896
|
+
const [priceStr, setPriceStr] = useState("");
|
|
30897
|
+
const [defaultCurrency, setDefaultCurrency] = useState("INR");
|
|
30907
30898
|
const [minSelectableItems, setMinSelectableItems] = useState(1);
|
|
30908
30899
|
const [maxSelectableItems, setMaxSelectableItems] = useState(1);
|
|
30909
30900
|
const [status, setStatus] = useState("active");
|
|
@@ -30913,23 +30904,14 @@ function ComboEditPage({ comboId }) {
|
|
|
30913
30904
|
const [productOptions, setProductOptions] = useState([]);
|
|
30914
30905
|
const [fixedItems, setFixedItems] = useState([]);
|
|
30915
30906
|
const [addonItems, setAddonItems] = useState([]);
|
|
30916
|
-
useEffect(() => {
|
|
30917
|
-
let cancelled = false;
|
|
30918
|
-
fetchActiveCurrencyOptions().then(({ options }) => {
|
|
30919
|
-
if (!cancelled && options.length) setCurrencySchemaOptions(options);
|
|
30920
|
-
});
|
|
30921
|
-
return () => {
|
|
30922
|
-
cancelled = true;
|
|
30923
|
-
};
|
|
30924
|
-
}, []);
|
|
30925
30907
|
useEffect(() => {
|
|
30926
30908
|
if (!eventId) {
|
|
30927
|
-
|
|
30909
|
+
setDefaultCurrency("INR");
|
|
30928
30910
|
return;
|
|
30929
30911
|
}
|
|
30930
30912
|
let cancelled = false;
|
|
30931
|
-
|
|
30932
|
-
if (!cancelled)
|
|
30913
|
+
fetchEventDefaultCurrency(eventId).then((code) => {
|
|
30914
|
+
if (!cancelled) setDefaultCurrency(code);
|
|
30933
30915
|
});
|
|
30934
30916
|
return () => {
|
|
30935
30917
|
cancelled = true;
|
|
@@ -30937,10 +30919,6 @@ function ComboEditPage({ comboId }) {
|
|
|
30937
30919
|
}, [
|
|
30938
30920
|
eventId
|
|
30939
30921
|
]);
|
|
30940
|
-
const pricingCurrencyOptions = currencyOptionsIncluding(currencySchemaOptions, [
|
|
30941
|
-
pricingConfig.defaultCurrency,
|
|
30942
|
-
...pricingConfig.supportedCurrencies
|
|
30943
|
-
]).filter((c) => pricingConfig.supportedCurrencies.map((x) => x.toUpperCase()).includes(c.code.toUpperCase()));
|
|
30944
30922
|
useEffect(() => {
|
|
30945
30923
|
let cancelled = false;
|
|
30946
30924
|
(async () => {
|
|
@@ -31029,11 +31007,11 @@ function ComboEditPage({ comboId }) {
|
|
|
31029
31007
|
setName(data.name ?? "");
|
|
31030
31008
|
setDesc(data.desc ?? "");
|
|
31031
31009
|
setEventId(data.eventId != null ? String(data.eventId) : "");
|
|
31032
|
-
|
|
31033
|
-
|
|
31034
|
-
|
|
31035
|
-
|
|
31036
|
-
|
|
31010
|
+
if (data.eventId != null) {
|
|
31011
|
+
const currency = await fetchEventDefaultCurrency(String(data.eventId));
|
|
31012
|
+
if (!cancelled) setDefaultCurrency(currency);
|
|
31013
|
+
}
|
|
31014
|
+
setPriceStr(data.price != null && Number.isFinite(Number(data.price)) ? String(data.price) : "");
|
|
31037
31015
|
setMinSelectableItems(Number(data.minSelectableItems) || 1);
|
|
31038
31016
|
setMaxSelectableItems(Number(data.maxSelectableItems) || 1);
|
|
31039
31017
|
setStatus(data.status ?? "active");
|
|
@@ -31084,10 +31062,6 @@ function ComboEditPage({ comboId }) {
|
|
|
31084
31062
|
create,
|
|
31085
31063
|
duplicateSourceId
|
|
31086
31064
|
]);
|
|
31087
|
-
/* @__PURE__ */ new Set([
|
|
31088
|
-
...fixedItems.map((i) => i.productId),
|
|
31089
|
-
...addonItems.map((i) => i.productId)
|
|
31090
|
-
]);
|
|
31091
31065
|
const availableForFixed = productOptions.filter((p) => !addonItems.some((i) => i.productId === p.value));
|
|
31092
31066
|
const availableForAddon = productOptions.filter((p) => !fixedItems.some((i) => i.productId === p.value));
|
|
31093
31067
|
const addFixed = /* @__PURE__ */ __name((val) => {
|
|
@@ -31128,16 +31102,16 @@ function ComboEditPage({ comboId }) {
|
|
|
31128
31102
|
]);
|
|
31129
31103
|
return;
|
|
31130
31104
|
}
|
|
31131
|
-
if (!
|
|
31105
|
+
if (!priceStr.trim()) {
|
|
31132
31106
|
setErrors([
|
|
31133
|
-
|
|
31107
|
+
`Price (${defaultCurrency}) is required`
|
|
31134
31108
|
]);
|
|
31135
31109
|
return;
|
|
31136
31110
|
}
|
|
31137
|
-
const
|
|
31138
|
-
if (!
|
|
31111
|
+
const price = Number(priceStr);
|
|
31112
|
+
if (!Number.isFinite(price) || price < 0) {
|
|
31139
31113
|
setErrors([
|
|
31140
|
-
|
|
31114
|
+
`Price (${defaultCurrency}) must be a valid non-negative number`
|
|
31141
31115
|
]);
|
|
31142
31116
|
return;
|
|
31143
31117
|
}
|
|
@@ -31185,8 +31159,8 @@ function ComboEditPage({ comboId }) {
|
|
|
31185
31159
|
name: trimmedName,
|
|
31186
31160
|
desc: desc || null,
|
|
31187
31161
|
eventId: Number(trimmedEventId),
|
|
31188
|
-
price
|
|
31189
|
-
currencyPrices:
|
|
31162
|
+
price,
|
|
31163
|
+
currencyPrices: null,
|
|
31190
31164
|
minSelectableItems,
|
|
31191
31165
|
maxSelectableItems,
|
|
31192
31166
|
status,
|
|
@@ -31353,21 +31327,19 @@ function ComboEditPage({ comboId }) {
|
|
|
31353
31327
|
className: "text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2"
|
|
31354
31328
|
}, "Pricing & add-on selection"), /* @__PURE__ */ React.createElement("div", {
|
|
31355
31329
|
className: "min-w-0 overflow-hidden border border-gray-200 rounded-lg p-4 bg-gray-50/50 space-y-4"
|
|
31356
|
-
},
|
|
31357
|
-
|
|
31358
|
-
|
|
31359
|
-
|
|
31360
|
-
|
|
31361
|
-
|
|
31362
|
-
|
|
31363
|
-
|
|
31364
|
-
|
|
31365
|
-
|
|
31366
|
-
|
|
31367
|
-
|
|
31368
|
-
}
|
|
31369
|
-
className: "text-sm text-gray-500"
|
|
31370
|
-
}, "Select an event to configure combo pricing currencies."), /* @__PURE__ */ React.createElement("div", {
|
|
31330
|
+
}, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
|
|
31331
|
+
className: "block text-xs font-medium text-gray-600 mb-1"
|
|
31332
|
+
}, "Price (", defaultCurrency, ") *"), /* @__PURE__ */ React.createElement("input", {
|
|
31333
|
+
type: "number",
|
|
31334
|
+
min: 0,
|
|
31335
|
+
step: "0.01",
|
|
31336
|
+
value: priceStr,
|
|
31337
|
+
onChange: /* @__PURE__ */ __name((e) => setPriceStr(e.target.value), "onChange"),
|
|
31338
|
+
disabled: !eventId,
|
|
31339
|
+
className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm disabled:bg-gray-100"
|
|
31340
|
+
}), /* @__PURE__ */ React.createElement("p", {
|
|
31341
|
+
className: "text-xs text-gray-400 mt-1"
|
|
31342
|
+
}, eventId ? "Other currencies use the event\u2019s supported currencies and exchange rates." : "Select an event to set the combo price.")), /* @__PURE__ */ React.createElement("div", {
|
|
31371
31343
|
className: "grid grid-cols-2 gap-4"
|
|
31372
31344
|
}, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
|
|
31373
31345
|
className: "block text-xs font-medium text-gray-600 mb-1"
|
|
@@ -31425,26 +31397,17 @@ function ComboEditPage({ comboId }) {
|
|
|
31425
31397
|
sidebar: /* @__PURE__ */ React.createElement(React.Fragment, null)
|
|
31426
31398
|
}));
|
|
31427
31399
|
}
|
|
31428
|
-
var isCreate5
|
|
31400
|
+
var isCreate5;
|
|
31429
31401
|
var init_ComboEditPage = __esm({
|
|
31430
31402
|
"src/admin/pages/ComboEditPage.tsx"() {
|
|
31431
31403
|
"use client";
|
|
31432
31404
|
init_admin_list_return_url();
|
|
31433
31405
|
init_DetailPageLayout();
|
|
31434
31406
|
init_DetailPageHeader();
|
|
31435
|
-
init_EventManagementFields();
|
|
31436
|
-
init_currency_options();
|
|
31437
|
-
init_currency_prices();
|
|
31438
31407
|
init_inventory_validation();
|
|
31439
31408
|
isCreate5 = /* @__PURE__ */ __name((id) => id === "create", "isCreate");
|
|
31440
31409
|
__name(formatDateTimeLocal, "formatDateTimeLocal");
|
|
31441
|
-
|
|
31442
|
-
defaultCurrency: "INR",
|
|
31443
|
-
supportedCurrencies: [
|
|
31444
|
-
"INR"
|
|
31445
|
-
]
|
|
31446
|
-
};
|
|
31447
|
-
__name(fetchEventPricing, "fetchEventPricing");
|
|
31410
|
+
__name(fetchEventDefaultCurrency, "fetchEventDefaultCurrency");
|
|
31448
31411
|
__name(validateComboProductInventory, "validateComboProductInventory");
|
|
31449
31412
|
__name(ComboEditPage, "ComboEditPage");
|
|
31450
31413
|
}
|