@infuro/cms-core 1.0.47 → 1.0.50

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.
Files changed (34) hide show
  1. package/README.md +30 -16
  2. package/dist/admin.cjs +1172 -38
  3. package/dist/admin.js +1173 -39
  4. package/dist/api.cjs +37 -37
  5. package/dist/api.js +5 -5
  6. package/dist/auth.cjs +52 -23
  7. package/dist/auth.d.cts +101 -2
  8. package/dist/auth.d.ts +101 -2
  9. package/dist/auth.js +3 -2
  10. package/dist/chunk-3EOM4V2M.cjs +452 -0
  11. package/dist/{chunk-2HU5R2JE.js → chunk-AYERBA7I.js} +1 -7
  12. package/dist/{chunk-YXH2UUEZ.js → chunk-IPDHT2UV.js} +257 -16
  13. package/dist/{chunk-XUCKZPML.cjs → chunk-JCMOOPNX.cjs} +2696 -2601
  14. package/dist/{chunk-W42UZLQO.js → chunk-LT2WOPKA.js} +2642 -2540
  15. package/dist/chunk-RK5ETF2I.js +434 -0
  16. package/dist/{chunk-4PMK3RNA.cjs → chunk-TJ2MIQUT.cjs} +1 -7
  17. package/dist/{chunk-LMJ7RKPF.cjs → chunk-UUWAUHUW.cjs} +279 -16
  18. package/dist/{chunk-YC4NUZCS.js → chunk-WRKV6MHB.js} +414 -2
  19. package/dist/{chunk-Q3HQEM4R.cjs → chunk-YV5PK4JW.cjs} +421 -1
  20. package/dist/cli.cjs +13 -22
  21. package/dist/cli.js +13 -22
  22. package/dist/{event-order-defaults-7Z3UZOEH.cjs → event-order-defaults-DU7V3YND.cjs} +9 -9
  23. package/dist/{event-order-defaults-XQ3IDPD7.js → event-order-defaults-H4RT7NMR.js} +1 -1
  24. package/dist/index.cjs +327 -291
  25. package/dist/index.d.cts +132 -5
  26. package/dist/index.d.ts +132 -5
  27. package/dist/index.js +8 -8
  28. package/dist/migrations/1782400000000-CreateUserDeviceTokens.ts +36 -0
  29. package/dist/migrations/1782500000000-AddPushToOrderNotificationBindingsChannelEnum.ts +15 -0
  30. package/dist/{order-notification-dispatcher-6WNG24NY.js → order-notification-dispatcher-3TA4ETYJ.js} +1 -1
  31. package/dist/{order-notification-dispatcher-6XSU3AR7.cjs → order-notification-dispatcher-ZEAZ5SHV.cjs} +7 -3
  32. package/package.json +1 -1
  33. package/dist/chunk-VUQFARRT.cjs +0 -182
  34. package/dist/chunk-ZF2RQWXB.js +0 -171
