@infuro/cms-core 1.0.36 → 1.0.38
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-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-JS2W7XZ4.js → chunk-PYUXVU75.js} +243 -23
- package/dist/{chunk-FG6I64HP.js → chunk-SBRBR3VO.js} +1 -1
- package/dist/{chunk-LI2IXVAY.js → chunk-UXMDSNIG.js} +1 -1
- package/dist/{chunk-2AEVFOQP.cjs → chunk-VJDUKAJ3.cjs} +310 -90
- 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 +1 -1
package/dist/admin.cjs
CHANGED
|
@@ -454,7 +454,8 @@ var init_vendor_scope = __esm({
|
|
|
454
454
|
"vendor_customers",
|
|
455
455
|
"discounts",
|
|
456
456
|
"events",
|
|
457
|
-
"refund_policies"
|
|
457
|
+
"refund_policies",
|
|
458
|
+
"combos"
|
|
458
459
|
]);
|
|
459
460
|
VENDOR_STORE_RBAC_ENTITIES = /* @__PURE__ */ new Set([
|
|
460
461
|
...VENDOR_SCOPED_STORE_ENTITIES,
|
|
@@ -491,7 +492,7 @@ var init_admin_config_context = __esm({
|
|
|
491
492
|
var CMS_VERSION;
|
|
492
493
|
var init_cms_version = __esm({
|
|
493
494
|
"src/lib/cms-version.ts"() {
|
|
494
|
-
CMS_VERSION = "1.0.
|
|
495
|
+
CMS_VERSION = "1.0.38" ;
|
|
495
496
|
}
|
|
496
497
|
});
|
|
497
498
|
function useCatalogCategories(enabled = true) {
|
|
@@ -14303,203 +14304,149 @@ var init_email_recipients = __esm({
|
|
|
14303
14304
|
__name(serializeEmailRecipients, "serializeEmailRecipients");
|
|
14304
14305
|
}
|
|
14305
14306
|
});
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
|
|
14310
|
-
|
|
14311
|
-
|
|
14312
|
-
|
|
14313
|
-
|
|
14314
|
-
|
|
14315
|
-
|
|
14316
|
-
|
|
14317
|
-
|
|
14318
|
-
|
|
14319
|
-
|
|
14320
|
-
|
|
14321
|
-
|
|
14322
|
-
eventName: "Event title",
|
|
14323
|
-
eventSlug: "Event URL slug",
|
|
14324
|
-
venue: "Venue name",
|
|
14325
|
-
startDate: "Formatted event start",
|
|
14326
|
-
orderNumber: "Order number",
|
|
14327
|
-
orderTotal: "Order total amount",
|
|
14328
|
-
currency: "Currency code",
|
|
14329
|
-
customerName: "Buyer name",
|
|
14330
|
-
customerEmail: "Buyer email",
|
|
14331
|
-
customerPhone: "Buyer phone",
|
|
14332
|
-
productNames: "Comma-separated ticket names",
|
|
14333
|
-
trackUrl: "Public order tracking link (QR destination)",
|
|
14334
|
-
qrImageUrl: 'QR code image URL for email (use in <img src="\u2026">)',
|
|
14335
|
-
orderDetails: "Plain-text order summary with line items",
|
|
14336
|
-
orderDetailsHtml: "HTML order summary block",
|
|
14337
|
-
trackUrlWhatsApp: "Same as trackUrl \u2014 include in Meta template as last param"
|
|
14338
|
-
};
|
|
14307
|
+
function getEmailTriggerDefaults(triggerKey) {
|
|
14308
|
+
return EMAIL_TRIGGER_DEFAULTS[triggerKey] ?? {
|
|
14309
|
+
subject: "",
|
|
14310
|
+
body: "",
|
|
14311
|
+
subjectPlaceholder: "e.g. Order update for {{orderNumber}}",
|
|
14312
|
+
bodyPlaceholder: "Hi {{customerName}},\n\nYour order {{orderNumber}} has been updated."
|
|
14313
|
+
};
|
|
14314
|
+
}
|
|
14315
|
+
async function jsonFetch(url, init) {
|
|
14316
|
+
const res = await fetch(url, {
|
|
14317
|
+
credentials: "include",
|
|
14318
|
+
...init
|
|
14319
|
+
});
|
|
14320
|
+
const data = await res.json().catch(() => ({}));
|
|
14321
|
+
if (!res.ok) {
|
|
14322
|
+
throw new Error(data?.error || `Request failed (${res.status})`);
|
|
14339
14323
|
}
|
|
14340
|
-
|
|
14341
|
-
function mergeChannelUpdate(allItems, channel, updatedChannelItems) {
|
|
14342
|
-
return [
|
|
14343
|
-
...allItems.filter((item) => item.channel !== channel),
|
|
14344
|
-
...updatedChannelItems
|
|
14345
|
-
];
|
|
14324
|
+
return data;
|
|
14346
14325
|
}
|
|
14347
|
-
function
|
|
14348
|
-
const [
|
|
14349
|
-
const
|
|
14350
|
-
const
|
|
14351
|
-
|
|
14326
|
+
function useOrderNotificationTriggerBindings(channel) {
|
|
14327
|
+
const [triggers, setTriggers] = React26.useState([]);
|
|
14328
|
+
const [bindings, setBindings] = React26.useState([]);
|
|
14329
|
+
const [loading, setLoading] = React26.useState(true);
|
|
14330
|
+
const load = React26.useCallback(async () => {
|
|
14331
|
+
setLoading(true);
|
|
14332
|
+
try {
|
|
14333
|
+
const [triggersRes, bindingsRes] = await Promise.all([
|
|
14334
|
+
jsonFetch("/api/order_notification_triggers?limit=100&sortField=sortOrder&sortOrder=asc"),
|
|
14335
|
+
// Bindings for all channels are fetched and filtered client-side rather
|
|
14336
|
+
// than relying on server-side ?channel= filtering, since that's not
|
|
14337
|
+
// confirmed to be supported by the generic CRUD layer.
|
|
14338
|
+
jsonFetch("/api/order_notification_bindings?limit=200")
|
|
14339
|
+
]);
|
|
14340
|
+
setTriggers((triggersRes.data ?? []).filter((t) => t.enabled));
|
|
14341
|
+
setBindings((bindingsRes.data ?? []).filter((b) => b.channel === channel));
|
|
14342
|
+
} catch (e) {
|
|
14343
|
+
sonner.toast.error(e instanceof Error ? e.message : "Failed to load trigger bindings");
|
|
14344
|
+
} finally {
|
|
14345
|
+
setLoading(false);
|
|
14346
|
+
}
|
|
14347
|
+
}, [
|
|
14352
14348
|
channel
|
|
14353
14349
|
]);
|
|
14354
|
-
|
|
14355
|
-
|
|
14356
|
-
return [
|
|
14357
|
-
...EVENT_ORDER_WHATSAPP_PARAM_KEYS
|
|
14358
|
-
];
|
|
14350
|
+
React26.useEffect(() => {
|
|
14351
|
+
void load();
|
|
14359
14352
|
}, [
|
|
14353
|
+
load
|
|
14354
|
+
]);
|
|
14355
|
+
const rebind = React26.useCallback(async (triggerKey, messageTemplateId, templateLabel) => {
|
|
14356
|
+
const existing = bindings.find((b) => b.triggerKey === triggerKey);
|
|
14357
|
+
try {
|
|
14358
|
+
if (existing) {
|
|
14359
|
+
const updated = await jsonFetch(`/api/order_notification_bindings/${existing.id}`, {
|
|
14360
|
+
method: "PUT",
|
|
14361
|
+
headers: {
|
|
14362
|
+
"Content-Type": "application/json"
|
|
14363
|
+
},
|
|
14364
|
+
body: JSON.stringify({
|
|
14365
|
+
messageTemplateId
|
|
14366
|
+
})
|
|
14367
|
+
});
|
|
14368
|
+
setBindings((prev) => prev.map((b) => b.id === updated.id ? updated : b));
|
|
14369
|
+
} else {
|
|
14370
|
+
const created = await jsonFetch("/api/order_notification_bindings", {
|
|
14371
|
+
method: "POST",
|
|
14372
|
+
headers: {
|
|
14373
|
+
"Content-Type": "application/json"
|
|
14374
|
+
},
|
|
14375
|
+
body: JSON.stringify({
|
|
14376
|
+
triggerKey,
|
|
14377
|
+
channel,
|
|
14378
|
+
messageTemplateId,
|
|
14379
|
+
enabled: true
|
|
14380
|
+
})
|
|
14381
|
+
});
|
|
14382
|
+
setBindings((prev) => [
|
|
14383
|
+
...prev,
|
|
14384
|
+
created
|
|
14385
|
+
]);
|
|
14386
|
+
}
|
|
14387
|
+
sonner.toast.success(`"${triggerKey}" bound to "${templateLabel}"`);
|
|
14388
|
+
return true;
|
|
14389
|
+
} catch (e) {
|
|
14390
|
+
sonner.toast.error(e instanceof Error ? e.message : "Failed to update binding");
|
|
14391
|
+
return false;
|
|
14392
|
+
}
|
|
14393
|
+
}, [
|
|
14394
|
+
bindings,
|
|
14360
14395
|
channel
|
|
14361
14396
|
]);
|
|
14362
|
-
|
|
14363
|
-
|
|
14364
|
-
|
|
14365
|
-
|
|
14366
|
-
|
|
14367
|
-
|
|
14368
|
-
|
|
14369
|
-
}, "patchVisible");
|
|
14370
|
-
if (!visibleItems.length) return null;
|
|
14371
|
-
return /* @__PURE__ */ React.createElement("div", {
|
|
14372
|
-
className: "space-y-3 rounded-md border border-gray-200 dark:border-gray-600 p-3"
|
|
14373
|
-
}, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", {
|
|
14374
|
-
className: "text-sm font-medium text-gray-900 dark:text-white"
|
|
14375
|
-
}, copy.title), /* @__PURE__ */ React.createElement("p", {
|
|
14376
|
-
className: "text-xs text-gray-500 dark:text-gray-400 mt-1"
|
|
14377
|
-
}, copy.description)), /* @__PURE__ */ React.createElement("div", {
|
|
14378
|
-
className: "rounded-md bg-gray-50 dark:bg-gray-800/50 p-2"
|
|
14379
|
-
}, /* @__PURE__ */ React.createElement("p", {
|
|
14380
|
-
className: "text-[11px] font-medium text-gray-600 dark:text-gray-400 mb-1"
|
|
14381
|
-
}, "Variables"), /* @__PURE__ */ React.createElement("div", {
|
|
14382
|
-
className: "flex flex-wrap gap-1"
|
|
14383
|
-
}, (displayVariables.length ? displayVariables : templateVariables).map((v) => /* @__PURE__ */ React.createElement("span", {
|
|
14384
|
-
key: v,
|
|
14385
|
-
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",
|
|
14386
|
-
title: EVENT_ORDER_TEMPLATE_VARIABLE_HINTS[v] ?? v
|
|
14387
|
-
}, `{{${v}}}`)))), visibleItems.map((t) => /* @__PURE__ */ React.createElement("div", {
|
|
14388
|
-
key: t.channel,
|
|
14389
|
-
className: "space-y-3 border-t border-gray-100 pt-3 dark:border-gray-700"
|
|
14390
|
-
}, /* @__PURE__ */ React.createElement("div", {
|
|
14391
|
-
className: "flex flex-wrap items-center justify-between gap-2"
|
|
14392
|
-
}, /* @__PURE__ */ React.createElement("div", {
|
|
14393
|
-
className: "text-sm font-medium"
|
|
14394
|
-
}, t.name), /* @__PURE__ */ React.createElement("div", {
|
|
14395
|
-
className: "flex items-center gap-2"
|
|
14396
|
-
}, /* @__PURE__ */ React.createElement(Label3, {
|
|
14397
|
-
htmlFor: `${settingsGroup}-evt-${t.channel}-enabled`,
|
|
14398
|
-
className: "text-xs text-gray-600"
|
|
14399
|
-
}, "Enabled"), /* @__PURE__ */ React.createElement(Switch, {
|
|
14400
|
-
id: `${settingsGroup}-evt-${t.channel}-enabled`,
|
|
14401
|
-
checked: t.enabled,
|
|
14402
|
-
onCheckedChange: /* @__PURE__ */ __name((v) => patchVisible(visibleItems.map((x) => x.channel === t.channel ? {
|
|
14403
|
-
...x,
|
|
14404
|
-
enabled: v
|
|
14405
|
-
} : x)), "onCheckedChange")
|
|
14406
|
-
}))), t.channel === "email" ? /* @__PURE__ */ React.createElement("div", {
|
|
14407
|
-
className: "space-y-1"
|
|
14408
|
-
}, /* @__PURE__ */ React.createElement(Label3, {
|
|
14409
|
-
className: "text-xs"
|
|
14410
|
-
}, "Subject"), /* @__PURE__ */ React.createElement(Input, {
|
|
14411
|
-
value: t.subject,
|
|
14412
|
-
onChange: /* @__PURE__ */ __name((e) => patchVisible(visibleItems.map((x) => x.channel === t.channel ? {
|
|
14413
|
-
...x,
|
|
14414
|
-
subject: e.target.value
|
|
14415
|
-
} : x)), "onChange"),
|
|
14416
|
-
className: "h-8 text-sm"
|
|
14417
|
-
})) : null, /* @__PURE__ */ React.createElement("div", {
|
|
14418
|
-
className: "space-y-1"
|
|
14419
|
-
}, /* @__PURE__ */ React.createElement(Label3, {
|
|
14420
|
-
className: "text-xs"
|
|
14421
|
-
}, t.channel === "email" ? "Body (HTML)" : "Body (reference only)"), /* @__PURE__ */ React.createElement(Textarea, {
|
|
14422
|
-
value: t.body,
|
|
14423
|
-
onChange: /* @__PURE__ */ __name((e) => patchVisible(visibleItems.map((x) => x.channel === t.channel ? {
|
|
14424
|
-
...x,
|
|
14425
|
-
body: e.target.value
|
|
14426
|
-
} : x)), "onChange"),
|
|
14427
|
-
rows: t.channel === "email" ? 8 : 3,
|
|
14428
|
-
className: "text-sm font-mono"
|
|
14429
|
-
})), t.channel === "whatsapp" ? /* @__PURE__ */ React.createElement("div", {
|
|
14430
|
-
className: "space-y-1"
|
|
14431
|
-
}, /* @__PURE__ */ React.createElement(Label3, {
|
|
14432
|
-
className: "text-xs"
|
|
14433
|
-
}, "Meta approved template name"), /* @__PURE__ */ React.createElement(Input, {
|
|
14434
|
-
value: t.externalTemplateRef,
|
|
14435
|
-
onChange: /* @__PURE__ */ __name((e) => patchVisible(visibleItems.map((x) => x.channel === t.channel ? {
|
|
14436
|
-
...x,
|
|
14437
|
-
externalTemplateRef: e.target.value
|
|
14438
|
-
} : x)), "onChange"),
|
|
14439
|
-
placeholder: "event_order_placed",
|
|
14440
|
-
className: "h-8 text-sm"
|
|
14441
|
-
})) : null)));
|
|
14442
|
-
}
|
|
14443
|
-
async function fetchEventOrderTemplateRows() {
|
|
14444
|
-
const res = await fetch("/api/message-templates/event-order");
|
|
14445
|
-
if (!res.ok) return [];
|
|
14446
|
-
const data = await res.json();
|
|
14447
|
-
return data.items ?? [];
|
|
14448
|
-
}
|
|
14449
|
-
async function saveEventOrderTemplateRows(items) {
|
|
14450
|
-
const res = await fetch("/api/message-templates/event-order", {
|
|
14451
|
-
method: "PUT",
|
|
14452
|
-
headers: {
|
|
14453
|
-
"Content-Type": "application/json"
|
|
14454
|
-
},
|
|
14455
|
-
body: JSON.stringify({
|
|
14456
|
-
items: items.map((t) => ({
|
|
14457
|
-
channel: t.channel,
|
|
14458
|
-
subject: t.subject,
|
|
14459
|
-
body: t.body,
|
|
14460
|
-
externalTemplateRef: t.externalTemplateRef,
|
|
14461
|
-
enabled: t.enabled
|
|
14462
|
-
}))
|
|
14463
|
-
})
|
|
14464
|
-
});
|
|
14465
|
-
return res.ok;
|
|
14397
|
+
return {
|
|
14398
|
+
triggers,
|
|
14399
|
+
bindings,
|
|
14400
|
+
loading,
|
|
14401
|
+
reload: load,
|
|
14402
|
+
rebind
|
|
14403
|
+
};
|
|
14466
14404
|
}
|
|
14467
|
-
var
|
|
14468
|
-
var
|
|
14469
|
-
"src/admin/components/
|
|
14405
|
+
var KNOWN_VARS, EMAIL_TRIGGER_DEFAULTS;
|
|
14406
|
+
var init_order_notification_bindings_shared = __esm({
|
|
14407
|
+
"src/admin/components/order-notification-bindings-shared.ts"() {
|
|
14470
14408
|
"use client";
|
|
14471
|
-
|
|
14472
|
-
|
|
14473
|
-
init_textarea();
|
|
14474
|
-
init_switch();
|
|
14475
|
-
init_event_order_defaults();
|
|
14476
|
-
CHANNEL_COPY = {
|
|
14477
|
-
email: {
|
|
14478
|
-
title: "Email template",
|
|
14479
|
-
description: "HTML email with order details and QR code. Use {{qrImageUrl}} for the QR image and {{orderDetailsHtml}} for line items."
|
|
14480
|
-
},
|
|
14481
|
-
whatsapp: {
|
|
14482
|
-
title: "WhatsApp template",
|
|
14483
|
-
description: "Must match a Meta-approved template. Body params are sent in order: " + EVENT_ORDER_WHATSAPP_PARAM_KEYS.join(", ") + "."
|
|
14484
|
-
}
|
|
14485
|
-
};
|
|
14486
|
-
EMAIL_VARIABLES = [
|
|
14487
|
-
"customerName",
|
|
14488
|
-
"eventName",
|
|
14409
|
+
KNOWN_VARS = [
|
|
14410
|
+
"orderId",
|
|
14489
14411
|
"orderNumber",
|
|
14490
|
-
"
|
|
14412
|
+
"customerName",
|
|
14413
|
+
"customerPhone",
|
|
14414
|
+
"total",
|
|
14491
14415
|
"currency",
|
|
14492
|
-
"
|
|
14493
|
-
"
|
|
14494
|
-
"venue",
|
|
14495
|
-
"startDate",
|
|
14496
|
-
"qrImageUrl",
|
|
14497
|
-
"trackUrl"
|
|
14416
|
+
"vendorId",
|
|
14417
|
+
"refundAmount"
|
|
14498
14418
|
];
|
|
14499
|
-
|
|
14500
|
-
|
|
14501
|
-
|
|
14502
|
-
|
|
14419
|
+
EMAIL_TRIGGER_DEFAULTS = {
|
|
14420
|
+
order_placed: {
|
|
14421
|
+
subject: "Your order {{orderNumber}} is confirmed",
|
|
14422
|
+
subjectPlaceholder: "e.g. Your order {{orderNumber}} is confirmed",
|
|
14423
|
+
body: `Hi {{customerName}},
|
|
14424
|
+
|
|
14425
|
+
Thank you for your order! Your order {{orderNumber}} for {{total}} {{currency}} has been confirmed.
|
|
14426
|
+
|
|
14427
|
+
We'll keep you updated on any changes.
|
|
14428
|
+
|
|
14429
|
+
Thank you!`,
|
|
14430
|
+
bodyPlaceholder: "Hi {{customerName}},\n\nYour order {{orderNumber}} for {{total}} {{currency}} is confirmed.\n\nThank you!"
|
|
14431
|
+
},
|
|
14432
|
+
order_cancelled: {
|
|
14433
|
+
subject: "Your order {{orderNumber}} has been cancelled",
|
|
14434
|
+
subjectPlaceholder: "e.g. Your order {{orderNumber}} has been cancelled",
|
|
14435
|
+
body: `Hi {{customerName}},
|
|
14436
|
+
|
|
14437
|
+
Your order {{orderNumber}} for {{total}} {{currency}} has been cancelled.
|
|
14438
|
+
|
|
14439
|
+
If a refund is due, you will receive {{refundAmount}} {{currency}} back to your original payment method.
|
|
14440
|
+
|
|
14441
|
+
If you have any questions, please contact us.
|
|
14442
|
+
|
|
14443
|
+
Thank you!`,
|
|
14444
|
+
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."
|
|
14445
|
+
}
|
|
14446
|
+
};
|
|
14447
|
+
__name(getEmailTriggerDefaults, "getEmailTriggerDefaults");
|
|
14448
|
+
__name(jsonFetch, "jsonFetch");
|
|
14449
|
+
__name(useOrderNotificationTriggerBindings, "useOrderNotificationTriggerBindings");
|
|
14503
14450
|
}
|
|
14504
14451
|
});
|
|
14505
14452
|
function varCount(t) {
|
|
@@ -14545,22 +14492,10 @@ function contextBeforePlaceholder(bodyText, n) {
|
|
|
14545
14492
|
if (idx === -1) return "";
|
|
14546
14493
|
return bodyText.slice(Math.max(0, idx - 25), idx);
|
|
14547
14494
|
}
|
|
14548
|
-
async function jsonFetch(url, init) {
|
|
14549
|
-
const res = await fetch(url, {
|
|
14550
|
-
credentials: "include",
|
|
14551
|
-
...init
|
|
14552
|
-
});
|
|
14553
|
-
const data = await res.json().catch(() => ({}));
|
|
14554
|
-
if (!res.ok) {
|
|
14555
|
-
throw new Error(data?.error || `Request failed (${res.status})`);
|
|
14556
|
-
}
|
|
14557
|
-
return data;
|
|
14558
|
-
}
|
|
14559
14495
|
function WhatsAppTriggerBindings() {
|
|
14560
|
-
const
|
|
14561
|
-
const [bindings, setBindings] = React26.useState([]);
|
|
14496
|
+
const { triggers, bindings, loading, reload, rebind } = useOrderNotificationTriggerBindings("whatsapp");
|
|
14562
14497
|
const [templates, setTemplates] = React26.useState([]);
|
|
14563
|
-
const [
|
|
14498
|
+
const [templatesLoaded, setTemplatesLoaded] = React26.useState(false);
|
|
14564
14499
|
const [syncing, setSyncing] = React26.useState(false);
|
|
14565
14500
|
const [editingTemplateId, setEditingTemplateId] = React26.useState(null);
|
|
14566
14501
|
const templatesById = React26.useMemo(() => {
|
|
@@ -14570,27 +14505,20 @@ function WhatsAppTriggerBindings() {
|
|
|
14570
14505
|
}, [
|
|
14571
14506
|
templates
|
|
14572
14507
|
]);
|
|
14573
|
-
const
|
|
14574
|
-
setLoading(true);
|
|
14508
|
+
const loadTemplates = /* @__PURE__ */ __name(async () => {
|
|
14575
14509
|
try {
|
|
14576
|
-
const
|
|
14577
|
-
|
|
14578
|
-
jsonFetch("/api/whatsapp_trigger_bindings?limit=100"),
|
|
14579
|
-
jsonFetch("/api/message-templates/whatsapp")
|
|
14580
|
-
]);
|
|
14581
|
-
setTriggers((triggersRes.data ?? []).filter((t) => t.enabled));
|
|
14582
|
-
setBindings(bindingsRes.data ?? []);
|
|
14583
|
-
setTemplates(templatesRes.items ?? []);
|
|
14510
|
+
const res = await jsonFetch("/api/message-templates/whatsapp");
|
|
14511
|
+
setTemplates(res.items ?? []);
|
|
14584
14512
|
} catch (e) {
|
|
14585
|
-
sonner.toast.error(e instanceof Error ? e.message : "Failed to load WhatsApp
|
|
14513
|
+
sonner.toast.error(e instanceof Error ? e.message : "Failed to load WhatsApp templates");
|
|
14586
14514
|
} finally {
|
|
14587
|
-
|
|
14515
|
+
setTemplatesLoaded(true);
|
|
14588
14516
|
}
|
|
14589
|
-
},
|
|
14590
|
-
React26.
|
|
14591
|
-
void
|
|
14517
|
+
}, "loadTemplates");
|
|
14518
|
+
React26.useMemo(() => {
|
|
14519
|
+
if (!templatesLoaded) void loadTemplates();
|
|
14592
14520
|
}, [
|
|
14593
|
-
|
|
14521
|
+
templatesLoaded
|
|
14594
14522
|
]);
|
|
14595
14523
|
const handleSync = /* @__PURE__ */ __name(async () => {
|
|
14596
14524
|
setSyncing(true);
|
|
@@ -14598,7 +14526,10 @@ function WhatsAppTriggerBindings() {
|
|
|
14598
14526
|
await jsonFetch("/api/message-templates/whatsapp/sync", {
|
|
14599
14527
|
method: "POST"
|
|
14600
14528
|
});
|
|
14601
|
-
await
|
|
14529
|
+
await Promise.all([
|
|
14530
|
+
loadTemplates(),
|
|
14531
|
+
reload()
|
|
14532
|
+
]);
|
|
14602
14533
|
sonner.toast.success("Synced templates from Meta");
|
|
14603
14534
|
} catch (e) {
|
|
14604
14535
|
sonner.toast.error(e instanceof Error ? e.message : "Sync failed");
|
|
@@ -14607,40 +14538,8 @@ function WhatsAppTriggerBindings() {
|
|
|
14607
14538
|
}
|
|
14608
14539
|
}, "handleSync");
|
|
14609
14540
|
const handleRebind = /* @__PURE__ */ __name(async (triggerKey, messageTemplateId) => {
|
|
14610
|
-
const
|
|
14611
|
-
|
|
14612
|
-
if (existing) {
|
|
14613
|
-
const updated = await jsonFetch(`/api/whatsapp_trigger_bindings/${existing.id}`, {
|
|
14614
|
-
method: "PUT",
|
|
14615
|
-
headers: {
|
|
14616
|
-
"Content-Type": "application/json"
|
|
14617
|
-
},
|
|
14618
|
-
body: JSON.stringify({
|
|
14619
|
-
messageTemplateId
|
|
14620
|
-
})
|
|
14621
|
-
});
|
|
14622
|
-
setBindings((prev) => prev.map((b) => b.id === updated.id ? updated : b));
|
|
14623
|
-
} else {
|
|
14624
|
-
const created = await jsonFetch("/api/whatsapp_trigger_bindings", {
|
|
14625
|
-
method: "POST",
|
|
14626
|
-
headers: {
|
|
14627
|
-
"Content-Type": "application/json"
|
|
14628
|
-
},
|
|
14629
|
-
body: JSON.stringify({
|
|
14630
|
-
triggerKey,
|
|
14631
|
-
messageTemplateId,
|
|
14632
|
-
enabled: true
|
|
14633
|
-
})
|
|
14634
|
-
});
|
|
14635
|
-
setBindings((prev) => [
|
|
14636
|
-
...prev,
|
|
14637
|
-
created
|
|
14638
|
-
]);
|
|
14639
|
-
}
|
|
14640
|
-
sonner.toast.success(`"${triggerKey}" bound to "${templatesById.get(messageTemplateId)?.name ?? messageTemplateId}"`);
|
|
14641
|
-
} catch (e) {
|
|
14642
|
-
sonner.toast.error(e instanceof Error ? e.message : "Failed to update binding");
|
|
14643
|
-
}
|
|
14541
|
+
const label = templatesById.get(messageTemplateId)?.name ?? String(messageTemplateId);
|
|
14542
|
+
await rebind(triggerKey, messageTemplateId, label);
|
|
14644
14543
|
}, "handleRebind");
|
|
14645
14544
|
const handleSaveParamOrder = /* @__PURE__ */ __name(async (templateId, paramOrder) => {
|
|
14646
14545
|
try {
|
|
@@ -14660,7 +14559,7 @@ function WhatsAppTriggerBindings() {
|
|
|
14660
14559
|
sonner.toast.error(e instanceof Error ? e.message : "Failed to save param order");
|
|
14661
14560
|
}
|
|
14662
14561
|
}, "handleSaveParamOrder");
|
|
14663
|
-
if (loading) {
|
|
14562
|
+
if (loading || !templatesLoaded) {
|
|
14664
14563
|
return /* @__PURE__ */ React.createElement("div", {
|
|
14665
14564
|
className: "flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400"
|
|
14666
14565
|
}, /* @__PURE__ */ React.createElement(LucideIcons.Loader2, {
|
|
@@ -14675,7 +14574,7 @@ function WhatsAppTriggerBindings() {
|
|
|
14675
14574
|
className: "text-sm font-medium text-gray-900 dark:text-white"
|
|
14676
14575
|
}, "Order trigger templates"), /* @__PURE__ */ React.createElement("p", {
|
|
14677
14576
|
className: "text-xs text-gray-500 dark:text-gray-400 mt-1"
|
|
14678
|
-
}, "When
|
|
14577
|
+
}, "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, {
|
|
14679
14578
|
type: "button",
|
|
14680
14579
|
variant: "outline",
|
|
14681
14580
|
size: "sm",
|
|
@@ -14852,24 +14751,15 @@ function ParamOrderEditor({ template, onCancel, onSave }) {
|
|
|
14852
14751
|
onClick: /* @__PURE__ */ __name(() => onSave(values), "onClick")
|
|
14853
14752
|
}, "Save"))));
|
|
14854
14753
|
}
|
|
14855
|
-
var
|
|
14754
|
+
var CONTEXT_HINTS;
|
|
14856
14755
|
var init_whatsappTriggerBindingsPage = __esm({
|
|
14857
14756
|
"src/admin/pages/whatsappTriggerBindingsPage.tsx"() {
|
|
14858
14757
|
"use client";
|
|
14859
14758
|
init_button();
|
|
14860
14759
|
init_select();
|
|
14760
|
+
init_order_notification_bindings_shared();
|
|
14861
14761
|
__name(varCount, "varCount");
|
|
14862
14762
|
__name(bodyExample, "bodyExample");
|
|
14863
|
-
KNOWN_VARS = [
|
|
14864
|
-
"orderId",
|
|
14865
|
-
"orderNumber",
|
|
14866
|
-
"customerName",
|
|
14867
|
-
"customerPhone",
|
|
14868
|
-
"total",
|
|
14869
|
-
"currency",
|
|
14870
|
-
"vendorId",
|
|
14871
|
-
"refundAmount"
|
|
14872
|
-
];
|
|
14873
14763
|
CONTEXT_HINTS = [
|
|
14874
14764
|
{
|
|
14875
14765
|
pattern: /refund/,
|
|
@@ -14908,42 +14798,280 @@ var init_whatsappTriggerBindingsPage = __esm({
|
|
|
14908
14798
|
__name(guessFromExampleShape, "guessFromExampleShape");
|
|
14909
14799
|
__name(guessKnownVar, "guessKnownVar");
|
|
14910
14800
|
__name(contextBeforePlaceholder, "contextBeforePlaceholder");
|
|
14911
|
-
__name(jsonFetch, "jsonFetch");
|
|
14912
14801
|
__name(WhatsAppTriggerBindings, "WhatsAppTriggerBindings");
|
|
14913
14802
|
__name(ParamOrderEditor, "ParamOrderEditor");
|
|
14914
14803
|
}
|
|
14915
14804
|
});
|
|
14916
|
-
function
|
|
14917
|
-
const
|
|
14918
|
-
const [
|
|
14919
|
-
const [
|
|
14805
|
+
function EmailTriggerBindings() {
|
|
14806
|
+
const { triggers, bindings, loading, rebind } = useOrderNotificationTriggerBindings("email");
|
|
14807
|
+
const [templates, setTemplates] = React26.useState([]);
|
|
14808
|
+
const [templatesLoaded, setTemplatesLoaded] = React26.useState(false);
|
|
14809
|
+
const templatesById = React26.useMemo(() => {
|
|
14810
|
+
const m = /* @__PURE__ */ new Map();
|
|
14811
|
+
for (const t of templates) m.set(t.id, t);
|
|
14812
|
+
return m;
|
|
14813
|
+
}, [
|
|
14814
|
+
templates
|
|
14815
|
+
]);
|
|
14920
14816
|
React26.useEffect(() => {
|
|
14921
14817
|
let cancelled = false;
|
|
14922
|
-
|
|
14923
|
-
|
|
14924
|
-
|
|
14925
|
-
|
|
14926
|
-
|
|
14927
|
-
|
|
14818
|
+
(async () => {
|
|
14819
|
+
try {
|
|
14820
|
+
const res = await jsonFetch("/api/message-templates/email");
|
|
14821
|
+
if (!cancelled) setTemplates(res.items ?? []);
|
|
14822
|
+
} catch (e) {
|
|
14823
|
+
if (!cancelled) sonner.toast.error(e instanceof Error ? e.message : "Failed to load email templates");
|
|
14824
|
+
} finally {
|
|
14825
|
+
if (!cancelled) setTemplatesLoaded(true);
|
|
14826
|
+
}
|
|
14827
|
+
})();
|
|
14928
14828
|
return () => {
|
|
14929
14829
|
cancelled = true;
|
|
14930
14830
|
};
|
|
14931
14831
|
}, []);
|
|
14932
|
-
const
|
|
14933
|
-
|
|
14934
|
-
|
|
14935
|
-
|
|
14936
|
-
|
|
14832
|
+
const handleTemplateSaved = /* @__PURE__ */ __name((template) => {
|
|
14833
|
+
setTemplates((prev) => {
|
|
14834
|
+
const exists = prev.some((t) => t.id === template.id);
|
|
14835
|
+
return exists ? prev.map((t) => t.id === template.id ? template : t) : [
|
|
14836
|
+
...prev,
|
|
14837
|
+
template
|
|
14838
|
+
].sort((a, b) => a.name.localeCompare(b.name));
|
|
14839
|
+
});
|
|
14840
|
+
}, "handleTemplateSaved");
|
|
14841
|
+
if (loading || !templatesLoaded) {
|
|
14842
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
14843
|
+
className: "flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400"
|
|
14844
|
+
}, /* @__PURE__ */ React.createElement(LucideIcons.Loader2, {
|
|
14845
|
+
className: "h-4 w-4 animate-spin"
|
|
14846
|
+
}), "Loading trigger bindings\u2026");
|
|
14847
|
+
}
|
|
14848
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
14849
|
+
className: "space-y-3 rounded-md border border-gray-200 dark:border-gray-600 p-3"
|
|
14850
|
+
}, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", {
|
|
14851
|
+
className: "text-sm font-medium text-gray-900 dark:text-white"
|
|
14852
|
+
}, "Order trigger emails"), /* @__PURE__ */ React.createElement("p", {
|
|
14853
|
+
className: "text-xs text-gray-500 dark:text-gray-400 mt-1"
|
|
14854
|
+
}, "Write the subject and body for each trigger below. Use", " ", /* @__PURE__ */ React.createElement("code", {
|
|
14855
|
+
className: "text-[11px]"
|
|
14856
|
+
}, "{{variable}}"), " tokens \u2014 click a variable chip to insert it at your cursor.")), /* @__PURE__ */ React.createElement("div", {
|
|
14857
|
+
className: "space-y-4"
|
|
14858
|
+
}, triggers.length === 0 ? /* @__PURE__ */ React.createElement("p", {
|
|
14859
|
+
className: "text-xs text-amber-600 dark:text-amber-400"
|
|
14860
|
+
}, "No order triggers found. Run database migrations to seed default triggers.") : null, triggers.map(({ triggerKey, label, description }) => {
|
|
14861
|
+
const binding = bindings.find((b) => b.triggerKey === triggerKey) ?? null;
|
|
14862
|
+
const template = binding?.messageTemplateId != null ? templatesById.get(binding.messageTemplateId) : void 0;
|
|
14863
|
+
return /* @__PURE__ */ React.createElement(TriggerEmailEditor, {
|
|
14864
|
+
key: triggerKey,
|
|
14865
|
+
triggerKey,
|
|
14866
|
+
label,
|
|
14867
|
+
description,
|
|
14868
|
+
template,
|
|
14869
|
+
onSaved: handleTemplateSaved,
|
|
14870
|
+
onBind: /* @__PURE__ */ __name((templateId, templateLabel) => rebind(triggerKey, templateId, templateLabel), "onBind")
|
|
14871
|
+
});
|
|
14872
|
+
})));
|
|
14873
|
+
}
|
|
14874
|
+
function TriggerEmailEditor({ triggerKey, label, description, template, onSaved, onBind }) {
|
|
14875
|
+
const defaults = React26.useMemo(() => getEmailTriggerDefaults(triggerKey), [
|
|
14876
|
+
triggerKey
|
|
14877
|
+
]);
|
|
14878
|
+
const [subject, setSubject] = React26.useState(() => template?.subject ?? defaults.subject);
|
|
14879
|
+
const [body, setBody] = React26.useState(() => template?.body ?? defaults.body);
|
|
14880
|
+
const [saving, setSaving] = React26.useState(false);
|
|
14881
|
+
const [focusedField, setFocusedField] = React26.useState("body");
|
|
14882
|
+
const subjectRef = React26.useRef(null);
|
|
14883
|
+
const bodyRef = React26.useRef(null);
|
|
14884
|
+
React26.useEffect(() => {
|
|
14885
|
+
if (template) {
|
|
14886
|
+
setSubject(template.subject ?? "");
|
|
14887
|
+
setBody(template.body ?? "");
|
|
14888
|
+
} else {
|
|
14889
|
+
setSubject(defaults.subject);
|
|
14890
|
+
setBody(defaults.body);
|
|
14937
14891
|
}
|
|
14938
|
-
|
|
14939
|
-
|
|
14940
|
-
|
|
14941
|
-
|
|
14892
|
+
}, [
|
|
14893
|
+
template,
|
|
14894
|
+
defaults.subject,
|
|
14895
|
+
defaults.body
|
|
14896
|
+
]);
|
|
14897
|
+
const savedSubject = template?.subject ?? defaults.subject;
|
|
14898
|
+
const savedBody = template?.body ?? defaults.body;
|
|
14899
|
+
const isDirty = subject !== savedSubject || body !== savedBody;
|
|
14900
|
+
function insertVariable(v) {
|
|
14901
|
+
const token = `{{${v}}}`;
|
|
14902
|
+
if (focusedField === "subject") {
|
|
14903
|
+
const el = subjectRef.current;
|
|
14904
|
+
const start = el?.selectionStart ?? subject.length;
|
|
14905
|
+
const end = el?.selectionEnd ?? subject.length;
|
|
14906
|
+
const next = subject.slice(0, start) + token + subject.slice(end);
|
|
14907
|
+
setSubject(next);
|
|
14908
|
+
requestAnimationFrame(() => {
|
|
14909
|
+
el?.focus();
|
|
14910
|
+
el?.setSelectionRange(start + token.length, start + token.length);
|
|
14911
|
+
});
|
|
14912
|
+
} else {
|
|
14913
|
+
const el = bodyRef.current;
|
|
14914
|
+
const start = el?.selectionStart ?? body.length;
|
|
14915
|
+
const end = el?.selectionEnd ?? body.length;
|
|
14916
|
+
const next = body.slice(0, start) + token + body.slice(end);
|
|
14917
|
+
setBody(next);
|
|
14918
|
+
requestAnimationFrame(() => {
|
|
14919
|
+
el?.focus();
|
|
14920
|
+
el?.setSelectionRange(start + token.length, start + token.length);
|
|
14921
|
+
});
|
|
14922
|
+
}
|
|
14923
|
+
}
|
|
14924
|
+
__name(insertVariable, "insertVariable");
|
|
14925
|
+
const canSave = body.trim() !== "";
|
|
14926
|
+
const handleSave = /* @__PURE__ */ __name(async () => {
|
|
14927
|
+
if (!canSave) return;
|
|
14928
|
+
setSaving(true);
|
|
14929
|
+
try {
|
|
14930
|
+
const templateName = template?.name ?? `${label} email`;
|
|
14931
|
+
let saved;
|
|
14932
|
+
if (template) {
|
|
14933
|
+
const res = await jsonFetch(`/api/message-templates/email/${template.id}`, {
|
|
14934
|
+
method: "PATCH",
|
|
14935
|
+
headers: {
|
|
14936
|
+
"Content-Type": "application/json"
|
|
14937
|
+
},
|
|
14938
|
+
body: JSON.stringify({
|
|
14939
|
+
name: templateName,
|
|
14940
|
+
subject,
|
|
14941
|
+
body
|
|
14942
|
+
})
|
|
14943
|
+
});
|
|
14944
|
+
saved = res.item;
|
|
14945
|
+
sonner.toast.success(`"${label}" email template saved`);
|
|
14946
|
+
} else {
|
|
14947
|
+
const res = await jsonFetch("/api/message-templates/email", {
|
|
14948
|
+
method: "POST",
|
|
14949
|
+
headers: {
|
|
14950
|
+
"Content-Type": "application/json"
|
|
14951
|
+
},
|
|
14952
|
+
body: JSON.stringify({
|
|
14953
|
+
name: templateName,
|
|
14954
|
+
subject,
|
|
14955
|
+
body
|
|
14956
|
+
})
|
|
14957
|
+
});
|
|
14958
|
+
saved = res.item;
|
|
14959
|
+
onSaved(saved);
|
|
14960
|
+
const bound = await onBind(saved.id, saved.name);
|
|
14961
|
+
if (!bound) {
|
|
14962
|
+
sonner.toast.success(`"${label}" email template created`);
|
|
14963
|
+
}
|
|
14942
14964
|
return;
|
|
14943
14965
|
}
|
|
14966
|
+
onSaved(saved);
|
|
14967
|
+
} catch (e) {
|
|
14968
|
+
sonner.toast.error(e instanceof Error ? e.message : "Failed to save template");
|
|
14969
|
+
} finally {
|
|
14970
|
+
setSaving(false);
|
|
14944
14971
|
}
|
|
14945
|
-
|
|
14946
|
-
|
|
14972
|
+
}, "handleSave");
|
|
14973
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
14974
|
+
className: "space-y-3 border-t border-gray-100 pt-4 first:border-t-0 first:pt-0 dark:border-gray-700"
|
|
14975
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
14976
|
+
className: "flex items-start justify-between gap-2"
|
|
14977
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
14978
|
+
className: "min-w-0"
|
|
14979
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
14980
|
+
className: "flex items-center gap-2"
|
|
14981
|
+
}, /* @__PURE__ */ React.createElement(LucideIcons.Mail, {
|
|
14982
|
+
className: "h-3.5 w-3.5 text-gray-400 shrink-0"
|
|
14983
|
+
}), /* @__PURE__ */ React.createElement("span", {
|
|
14984
|
+
className: "text-sm font-medium text-gray-900 dark:text-gray-100"
|
|
14985
|
+
}, label), /* @__PURE__ */ React.createElement("span", {
|
|
14986
|
+
className: "text-[11px] font-mono text-gray-400"
|
|
14987
|
+
}, triggerKey)), description ? /* @__PURE__ */ React.createElement("p", {
|
|
14988
|
+
className: "text-[11px] text-gray-500 dark:text-gray-400 mt-0.5 ml-5"
|
|
14989
|
+
}, description) : null), /* @__PURE__ */ React.createElement(Button, {
|
|
14990
|
+
type: "button",
|
|
14991
|
+
size: "sm",
|
|
14992
|
+
className: "h-8 gap-1.5 shrink-0",
|
|
14993
|
+
disabled: !canSave || saving || !isDirty && Boolean(template),
|
|
14994
|
+
onClick: /* @__PURE__ */ __name(() => void handleSave(), "onClick")
|
|
14995
|
+
}, saving ? /* @__PURE__ */ React.createElement(LucideIcons.Loader2, {
|
|
14996
|
+
className: "h-3.5 w-3.5 animate-spin"
|
|
14997
|
+
}) : /* @__PURE__ */ React.createElement(LucideIcons.Save, {
|
|
14998
|
+
className: "h-3.5 w-3.5"
|
|
14999
|
+
}), template ? "Save" : "Create")), !template ? /* @__PURE__ */ React.createElement("p", {
|
|
15000
|
+
className: "text-xs text-amber-600 dark:text-amber-400 ml-5"
|
|
15001
|
+
}, "No email saved for this trigger yet \u2014 review the draft below and click Create.") : null, /* @__PURE__ */ React.createElement("div", {
|
|
15002
|
+
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"
|
|
15003
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
15004
|
+
className: "space-y-1"
|
|
15005
|
+
}, /* @__PURE__ */ React.createElement(Label3, {
|
|
15006
|
+
className: "text-xs text-gray-600 dark:text-gray-400"
|
|
15007
|
+
}, "Subject"), /* @__PURE__ */ React.createElement(Input, {
|
|
15008
|
+
ref: subjectRef,
|
|
15009
|
+
value: subject,
|
|
15010
|
+
onChange: /* @__PURE__ */ __name((e) => setSubject(e.target.value), "onChange"),
|
|
15011
|
+
onFocus: /* @__PURE__ */ __name(() => setFocusedField("subject"), "onFocus"),
|
|
15012
|
+
placeholder: defaults.subjectPlaceholder,
|
|
15013
|
+
className: "h-8 text-sm bg-white dark:bg-gray-900"
|
|
15014
|
+
})), /* @__PURE__ */ React.createElement("div", {
|
|
15015
|
+
className: "space-y-1"
|
|
15016
|
+
}, /* @__PURE__ */ React.createElement(Label3, {
|
|
15017
|
+
className: "text-xs text-gray-600 dark:text-gray-400"
|
|
15018
|
+
}, "Body"), /* @__PURE__ */ React.createElement(Textarea, {
|
|
15019
|
+
ref: bodyRef,
|
|
15020
|
+
value: body,
|
|
15021
|
+
onChange: /* @__PURE__ */ __name((e) => setBody(e.target.value), "onChange"),
|
|
15022
|
+
onFocus: /* @__PURE__ */ __name(() => setFocusedField("body"), "onFocus"),
|
|
15023
|
+
rows: 6,
|
|
15024
|
+
placeholder: defaults.bodyPlaceholder,
|
|
15025
|
+
className: "text-sm font-mono bg-white dark:bg-gray-900 resize-y min-h-[120px]"
|
|
15026
|
+
})), /* @__PURE__ */ React.createElement("div", {
|
|
15027
|
+
className: "space-y-1.5"
|
|
15028
|
+
}, /* @__PURE__ */ React.createElement(Label3, {
|
|
15029
|
+
className: "text-xs text-gray-500 dark:text-gray-400"
|
|
15030
|
+
}, "Variables \u2014 inserts into ", focusedField === "subject" ? "subject" : "body", ":"), /* @__PURE__ */ React.createElement("div", {
|
|
15031
|
+
className: "flex flex-wrap gap-1.5"
|
|
15032
|
+
}, KNOWN_VARS.map((v) => /* @__PURE__ */ React.createElement("button", {
|
|
15033
|
+
key: v,
|
|
15034
|
+
type: "button",
|
|
15035
|
+
title: VAR_HINTS[v],
|
|
15036
|
+
onClick: /* @__PURE__ */ __name(() => insertVariable(v), "onClick"),
|
|
15037
|
+
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"
|
|
15038
|
+
}, `{{${v}}}`))), /* @__PURE__ */ React.createElement("p", {
|
|
15039
|
+
className: "text-[10px] text-gray-400 dark:text-gray-500"
|
|
15040
|
+
}, "Hover a chip to see what each variable contains at send time."))));
|
|
15041
|
+
}
|
|
15042
|
+
var VAR_HINTS;
|
|
15043
|
+
var init_emailTriggersBindingsPage = __esm({
|
|
15044
|
+
"src/admin/pages/emailTriggersBindingsPage.tsx"() {
|
|
15045
|
+
"use client";
|
|
15046
|
+
init_button();
|
|
15047
|
+
init_input();
|
|
15048
|
+
init_textarea();
|
|
15049
|
+
init_label();
|
|
15050
|
+
init_order_notification_bindings_shared();
|
|
15051
|
+
VAR_HINTS = {
|
|
15052
|
+
orderId: "Internal order ID",
|
|
15053
|
+
orderNumber: "Display order number",
|
|
15054
|
+
customerName: "Customer full name",
|
|
15055
|
+
customerPhone: "Customer phone",
|
|
15056
|
+
total: "Order total amount",
|
|
15057
|
+
currency: "Currency code (e.g. INR)",
|
|
15058
|
+
vendorId: "Vendor / store ID",
|
|
15059
|
+
refundAmount: "Refund amount (refund triggers)"
|
|
15060
|
+
};
|
|
15061
|
+
__name(EmailTriggerBindings, "EmailTriggerBindings");
|
|
15062
|
+
__name(TriggerEmailEditor, "TriggerEmailEditor");
|
|
15063
|
+
}
|
|
15064
|
+
});
|
|
15065
|
+
function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledChange, onSaveSettings, saving }) {
|
|
15066
|
+
const [tab, setTab] = React26.useState("email");
|
|
15067
|
+
const handleSaveEnabled = /* @__PURE__ */ __name(async () => {
|
|
15068
|
+
const ok = await onSaveSettings();
|
|
15069
|
+
if (!ok) {
|
|
15070
|
+
sonner.toast.error("Failed to save plugin settings");
|
|
15071
|
+
return;
|
|
15072
|
+
}
|
|
15073
|
+
sonner.toast.success("Settings saved");
|
|
15074
|
+
}, "handleSaveEnabled");
|
|
14947
15075
|
return /* @__PURE__ */ React.createElement("div", {
|
|
14948
15076
|
className: "space-y-5"
|
|
14949
15077
|
}, /* @__PURE__ */ React.createElement("div", {
|
|
@@ -14958,11 +15086,13 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
|
|
|
14958
15086
|
className: "font-medium text-gray-900 dark:text-white"
|
|
14959
15087
|
}, "Order notifications"), /* @__PURE__ */ React.createElement("ol", {
|
|
14960
15088
|
className: "list-decimal pl-4 space-y-1 text-xs leading-relaxed"
|
|
14961
|
-
}, /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement("strong", null, "Email"), " tab:
|
|
15089
|
+
}, /* @__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", {
|
|
15090
|
+
className: "text-[11px]"
|
|
15091
|
+
}, "{{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", {
|
|
14962
15092
|
className: "text-[11px]"
|
|
14963
|
-
}, "order_notification_triggers"), "
|
|
15093
|
+
}, "order_notification_triggers"), ") and share", " ", /* @__PURE__ */ React.createElement("code", {
|
|
14964
15094
|
className: "text-[11px]"
|
|
14965
|
-
}, "
|
|
15095
|
+
}, "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", {
|
|
14966
15096
|
className: "text-xs text-gray-500 dark:text-gray-400"
|
|
14967
15097
|
}, "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", {
|
|
14968
15098
|
className: "flex items-center justify-between gap-2"
|
|
@@ -14989,16 +15119,9 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
|
|
|
14989
15119
|
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"}`
|
|
14990
15120
|
}, /* @__PURE__ */ React.createElement(LucideIcons.MessageCircle, {
|
|
14991
15121
|
className: "h-4 w-4"
|
|
14992
|
-
}), "WhatsApp template"))), tab === "whatsapp" ? /* @__PURE__ */ React.createElement(WhatsAppTriggerBindings, null) :
|
|
14993
|
-
className: "text-sm text-gray-500"
|
|
14994
|
-
}, "Loading templates\u2026") : /* @__PURE__ */ React.createElement(EventOrderGlobalTemplates, {
|
|
14995
|
-
items: templateItems,
|
|
14996
|
-
onChange: setTemplateItems,
|
|
14997
|
-
channel: tab,
|
|
14998
|
-
settingsGroup
|
|
14999
|
-
}), /* @__PURE__ */ React.createElement(Button, {
|
|
15122
|
+
}), "WhatsApp template"))), tab === "whatsapp" ? /* @__PURE__ */ React.createElement(WhatsAppTriggerBindings, null) : /* @__PURE__ */ React.createElement(EmailTriggerBindings, null), /* @__PURE__ */ React.createElement(Button, {
|
|
15000
15123
|
size: "sm",
|
|
15001
|
-
onClick: /* @__PURE__ */ __name(() => void
|
|
15124
|
+
onClick: /* @__PURE__ */ __name(() => void handleSaveEnabled(), "onClick"),
|
|
15002
15125
|
disabled: saving,
|
|
15003
15126
|
className: "gap-1"
|
|
15004
15127
|
}, /* @__PURE__ */ React.createElement(LucideIcons.Save, {
|
|
@@ -15011,8 +15134,8 @@ var init_EventNotificationsPluginSettings = __esm({
|
|
|
15011
15134
|
init_button();
|
|
15012
15135
|
init_label();
|
|
15013
15136
|
init_switch();
|
|
15014
|
-
init_EventOrderGlobalTemplates();
|
|
15015
15137
|
init_whatsappTriggerBindingsPage();
|
|
15138
|
+
init_emailTriggersBindingsPage();
|
|
15016
15139
|
__name(EventNotificationsPluginSettings, "EventNotificationsPluginSettings");
|
|
15017
15140
|
}
|
|
15018
15141
|
});
|
|
@@ -26151,47 +26274,6 @@ var init_ProductIncomeCalculatorDialog = __esm({
|
|
|
26151
26274
|
});
|
|
26152
26275
|
|
|
26153
26276
|
// src/lib/currency-prices.ts
|
|
26154
|
-
function normalizeCurrencyCode(code) {
|
|
26155
|
-
return code.trim().toUpperCase();
|
|
26156
|
-
}
|
|
26157
|
-
function parseCurrencyPrices(raw) {
|
|
26158
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
26159
|
-
const out = {};
|
|
26160
|
-
for (const [key, value] of Object.entries(raw)) {
|
|
26161
|
-
const code = normalizeCurrencyCode(key);
|
|
26162
|
-
if (!code) continue;
|
|
26163
|
-
const n = Number(value);
|
|
26164
|
-
if (Number.isFinite(n) && n >= 0) out[code] = n;
|
|
26165
|
-
}
|
|
26166
|
-
return out;
|
|
26167
|
-
}
|
|
26168
|
-
function serializeCurrencyPrices(defaultCurrency, defaultPrice, additional) {
|
|
26169
|
-
const base = normalizeCurrencyCode(defaultCurrency) || "INR";
|
|
26170
|
-
const out = {
|
|
26171
|
-
[base]: defaultPrice
|
|
26172
|
-
};
|
|
26173
|
-
for (const [code, amount] of Object.entries(additional)) {
|
|
26174
|
-
const normalized = normalizeCurrencyCode(code);
|
|
26175
|
-
if (!normalized || normalized === base) continue;
|
|
26176
|
-
if (Number.isFinite(amount) && amount >= 0) out[normalized] = amount;
|
|
26177
|
-
}
|
|
26178
|
-
return Object.keys(out).length ? out : null;
|
|
26179
|
-
}
|
|
26180
|
-
function currencyPricesToForm(defaultCurrency, storedPrice, currencyPrices) {
|
|
26181
|
-
const base = normalizeCurrencyCode(defaultCurrency) || "INR";
|
|
26182
|
-
const map = parseCurrencyPrices(currencyPrices);
|
|
26183
|
-
const defaultFromMap = map[base];
|
|
26184
|
-
const defaultPrice = defaultFromMap != null ? String(defaultFromMap) : storedPrice != null && Number.isFinite(Number(storedPrice)) ? String(storedPrice) : "";
|
|
26185
|
-
const additional = {};
|
|
26186
|
-
for (const [code, amount] of Object.entries(map)) {
|
|
26187
|
-
if (code === base) continue;
|
|
26188
|
-
additional[code] = String(amount);
|
|
26189
|
-
}
|
|
26190
|
-
return {
|
|
26191
|
-
defaultPrice,
|
|
26192
|
-
additional
|
|
26193
|
-
};
|
|
26194
|
-
}
|
|
26195
26277
|
function parseDefaultProductPrice(defaultPriceRaw) {
|
|
26196
26278
|
const trimmed = defaultPriceRaw.trim();
|
|
26197
26279
|
if (!trimmed) return null;
|
|
@@ -26201,31 +26283,9 @@ function parseDefaultProductPrice(defaultPriceRaw) {
|
|
|
26201
26283
|
price: Math.round(price * 100) / 100
|
|
26202
26284
|
};
|
|
26203
26285
|
}
|
|
26204
|
-
function formToCurrencyPricesPayload(defaultCurrency, defaultPriceRaw, additionalForm) {
|
|
26205
|
-
const base = normalizeCurrencyCode(defaultCurrency) || "INR";
|
|
26206
|
-
const defaultPrice = Number(defaultPriceRaw.trim());
|
|
26207
|
-
if (!Number.isFinite(defaultPrice) || defaultPrice < 0) return null;
|
|
26208
|
-
const additional = {};
|
|
26209
|
-
for (const [code, raw] of Object.entries(additionalForm)) {
|
|
26210
|
-
const trimmed = raw.trim();
|
|
26211
|
-
if (!trimmed) continue;
|
|
26212
|
-
const n = Number(trimmed);
|
|
26213
|
-
if (!Number.isFinite(n) || n < 0) continue;
|
|
26214
|
-
additional[normalizeCurrencyCode(code)] = n;
|
|
26215
|
-
}
|
|
26216
|
-
return {
|
|
26217
|
-
price: defaultPrice,
|
|
26218
|
-
currencyPrices: serializeCurrencyPrices(base, defaultPrice, additional)
|
|
26219
|
-
};
|
|
26220
|
-
}
|
|
26221
26286
|
var init_currency_prices = __esm({
|
|
26222
26287
|
"src/lib/currency-prices.ts"() {
|
|
26223
|
-
__name(normalizeCurrencyCode, "normalizeCurrencyCode");
|
|
26224
|
-
__name(parseCurrencyPrices, "parseCurrencyPrices");
|
|
26225
|
-
__name(serializeCurrencyPrices, "serializeCurrencyPrices");
|
|
26226
|
-
__name(currencyPricesToForm, "currencyPricesToForm");
|
|
26227
26288
|
__name(parseDefaultProductPrice, "parseDefaultProductPrice");
|
|
26228
|
-
__name(formToCurrencyPricesPayload, "formToCurrencyPricesPayload");
|
|
26229
26289
|
}
|
|
26230
26290
|
});
|
|
26231
26291
|
|
|
@@ -29663,44 +29723,6 @@ function SupportedCurrenciesPicker({ currencies, defaultCurrency, supported, onD
|
|
|
29663
29723
|
className: "mt-1 text-xs text-gray-500"
|
|
29664
29724
|
}, "Default currency is always included.")));
|
|
29665
29725
|
}
|
|
29666
|
-
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" }) {
|
|
29667
|
-
const base = defaultCurrency.trim().toUpperCase() || "INR";
|
|
29668
|
-
const others = currencies.filter((c) => c.code.toUpperCase() !== base);
|
|
29669
|
-
return /* @__PURE__ */ React.createElement("div", {
|
|
29670
|
-
className: "space-y-4"
|
|
29671
|
-
}, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
|
|
29672
|
-
className: labelClassName
|
|
29673
|
-
}, base, " price ", /* @__PURE__ */ React.createElement("span", {
|
|
29674
|
-
className: "text-red-600"
|
|
29675
|
-
}, "*")), /* @__PURE__ */ React.createElement("input", {
|
|
29676
|
-
type: "number",
|
|
29677
|
-
min: 0,
|
|
29678
|
-
step: "any",
|
|
29679
|
-
value: defaultPrice,
|
|
29680
|
-
onChange: /* @__PURE__ */ __name((e) => onDefaultPriceChange(e.target.value), "onChange"),
|
|
29681
|
-
className: inputClassName,
|
|
29682
|
-
required: true
|
|
29683
|
-
})), others.length > 0 ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", {
|
|
29684
|
-
className: `${labelClassName} mb-2`
|
|
29685
|
-
}, "Additional currencies (optional)"), /* @__PURE__ */ React.createElement("div", {
|
|
29686
|
-
className: "grid gap-3 sm:grid-cols-2"
|
|
29687
|
-
}, others.map((c) => {
|
|
29688
|
-
const code = c.code.toUpperCase();
|
|
29689
|
-
return /* @__PURE__ */ React.createElement("div", {
|
|
29690
|
-
key: code
|
|
29691
|
-
}, /* @__PURE__ */ React.createElement("label", {
|
|
29692
|
-
className: labelClassName
|
|
29693
|
-
}, code), /* @__PURE__ */ React.createElement("input", {
|
|
29694
|
-
type: "number",
|
|
29695
|
-
min: 0,
|
|
29696
|
-
step: "any",
|
|
29697
|
-
value: additionalPrices[code] ?? "",
|
|
29698
|
-
onChange: /* @__PURE__ */ __name((e) => onAdditionalPriceChange(code, e.target.value), "onChange"),
|
|
29699
|
-
className: inputClassName,
|
|
29700
|
-
placeholder: "Optional"
|
|
29701
|
-
}));
|
|
29702
|
-
}))) : null);
|
|
29703
|
-
}
|
|
29704
29726
|
var inputCls5, labelCls5, ICONS;
|
|
29705
29727
|
var init_EventManagementFields = __esm({
|
|
29706
29728
|
"src/components/Admin/EventManagementFields.tsx"() {
|
|
@@ -29722,7 +29744,6 @@ var init_EventManagementFields = __esm({
|
|
|
29722
29744
|
__name(SocialMediaLinksDisplay, "SocialMediaLinksDisplay");
|
|
29723
29745
|
__name(TimezoneSelect, "TimezoneSelect");
|
|
29724
29746
|
__name(SupportedCurrenciesPicker, "SupportedCurrenciesPicker");
|
|
29725
|
-
__name(MultiCurrencyPriceFields, "MultiCurrencyPriceFields");
|
|
29726
29747
|
}
|
|
29727
29748
|
});
|
|
29728
29749
|
|
|
@@ -29874,28 +29895,11 @@ function intersectSupportedCurrencies(supported, defaultCurrency, options) {
|
|
|
29874
29895
|
])
|
|
29875
29896
|
];
|
|
29876
29897
|
}
|
|
29877
|
-
function currencyOptionsIncluding(options, codes) {
|
|
29878
|
-
const map = new Map(options.map((o) => [
|
|
29879
|
-
o.code.toUpperCase(),
|
|
29880
|
-
o
|
|
29881
|
-
]));
|
|
29882
|
-
for (const code of codes) {
|
|
29883
|
-
const normalized = code.trim().toUpperCase();
|
|
29884
|
-
if (!normalized || map.has(normalized)) continue;
|
|
29885
|
-
map.set(normalized, {
|
|
29886
|
-
code: normalized
|
|
29887
|
-
});
|
|
29888
|
-
}
|
|
29889
|
-
return [
|
|
29890
|
-
...map.values()
|
|
29891
|
-
].sort((a, b) => a.code.localeCompare(b.code));
|
|
29892
|
-
}
|
|
29893
29898
|
var init_currency_options = __esm({
|
|
29894
29899
|
"src/lib/currency-options.ts"() {
|
|
29895
29900
|
__name(parseCurrencyApiRows, "parseCurrencyApiRows");
|
|
29896
29901
|
__name(fetchActiveCurrencyOptions, "fetchActiveCurrencyOptions");
|
|
29897
29902
|
__name(intersectSupportedCurrencies, "intersectSupportedCurrencies");
|
|
29898
|
-
__name(currencyOptionsIncluding, "currencyOptionsIncluding");
|
|
29899
29903
|
}
|
|
29900
29904
|
});
|
|
29901
29905
|
|
|
@@ -30875,25 +30879,14 @@ function formatDateTimeLocal(value) {
|
|
|
30875
30879
|
if (isNaN(date.getTime())) return "";
|
|
30876
30880
|
return date.toISOString().slice(0, 16);
|
|
30877
30881
|
}
|
|
30878
|
-
async function
|
|
30882
|
+
async function fetchEventDefaultCurrency(eventId) {
|
|
30879
30883
|
try {
|
|
30880
30884
|
const res = await fetch(`/api/events/${eventId}`);
|
|
30881
|
-
if (!res.ok) return
|
|
30885
|
+
if (!res.ok) return "INR";
|
|
30882
30886
|
const data = await res.json();
|
|
30883
|
-
|
|
30884
|
-
const supported = Array.isArray(data.supportedCurrencies) ? data.supportedCurrencies.map((c) => String(c).trim().toUpperCase()).filter((c) => Boolean(c)) : [
|
|
30885
|
-
defaultCurrency
|
|
30886
|
-
];
|
|
30887
|
-
const set = new Set(supported);
|
|
30888
|
-
set.add(defaultCurrency);
|
|
30889
|
-
return {
|
|
30890
|
-
defaultCurrency,
|
|
30891
|
-
supportedCurrencies: [
|
|
30892
|
-
...set
|
|
30893
|
-
]
|
|
30894
|
-
};
|
|
30887
|
+
return String(data.defaultCurrency ?? "INR").trim().toUpperCase() || "INR";
|
|
30895
30888
|
} catch {
|
|
30896
|
-
return
|
|
30889
|
+
return "INR";
|
|
30897
30890
|
}
|
|
30898
30891
|
}
|
|
30899
30892
|
async function validateComboProductInventory(productIds) {
|
|
@@ -30935,10 +30928,8 @@ function ComboEditPage({ comboId }) {
|
|
|
30935
30928
|
const [name, setName] = React26.useState("");
|
|
30936
30929
|
const [desc, setDesc] = React26.useState("");
|
|
30937
30930
|
const [eventId, setEventId] = React26.useState("");
|
|
30938
|
-
const [
|
|
30939
|
-
const [
|
|
30940
|
-
const [pricingConfig, setPricingConfig] = React26.useState(DEFAULT_PRICING);
|
|
30941
|
-
const [currencySchemaOptions, setCurrencySchemaOptions] = React26.useState([]);
|
|
30931
|
+
const [priceStr, setPriceStr] = React26.useState("");
|
|
30932
|
+
const [defaultCurrency, setDefaultCurrency] = React26.useState("INR");
|
|
30942
30933
|
const [minSelectableItems, setMinSelectableItems] = React26.useState(1);
|
|
30943
30934
|
const [maxSelectableItems, setMaxSelectableItems] = React26.useState(1);
|
|
30944
30935
|
const [status, setStatus] = React26.useState("active");
|
|
@@ -30948,23 +30939,14 @@ function ComboEditPage({ comboId }) {
|
|
|
30948
30939
|
const [productOptions, setProductOptions] = React26.useState([]);
|
|
30949
30940
|
const [fixedItems, setFixedItems] = React26.useState([]);
|
|
30950
30941
|
const [addonItems, setAddonItems] = React26.useState([]);
|
|
30951
|
-
React26.useEffect(() => {
|
|
30952
|
-
let cancelled = false;
|
|
30953
|
-
fetchActiveCurrencyOptions().then(({ options }) => {
|
|
30954
|
-
if (!cancelled && options.length) setCurrencySchemaOptions(options);
|
|
30955
|
-
});
|
|
30956
|
-
return () => {
|
|
30957
|
-
cancelled = true;
|
|
30958
|
-
};
|
|
30959
|
-
}, []);
|
|
30960
30942
|
React26.useEffect(() => {
|
|
30961
30943
|
if (!eventId) {
|
|
30962
|
-
|
|
30944
|
+
setDefaultCurrency("INR");
|
|
30963
30945
|
return;
|
|
30964
30946
|
}
|
|
30965
30947
|
let cancelled = false;
|
|
30966
|
-
|
|
30967
|
-
if (!cancelled)
|
|
30948
|
+
fetchEventDefaultCurrency(eventId).then((code) => {
|
|
30949
|
+
if (!cancelled) setDefaultCurrency(code);
|
|
30968
30950
|
});
|
|
30969
30951
|
return () => {
|
|
30970
30952
|
cancelled = true;
|
|
@@ -30972,10 +30954,6 @@ function ComboEditPage({ comboId }) {
|
|
|
30972
30954
|
}, [
|
|
30973
30955
|
eventId
|
|
30974
30956
|
]);
|
|
30975
|
-
const pricingCurrencyOptions = currencyOptionsIncluding(currencySchemaOptions, [
|
|
30976
|
-
pricingConfig.defaultCurrency,
|
|
30977
|
-
...pricingConfig.supportedCurrencies
|
|
30978
|
-
]).filter((c) => pricingConfig.supportedCurrencies.map((x) => x.toUpperCase()).includes(c.code.toUpperCase()));
|
|
30979
30957
|
React26.useEffect(() => {
|
|
30980
30958
|
let cancelled = false;
|
|
30981
30959
|
(async () => {
|
|
@@ -31064,11 +31042,11 @@ function ComboEditPage({ comboId }) {
|
|
|
31064
31042
|
setName(data.name ?? "");
|
|
31065
31043
|
setDesc(data.desc ?? "");
|
|
31066
31044
|
setEventId(data.eventId != null ? String(data.eventId) : "");
|
|
31067
|
-
|
|
31068
|
-
|
|
31069
|
-
|
|
31070
|
-
|
|
31071
|
-
|
|
31045
|
+
if (data.eventId != null) {
|
|
31046
|
+
const currency = await fetchEventDefaultCurrency(String(data.eventId));
|
|
31047
|
+
if (!cancelled) setDefaultCurrency(currency);
|
|
31048
|
+
}
|
|
31049
|
+
setPriceStr(data.price != null && Number.isFinite(Number(data.price)) ? String(data.price) : "");
|
|
31072
31050
|
setMinSelectableItems(Number(data.minSelectableItems) || 1);
|
|
31073
31051
|
setMaxSelectableItems(Number(data.maxSelectableItems) || 1);
|
|
31074
31052
|
setStatus(data.status ?? "active");
|
|
@@ -31119,10 +31097,6 @@ function ComboEditPage({ comboId }) {
|
|
|
31119
31097
|
create,
|
|
31120
31098
|
duplicateSourceId
|
|
31121
31099
|
]);
|
|
31122
|
-
/* @__PURE__ */ new Set([
|
|
31123
|
-
...fixedItems.map((i) => i.productId),
|
|
31124
|
-
...addonItems.map((i) => i.productId)
|
|
31125
|
-
]);
|
|
31126
31100
|
const availableForFixed = productOptions.filter((p) => !addonItems.some((i) => i.productId === p.value));
|
|
31127
31101
|
const availableForAddon = productOptions.filter((p) => !fixedItems.some((i) => i.productId === p.value));
|
|
31128
31102
|
const addFixed = /* @__PURE__ */ __name((val) => {
|
|
@@ -31163,16 +31137,16 @@ function ComboEditPage({ comboId }) {
|
|
|
31163
31137
|
]);
|
|
31164
31138
|
return;
|
|
31165
31139
|
}
|
|
31166
|
-
if (!
|
|
31140
|
+
if (!priceStr.trim()) {
|
|
31167
31141
|
setErrors([
|
|
31168
|
-
|
|
31142
|
+
`Price (${defaultCurrency}) is required`
|
|
31169
31143
|
]);
|
|
31170
31144
|
return;
|
|
31171
31145
|
}
|
|
31172
|
-
const
|
|
31173
|
-
if (!
|
|
31146
|
+
const price = Number(priceStr);
|
|
31147
|
+
if (!Number.isFinite(price) || price < 0) {
|
|
31174
31148
|
setErrors([
|
|
31175
|
-
|
|
31149
|
+
`Price (${defaultCurrency}) must be a valid non-negative number`
|
|
31176
31150
|
]);
|
|
31177
31151
|
return;
|
|
31178
31152
|
}
|
|
@@ -31220,8 +31194,8 @@ function ComboEditPage({ comboId }) {
|
|
|
31220
31194
|
name: trimmedName,
|
|
31221
31195
|
desc: desc || null,
|
|
31222
31196
|
eventId: Number(trimmedEventId),
|
|
31223
|
-
price
|
|
31224
|
-
currencyPrices:
|
|
31197
|
+
price,
|
|
31198
|
+
currencyPrices: null,
|
|
31225
31199
|
minSelectableItems,
|
|
31226
31200
|
maxSelectableItems,
|
|
31227
31201
|
status,
|
|
@@ -31388,21 +31362,19 @@ function ComboEditPage({ comboId }) {
|
|
|
31388
31362
|
className: "text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2"
|
|
31389
31363
|
}, "Pricing & add-on selection"), /* @__PURE__ */ React.createElement("div", {
|
|
31390
31364
|
className: "min-w-0 overflow-hidden border border-gray-200 rounded-lg p-4 bg-gray-50/50 space-y-4"
|
|
31391
|
-
},
|
|
31392
|
-
|
|
31393
|
-
|
|
31394
|
-
|
|
31395
|
-
|
|
31396
|
-
|
|
31397
|
-
|
|
31398
|
-
|
|
31399
|
-
|
|
31400
|
-
|
|
31401
|
-
|
|
31402
|
-
|
|
31403
|
-
}
|
|
31404
|
-
className: "text-sm text-gray-500"
|
|
31405
|
-
}, "Select an event to configure combo pricing currencies."), /* @__PURE__ */ React.createElement("div", {
|
|
31365
|
+
}, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
|
|
31366
|
+
className: "block text-xs font-medium text-gray-600 mb-1"
|
|
31367
|
+
}, "Price (", defaultCurrency, ") *"), /* @__PURE__ */ React.createElement("input", {
|
|
31368
|
+
type: "number",
|
|
31369
|
+
min: 0,
|
|
31370
|
+
step: "0.01",
|
|
31371
|
+
value: priceStr,
|
|
31372
|
+
onChange: /* @__PURE__ */ __name((e) => setPriceStr(e.target.value), "onChange"),
|
|
31373
|
+
disabled: !eventId,
|
|
31374
|
+
className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm disabled:bg-gray-100"
|
|
31375
|
+
}), /* @__PURE__ */ React.createElement("p", {
|
|
31376
|
+
className: "text-xs text-gray-400 mt-1"
|
|
31377
|
+
}, eventId ? "Other currencies use the event\u2019s supported currencies and exchange rates." : "Select an event to set the combo price.")), /* @__PURE__ */ React.createElement("div", {
|
|
31406
31378
|
className: "grid grid-cols-2 gap-4"
|
|
31407
31379
|
}, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
|
|
31408
31380
|
className: "block text-xs font-medium text-gray-600 mb-1"
|
|
@@ -31460,26 +31432,17 @@ function ComboEditPage({ comboId }) {
|
|
|
31460
31432
|
sidebar: /* @__PURE__ */ React.createElement(React.Fragment, null)
|
|
31461
31433
|
}));
|
|
31462
31434
|
}
|
|
31463
|
-
var isCreate5
|
|
31435
|
+
var isCreate5;
|
|
31464
31436
|
var init_ComboEditPage = __esm({
|
|
31465
31437
|
"src/admin/pages/ComboEditPage.tsx"() {
|
|
31466
31438
|
"use client";
|
|
31467
31439
|
init_admin_list_return_url();
|
|
31468
31440
|
init_DetailPageLayout();
|
|
31469
31441
|
init_DetailPageHeader();
|
|
31470
|
-
init_EventManagementFields();
|
|
31471
|
-
init_currency_options();
|
|
31472
|
-
init_currency_prices();
|
|
31473
31442
|
init_inventory_validation();
|
|
31474
31443
|
isCreate5 = /* @__PURE__ */ __name((id) => id === "create", "isCreate");
|
|
31475
31444
|
__name(formatDateTimeLocal, "formatDateTimeLocal");
|
|
31476
|
-
|
|
31477
|
-
defaultCurrency: "INR",
|
|
31478
|
-
supportedCurrencies: [
|
|
31479
|
-
"INR"
|
|
31480
|
-
]
|
|
31481
|
-
};
|
|
31482
|
-
__name(fetchEventPricing, "fetchEventPricing");
|
|
31445
|
+
__name(fetchEventDefaultCurrency, "fetchEventDefaultCurrency");
|
|
31483
31446
|
__name(validateComboProductInventory, "validateComboProductInventory");
|
|
31484
31447
|
__name(ComboEditPage, "ComboEditPage");
|
|
31485
31448
|
}
|