package/dist/admin.cjs CHANGED
@@ -489,7 +489,7 @@ var init_admin_config_context = __esm({
489
489
  var CMS_VERSION;
490
490
  var init_cms_version = __esm({
491
491
  "src/lib/cms-version.ts"() {
492
- CMS_VERSION = "1.0.47" ;
492
+ CMS_VERSION = "1.0.50" ;
493
493
  }
494
494
  });
495
495
  function useCatalogCategories(enabled = true) {
@@ -1180,6 +1180,15 @@ var init_builtin_plugin_descriptors = __esm({
1180
1180
  enabled: true,
1181
1181
  icon: "event_notifications",
1182
1182
  settingsGroup: "event_notifications"
1183
+ },
1184
+ {
1185
+ name: "auth_providers",
1186
+ label: "Authentication Provider",
1187
+ version: "1.0.0",
1188
+ description: "Enable Google sign-in for admin and vendor users (existing accounts only).",
1189
+ enabled: true,
1190
+ icon: "auth_providers",
1191
+ settingsGroup: "auth_providers"
1183
1192
  }
1184
1193
  ];
1185
1194
  }
@@ -9174,28 +9183,75 @@ var init_AuthPageLayout = __esm({
9174
9183
  }
9175
9184
  });
9176
9185
 
9186
+ // src/auth/auth-providers-settings.ts
9187
+ function parseEnabled(raw) {
9188
+ if (typeof raw === "boolean") return raw;
9189
+ if (typeof raw === "number") return raw !== 0;
9190
+ if (typeof raw === "string") {
9191
+ const n = raw.trim().toLowerCase();
9192
+ return n === "true" || n === "1" || n === "yes" || n === "on";
9193
+ }
9194
+ return false;
9195
+ }
9196
+ function isGoogleAuthPubliclyEnabled(settings) {
9197
+ if (!settings) return false;
9198
+ if (settings.googleEnabled !== void 0 && settings.googleEnabled !== "") {
9199
+ return parseEnabled(settings.googleEnabled);
9200
+ }
9201
+ return parseEnabled(settings.enabled);
9202
+ }
9203
+ var init_auth_providers_settings = __esm({
9204
+ "src/auth/auth-providers-settings.ts"() {
9205
+ init_auth_debug();
9206
+ __name(parseEnabled, "parseEnabled");
9207
+ __name(isGoogleAuthPubliclyEnabled, "isGoogleAuthPubliclyEnabled");
9208
+ }
9209
+ });
9210
+
9177
9211
  // src/admin/pages/SignInPage.tsx
9178
9212
  var SignInPage_exports = {};
9179
9213
  __export(SignInPage_exports, {
9180
9214
  default: () => exports.AdminSignInPage
9181
9215
  });
9216
+ function oauthErrorMessage(code) {
9217
+ if (!code) return "";
9218
+ const c = code.toLowerCase();
9219
+ if (c === "accessdenied" || c === "access_denied") {
9220
+ return "No account for this Google email. Ask an admin to invite you first.";
9221
+ }
9222
+ if (c === "oauthaccountnotlinked") {
9223
+ return "This Google account is not linked to an existing user. Sign in with email/password or ask an admin to invite you.";
9224
+ }
9225
+ if (c === "oauthcallback" || c === "oauthsignin" || c === "oauthcreateaccount") {
9226
+ return "Google sign-in failed. Check Authentication Provider settings and the redirect URI in Google Cloud Console.";
9227
+ }
9228
+ if (c === "configuration") {
9229
+ return "Google sign-in is not configured correctly.";
9230
+ }
9231
+ return "Invalid email or password";
9232
+ }
9182
9233
  var SigninPage; exports.AdminSignInPage = void 0;
9183
9234
  var init_SignInPage = __esm({
9184
9235
  "src/admin/pages/SignInPage.tsx"() {
9185
9236
  "use client";
9186
9237
  init_AuthPageLayout();
9187
9238
  init_auth_debug();
9239
+ init_auth_providers_settings();
9240
+ __name(oauthErrorMessage, "oauthErrorMessage");
9188
9241
  SigninPage = /* @__PURE__ */ __name(() => {
9189
9242
  const [email, setEmail] = React25.useState("");
9190
9243
  const [password, setPassword] = React25.useState("");
9191
9244
  const [error, setError] = React25.useState("");
9192
9245
  const [success, setSuccess] = React25.useState("");
9193
9246
  const [loading, setLoading] = React25.useState(false);
9247
+ const [googleLoading, setGoogleLoading] = React25.useState(false);
9248
+ const [googleEnabled, setGoogleEnabled] = React25.useState(false);
9194
9249
  const { status } = react.useSession();
9195
9250
  const searchParams = navigation.useSearchParams();
9196
9251
  React25.useEffect(() => {
9197
- if (searchParams.get("error")) {
9198
- setError("Invalid email or password");
9252
+ const err = searchParams.get("error");
9253
+ if (err) {
9254
+ setError(oauthErrorMessage(err));
9199
9255
  }
9200
9256
  if (searchParams.get("activated") === "1") {
9201
9257
  setSuccess("Account activated. Sign in with your new password.");
@@ -9206,6 +9262,23 @@ var init_SignInPage = __esm({
9206
9262
  }, [
9207
9263
  searchParams
9208
9264
  ]);
9265
+ React25.useEffect(() => {
9266
+ let cancelled = false;
9267
+ (async () => {
9268
+ try {
9269
+ const res = await fetch("/api/settings/auth_providers", {
9270
+ cache: "no-store"
9271
+ });
9272
+ if (!res.ok) return;
9273
+ const data = await res.json();
9274
+ if (!cancelled) setGoogleEnabled(isGoogleAuthPubliclyEnabled(data));
9275
+ } catch {
9276
+ }
9277
+ })();
9278
+ return () => {
9279
+ cancelled = true;
9280
+ };
9281
+ }, []);
9209
9282
  React25.useEffect(() => {
9210
9283
  logAuthClient("SignInPage session status", {
9211
9284
  status
@@ -9263,6 +9336,19 @@ var init_SignInPage = __esm({
9263
9336
  setLoading(false);
9264
9337
  }
9265
9338
  }, "handleSubmit");
9339
+ const handleGoogle = /* @__PURE__ */ __name(async () => {
9340
+ setGoogleLoading(true);
9341
+ setError("");
9342
+ try {
9343
+ logAuthClient("SignInPage Google signIn start");
9344
+ await react.signIn("google", {
9345
+ callbackUrl: "/admin/dashboard"
9346
+ });
9347
+ } catch {
9348
+ setError("Google sign-in failed. Please try again.");
9349
+ setGoogleLoading(false);
9350
+ }
9351
+ }, "handleGoogle");
9266
9352
  return /* @__PURE__ */ React.createElement(AuthPageLayout, null, /* @__PURE__ */ React.createElement("div", {
9267
9353
  className: "w-full shadow-md rounded-lg bg-white px-4 py-6 sm:px-6 sm:py-8"
9268
9354
  }, /* @__PURE__ */ React.createElement("h3", {
@@ -9315,9 +9401,42 @@ var init_SignInPage = __esm({
9315
9401
  className: "text-xs font-medium text-gray-600 hover:underline"
9316
9402
  }, "Forgot password?")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("button", {
9317
9403
  type: "submit",
9318
- disabled: loading,
9404
+ disabled: loading || googleLoading,
9319
9405
  className: "w-full rounded bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-800 disabled:opacity-50 disabled:cursor-not-allowed"
9320
- }, loading ? "Signing in..." : "Sign in")))));
9406
+ }, loading ? "Signing in..." : "Sign in"))), googleEnabled && /* @__PURE__ */ React.createElement("div", {
9407
+ className: "mt-6"
9408
+ }, /* @__PURE__ */ React.createElement("div", {
9409
+ className: "relative mb-4"
9410
+ }, /* @__PURE__ */ React.createElement("div", {
9411
+ className: "absolute inset-0 flex items-center"
9412
+ }, /* @__PURE__ */ React.createElement("div", {
9413
+ className: "w-full border-t border-gray-200"
9414
+ })), /* @__PURE__ */ React.createElement("div", {
9415
+ className: "relative flex justify-center text-xs"
9416
+ }, /* @__PURE__ */ React.createElement("span", {
9417
+ className: "bg-white px-2 text-gray-500"
9418
+ }, "or"))), /* @__PURE__ */ React.createElement("button", {
9419
+ type: "button",
9420
+ onClick: handleGoogle,
9421
+ disabled: loading || googleLoading,
9422
+ className: "flex w-full items-center justify-center gap-2 rounded border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-800 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
9423
+ }, /* @__PURE__ */ React.createElement("svg", {
9424
+ className: "h-4 w-4",
9425
+ viewBox: "0 0 24 24",
9426
+ "aria-hidden": "true"
9427
+ }, /* @__PURE__ */ React.createElement("path", {
9428
+ fill: "#4285F4",
9429
+ d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
9430
+ }), /* @__PURE__ */ React.createElement("path", {
9431
+ fill: "#34A853",
9432
+ d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
9433
+ }), /* @__PURE__ */ React.createElement("path", {
9434
+ fill: "#FBBC05",
9435
+ d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
9436
+ }), /* @__PURE__ */ React.createElement("path", {
9437
+ fill: "#EA4335",
9438
+ d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
9439
+ })), googleLoading ? "Redirecting\u2026" : "Continue with Google"))));
9321
9440
  }, "SigninPage");
9322
9441
  exports.AdminSignInPage = SigninPage;
9323
9442
  }
@@ -15441,6 +15560,30 @@ function getEmailTriggerDefaults(triggerKey, audience = "customer") {
15441
15560
  bodyPlaceholder: "Hi {{customerName}},\n\nYour order {{orderNumber}} has been updated."
15442
15561
  };
15443
15562
  }
15563
+ function getPushTriggerDefaults(triggerKey, audience = "customer") {
15564
+ if (audience === "vendor") {
15565
+ return VENDOR_PUSH_TRIGGER_DEFAULTS[triggerKey] ?? {
15566
+ subject: "Vendor Push Update",
15567
+ body: "Hi {{vendorName}}, order {{orderNumber}} update.",
15568
+ subjectPlaceholder: "e.g. Vendor Push Update",
15569
+ bodyPlaceholder: "Hi {{vendorName}}, order {{orderNumber}} update."
15570
+ };
15571
+ }
15572
+ if (audience === "admin") {
15573
+ return ADMIN_PUSH_TRIGGER_DEFAULTS[triggerKey] ?? {
15574
+ subject: "Admin Push Alert",
15575
+ body: "Admin Alert: Order {{orderNumber}} update.",
15576
+ subjectPlaceholder: "e.g. Admin Push Alert",
15577
+ bodyPlaceholder: "Admin Alert: Order {{orderNumber}} update."
15578
+ };
15579
+ }
15580
+ return PUSH_TRIGGER_DEFAULTS[triggerKey] ?? {
15581
+ subject: "Order Update",
15582
+ body: "Hi {{customerName}}, order {{orderNumber}} update.",
15583
+ subjectPlaceholder: "e.g. Order Update",
15584
+ bodyPlaceholder: "Hi {{customerName}}, order {{orderNumber}} update."
15585
+ };
15586
+ }
15444
15587
  async function jsonFetch(url, init) {
15445
15588
  const res = await fetch(url, {
15446
15589
  credentials: "include",
@@ -15532,7 +15675,7 @@ function useOrderNotificationTriggerBindings(channel, audienceType = "customers"
15532
15675
  rebind
15533
15676
  };
15534
15677
  }
15535
- var KNOWN_VARS, EMAIL_TRIGGER_DEFAULTS, VENDOR_EMAIL_TRIGGER_DEFAULTS, ADMIN_EMAIL_TRIGGER_DEFAULTS;
15678
+ var KNOWN_VARS, EMAIL_TRIGGER_DEFAULTS, VENDOR_EMAIL_TRIGGER_DEFAULTS, ADMIN_EMAIL_TRIGGER_DEFAULTS, PUSH_TRIGGER_DEFAULTS, VENDOR_PUSH_TRIGGER_DEFAULTS, ADMIN_PUSH_TRIGGER_DEFAULTS;
15536
15679
  var init_order_notification_bindings_shared = __esm({
15537
15680
  "src/admin/components/order-notification-bindings-shared.ts"() {
15538
15681
  "use client";
@@ -15548,9 +15691,7 @@ var init_order_notification_bindings_shared = __esm({
15548
15691
  "vendorId",
15549
15692
  "refundAmount",
15550
15693
  "invoiceNumber",
15551
- "invoiceUrl",
15552
- "ticketQr",
15553
- "qrImageUrl"
15694
+ "invoiceUrl"
15554
15695
  ];
15555
15696
  EMAIL_TRIGGER_DEFAULTS = {
15556
15697
  order_placed: {
@@ -15629,7 +15770,7 @@ Order Summary:
15629
15770
  Customer Name: {{customerName}}
15630
15771
  Customer Email: {{customerEmail}}
15631
15772
  Customer Phone: {{customerPhone}}`,
15632
- bodyPlaceholder: "Admin Notification:\n\nNew order {{orderNumber}} placed by {{customerName}}."
15773
+ bodyPlaceholder: "New order {{orderNumber}} placed by {{customerName}}."
15633
15774
  },
15634
15775
  order_cancelled: {
15635
15776
  subject: "Order {{orderNumber}} cancelled",
@@ -15645,6 +15786,66 @@ Refund Due: {{refundAmount}} {{currency}}`,
15645
15786
  }
15646
15787
  };
15647
15788
  __name(getEmailTriggerDefaults, "getEmailTriggerDefaults");
15789
+ PUSH_TRIGGER_DEFAULTS = {
15790
+ order_placed: {
15791
+ subject: "Order {{orderNumber}} Confirmed!",
15792
+ subjectPlaceholder: "e.g. Order {{orderNumber}} Confirmed!",
15793
+ body: `Hi {{customerName}}, your order {{orderNumber}} for {{total}} {{currency}} is confirmed.`,
15794
+ bodyPlaceholder: "Hi {{customerName}}, your order {{orderNumber}} is confirmed."
15795
+ },
15796
+ order_cancelled: {
15797
+ subject: "Order {{orderNumber}} Cancelled",
15798
+ subjectPlaceholder: "e.g. Order {{orderNumber}} Cancelled",
15799
+ body: `Hi {{customerName}}, your order {{orderNumber}} for {{total}} {{currency}} has been cancelled.`,
15800
+ bodyPlaceholder: "Hi {{customerName}}, your order {{orderNumber}} has been cancelled."
15801
+ }
15802
+ };
15803
+ VENDOR_PUSH_TRIGGER_DEFAULTS = {
15804
+ order_placed: {
15805
+ subject: "New Order Received: {{orderNumber}}",
15806
+ subjectPlaceholder: "e.g. New Order Received: {{orderNumber}}",
15807
+ body: `Hi {{vendorName}}, you received a new order {{orderNumber}} for {{total}} {{currency}} from {{customerName}}.`,
15808
+ bodyPlaceholder: "Hi {{vendorName}}, you received a new order {{orderNumber}}."
15809
+ },
15810
+ order_cancelled: {
15811
+ subject: "Order {{orderNumber}} Cancelled",
15812
+ subjectPlaceholder: "e.g. Order {{orderNumber}} Cancelled",
15813
+ body: `Hi {{vendorName}}, order {{orderNumber}} from {{customerName}} has been cancelled.`,
15814
+ bodyPlaceholder: "Hi {{vendorName}}, order {{orderNumber}} was cancelled."
15815
+ }
15816
+ };
15817
+ ADMIN_PUSH_TRIGGER_DEFAULTS = {
15818
+ order_placed: {
15819
+ subject: "New Order {{orderNumber}} ({{total}} {{currency}})",
15820
+ subjectPlaceholder: "e.g. New Order {{orderNumber}} placed",
15821
+ body: `Hi Admin,
15822
+
15823
+ A new order {{orderNumber}} of {{total}} {{currency}} was placed.
15824
+
15825
+ Vendor: {{vendorName}}
15826
+
15827
+ Order Summary:
15828
+ {{orderDetails}}
15829
+
15830
+ Customer Name: {{customerName}}
15831
+ Customer Email: {{customerEmail}}
15832
+ Customer Phone: {{customerPhone}}`,
15833
+ bodyPlaceholder: "New order {{orderNumber}} placed by {{customerName}}."
15834
+ },
15835
+ order_cancelled: {
15836
+ subject: "Order {{orderNumber}} Cancelled",
15837
+ subjectPlaceholder: "e.g. Order {{orderNumber}} cancelled",
15838
+ body: `Hi Admin,
15839
+
15840
+ Order {{orderNumber}} has been cancelled.
15841
+
15842
+ Customer: {{customerName}}
15843
+ Vendor: {{vendorName}}
15844
+ Refund Due: {{refundAmount}} {{currency}}`,
15845
+ bodyPlaceholder: "Hi Admin,\n\nOrder {{orderNumber}} cancelled."
15846
+ }
15847
+ };
15848
+ __name(getPushTriggerDefaults, "getPushTriggerDefaults");
15648
15849
  __name(jsonFetch, "jsonFetch");
15649
15850
  __name(useOrderNotificationTriggerBindings, "useOrderNotificationTriggerBindings");
15650
15851
  }
@@ -16295,14 +16496,582 @@ var init_emailTriggersBindingsPage = __esm({
16295
16496
  vendorId: "Vendor / store ID",
16296
16497
  refundAmount: "Refund amount (refund triggers)",
16297
16498
  invoiceNumber: "Generated invoice number",
16298
- invoiceUrl: "Link to download invoice PDF",
16299
- ticketQr: "Embedded ticket QR code image",
16300
- qrImageUrl: "Direct QR code image URL"
16499
+ invoiceUrl: "Link to download invoice PDF"
16301
16500
  };
16302
16501
  __name(EmailTriggerBindings, "EmailTriggerBindings");
16303
16502
  __name(TriggerEmailEditor, "TriggerEmailEditor");
16304
16503
  }
16305
16504
  });
16505
+ function PushTriggerBindings({ audience = "customer" }) {
16506
+ const audienceType = audience === "vendor" ? "vendors" : audience === "admin" ? "admin" : "customers";
16507
+ const { triggers, bindings, loading, reload, rebind } = useOrderNotificationTriggerBindings("mobile", audienceType);
16508
+ const [selectedKey, setSelectedKey] = React25.useState("");
16509
+ const [templates, setTemplates] = React25.useState([]);
16510
+ const [loadingTpl, setLoadingTpl] = React25.useState(true);
16511
+ const [title, setTitle] = React25.useState("");
16512
+ const [body, setBody] = React25.useState("");
16513
+ const [saving, setSaving] = React25.useState(false);
16514
+ const bodyRef = React25.useRef(null);
16515
+ const subjectRef = React25.useRef(null);
16516
+ const lastFocused = React25.useRef("body");
16517
+ React25.useEffect(() => {
16518
+ let cancelled = false;
16519
+ (async () => {
16520
+ try {
16521
+ setLoadingTpl(true);
16522
+ const res = await fetch("/api/message-templates/email", {
16523
+ cache: "no-store"
16524
+ });
16525
+ if (!res.ok) throw new Error("Failed to load message templates");
16526
+ const data = await res.json();
16527
+ if (!cancelled) setTemplates(Array.isArray(data.items) ? data.items : []);
16528
+ } catch {
16529
+ if (!cancelled) sonner.toast.error("Could not load message templates");
16530
+ } finally {
16531
+ if (!cancelled) setLoadingTpl(false);
16532
+ }
16533
+ })();
16534
+ return () => {
16535
+ cancelled = true;
16536
+ };
16537
+ }, []);
16538
+ React25.useEffect(() => {
16539
+ if (triggers.length > 0 && !selectedKey) {
16540
+ setSelectedKey(triggers[0].triggerKey);
16541
+ }
16542
+ }, [
16543
+ triggers,
16544
+ selectedKey
16545
+ ]);
16546
+ const activeTrigger = React25.useMemo(() => triggers.find((t) => t.triggerKey === selectedKey), [
16547
+ triggers,
16548
+ selectedKey
16549
+ ]);
16550
+ const bindingMap = React25.useMemo(() => {
16551
+ const m = /* @__PURE__ */ new Map();
16552
+ for (const b of bindings) {
16553
+ if (b.messageTemplateId != null) m.set(b.triggerKey, b.messageTemplateId);
16554
+ }
16555
+ return m;
16556
+ }, [
16557
+ bindings
16558
+ ]);
16559
+ const activeBindingTplId = selectedKey ? bindingMap.get(selectedKey) ?? null : null;
16560
+ const activeTemplate = React25.useMemo(() => {
16561
+ if (!activeBindingTplId) return null;
16562
+ return templates.find((t) => t.id === activeBindingTplId) ?? null;
16563
+ }, [
16564
+ activeBindingTplId,
16565
+ templates
16566
+ ]);
16567
+ const defaults = React25.useMemo(() => {
16568
+ if (!selectedKey) return {
16569
+ subject: "",
16570
+ body: "",
16571
+ subjectPlaceholder: "",
16572
+ bodyPlaceholder: ""
16573
+ };
16574
+ return getPushTriggerDefaults(selectedKey, audience);
16575
+ }, [
16576
+ selectedKey,
16577
+ audience
16578
+ ]);
16579
+ React25.useEffect(() => {
16580
+ if (activeTemplate) {
16581
+ setTitle(activeTemplate.subject ?? "");
16582
+ setBody(activeTemplate.body ?? "");
16583
+ } else {
16584
+ setTitle(defaults.subject);
16585
+ setBody(defaults.body);
16586
+ }
16587
+ }, [
16588
+ activeTemplate,
16589
+ defaults,
16590
+ selectedKey
16591
+ ]);
16592
+ const insertVariable = /* @__PURE__ */ __name((varName) => {
16593
+ const token = `{{${varName}}}`;
16594
+ if (lastFocused.current === "subject" && subjectRef.current) {
16595
+ const el = subjectRef.current;
16596
+ const start = el.selectionStart ?? title.length;
16597
+ const end = el.selectionEnd ?? title.length;
16598
+ const next = title.slice(0, start) + token + title.slice(end);
16599
+ setTitle(next);
16600
+ setTimeout(() => {
16601
+ el.focus();
16602
+ el.setSelectionRange(start + token.length, start + token.length);
16603
+ }, 0);
16604
+ return;
16605
+ }
16606
+ if (bodyRef.current) {
16607
+ const el = bodyRef.current;
16608
+ const start = el.selectionStart ?? body.length;
16609
+ const end = el.selectionEnd ?? body.length;
16610
+ const next = body.slice(0, start) + token + body.slice(end);
16611
+ setBody(next);
16612
+ setTimeout(() => {
16613
+ el.focus();
16614
+ el.setSelectionRange(start + token.length, start + token.length);
16615
+ }, 0);
16616
+ } else {
16617
+ setBody((prev) => prev + token);
16618
+ }
16619
+ }, "insertVariable");
16620
+ const handleSave = /* @__PURE__ */ __name(async () => {
16621
+ if (!selectedKey) return;
16622
+ setSaving(true);
16623
+ try {
16624
+ let tplId = activeBindingTplId;
16625
+ if (tplId) {
16626
+ const res = await jsonFetch(`/api/message-templates/email/${tplId}`, {
16627
+ method: "PATCH",
16628
+ headers: {
16629
+ "Content-Type": "application/json"
16630
+ },
16631
+ body: JSON.stringify({
16632
+ name: `${audienceType}_push_${selectedKey}`,
16633
+ subject: title,
16634
+ body
16635
+ })
16636
+ });
16637
+ if (res.item) {
16638
+ setTemplates((prev) => prev.map((t) => t.id === res.item.id ? res.item : t));
16639
+ }
16640
+ } else {
16641
+ const res = await jsonFetch("/api/message-templates/email", {
16642
+ method: "POST",
16643
+ headers: {
16644
+ "Content-Type": "application/json"
16645
+ },
16646
+ body: JSON.stringify({
16647
+ name: `${audienceType}_push_${selectedKey}`,
16648
+ subject: title,
16649
+ body
16650
+ })
16651
+ });
16652
+ const createdItem = res.item || res;
16653
+ tplId = createdItem.id;
16654
+ if (createdItem) {
16655
+ setTemplates((prev) => [
16656
+ createdItem,
16657
+ ...prev
16658
+ ]);
16659
+ }
16660
+ }
16661
+ await rebind(selectedKey, tplId, `${audienceType}_push_${selectedKey}`);
16662
+ sonner.toast.success("Push notification template saved");
16663
+ await reload();
16664
+ } catch (e) {
16665
+ const msg = e instanceof Error ? e.message : "Save failed";
16666
+ sonner.toast.error(msg);
16667
+ } finally {
16668
+ setSaving(false);
16669
+ }
16670
+ }, "handleSave");
16671
+ if (loading || loadingTpl) {
16672
+ return /* @__PURE__ */ React.createElement("div", {
16673
+ className: "flex items-center gap-2 p-6 text-sm text-gray-500"
16674
+ }, /* @__PURE__ */ React.createElement(LucideIcons.Loader2, {
16675
+ className: "h-4 w-4 animate-spin"
16676
+ }), " Loading push notification triggers\u2026");
16677
+ }
16678
+ return /* @__PURE__ */ React.createElement("div", {
16679
+ className: "space-y-6"
16680
+ }, /* @__PURE__ */ React.createElement("div", {
16681
+ className: "flex flex-col sm:flex-row sm:items-center justify-between gap-4"
16682
+ }, /* @__PURE__ */ React.createElement("div", {
16683
+ className: "space-y-1"
16684
+ }, /* @__PURE__ */ React.createElement("h3", {
16685
+ className: "text-base font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2"
16686
+ }, /* @__PURE__ */ React.createElement(LucideIcons.Smartphone, {
16687
+ className: "h-5 w-5 text-gray-700 dark:text-gray-300"
16688
+ }), "Mobile Push Notification Templates"), /* @__PURE__ */ React.createElement("p", {
16689
+ className: "text-xs text-gray-500"
16690
+ }, "Configure push notification title and message body per trigger for", " ", /* @__PURE__ */ React.createElement("span", {
16691
+ className: "font-semibold capitalize text-gray-700 dark:text-gray-300"
16692
+ }, audience, "s"), ".")), /* @__PURE__ */ React.createElement(Button, {
16693
+ type: "button",
16694
+ onClick: handleSave,
16695
+ disabled: saving || !selectedKey,
16696
+ className: "gap-2 shrink-0"
16697
+ }, saving ? /* @__PURE__ */ React.createElement(LucideIcons.Loader2, {
16698
+ className: "h-4 w-4 animate-spin"
16699
+ }) : /* @__PURE__ */ React.createElement(LucideIcons.Save, {
16700
+ className: "h-4 w-4"
16701
+ }), "Save Template")), /* @__PURE__ */ React.createElement("div", {
16702
+ className: "grid grid-cols-1 md:grid-cols-4 gap-6"
16703
+ }, /* @__PURE__ */ React.createElement("div", {
16704
+ className: "md:col-span-1 space-y-2"
16705
+ }, /* @__PURE__ */ React.createElement(Label3, {
16706
+ className: "text-xs font-semibold text-gray-500 uppercase tracking-wider"
16707
+ }, "Triggers"), /* @__PURE__ */ React.createElement("div", {
16708
+ className: "flex flex-col gap-1"
16709
+ }, triggers.map((t) => {
16710
+ const isBound = bindingMap.has(t.triggerKey);
16711
+ const isSelected = t.triggerKey === selectedKey;
16712
+ return /* @__PURE__ */ React.createElement("button", {
16713
+ key: t.triggerKey,
16714
+ type: "button",
16715
+ onClick: /* @__PURE__ */ __name(() => setSelectedKey(t.triggerKey), "onClick"),
16716
+ className: `flex flex-col items-start p-3 rounded-lg border text-left transition-all ${isSelected ? "border-gray-900 bg-gray-900 text-white dark:border-gray-100 dark:bg-gray-100 dark:text-gray-900 shadow-sm" : "border-gray-200 bg-white hover:bg-gray-50 text-gray-700 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300"}`
16717
+ }, /* @__PURE__ */ React.createElement("div", {
16718
+ className: "flex items-center justify-between w-full"
16719
+ }, /* @__PURE__ */ React.createElement("span", {
16720
+ className: "font-medium text-sm"
16721
+ }, t.label), isBound && /* @__PURE__ */ React.createElement("span", {
16722
+ className: `text-[10px] px-1.5 py-0.5 rounded font-mono ${isSelected ? "bg-white/20 text-white dark:bg-black/20 dark:text-gray-900" : "bg-green-100 text-green-700 dark:bg-green-950 dark:text-green-300"}`
16723
+ }, "Configured")), /* @__PURE__ */ React.createElement("span", {
16724
+ className: `text-xs mt-1 ${isSelected ? "text-gray-300 dark:text-gray-600" : "text-gray-500 dark:text-gray-400"}`
16725
+ }, t.triggerKey));
16726
+ }))), /* @__PURE__ */ React.createElement("div", {
16727
+ className: "md:col-span-3 space-y-5 bg-white dark:bg-gray-900 p-5 rounded-xl border border-gray-200 dark:border-gray-800 shadow-sm"
16728
+ }, activeTrigger ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h4", {
16729
+ className: "text-sm font-semibold text-gray-900 dark:text-gray-100"
16730
+ }, activeTrigger.label, " (", activeTrigger.triggerKey, ")"), activeTrigger.description && /* @__PURE__ */ React.createElement("p", {
16731
+ className: "text-xs text-gray-500 mt-0.5"
16732
+ }, activeTrigger.description)), /* @__PURE__ */ React.createElement("div", {
16733
+ className: "space-y-1.5"
16734
+ }, /* @__PURE__ */ React.createElement(Label3, {
16735
+ className: "text-xs font-medium text-gray-700 dark:text-gray-300"
16736
+ }, "Insert Variables"), /* @__PURE__ */ React.createElement("div", {
16737
+ className: "flex flex-wrap gap-1.5"
16738
+ }, KNOWN_VARS.map((v) => /* @__PURE__ */ React.createElement("button", {
16739
+ key: v,
16740
+ type: "button",
16741
+ onClick: /* @__PURE__ */ __name(() => insertVariable(v), "onClick"),
16742
+ title: VAR_HINTS2[v],
16743
+ className: "px-2 py-1 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 rounded text-xs font-mono border border-gray-200 dark:border-gray-700 transition-colors"
16744
+ }, `{{${v}}}`)))), /* @__PURE__ */ React.createElement("div", {
16745
+ className: "space-y-1.5"
16746
+ }, /* @__PURE__ */ React.createElement(Label3, {
16747
+ htmlFor: "push-title",
16748
+ className: "text-xs font-medium"
16749
+ }, "Notification Title"), /* @__PURE__ */ React.createElement(Input, {
16750
+ id: "push-title",
16751
+ ref: subjectRef,
16752
+ value: title,
16753
+ onFocus: /* @__PURE__ */ __name(() => {
16754
+ lastFocused.current = "subject";
16755
+ }, "onFocus"),
16756
+ onChange: /* @__PURE__ */ __name((e) => setTitle(e.target.value), "onChange"),
16757
+ placeholder: defaults.subjectPlaceholder
16758
+ })), /* @__PURE__ */ React.createElement("div", {
16759
+ className: "space-y-1.5"
16760
+ }, /* @__PURE__ */ React.createElement(Label3, {
16761
+ htmlFor: "push-body",
16762
+ className: "text-xs font-medium"
16763
+ }, "Notification Body"), /* @__PURE__ */ React.createElement(Textarea, {
16764
+ id: "push-body",
16765
+ ref: bodyRef,
16766
+ value: body,
16767
+ onFocus: /* @__PURE__ */ __name(() => {
16768
+ lastFocused.current = "body";
16769
+ }, "onFocus"),
16770
+ onChange: /* @__PURE__ */ __name((e) => setBody(e.target.value), "onChange"),
16771
+ placeholder: defaults.bodyPlaceholder,
16772
+ className: "h-36 font-mono text-xs"
16773
+ }))) : /* @__PURE__ */ React.createElement("div", {
16774
+ className: "p-12 text-center text-sm text-gray-500"
16775
+ }, "Select a trigger from the left to configure push notification template."))));
16776
+ }
16777
+ var VAR_HINTS2;
16778
+ var init_pushTriggersBindingsPage = __esm({
16779
+ "src/admin/pages/pushTriggersBindingsPage.tsx"() {
16780
+ "use client";
16781
+ init_button();
16782
+ init_input();
16783
+ init_textarea();
16784
+ init_label();
16785
+ init_order_notification_bindings_shared();
16786
+ VAR_HINTS2 = {
16787
+ orderId: "Internal order ID",
16788
+ orderNumber: "Display order number",
16789
+ customerName: "Customer full name",
16790
+ customerPhone: "Customer phone",
16791
+ eventName: "Event title / product name",
16792
+ vendorName: "Vendor / store name",
16793
+ total: "Order total amount",
16794
+ currency: "Currency code (e.g. INR)",
16795
+ vendorId: "Vendor / store ID",
16796
+ refundAmount: "Refund amount (refund triggers)",
16797
+ invoiceNumber: "Generated invoice number",
16798
+ invoiceUrl: "Link to download invoice PDF"
16799
+ };
16800
+ __name(PushTriggerBindings, "PushTriggerBindings");
16801
+ }
16802
+ });
16803
+ function PushConfigModal({ open, onOpenChange }) {
16804
+ const [loading, setLoading] = React25.useState(false);
16805
+ const [saving, setSaving] = React25.useState(false);
16806
+ const [testing, setTesting] = React25.useState(false);
16807
+ const [serviceAccountJson, setServiceAccountJson] = React25.useState("");
16808
+ const [projectId, setProjectId] = React25.useState("");
16809
+ const [clientEmail, setClientEmail] = React25.useState("");
16810
+ const [privateKey, setPrivateKey] = React25.useState("");
16811
+ const [isValidJson, setIsValidJson] = React25.useState(false);
16812
+ const [testToken, setTestToken] = React25.useState("");
16813
+ const [testTitle, setTestTitle] = React25.useState("Test Push Notification");
16814
+ const [testBody, setTestBody] = React25.useState("Firebase Push Notification setup is working!");
16815
+ React25.useEffect(() => {
16816
+ if (!open) return;
16817
+ let cancelled = false;
16818
+ (async () => {
16819
+ try {
16820
+ setLoading(true);
16821
+ const res = await fetch("/api/settings/push", {
16822
+ cache: "no-store"
16823
+ });
16824
+ if (res.ok) {
16825
+ const data = await res.json();
16826
+ if (!cancelled && data) {
16827
+ const rawJson = data.firebase_service_account_json || "";
16828
+ setServiceAccountJson(rawJson);
16829
+ if (rawJson.trim()) {
16830
+ parseAndExtract(rawJson, false);
16831
+ } else {
16832
+ setProjectId(data.firebase_project_id || "");
16833
+ setClientEmail(data.firebase_client_email || "");
16834
+ setPrivateKey(data.firebase_private_key || "");
16835
+ }
16836
+ }
16837
+ }
16838
+ } catch {
16839
+ sonner.toast.error("Failed to load push configuration");
16840
+ } finally {
16841
+ if (!cancelled) setLoading(false);
16842
+ }
16843
+ })();
16844
+ return () => {
16845
+ cancelled = true;
16846
+ };
16847
+ }, [
16848
+ open
16849
+ ]);
16850
+ const parseAndExtract = /* @__PURE__ */ __name((input, showToast = true) => {
16851
+ if (!input.trim()) {
16852
+ setIsValidJson(false);
16853
+ return;
16854
+ }
16855
+ try {
16856
+ const parsed = JSON.parse(input);
16857
+ if (parsed.project_id) setProjectId(parsed.project_id);
16858
+ if (parsed.client_email) setClientEmail(parsed.client_email);
16859
+ if (parsed.private_key) setPrivateKey(parsed.private_key);
16860
+ setIsValidJson(true);
16861
+ if (showToast) {
16862
+ sonner.toast.success("Valid Service Account JSON loaded");
16863
+ }
16864
+ } catch {
16865
+ setIsValidJson(false);
16866
+ if (showToast) {
16867
+ sonner.toast.error("Invalid Service Account JSON format");
16868
+ }
16869
+ }
16870
+ }, "parseAndExtract");
16871
+ const handleJsonChange = /* @__PURE__ */ __name((val) => {
16872
+ setServiceAccountJson(val);
16873
+ if (val.trim()) {
16874
+ parseAndExtract(val, false);
16875
+ } else {
16876
+ setIsValidJson(false);
16877
+ }
16878
+ }, "handleJsonChange");
16879
+ const handleSaveConfig = /* @__PURE__ */ __name(async () => {
16880
+ if (serviceAccountJson.trim() && !isValidJson) {
16881
+ sonner.toast.error("Please enter a valid Firebase Service Account JSON file");
16882
+ return;
16883
+ }
16884
+ setSaving(true);
16885
+ try {
16886
+ const res = await fetch("/api/settings/push", {
16887
+ method: "PUT",
16888
+ headers: {
16889
+ "Content-Type": "application/json"
16890
+ },
16891
+ body: JSON.stringify({
16892
+ firebase_service_account_json: {
16893
+ value: serviceAccountJson.trim(),
16894
+ type: "secret"
16895
+ },
16896
+ firebase_project_id: {
16897
+ value: projectId.trim(),
16898
+ type: "secret"
16899
+ },
16900
+ firebase_client_email: {
16901
+ value: clientEmail.trim(),
16902
+ type: "secret"
16903
+ },
16904
+ firebase_private_key: {
16905
+ value: privateKey.trim(),
16906
+ type: "secret"
16907
+ }
16908
+ })
16909
+ });
16910
+ if (!res.ok) {
16911
+ sonner.toast.error("Failed to save push configuration");
16912
+ return;
16913
+ }
16914
+ sonner.toast.success("Firebase Service Account JSON saved successfully");
16915
+ onOpenChange(false);
16916
+ } catch {
16917
+ sonner.toast.error("Failed to save push configuration");
16918
+ } finally {
16919
+ setSaving(false);
16920
+ }
16921
+ }, "handleSaveConfig");
16922
+ const handleSendTestPush = /* @__PURE__ */ __name(async () => {
16923
+ if (!testToken.trim()) {
16924
+ sonner.toast.error("Please enter a target FCM device token to test");
16925
+ return;
16926
+ }
16927
+ setTesting(true);
16928
+ try {
16929
+ const res = await fetch("/api/settings/push/test", {
16930
+ method: "POST",
16931
+ headers: {
16932
+ "Content-Type": "application/json"
16933
+ },
16934
+ body: JSON.stringify({
16935
+ token: testToken.trim(),
16936
+ title: testTitle.trim(),
16937
+ body: testBody.trim()
16938
+ })
16939
+ });
16940
+ const data = await res.json();
16941
+ if (!res.ok || !data.success) {
16942
+ sonner.toast.error(data.error || "Failed to send test push notification");
16943
+ return;
16944
+ }
16945
+ sonner.toast.success("Test push notification sent successfully!");
16946
+ } catch {
16947
+ sonner.toast.error("Failed to send test push notification");
16948
+ } finally {
16949
+ setTesting(false);
16950
+ }
16951
+ }, "handleSendTestPush");
16952
+ if (!open) return null;
16953
+ return /* @__PURE__ */ React.createElement("div", {
16954
+ className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4 backdrop-blur-sm animate-in fade-in duration-150"
16955
+ }, /* @__PURE__ */ React.createElement("div", {
16956
+ className: "w-full max-w-2xl max-h-[90vh] overflow-y-auto rounded-xl bg-white p-6 shadow-2xl dark:bg-gray-900 border border-gray-200 dark:border-gray-800 space-y-6"
16957
+ }, /* @__PURE__ */ React.createElement("div", {
16958
+ className: "flex items-center justify-between border-b border-gray-200 pb-4 dark:border-gray-800"
16959
+ }, /* @__PURE__ */ React.createElement("div", {
16960
+ className: "flex items-center gap-2"
16961
+ }, /* @__PURE__ */ React.createElement(LucideIcons.Smartphone, {
16962
+ className: "h-5 w-5 text-gray-800 dark:text-gray-200"
16963
+ }), /* @__PURE__ */ React.createElement("h2", {
16964
+ className: "text-lg font-semibold text-gray-900 dark:text-gray-100"
16965
+ }, "Firebase Service Account JSON Configuration")), /* @__PURE__ */ React.createElement("button", {
16966
+ type: "button",
16967
+ onClick: /* @__PURE__ */ __name(() => onOpenChange(false), "onClick"),
16968
+ className: "text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
16969
+ }, "\u2715")), loading ? /* @__PURE__ */ React.createElement("div", {
16970
+ className: "flex items-center justify-center py-12"
16971
+ }, /* @__PURE__ */ React.createElement(LucideIcons.Loader2, {
16972
+ className: "h-6 w-6 animate-spin text-gray-500"
16973
+ })) : /* @__PURE__ */ React.createElement("div", {
16974
+ className: "space-y-6"
16975
+ }, /* @__PURE__ */ React.createElement("div", {
16976
+ className: "space-y-2"
16977
+ }, /* @__PURE__ */ React.createElement(Label3, {
16978
+ htmlFor: "service-account-json",
16979
+ className: "text-xs font-semibold text-gray-800 dark:text-gray-200 flex items-center justify-between"
16980
+ }, /* @__PURE__ */ React.createElement("span", {
16981
+ className: "flex items-center gap-1.5"
16982
+ }, /* @__PURE__ */ React.createElement(LucideIcons.KeyRound, {
16983
+ className: "h-4 w-4 text-blue-500"
16984
+ }), "Firebase Service Account JSON File"), isValidJson && /* @__PURE__ */ React.createElement("span", {
16985
+ className: "text-xs font-normal text-green-600 flex items-center gap-1"
16986
+ }, /* @__PURE__ */ React.createElement(LucideIcons.CheckCircle2, {
16987
+ className: "h-3.5 w-3.5"
16988
+ }), " Valid JSON")), /* @__PURE__ */ React.createElement(Textarea, {
16989
+ id: "service-account-json",
16990
+ value: serviceAccountJson,
16991
+ onChange: /* @__PURE__ */ __name((e) => handleJsonChange(e.target.value), "onChange"),
16992
+ placeholder: `Paste your entire service-account.json content here...
16993
+ {
16994
+ "type": "service_account",
16995
+ "project_id": "...",
16996
+ "private_key": "...",
16997
+ "client_email": "..."
16998
+ }`,
16999
+ className: "h-44 font-mono text-xs border-gray-300 dark:border-gray-700"
17000
+ })), isValidJson && /* @__PURE__ */ React.createElement("div", {
17001
+ className: "rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-800 dark:bg-gray-800/40 space-y-3"
17002
+ }, /* @__PURE__ */ React.createElement(Label3, {
17003
+ className: "text-xs font-semibold text-gray-700 dark:text-gray-300"
17004
+ }, "Extracted Firebase Credentials Preview"), /* @__PURE__ */ React.createElement("div", {
17005
+ className: "grid grid-cols-1 sm:grid-cols-2 gap-3 text-xs"
17006
+ }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("span", {
17007
+ className: "font-medium text-gray-500"
17008
+ }, "Project ID:"), /* @__PURE__ */ React.createElement("p", {
17009
+ className: "font-mono text-gray-900 dark:text-gray-100 truncate"
17010
+ }, projectId || "\u2014")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("span", {
17011
+ className: "font-medium text-gray-500"
17012
+ }, "Client Email:"), /* @__PURE__ */ React.createElement("p", {
17013
+ className: "font-mono text-gray-900 dark:text-gray-100 truncate"
17014
+ }, clientEmail || "\u2014")))), /* @__PURE__ */ React.createElement("div", {
17015
+ className: "rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-800 dark:bg-gray-800/40 space-y-3"
17016
+ }, /* @__PURE__ */ React.createElement(Label3, {
17017
+ className: "text-xs font-semibold text-gray-800 dark:text-gray-200 flex items-center gap-1.5"
17018
+ }, /* @__PURE__ */ React.createElement(LucideIcons.Send, {
17019
+ className: "h-3.5 w-3.5 text-green-500"
17020
+ }), "Test Push Notification (Target Device Token)"), /* @__PURE__ */ React.createElement("div", {
17021
+ className: "grid grid-cols-1 sm:grid-cols-2 gap-2"
17022
+ }, /* @__PURE__ */ React.createElement(Input, {
17023
+ value: testTitle,
17024
+ onChange: /* @__PURE__ */ __name((e) => setTestTitle(e.target.value), "onChange"),
17025
+ placeholder: "Test Notification Title",
17026
+ className: "text-xs"
17027
+ }), /* @__PURE__ */ React.createElement(Input, {
17028
+ value: testBody,
17029
+ onChange: /* @__PURE__ */ __name((e) => setTestBody(e.target.value), "onChange"),
17030
+ placeholder: "Test Notification Body",
17031
+ className: "text-xs"
17032
+ })), /* @__PURE__ */ React.createElement("div", {
17033
+ className: "flex gap-2"
17034
+ }, /* @__PURE__ */ React.createElement(Input, {
17035
+ value: testToken,
17036
+ onChange: /* @__PURE__ */ __name((e) => setTestToken(e.target.value), "onChange"),
17037
+ placeholder: "Target FCM Device Token",
17038
+ className: "text-xs font-mono"
17039
+ }), /* @__PURE__ */ React.createElement(Button, {
17040
+ type: "button",
17041
+ variant: "secondary",
17042
+ size: "sm",
17043
+ disabled: testing,
17044
+ onClick: handleSendTestPush,
17045
+ className: "shrink-0 gap-1.5"
17046
+ }, testing ? /* @__PURE__ */ React.createElement(LucideIcons.Loader2, {
17047
+ className: "h-3.5 w-3.5 animate-spin"
17048
+ }) : /* @__PURE__ */ React.createElement(LucideIcons.Send, {
17049
+ className: "h-3.5 w-3.5"
17050
+ }), "Send Test"))), /* @__PURE__ */ React.createElement("div", {
17051
+ className: "flex justify-end gap-2 border-t border-gray-200 pt-4 dark:border-gray-800"
17052
+ }, /* @__PURE__ */ React.createElement(Button, {
17053
+ type: "button",
17054
+ variant: "outline",
17055
+ onClick: /* @__PURE__ */ __name(() => onOpenChange(false), "onClick")
17056
+ }, "Cancel"), /* @__PURE__ */ React.createElement(Button, {
17057
+ type: "button",
17058
+ disabled: saving,
17059
+ onClick: handleSaveConfig,
17060
+ className: "gap-1.5"
17061
+ }, saving && /* @__PURE__ */ React.createElement(LucideIcons.Loader2, {
17062
+ className: "h-4 w-4 animate-spin"
17063
+ }), "Save Service Account JSON")))));
17064
+ }
17065
+ var init_PushConfigModal = __esm({
17066
+ "src/admin/components/PushConfigModal.tsx"() {
17067
+ "use client";
17068
+ init_button();
17069
+ init_input();
17070
+ init_textarea();
17071
+ init_label();
17072
+ __name(PushConfigModal, "PushConfigModal");
17073
+ }
17074
+ });
16306
17075
  function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledChange, onSaveSettings, saving }) {
16307
17076
  const [mainTab, setMainTab] = React25.useState("email");
16308
17077
  const [audienceTab, setAudienceTab] = React25.useState("customer");
@@ -16310,6 +17079,10 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
16310
17079
  const [emailVendorEnabled, setEmailVendorEnabled] = React25.useState(true);
16311
17080
  const [emailAdminEnabled, setEmailAdminEnabled] = React25.useState(true);
16312
17081
  const [whatsappEnabled, setWhatsappEnabled] = React25.useState(true);
17082
+ const [pushCustomerEnabled, setPushCustomerEnabled] = React25.useState(true);
17083
+ const [pushVendorEnabled, setPushVendorEnabled] = React25.useState(true);
17084
+ const [pushAdminEnabled, setPushAdminEnabled] = React25.useState(true);
17085
+ const [showPushConfigModal, setShowPushConfigModal] = React25.useState(false);
16313
17086
  const [multiVendorOn, setMultiVendorOn] = React25.useState(true);
16314
17087
  const [loading, setLoading] = React25.useState(true);
16315
17088
  const [savingChannel, setSavingChannel] = React25.useState(false);
@@ -16340,6 +17113,9 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
16340
17113
  setEmailVendorEnabled(data.email_vendor_enabled !== "false" && data.email_vendor_enabled !== "0");
16341
17114
  setEmailAdminEnabled(data.email_admin_enabled !== "false" && data.email_admin_enabled !== "0");
16342
17115
  setWhatsappEnabled(data.whatsapp_enabled !== "false" && data.whatsapp_enabled !== "0");
17116
+ setPushCustomerEnabled(data.push_customer_enabled !== "false" && data.push_customer_enabled !== "0");
17117
+ setPushVendorEnabled(data.push_vendor_enabled !== "false" && data.push_vendor_enabled !== "0");
17118
+ setPushAdminEnabled(data.push_admin_enabled !== "false" && data.push_admin_enabled !== "0");
16343
17119
  }
16344
17120
  }
16345
17121
  } catch {
@@ -16357,7 +17133,8 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
16357
17133
  setSavingChannel(true);
16358
17134
  try {
16359
17135
  const isAnyEmailEnabled = emailCustomerEnabled || emailVendorEnabled || emailAdminEnabled;
16360
- const isAnyChannelEnabled = isAnyEmailEnabled || whatsappEnabled;
17136
+ const isAnyPushEnabled = pushCustomerEnabled || pushVendorEnabled || pushAdminEnabled;
17137
+ const isAnyChannelEnabled = isAnyEmailEnabled || whatsappEnabled || isAnyPushEnabled;
16361
17138
  const res = await fetch(`/api/settings/${settingsGroup}`, {
16362
17139
  method: "PUT",
16363
17140
  headers: {
@@ -16384,6 +17161,22 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
16384
17161
  value: whatsappEnabled ? "true" : "false",
16385
17162
  type: "public"
16386
17163
  },
17164
+ push_enabled: {
17165
+ value: isAnyPushEnabled ? "true" : "false",
17166
+ type: "public"
17167
+ },
17168
+ push_customer_enabled: {
17169
+ value: pushCustomerEnabled ? "true" : "false",
17170
+ type: "public"
17171
+ },
17172
+ push_vendor_enabled: {
17173
+ value: pushVendorEnabled ? "true" : "false",
17174
+ type: "public"
17175
+ },
17176
+ push_admin_enabled: {
17177
+ value: pushAdminEnabled ? "true" : "false",
17178
+ type: "public"
17179
+ },
16387
17180
  enabled: {
16388
17181
  value: isAnyChannelEnabled ? "true" : "false",
16389
17182
  type: "public"
@@ -16405,7 +17198,10 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
16405
17198
  }, "handleSaveChannels");
16406
17199
  return /* @__PURE__ */ React.createElement("div", {
16407
17200
  className: "space-y-5"
16408
- }, /* @__PURE__ */ React.createElement("div", {
17201
+ }, /* @__PURE__ */ React.createElement(PushConfigModal, {
17202
+ open: showPushConfigModal,
17203
+ onOpenChange: setShowPushConfigModal
17204
+ }), /* @__PURE__ */ React.createElement("div", {
16409
17205
  className: "border-b border-gray-200 dark:border-gray-700"
16410
17206
  }, /* @__PURE__ */ React.createElement("div", {
16411
17207
  className: "flex gap-1"
@@ -16421,7 +17217,13 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
16421
17217
  className: `inline-flex items-center gap-2 px-4 py-2.5 text-sm font-medium border-b-2 ${mainTab === "whatsapp" ? "border-gray-800 text-gray-800 dark:border-gray-200 dark:text-gray-100" : "border-transparent text-gray-500 hover:text-gray-700"}`
16422
17218
  }, /* @__PURE__ */ React.createElement(LucideIcons.MessageCircle, {
16423
17219
  className: "h-4 w-4"
16424
- }), "WhatsApp template"))), mainTab === "email" && /* @__PURE__ */ React.createElement("div", {
17220
+ }), "WhatsApp template"), /* @__PURE__ */ React.createElement("button", {
17221
+ type: "button",
17222
+ onClick: /* @__PURE__ */ __name(() => setMainTab("push"), "onClick"),
17223
+ className: `inline-flex items-center gap-2 px-4 py-2.5 text-sm font-medium border-b-2 ${mainTab === "push" ? "border-gray-800 text-gray-800 dark:border-gray-200 dark:text-gray-100" : "border-transparent text-gray-500 hover:text-gray-700"}`
17224
+ }, /* @__PURE__ */ React.createElement(LucideIcons.Smartphone, {
17225
+ className: "h-4 w-4"
17226
+ }), "Mobile notification template"))), mainTab === "email" && /* @__PURE__ */ React.createElement("div", {
16425
17227
  className: "space-y-5"
16426
17228
  }, /* @__PURE__ */ React.createElement("div", {
16427
17229
  className: "flex rounded-md bg-gray-100 p-1 dark:bg-gray-800/60 w-fit"
@@ -16455,7 +17257,7 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
16455
17257
  className: "h-4 w-4 text-blue-600 dark:text-blue-400"
16456
17258
  }), "Enable Customer Email Notifications"), /* @__PURE__ */ React.createElement("p", {
16457
17259
  className: "text-xs text-gray-500 dark:text-gray-400"
16458
- }, "When enabled, order confirmations, tickets, and status updates are emailed to customers.")), /* @__PURE__ */ React.createElement(Switch, {
17260
+ }, "When enabled, order updates, invoices, and ticket details are emailed to customers.")), /* @__PURE__ */ React.createElement(Switch, {
16459
17261
  id: "email-customer-notifications-enabled",
16460
17262
  checked: emailCustomerEnabled,
16461
17263
  onCheckedChange: setEmailCustomerEnabled,
@@ -16471,10 +17273,10 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
16471
17273
  }, /* @__PURE__ */ React.createElement("div", {
16472
17274
  className: "text-sm font-medium text-gray-900 dark:text-gray-100 flex items-center gap-2"
16473
17275
  }, /* @__PURE__ */ React.createElement(LucideIcons.Store, {
16474
- className: "h-4 w-4 text-purple-600 dark:text-purple-400"
17276
+ className: "h-4 w-4 text-purple-600 dark:purple-400"
16475
17277
  }), "Enable Vendor Email Notifications"), /* @__PURE__ */ React.createElement("p", {
16476
17278
  className: "text-xs text-gray-500 dark:text-gray-400"
16477
- }, "When enabled, vendors are emailed automatically when order is placed/cancelled for their products.")), /* @__PURE__ */ React.createElement(Switch, {
17279
+ }, "When enabled, vendors receive email notifications for new orders assigned to them.")), /* @__PURE__ */ React.createElement(Switch, {
16478
17280
  id: "email-vendor-notifications-enabled",
16479
17281
  checked: emailVendorEnabled,
16480
17282
  onCheckedChange: setEmailVendorEnabled,
@@ -16517,7 +17319,104 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
16517
17319
  checked: whatsappEnabled,
16518
17320
  onCheckedChange: setWhatsappEnabled,
16519
17321
  disabled: loading
16520
- })), /* @__PURE__ */ React.createElement(WhatsAppTriggerBindings, null)), /* @__PURE__ */ React.createElement(Button, {
17322
+ })), /* @__PURE__ */ React.createElement(WhatsAppTriggerBindings, null)), mainTab === "push" && /* @__PURE__ */ React.createElement("div", {
17323
+ className: "space-y-5"
17324
+ }, /* @__PURE__ */ React.createElement("div", {
17325
+ className: "flex items-center justify-between gap-4 rounded-lg border border-blue-100 bg-blue-50/50 p-4 dark:border-blue-900/40 dark:bg-blue-950/20"
17326
+ }, /* @__PURE__ */ React.createElement("div", {
17327
+ className: "space-y-0.5"
17328
+ }, /* @__PURE__ */ React.createElement("div", {
17329
+ className: "text-sm font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2"
17330
+ }, /* @__PURE__ */ React.createElement(LucideIcons.Smartphone, {
17331
+ className: "h-4 w-4 text-blue-600 dark:text-blue-400"
17332
+ }), "Firebase Cloud Messaging (FCM) Setup"), /* @__PURE__ */ React.createElement("p", {
17333
+ className: "text-xs text-gray-500 dark:text-gray-400"
17334
+ }, "Set up your Firebase project credentials to send mobile push notifications to iOS and Android apps.")), /* @__PURE__ */ React.createElement(Button, {
17335
+ type: "button",
17336
+ variant: "outline",
17337
+ size: "sm",
17338
+ onClick: /* @__PURE__ */ __name(() => setShowPushConfigModal(true), "onClick"),
17339
+ className: "gap-1.5 bg-white dark:bg-gray-900 shrink-0"
17340
+ }, /* @__PURE__ */ React.createElement(LucideIcons.Settings, {
17341
+ className: "h-4 w-4 text-gray-600"
17342
+ }), "Set Configuration")), /* @__PURE__ */ React.createElement("div", {
17343
+ className: "flex rounded-md bg-gray-100 p-1 dark:bg-gray-800/60 w-fit"
17344
+ }, /* @__PURE__ */ React.createElement("button", {
17345
+ type: "button",
17346
+ onClick: /* @__PURE__ */ __name(() => setAudienceTab("customer"), "onClick"),
17347
+ className: `inline-flex items-center gap-1.5 rounded px-3 py-1.5 text-xs font-medium transition-all ${audienceTab === "customer" ? "bg-white text-gray-900 shadow-sm dark:bg-gray-900 dark:text-white" : "text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"}`
17348
+ }, /* @__PURE__ */ React.createElement(LucideIcons.User, {
17349
+ className: "h-3.5 w-3.5 text-blue-500"
17350
+ }), "Customers"), multiVendorOn && /* @__PURE__ */ React.createElement("button", {
17351
+ type: "button",
17352
+ onClick: /* @__PURE__ */ __name(() => setAudienceTab("vendor"), "onClick"),
17353
+ className: `inline-flex items-center gap-1.5 rounded px-3 py-1.5 text-xs font-medium transition-all ${audienceTab === "vendor" ? "bg-white text-gray-900 shadow-sm dark:bg-gray-900 dark:text-white" : "text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"}`
17354
+ }, /* @__PURE__ */ React.createElement(LucideIcons.Store, {
17355
+ className: "h-3.5 w-3.5 text-purple-500"
17356
+ }), "Vendors"), /* @__PURE__ */ React.createElement("button", {
17357
+ type: "button",
17358
+ onClick: /* @__PURE__ */ __name(() => setAudienceTab("admin"), "onClick"),
17359
+ className: `inline-flex items-center gap-1.5 rounded px-3 py-1.5 text-xs font-medium transition-all ${audienceTab === "admin" ? "bg-white text-gray-900 shadow-sm dark:bg-gray-900 dark:text-white" : "text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"}`
17360
+ }, /* @__PURE__ */ React.createElement(LucideIcons.ShieldCheck, {
17361
+ className: "h-3.5 w-3.5 text-amber-500"
17362
+ }), "Admin")), audienceTab === "customer" && /* @__PURE__ */ React.createElement("div", {
17363
+ className: "space-y-4"
17364
+ }, /* @__PURE__ */ React.createElement("div", {
17365
+ className: "flex items-center justify-between gap-4 rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-800 dark:bg-gray-900/50"
17366
+ }, /* @__PURE__ */ React.createElement("div", {
17367
+ className: "space-y-0.5"
17368
+ }, /* @__PURE__ */ React.createElement("div", {
17369
+ className: "text-sm font-medium text-gray-900 dark:text-gray-100 flex items-center gap-2"
17370
+ }, /* @__PURE__ */ React.createElement(LucideIcons.User, {
17371
+ className: "h-4 w-4 text-blue-600 dark:text-blue-400"
17372
+ }), "Enable Customer Mobile Push Notifications"), /* @__PURE__ */ React.createElement("p", {
17373
+ className: "text-xs text-gray-500 dark:text-gray-400"
17374
+ }, "When enabled, order confirmation alerts and updates are pushed directly to customer mobile apps.")), /* @__PURE__ */ React.createElement(Switch, {
17375
+ id: "push-customer-notifications-enabled",
17376
+ checked: pushCustomerEnabled,
17377
+ onCheckedChange: setPushCustomerEnabled,
17378
+ disabled: loading
17379
+ })), /* @__PURE__ */ React.createElement(PushTriggerBindings, {
17380
+ audience: "customer"
17381
+ })), audienceTab === "vendor" && multiVendorOn && /* @__PURE__ */ React.createElement("div", {
17382
+ className: "space-y-4"
17383
+ }, /* @__PURE__ */ React.createElement("div", {
17384
+ className: "flex items-center justify-between gap-4 rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-800 dark:bg-gray-900/50"
17385
+ }, /* @__PURE__ */ React.createElement("div", {
17386
+ className: "space-y-0.5"
17387
+ }, /* @__PURE__ */ React.createElement("div", {
17388
+ className: "text-sm font-medium text-gray-900 dark:text-gray-100 flex items-center gap-2"
17389
+ }, /* @__PURE__ */ React.createElement(LucideIcons.Store, {
17390
+ className: "h-4 w-4 text-purple-600 dark:text-purple-400"
17391
+ }), "Enable Vendor Mobile Push Notifications"), /* @__PURE__ */ React.createElement("p", {
17392
+ className: "text-xs text-gray-500 dark:text-gray-400"
17393
+ }, "When enabled, vendor mobile app receives instant push alerts for assigned orders.")), /* @__PURE__ */ React.createElement(Switch, {
17394
+ id: "push-vendor-notifications-enabled",
17395
+ checked: pushVendorEnabled,
17396
+ onCheckedChange: setPushVendorEnabled,
17397
+ disabled: loading
17398
+ })), /* @__PURE__ */ React.createElement(PushTriggerBindings, {
17399
+ audience: "vendor"
17400
+ })), audienceTab === "admin" && /* @__PURE__ */ React.createElement("div", {
17401
+ className: "space-y-4"
17402
+ }, /* @__PURE__ */ React.createElement("div", {
17403
+ className: "flex items-center justify-between gap-4 rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-800 dark:bg-gray-900/50"
17404
+ }, /* @__PURE__ */ React.createElement("div", {
17405
+ className: "space-y-0.5"
17406
+ }, /* @__PURE__ */ React.createElement("div", {
17407
+ className: "text-sm font-medium text-gray-900 dark:text-gray-100 flex items-center gap-2"
17408
+ }, /* @__PURE__ */ React.createElement(LucideIcons.ShieldCheck, {
17409
+ className: "h-4 w-4 text-amber-600 dark:text-amber-400"
17410
+ }), "Enable Admin Mobile Push Notifications"), /* @__PURE__ */ React.createElement("p", {
17411
+ className: "text-xs text-gray-500 dark:text-gray-400"
17412
+ }, "When enabled, store administrators receive mobile push alerts for all orders placed/cancelled.")), /* @__PURE__ */ React.createElement(Switch, {
17413
+ id: "push-admin-notifications-enabled",
17414
+ checked: pushAdminEnabled,
17415
+ onCheckedChange: setPushAdminEnabled,
17416
+ disabled: loading
17417
+ })), /* @__PURE__ */ React.createElement(PushTriggerBindings, {
17418
+ audience: "admin"
17419
+ }))), /* @__PURE__ */ React.createElement(Button, {
16521
17420
  size: "sm",
16522
17421
  onClick: /* @__PURE__ */ __name(() => void handleSaveChannels(), "onClick"),
16523
17422
  disabled: saving || savingChannel || loading,
@@ -16533,9 +17432,207 @@ var init_EventNotificationsPluginSettings = __esm({
16533
17432
  init_switch();
16534
17433
  init_whatsappTriggerBindingsPage();
16535
17434
  init_emailTriggersBindingsPage();
17435
+ init_pushTriggersBindingsPage();
17436
+ init_PushConfigModal();
16536
17437
  __name(EventNotificationsPluginSettings, "EventNotificationsPluginSettings");
16537
17438
  }
16538
17439
  });
17440
+ function googleRedirectUri() {
17441
+ if (typeof window === "undefined") {
17442
+ const path2 = typeof process !== "undefined" && process.env.NEXT_PUBLIC_GOOGLE_OAUTH_CALLBACK_PATH?.trim() || "/api/auth/callback/google";
17443
+ return path2.startsWith("http") ? path2 : path2;
17444
+ }
17445
+ const configured = process.env.NEXT_PUBLIC_GOOGLE_OAUTH_CALLBACK_PATH?.trim();
17446
+ if (configured?.startsWith("http")) return configured;
17447
+ const path = configured || "/api/auth/callback/google";
17448
+ return `${window.location.origin}${path.startsWith("/") ? path : `/${path}`}`;
17449
+ }
17450
+ function AuthProvidersPluginSettings({ settingsGroup, onEnabledChange }) {
17451
+ const [loading, setLoading] = React25.useState(true);
17452
+ const [saving, setSaving] = React25.useState(false);
17453
+ const [googleEnabled, setGoogleEnabled] = React25.useState(false);
17454
+ const [googleClientId, setGoogleClientId] = React25.useState("");
17455
+ const [googleClientSecret, setGoogleClientSecret] = React25.useState("");
17456
+ const [hasSavedSecret, setHasSavedSecret] = React25.useState(false);
17457
+ const [copied, setCopied] = React25.useState(false);
17458
+ const onEnabledChangeRef = React25.useRef(onEnabledChange);
17459
+ onEnabledChangeRef.current = onEnabledChange;
17460
+ const redirectUri = React25.useMemo(() => googleRedirectUri(), []);
17461
+ React25.useEffect(() => {
17462
+ let cancelled = false;
17463
+ (async () => {
17464
+ setLoading(true);
17465
+ try {
17466
+ const res = await fetch(`/api/settings/${settingsGroup}`, {
17467
+ cache: "no-store"
17468
+ });
17469
+ const data = res.ok ? await res.json() : {};
17470
+ if (cancelled) return;
17471
+ const enabled = data.googleEnabled === "true" || data.googleEnabled === "1" || data.enabled === "true" && data.googleEnabled !== "false";
17472
+ setGoogleEnabled(enabled);
17473
+ setGoogleClientId(String(data.googleClientId ?? "").trim());
17474
+ const secret = String(data.googleClientSecret ?? "").trim();
17475
+ setGoogleClientSecret("");
17476
+ setHasSavedSecret(Boolean(secret));
17477
+ } catch {
17478
+ } finally {
17479
+ if (!cancelled) setLoading(false);
17480
+ }
17481
+ })();
17482
+ return () => {
17483
+ cancelled = true;
17484
+ };
17485
+ }, [
17486
+ settingsGroup
17487
+ ]);
17488
+ const handleCopyRedirect = /* @__PURE__ */ __name(async () => {
17489
+ try {
17490
+ await navigator.clipboard.writeText(redirectUri);
17491
+ setCopied(true);
17492
+ sonner.toast.success("Redirect URI copied");
17493
+ setTimeout(() => setCopied(false), 2e3);
17494
+ } catch {
17495
+ sonner.toast.error("Could not copy redirect URI");
17496
+ }
17497
+ }, "handleCopyRedirect");
17498
+ const handleSave = /* @__PURE__ */ __name(async () => {
17499
+ setSaving(true);
17500
+ try {
17501
+ const payload = {
17502
+ enabled: {
17503
+ value: googleEnabled ? "true" : "false",
17504
+ type: "public"
17505
+ },
17506
+ googleEnabled: {
17507
+ value: googleEnabled ? "true" : "false",
17508
+ type: "public"
17509
+ },
17510
+ googleClientId: {
17511
+ value: googleClientId.trim(),
17512
+ type: "private"
17513
+ }
17514
+ };
17515
+ if (googleClientSecret.trim()) {
17516
+ payload.googleClientSecret = {
17517
+ value: googleClientSecret.trim(),
17518
+ type: "private",
17519
+ encrypted: true
17520
+ };
17521
+ }
17522
+ const res = await fetch(`/api/settings/${settingsGroup}`, {
17523
+ method: "PUT",
17524
+ headers: {
17525
+ "Content-Type": "application/json"
17526
+ },
17527
+ body: JSON.stringify(payload)
17528
+ });
17529
+ if (!res.ok) {
17530
+ sonner.toast.error("Failed to save Google auth settings");
17531
+ return;
17532
+ }
17533
+ if (googleClientSecret.trim()) {
17534
+ setHasSavedSecret(true);
17535
+ setGoogleClientSecret("");
17536
+ }
17537
+ onEnabledChangeRef.current?.(googleEnabled);
17538
+ sonner.toast.success("Authentication provider settings saved");
17539
+ } catch {
17540
+ sonner.toast.error("Failed to save Google auth settings");
17541
+ } finally {
17542
+ setSaving(false);
17543
+ }
17544
+ }, "handleSave");
17545
+ if (loading) {
17546
+ return /* @__PURE__ */ React.createElement("p", {
17547
+ className: "text-sm text-gray-500 dark:text-gray-400"
17548
+ }, "Loading\u2026");
17549
+ }
17550
+ return /* @__PURE__ */ React.createElement("div", {
17551
+ className: "space-y-4"
17552
+ }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", {
17553
+ className: "text-sm font-semibold text-gray-900 dark:text-white"
17554
+ }, "Google (Web)"), /* @__PURE__ */ React.createElement("p", {
17555
+ className: "mt-1 text-xs text-gray-500 dark:text-gray-400"
17556
+ }, "Create a Web OAuth client in Google Cloud Console, add the redirect URI below, then paste Client ID and Client Secret here. Only existing admin/vendor users (matching Google email) can sign in.")), /* @__PURE__ */ React.createElement("div", {
17557
+ className: "flex items-center justify-between gap-2"
17558
+ }, /* @__PURE__ */ React.createElement(Label3, {
17559
+ htmlFor: `${settingsGroup}-googleEnabled`,
17560
+ className: "text-sm"
17561
+ }, "Enable Google sign-in"), /* @__PURE__ */ React.createElement(Switch, {
17562
+ id: `${settingsGroup}-googleEnabled`,
17563
+ checked: googleEnabled,
17564
+ onCheckedChange: setGoogleEnabled
17565
+ })), /* @__PURE__ */ React.createElement("div", {
17566
+ className: "space-y-1"
17567
+ }, /* @__PURE__ */ React.createElement(Label3, {
17568
+ htmlFor: `${settingsGroup}-redirectUri`,
17569
+ className: "text-sm"
17570
+ }, "Redirect URI (add in Google Console)"), /* @__PURE__ */ React.createElement("div", {
17571
+ className: "flex gap-2"
17572
+ }, /* @__PURE__ */ React.createElement(Input, {
17573
+ id: `${settingsGroup}-redirectUri`,
17574
+ value: redirectUri,
17575
+ readOnly: true,
17576
+ className: "h-9 flex-1 font-mono text-xs"
17577
+ }), /* @__PURE__ */ React.createElement(Button, {
17578
+ type: "button",
17579
+ size: "sm",
17580
+ variant: "outline",
17581
+ onClick: handleCopyRedirect,
17582
+ className: "shrink-0 gap-1"
17583
+ }, copied ? /* @__PURE__ */ React.createElement(LucideIcons.Check, {
17584
+ className: "h-3.5 w-3.5"
17585
+ }) : /* @__PURE__ */ React.createElement(LucideIcons.Copy, {
17586
+ className: "h-3.5 w-3.5"
17587
+ }), copied ? "Copied" : "Copy")), /* @__PURE__ */ React.createElement("p", {
17588
+ className: "text-xs text-gray-500 dark:text-gray-400"
17589
+ }, "Must match ", /* @__PURE__ */ React.createElement("code", {
17590
+ className: "text-[11px]"
17591
+ }, "NEXTAUTH_URL"), " for this environment.")), /* @__PURE__ */ React.createElement("div", {
17592
+ className: "space-y-1"
17593
+ }, /* @__PURE__ */ React.createElement(Label3, {
17594
+ htmlFor: `${settingsGroup}-googleClientId`,
17595
+ className: "text-sm"
17596
+ }, "Client ID"), /* @__PURE__ */ React.createElement(Input, {
17597
+ id: `${settingsGroup}-googleClientId`,
17598
+ value: googleClientId,
17599
+ onChange: /* @__PURE__ */ __name((e) => setGoogleClientId(e.target.value), "onChange"),
17600
+ placeholder: "xxxxx.apps.googleusercontent.com",
17601
+ className: "h-9 text-sm",
17602
+ autoComplete: "off"
17603
+ })), /* @__PURE__ */ React.createElement("div", {
17604
+ className: "space-y-1"
17605
+ }, /* @__PURE__ */ React.createElement(Label3, {
17606
+ htmlFor: `${settingsGroup}-googleClientSecret`,
17607
+ className: "text-sm"
17608
+ }, "Client Secret ", hasSavedSecret && !googleClientSecret.trim() ? "(saved)" : ""), /* @__PURE__ */ React.createElement(Input, {
17609
+ id: `${settingsGroup}-googleClientSecret`,
17610
+ type: "password",
17611
+ value: googleClientSecret,
17612
+ onChange: /* @__PURE__ */ __name((e) => setGoogleClientSecret(e.target.value), "onChange"),
17613
+ placeholder: hasSavedSecret ? "Leave blank to keep saved secret" : "Paste client secret",
17614
+ className: "h-9 text-sm",
17615
+ autoComplete: "new-password"
17616
+ })), /* @__PURE__ */ React.createElement(Button, {
17617
+ size: "sm",
17618
+ onClick: handleSave,
17619
+ disabled: saving,
17620
+ className: "gap-1"
17621
+ }, /* @__PURE__ */ React.createElement(LucideIcons.Save, {
17622
+ className: "h-3.5 w-3.5"
17623
+ }), saving ? "Saving\u2026" : "Save"));
17624
+ }
17625
+ var init_AuthProvidersPluginSettings = __esm({
17626
+ "src/admin/components/AuthProvidersPluginSettings.tsx"() {
17627
+ "use client";
17628
+ init_button();
17629
+ init_input();
17630
+ init_label();
17631
+ init_switch();
17632
+ __name(googleRedirectUri, "googleRedirectUri");
17633
+ __name(AuthProvidersPluginSettings, "AuthProvidersPluginSettings");
17634
+ }
17635
+ });
16539
17636
 
16540
17637
  // src/plugins/llm/chat-email-intent.ts
16541
17638
  function normalizeIntentKey(raw) {
@@ -16913,6 +18010,7 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
16913
18010
  const isEventNotifications = settingsGroup === "event_notifications";
16914
18011
  const isSocialMedia = settingsGroup === "social_media";
16915
18012
  const isMultiVendor = settingsGroup === "multi_vendor";
18013
+ const isAuthProviders = settingsGroup === "auth_providers";
16916
18014
  const [enabled, setEnabled] = React25.useState(true);
16917
18015
  const [botName, setBotName] = React25.useState("");
16918
18016
  const [icon, setIcon] = React25.useState("");
@@ -17262,6 +18360,10 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
17262
18360
  setWhatsappPhoneNumberId(String(data.phoneNumberId ?? data.WHATSAPP_PHONE_NUMBER_ID ?? "").trim());
17263
18361
  setWhatsappWabaId(String(data.wabaId ?? data.WHATSAPP_WABA_ID ?? "").trim());
17264
18362
  }
18363
+ if (isAuthProviders) {
18364
+ const googleOn = data.googleEnabled === "true" || data.googleEnabled === "1" || data.enabled === "true" && data.googleEnabled !== "false";
18365
+ setEnabled(googleOn);
18366
+ }
17265
18367
  if (isSocialMedia) {
17266
18368
  const savedLiToken = String(data.linkedin_access_token ?? "").trim();
17267
18369
  setLinkedinAccessToken(savedLiToken);
@@ -17299,7 +18401,8 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
17299
18401
  isWhatsapp,
17300
18402
  isEventNotifications,
17301
18403
  isSocialMedia,
17302
- isMultiVendor
18404
+ isMultiVendor,
18405
+ isAuthProviders
17303
18406
  ]);
17304
18407
  React25.useEffect(() => {
17305
18408
  if (!isSocialMedia || loading || linkedInOrgsPrefetchedRef.current) return;
@@ -17745,6 +18848,18 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
17745
18848
  }
17746
18849
  };
17747
18850
  }
18851
+ if (isAuthProviders) {
18852
+ return {
18853
+ enabled: {
18854
+ value: enabled ? "true" : "false",
18855
+ type: "public"
18856
+ },
18857
+ googleEnabled: {
18858
+ value: enabled ? "true" : "false",
18859
+ type: "public"
18860
+ }
18861
+ };
18862
+ }
17748
18863
  if (isSocialMedia) {
17749
18864
  const payload2 = {
17750
18865
  enabled: {
@@ -18230,6 +19345,15 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
18230
19345
  sonner.toast.success("Removed from agent");
18231
19346
  await fetchAttachedKnowledge(slug);
18232
19347
  }, "handleUnlinkKbDoc");
19348
+ if (isAuthProviders) {
19349
+ return /* @__PURE__ */ React.createElement(AuthProvidersPluginSettings, {
19350
+ settingsGroup,
19351
+ onEnabledChange: /* @__PURE__ */ __name((value) => {
19352
+ setEnabled(value);
19353
+ onSaved?.();
19354
+ }, "onEnabledChange")
19355
+ });
19356
+ }
18233
19357
  if (loading) return /* @__PURE__ */ React.createElement("div", {
18234
19358
  className: "text-sm text-gray-500 dark:text-gray-400"
18235
19359
  }, "Loading...");
@@ -19566,6 +20690,10 @@ function PluginsPage() {
19566
20690
  const en = pluginDescriptors.find((p) => p.name === "event_notifications");
19567
20691
  if (en) setSelectedName(en.name);
19568
20692
  }
20693
+ if (q === "auth_providers" || q === "auth-providers" || q === "authentication") {
20694
+ const ap = pluginDescriptors.find((p) => p.name === "auth_providers");
20695
+ if (ap) setSelectedName(ap.name);
20696
+ }
19569
20697
  }, [
19570
20698
  searchParams,
19571
20699
  pluginDescriptors
@@ -19573,10 +20701,13 @@ function PluginsPage() {
19573
20701
  React25.useEffect(() => {
19574
20702
  pluginDescriptors.forEach((p) => {
19575
20703
  if (!p.settingsGroup) return;
19576
- fetch(`/api/settings/${p.settingsGroup}`).then((r) => r.ok ? r.json() : {}).then((data) => setEnabledMap((prev) => ({
19577
- ...prev,
19578
- [p.name]: data.enabled !== "false"
19579
- }))).catch(() => {
20704
+ fetch(`/api/settings/${p.settingsGroup}`).then((r) => r.ok ? r.json() : {}).then((data) => {
20705
+ const on = p.settingsGroup === "auth_providers" ? data.googleEnabled === "true" || data.googleEnabled === "1" || data.enabled !== "false" && data.googleEnabled !== "false" && data.enabled === "true" : data.enabled !== "false";
20706
+ setEnabledMap((prev) => ({
20707
+ ...prev,
20708
+ [p.name]: on
20709
+ }));
20710
+ }).catch(() => {
19580
20711
  });
19581
20712
  });
19582
20713
  }, [
@@ -19586,10 +20717,13 @@ function PluginsPage() {
19586
20717
  const refreshEnabled = /* @__PURE__ */ __name((name) => {
19587
20718
  const d = pluginDescriptors.find((p) => p.name === name);
19588
20719
  if (!d?.settingsGroup) return;
19589
- fetch(`/api/settings/${d.settingsGroup}`).then((r) => r.ok ? r.json() : {}).then((data) => setEnabledMap((prev) => ({
19590
- ...prev,
19591
- [name]: data.enabled !== "false"
19592
- }))).catch(() => {
20720
+ fetch(`/api/settings/${d.settingsGroup}`).then((r) => r.ok ? r.json() : {}).then((data) => {
20721
+ const on = d.settingsGroup === "auth_providers" ? data.googleEnabled === "true" || data.googleEnabled === "1" || data.enabled === "true" && data.googleEnabled !== "false" : data.enabled !== "false";
20722
+ setEnabledMap((prev) => ({
20723
+ ...prev,
20724
+ [name]: on
20725
+ }));
20726
+ }).catch(() => {
19593
20727
  });
19594
20728
  }, "refreshEnabled");
19595
20729
  return /* @__PURE__ */ React.createElement("div", {
@@ -19671,6 +20805,7 @@ var init_PluginsPage = __esm({
19671
20805
  init_checkbox();
19672
20806
  init_select();
19673
20807
  init_EventNotificationsPluginSettings();
20808
+ init_AuthProvidersPluginSettings();
19674
20809
  init_ImageOrUrlField();
19675
20810
  init_chat_email_intent();
19676
20811
  init_llm_agent_scope();
@@ -19689,7 +20824,8 @@ var init_PluginsPage = __esm({
19689
20824
  pg_boss: LucideIcons.CalendarClock,
19690
20825
  share: LucideIcons.Share2,
19691
20826
  whatsapp: LucideIcons.MessageCircle,
19692
- event_notifications: LucideIcons.Bell
20827
+ event_notifications: LucideIcons.Bell,
20828
+ auth_providers: LucideIcons.KeyRound
19693
20829
  };
19694
20830
  __name(normalizeSmsProviderChoice, "normalizeSmsProviderChoice");
19695
20831
  __name(normalizeMsg91ApiMode, "normalizeMsg91ApiMode");
@@ -20162,9 +21298,7 @@ function VendorRolesPage() {
20162
21298
  className: "text-base font-semibold text-white"
20163
21299
  }, "Vendor Store Roles & Permissions"), /* @__PURE__ */ React25__namespace.default.createElement("p", {
20164
21300
  className: "mt-0.5 text-xs text-gray-300"
20165
- }, "Set CRUD permissions for any role (Viewer, Manager, Fulfillment, Catalog Staff, or custom roles). Permissions are stored in the ", /* @__PURE__ */ React25__namespace.default.createElement("code", {
20166
- className: "font-mono bg-gray-700 px-1 py-0.5 rounded text-gray-200"
20167
- }, "vendor_role_permissions"), " table and apply directly to team members assigned to that role.")), /* @__PURE__ */ React25__namespace.default.createElement("div", {
21301
+ }, "Set CRUD permissions for any role (Viewer, Manager, Fulfillment, Catalog Staff, or custom roles).")), /* @__PURE__ */ React25__namespace.default.createElement("div", {
20168
21302
  className: "min-w-0 p-6"
20169
21303
  }, error ? /* @__PURE__ */ React25__namespace.default.createElement("p", {
20170
21304
  className: "mb-4 text-sm text-red-600 dark:text-red-400"
@@ -20663,7 +21797,7 @@ function PlatformRolesPage() {
20663
21797
  className: "p-2"
20664
21798
  }, /* @__PURE__ */ React25__namespace.default.createElement("div", {
20665
21799
  className: "flex items-center justify-center gap-1"
20666
- }, /* @__PURE__ */ React25__namespace.default.createElement("span", null, "C"), /* @__PURE__ */ React25__namespace.default.createElement("input", {
21800
+ }, /* @__PURE__ */ React25__namespace.default.createElement("span", null, "Create"), /* @__PURE__ */ React25__namespace.default.createElement("input", {
20667
21801
  type: "checkbox",
20668
21802
  checked: isColumnAllChecked("canCreate"),
20669
21803
  onChange: /* @__PURE__ */ __name(() => toggleColumn("canCreate"), "onChange")
@@ -20671,7 +21805,7 @@ function PlatformRolesPage() {
20671
21805
  className: "p-2"
20672
21806
  }, /* @__PURE__ */ React25__namespace.default.createElement("div", {
20673
21807
  className: "flex items-center justify-center gap-1"
20674
- }, /* @__PURE__ */ React25__namespace.default.createElement("span", null, "R"), /* @__PURE__ */ React25__namespace.default.createElement("input", {
21808
+ }, /* @__PURE__ */ React25__namespace.default.createElement("span", null, "Read"), /* @__PURE__ */ React25__namespace.default.createElement("input", {
20675
21809
  type: "checkbox",
20676
21810
  checked: isColumnAllChecked("canRead"),
20677
21811
  onChange: /* @__PURE__ */ __name(() => toggleColumn("canRead"), "onChange")
@@ -20679,7 +21813,7 @@ function PlatformRolesPage() {
20679
21813
  className: "p-2"
20680
21814
  }, /* @__PURE__ */ React25__namespace.default.createElement("div", {
20681
21815
  className: "flex items-center justify-center gap-1"
20682
- }, /* @__PURE__ */ React25__namespace.default.createElement("span", null, "U"), /* @__PURE__ */ React25__namespace.default.createElement("input", {
21816
+ }, /* @__PURE__ */ React25__namespace.default.createElement("span", null, "Update"), /* @__PURE__ */ React25__namespace.default.createElement("input", {
20683
21817
  type: "checkbox",
20684
21818
  checked: isColumnAllChecked("canUpdate"),
20685
21819
  onChange: /* @__PURE__ */ __name(() => toggleColumn("canUpdate"), "onChange")
@@ -20687,7 +21821,7 @@ function PlatformRolesPage() {
20687
21821
  className: "p-2"
20688
21822
  }, /* @__PURE__ */ React25__namespace.default.createElement("div", {
20689
21823
  className: "flex items-center justify-center gap-1"
20690
- }, /* @__PURE__ */ React25__namespace.default.createElement("span", null, "D"), /* @__PURE__ */ React25__namespace.default.createElement("input", {
21824
+ }, /* @__PURE__ */ React25__namespace.default.createElement("span", null, "Delete"), /* @__PURE__ */ React25__namespace.default.createElement("input", {
20691
21825
  type: "checkbox",
20692
21826
  checked: isColumnAllChecked("canDelete"),
20693
21827
  onChange: /* @__PURE__ */ __name(() => toggleColumn("canDelete"), "onChange")
@@ -34199,7 +35333,7 @@ function VendorEditPage({ vendorId }) {
34199
35333
  }), /* @__PURE__ */ React25__namespace.default.createElement(Label3, {
34200
35334
  htmlFor: "termsAccepted",
34201
35335
  className: "font-normal cursor-pointer text-sm leading-snug"
34202
- }, "I confirm the vendor has read and accepted the terms and conditions for selling on this platform."))))),
35336
+ }, "I confirm I have read and accepted the terms and conditions for selling on this platform."))))),
34203
35337
  sidebar: /* @__PURE__ */ React25__namespace.default.createElement(React25__namespace.default.Fragment, null, !create && /* @__PURE__ */ React25__namespace.default.createElement("section", null, /* @__PURE__ */ React25__namespace.default.createElement("h2", {
34204
35338
  className: "text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2"
34205
35339
  }, "Status"), /* @__PURE__ */ React25__namespace.default.createElement("div", {