@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.js CHANGED
@@ -10,7 +10,7 @@ import { Slot } from '@radix-ui/react-slot';
10
10
  import { cva } from 'class-variance-authority';
11
11
  import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
12
12
  import * as LucideIcons from 'lucide-react';
13
- import { ChevronDown, User, Settings, LogOut, LayoutDashboard, Store, FolderTree, Layers, Building2, CalendarDays, ShoppingBag, Package, Receipt, BadgePercent, ShoppingCart, CreditCard, Users, Inbox, File, LinkIcon, ClipboardList, MessageSquare, Image as Image$1, Shield, FileText, Puzzle, X, Plus, MoreVertical, Upload, Download, Edit, Copy, RefreshCw, Trash2, Mail, Check, Save, Search, AlertCircle, MapPin, BarChart3, TrendingUp, UserCheck, Activity, Globe, Eye, MousePointer, ArrowLeftRight, RotateCcw, KeyRound, Loader2, CalendarClock, CheckSquare, Square, XSquare, Rss, ArrowLeft, Pencil, FolderPlus, LayoutGrid, List, Move, Archive, Edit2, ExternalLink, Power, Star, ChevronUp, Zap, Filter, CheckCircle2, ImageIcon, GripVertical, ChevronRight, ArrowRight, ArrowUpRight, IndianRupee, XCircle, Clock, Play, MessageCircle, Bot, FileUp, Tag, Box, SlidersHorizontal, Calculator, Link2 as Link2$1, Unlink, Gift, MoreHorizontal, ShieldCheck, QrCode, Car, Settings2, DollarSign, Hash, Circle, ChevronLeft, Bell, Share2, Smartphone, HardDrive, Folder, Film } from 'lucide-react';
13
+ import { ChevronDown, User, Settings, LogOut, LayoutDashboard, Store, FolderTree, Layers, Building2, CalendarDays, ShoppingBag, Package, Receipt, BadgePercent, ShoppingCart, CreditCard, Users, Inbox, File, LinkIcon, ClipboardList, MessageSquare, Image as Image$1, Shield, FileText, Puzzle, X, Plus, MoreVertical, Upload, Download, Edit, Copy, RefreshCw, Trash2, Mail, Check, Save, Search, AlertCircle, MapPin, BarChart3, TrendingUp, UserCheck, Activity, Globe, Eye, MousePointer, ArrowLeftRight, RotateCcw, KeyRound, Loader2, CalendarClock, CheckSquare, Square, XSquare, Rss, ArrowLeft, Pencil, FolderPlus, LayoutGrid, List, Move, Archive, Edit2, ExternalLink, Power, Star, ChevronUp, Zap, Filter, CheckCircle2, ImageIcon, GripVertical, ChevronRight, ArrowRight, ArrowUpRight, IndianRupee, XCircle, Clock, Play, MessageCircle, Bot, FileUp, Tag, Box, SlidersHorizontal, Calculator, Link2 as Link2$1, Unlink, Gift, MoreHorizontal, Smartphone, ShieldCheck, QrCode, Car, Send, Settings2, DollarSign, Hash, Circle, ChevronLeft, Bell, Share2, HardDrive, Folder, Film } from 'lucide-react';
14
14
  import Image from 'next/image';
15
15
  import Link2 from 'next/link';
16
16
  import { usePathname, useSearchParams, useRouter } from 'next/navigation';
@@ -455,7 +455,7 @@ var init_admin_config_context = __esm({
455
455
  var CMS_VERSION;
456
456
  var init_cms_version = __esm({
457
457
  "src/lib/cms-version.ts"() {
458
- CMS_VERSION = "1.0.47" ;
458
+ CMS_VERSION = "1.0.50" ;
459
459
  }
460
460
  });
461
461
  function useCatalogCategories(enabled = true) {
@@ -1146,6 +1146,15 @@ var init_builtin_plugin_descriptors = __esm({
1146
1146
  enabled: true,
1147
1147
  icon: "event_notifications",
1148
1148
  settingsGroup: "event_notifications"
1149
+ },
1150
+ {
1151
+ name: "auth_providers",
1152
+ label: "Authentication Provider",
1153
+ version: "1.0.0",
1154
+ description: "Enable Google sign-in for admin and vendor users (existing accounts only).",
1155
+ enabled: true,
1156
+ icon: "auth_providers",
1157
+ settingsGroup: "auth_providers"
1149
1158
  }
1150
1159
  ];
1151
1160
  }
@@ -9140,28 +9149,75 @@ var init_AuthPageLayout = __esm({
9140
9149
  }
9141
9150
  });
9142
9151
 
9152
+ // src/auth/auth-providers-settings.ts
9153
+ function parseEnabled(raw) {
9154
+ if (typeof raw === "boolean") return raw;
9155
+ if (typeof raw === "number") return raw !== 0;
9156
+ if (typeof raw === "string") {
9157
+ const n = raw.trim().toLowerCase();
9158
+ return n === "true" || n === "1" || n === "yes" || n === "on";
9159
+ }
9160
+ return false;
9161
+ }
9162
+ function isGoogleAuthPubliclyEnabled(settings) {
9163
+ if (!settings) return false;
9164
+ if (settings.googleEnabled !== void 0 && settings.googleEnabled !== "") {
9165
+ return parseEnabled(settings.googleEnabled);
9166
+ }
9167
+ return parseEnabled(settings.enabled);
9168
+ }
9169
+ var init_auth_providers_settings = __esm({
9170
+ "src/auth/auth-providers-settings.ts"() {
9171
+ init_auth_debug();
9172
+ __name(parseEnabled, "parseEnabled");
9173
+ __name(isGoogleAuthPubliclyEnabled, "isGoogleAuthPubliclyEnabled");
9174
+ }
9175
+ });
9176
+
9143
9177
  // src/admin/pages/SignInPage.tsx
9144
9178
  var SignInPage_exports = {};
9145
9179
  __export(SignInPage_exports, {
9146
9180
  default: () => SignInPage_default
9147
9181
  });
9182
+ function oauthErrorMessage(code) {
9183
+ if (!code) return "";
9184
+ const c = code.toLowerCase();
9185
+ if (c === "accessdenied" || c === "access_denied") {
9186
+ return "No account for this Google email. Ask an admin to invite you first.";
9187
+ }
9188
+ if (c === "oauthaccountnotlinked") {
9189
+ return "This Google account is not linked to an existing user. Sign in with email/password or ask an admin to invite you.";
9190
+ }
9191
+ if (c === "oauthcallback" || c === "oauthsignin" || c === "oauthcreateaccount") {
9192
+ return "Google sign-in failed. Check Authentication Provider settings and the redirect URI in Google Cloud Console.";
9193
+ }
9194
+ if (c === "configuration") {
9195
+ return "Google sign-in is not configured correctly.";
9196
+ }
9197
+ return "Invalid email or password";
9198
+ }
9148
9199
  var SigninPage, SignInPage_default;
9149
9200
  var init_SignInPage = __esm({
9150
9201
  "src/admin/pages/SignInPage.tsx"() {
9151
9202
  "use client";
9152
9203
  init_AuthPageLayout();
9153
9204
  init_auth_debug();
9205
+ init_auth_providers_settings();
9206
+ __name(oauthErrorMessage, "oauthErrorMessage");
9154
9207
  SigninPage = /* @__PURE__ */ __name(() => {
9155
9208
  const [email, setEmail] = useState("");
9156
9209
  const [password, setPassword] = useState("");
9157
9210
  const [error, setError] = useState("");
9158
9211
  const [success, setSuccess] = useState("");
9159
9212
  const [loading, setLoading] = useState(false);
9213
+ const [googleLoading, setGoogleLoading] = useState(false);
9214
+ const [googleEnabled, setGoogleEnabled] = useState(false);
9160
9215
  const { status } = useSession();
9161
9216
  const searchParams = useSearchParams();
9162
9217
  useEffect(() => {
9163
- if (searchParams.get("error")) {
9164
- setError("Invalid email or password");
9218
+ const err = searchParams.get("error");
9219
+ if (err) {
9220
+ setError(oauthErrorMessage(err));
9165
9221
  }
9166
9222
  if (searchParams.get("activated") === "1") {
9167
9223
  setSuccess("Account activated. Sign in with your new password.");
@@ -9172,6 +9228,23 @@ var init_SignInPage = __esm({
9172
9228
  }, [
9173
9229
  searchParams
9174
9230
  ]);
9231
+ useEffect(() => {
9232
+ let cancelled = false;
9233
+ (async () => {
9234
+ try {
9235
+ const res = await fetch("/api/settings/auth_providers", {
9236
+ cache: "no-store"
9237
+ });
9238
+ if (!res.ok) return;
9239
+ const data = await res.json();
9240
+ if (!cancelled) setGoogleEnabled(isGoogleAuthPubliclyEnabled(data));
9241
+ } catch {
9242
+ }
9243
+ })();
9244
+ return () => {
9245
+ cancelled = true;
9246
+ };
9247
+ }, []);
9175
9248
  useEffect(() => {
9176
9249
  logAuthClient("SignInPage session status", {
9177
9250
  status
@@ -9229,6 +9302,19 @@ var init_SignInPage = __esm({
9229
9302
  setLoading(false);
9230
9303
  }
9231
9304
  }, "handleSubmit");
9305
+ const handleGoogle = /* @__PURE__ */ __name(async () => {
9306
+ setGoogleLoading(true);
9307
+ setError("");
9308
+ try {
9309
+ logAuthClient("SignInPage Google signIn start");
9310
+ await signIn("google", {
9311
+ callbackUrl: "/admin/dashboard"
9312
+ });
9313
+ } catch {
9314
+ setError("Google sign-in failed. Please try again.");
9315
+ setGoogleLoading(false);
9316
+ }
9317
+ }, "handleGoogle");
9232
9318
  return /* @__PURE__ */ React.createElement(AuthPageLayout, null, /* @__PURE__ */ React.createElement("div", {
9233
9319
  className: "w-full shadow-md rounded-lg bg-white px-4 py-6 sm:px-6 sm:py-8"
9234
9320
  }, /* @__PURE__ */ React.createElement("h3", {
@@ -9281,9 +9367,42 @@ var init_SignInPage = __esm({
9281
9367
  className: "text-xs font-medium text-gray-600 hover:underline"
9282
9368
  }, "Forgot password?")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("button", {
9283
9369
  type: "submit",
9284
- disabled: loading,
9370
+ disabled: loading || googleLoading,
9285
9371
  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"
9286
- }, loading ? "Signing in..." : "Sign in")))));
9372
+ }, loading ? "Signing in..." : "Sign in"))), googleEnabled && /* @__PURE__ */ React.createElement("div", {
9373
+ className: "mt-6"
9374
+ }, /* @__PURE__ */ React.createElement("div", {
9375
+ className: "relative mb-4"
9376
+ }, /* @__PURE__ */ React.createElement("div", {
9377
+ className: "absolute inset-0 flex items-center"
9378
+ }, /* @__PURE__ */ React.createElement("div", {
9379
+ className: "w-full border-t border-gray-200"
9380
+ })), /* @__PURE__ */ React.createElement("div", {
9381
+ className: "relative flex justify-center text-xs"
9382
+ }, /* @__PURE__ */ React.createElement("span", {
9383
+ className: "bg-white px-2 text-gray-500"
9384
+ }, "or"))), /* @__PURE__ */ React.createElement("button", {
9385
+ type: "button",
9386
+ onClick: handleGoogle,
9387
+ disabled: loading || googleLoading,
9388
+ 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"
9389
+ }, /* @__PURE__ */ React.createElement("svg", {
9390
+ className: "h-4 w-4",
9391
+ viewBox: "0 0 24 24",
9392
+ "aria-hidden": "true"
9393
+ }, /* @__PURE__ */ React.createElement("path", {
9394
+ fill: "#4285F4",
9395
+ 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"
9396
+ }), /* @__PURE__ */ React.createElement("path", {
9397
+ fill: "#34A853",
9398
+ 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"
9399
+ }), /* @__PURE__ */ React.createElement("path", {
9400
+ fill: "#FBBC05",
9401
+ 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"
9402
+ }), /* @__PURE__ */ React.createElement("path", {
9403
+ fill: "#EA4335",
9404
+ 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"
9405
+ })), googleLoading ? "Redirecting\u2026" : "Continue with Google"))));
9287
9406
  }, "SigninPage");
9288
9407
  SignInPage_default = SigninPage;
9289
9408
  }
@@ -15407,6 +15526,30 @@ function getEmailTriggerDefaults(triggerKey, audience = "customer") {
15407
15526
  bodyPlaceholder: "Hi {{customerName}},\n\nYour order {{orderNumber}} has been updated."
15408
15527
  };
15409
15528
  }
15529
+ function getPushTriggerDefaults(triggerKey, audience = "customer") {
15530
+ if (audience === "vendor") {
15531
+ return VENDOR_PUSH_TRIGGER_DEFAULTS[triggerKey] ?? {
15532
+ subject: "Vendor Push Update",
15533
+ body: "Hi {{vendorName}}, order {{orderNumber}} update.",
15534
+ subjectPlaceholder: "e.g. Vendor Push Update",
15535
+ bodyPlaceholder: "Hi {{vendorName}}, order {{orderNumber}} update."
15536
+ };
15537
+ }
15538
+ if (audience === "admin") {
15539
+ return ADMIN_PUSH_TRIGGER_DEFAULTS[triggerKey] ?? {
15540
+ subject: "Admin Push Alert",
15541
+ body: "Admin Alert: Order {{orderNumber}} update.",
15542
+ subjectPlaceholder: "e.g. Admin Push Alert",
15543
+ bodyPlaceholder: "Admin Alert: Order {{orderNumber}} update."
15544
+ };
15545
+ }
15546
+ return PUSH_TRIGGER_DEFAULTS[triggerKey] ?? {
15547
+ subject: "Order Update",
15548
+ body: "Hi {{customerName}}, order {{orderNumber}} update.",
15549
+ subjectPlaceholder: "e.g. Order Update",
15550
+ bodyPlaceholder: "Hi {{customerName}}, order {{orderNumber}} update."
15551
+ };
15552
+ }
15410
15553
  async function jsonFetch(url, init) {
15411
15554
  const res = await fetch(url, {
15412
15555
  credentials: "include",
@@ -15498,7 +15641,7 @@ function useOrderNotificationTriggerBindings(channel, audienceType = "customers"
15498
15641
  rebind
15499
15642
  };
15500
15643
  }
15501
- var KNOWN_VARS, EMAIL_TRIGGER_DEFAULTS, VENDOR_EMAIL_TRIGGER_DEFAULTS, ADMIN_EMAIL_TRIGGER_DEFAULTS;
15644
+ 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;
15502
15645
  var init_order_notification_bindings_shared = __esm({
15503
15646
  "src/admin/components/order-notification-bindings-shared.ts"() {
15504
15647
  "use client";
@@ -15514,9 +15657,7 @@ var init_order_notification_bindings_shared = __esm({
15514
15657
  "vendorId",
15515
15658
  "refundAmount",
15516
15659
  "invoiceNumber",
15517
- "invoiceUrl",
15518
- "ticketQr",
15519
- "qrImageUrl"
15660
+ "invoiceUrl"
15520
15661
  ];
15521
15662
  EMAIL_TRIGGER_DEFAULTS = {
15522
15663
  order_placed: {
@@ -15595,7 +15736,7 @@ Order Summary:
15595
15736
  Customer Name: {{customerName}}
15596
15737
  Customer Email: {{customerEmail}}
15597
15738
  Customer Phone: {{customerPhone}}`,
15598
- bodyPlaceholder: "Admin Notification:\n\nNew order {{orderNumber}} placed by {{customerName}}."
15739
+ bodyPlaceholder: "New order {{orderNumber}} placed by {{customerName}}."
15599
15740
  },
15600
15741
  order_cancelled: {
15601
15742
  subject: "Order {{orderNumber}} cancelled",
@@ -15611,6 +15752,66 @@ Refund Due: {{refundAmount}} {{currency}}`,
15611
15752
  }
15612
15753
  };
15613
15754
  __name(getEmailTriggerDefaults, "getEmailTriggerDefaults");
15755
+ PUSH_TRIGGER_DEFAULTS = {
15756
+ order_placed: {
15757
+ subject: "Order {{orderNumber}} Confirmed!",
15758
+ subjectPlaceholder: "e.g. Order {{orderNumber}} Confirmed!",
15759
+ body: `Hi {{customerName}}, your order {{orderNumber}} for {{total}} {{currency}} is confirmed.`,
15760
+ bodyPlaceholder: "Hi {{customerName}}, your order {{orderNumber}} is confirmed."
15761
+ },
15762
+ order_cancelled: {
15763
+ subject: "Order {{orderNumber}} Cancelled",
15764
+ subjectPlaceholder: "e.g. Order {{orderNumber}} Cancelled",
15765
+ body: `Hi {{customerName}}, your order {{orderNumber}} for {{total}} {{currency}} has been cancelled.`,
15766
+ bodyPlaceholder: "Hi {{customerName}}, your order {{orderNumber}} has been cancelled."
15767
+ }
15768
+ };
15769
+ VENDOR_PUSH_TRIGGER_DEFAULTS = {
15770
+ order_placed: {
15771
+ subject: "New Order Received: {{orderNumber}}",
15772
+ subjectPlaceholder: "e.g. New Order Received: {{orderNumber}}",
15773
+ body: `Hi {{vendorName}}, you received a new order {{orderNumber}} for {{total}} {{currency}} from {{customerName}}.`,
15774
+ bodyPlaceholder: "Hi {{vendorName}}, you received a new order {{orderNumber}}."
15775
+ },
15776
+ order_cancelled: {
15777
+ subject: "Order {{orderNumber}} Cancelled",
15778
+ subjectPlaceholder: "e.g. Order {{orderNumber}} Cancelled",
15779
+ body: `Hi {{vendorName}}, order {{orderNumber}} from {{customerName}} has been cancelled.`,
15780
+ bodyPlaceholder: "Hi {{vendorName}}, order {{orderNumber}} was cancelled."
15781
+ }
15782
+ };
15783
+ ADMIN_PUSH_TRIGGER_DEFAULTS = {
15784
+ order_placed: {
15785
+ subject: "New Order {{orderNumber}} ({{total}} {{currency}})",
15786
+ subjectPlaceholder: "e.g. New Order {{orderNumber}} placed",
15787
+ body: `Hi Admin,
15788
+
15789
+ A new order {{orderNumber}} of {{total}} {{currency}} was placed.
15790
+
15791
+ Vendor: {{vendorName}}
15792
+
15793
+ Order Summary:
15794
+ {{orderDetails}}
15795
+
15796
+ Customer Name: {{customerName}}
15797
+ Customer Email: {{customerEmail}}
15798
+ Customer Phone: {{customerPhone}}`,
15799
+ bodyPlaceholder: "New order {{orderNumber}} placed by {{customerName}}."
15800
+ },
15801
+ order_cancelled: {
15802
+ subject: "Order {{orderNumber}} Cancelled",
15803
+ subjectPlaceholder: "e.g. Order {{orderNumber}} cancelled",
15804
+ body: `Hi Admin,
15805
+
15806
+ Order {{orderNumber}} has been cancelled.
15807
+
15808
+ Customer: {{customerName}}
15809
+ Vendor: {{vendorName}}
15810
+ Refund Due: {{refundAmount}} {{currency}}`,
15811
+ bodyPlaceholder: "Hi Admin,\n\nOrder {{orderNumber}} cancelled."
15812
+ }
15813
+ };
15814
+ __name(getPushTriggerDefaults, "getPushTriggerDefaults");
15614
15815
  __name(jsonFetch, "jsonFetch");
15615
15816
  __name(useOrderNotificationTriggerBindings, "useOrderNotificationTriggerBindings");
15616
15817
  }
@@ -16261,14 +16462,582 @@ var init_emailTriggersBindingsPage = __esm({
16261
16462
  vendorId: "Vendor / store ID",
16262
16463
  refundAmount: "Refund amount (refund triggers)",
16263
16464
  invoiceNumber: "Generated invoice number",
16264
- invoiceUrl: "Link to download invoice PDF",
16265
- ticketQr: "Embedded ticket QR code image",
16266
- qrImageUrl: "Direct QR code image URL"
16465
+ invoiceUrl: "Link to download invoice PDF"
16267
16466
  };
16268
16467
  __name(EmailTriggerBindings, "EmailTriggerBindings");
16269
16468
  __name(TriggerEmailEditor, "TriggerEmailEditor");
16270
16469
  }
16271
16470
  });
16471
+ function PushTriggerBindings({ audience = "customer" }) {
16472
+ const audienceType = audience === "vendor" ? "vendors" : audience === "admin" ? "admin" : "customers";
16473
+ const { triggers, bindings, loading, reload, rebind } = useOrderNotificationTriggerBindings("mobile", audienceType);
16474
+ const [selectedKey, setSelectedKey] = useState("");
16475
+ const [templates, setTemplates] = useState([]);
16476
+ const [loadingTpl, setLoadingTpl] = useState(true);
16477
+ const [title, setTitle] = useState("");
16478
+ const [body, setBody] = useState("");
16479
+ const [saving, setSaving] = useState(false);
16480
+ const bodyRef = useRef(null);
16481
+ const subjectRef = useRef(null);
16482
+ const lastFocused = useRef("body");
16483
+ useEffect(() => {
16484
+ let cancelled = false;
16485
+ (async () => {
16486
+ try {
16487
+ setLoadingTpl(true);
16488
+ const res = await fetch("/api/message-templates/email", {
16489
+ cache: "no-store"
16490
+ });
16491
+ if (!res.ok) throw new Error("Failed to load message templates");
16492
+ const data = await res.json();
16493
+ if (!cancelled) setTemplates(Array.isArray(data.items) ? data.items : []);
16494
+ } catch {
16495
+ if (!cancelled) toast.error("Could not load message templates");
16496
+ } finally {
16497
+ if (!cancelled) setLoadingTpl(false);
16498
+ }
16499
+ })();
16500
+ return () => {
16501
+ cancelled = true;
16502
+ };
16503
+ }, []);
16504
+ useEffect(() => {
16505
+ if (triggers.length > 0 && !selectedKey) {
16506
+ setSelectedKey(triggers[0].triggerKey);
16507
+ }
16508
+ }, [
16509
+ triggers,
16510
+ selectedKey
16511
+ ]);
16512
+ const activeTrigger = useMemo(() => triggers.find((t) => t.triggerKey === selectedKey), [
16513
+ triggers,
16514
+ selectedKey
16515
+ ]);
16516
+ const bindingMap = useMemo(() => {
16517
+ const m = /* @__PURE__ */ new Map();
16518
+ for (const b of bindings) {
16519
+ if (b.messageTemplateId != null) m.set(b.triggerKey, b.messageTemplateId);
16520
+ }
16521
+ return m;
16522
+ }, [
16523
+ bindings
16524
+ ]);
16525
+ const activeBindingTplId = selectedKey ? bindingMap.get(selectedKey) ?? null : null;
16526
+ const activeTemplate = useMemo(() => {
16527
+ if (!activeBindingTplId) return null;
16528
+ return templates.find((t) => t.id === activeBindingTplId) ?? null;
16529
+ }, [
16530
+ activeBindingTplId,
16531
+ templates
16532
+ ]);
16533
+ const defaults = useMemo(() => {
16534
+ if (!selectedKey) return {
16535
+ subject: "",
16536
+ body: "",
16537
+ subjectPlaceholder: "",
16538
+ bodyPlaceholder: ""
16539
+ };
16540
+ return getPushTriggerDefaults(selectedKey, audience);
16541
+ }, [
16542
+ selectedKey,
16543
+ audience
16544
+ ]);
16545
+ useEffect(() => {
16546
+ if (activeTemplate) {
16547
+ setTitle(activeTemplate.subject ?? "");
16548
+ setBody(activeTemplate.body ?? "");
16549
+ } else {
16550
+ setTitle(defaults.subject);
16551
+ setBody(defaults.body);
16552
+ }
16553
+ }, [
16554
+ activeTemplate,
16555
+ defaults,
16556
+ selectedKey
16557
+ ]);
16558
+ const insertVariable = /* @__PURE__ */ __name((varName) => {
16559
+ const token = `{{${varName}}}`;
16560
+ if (lastFocused.current === "subject" && subjectRef.current) {
16561
+ const el = subjectRef.current;
16562
+ const start = el.selectionStart ?? title.length;
16563
+ const end = el.selectionEnd ?? title.length;
16564
+ const next = title.slice(0, start) + token + title.slice(end);
16565
+ setTitle(next);
16566
+ setTimeout(() => {
16567
+ el.focus();
16568
+ el.setSelectionRange(start + token.length, start + token.length);
16569
+ }, 0);
16570
+ return;
16571
+ }
16572
+ if (bodyRef.current) {
16573
+ const el = bodyRef.current;
16574
+ const start = el.selectionStart ?? body.length;
16575
+ const end = el.selectionEnd ?? body.length;
16576
+ const next = body.slice(0, start) + token + body.slice(end);
16577
+ setBody(next);
16578
+ setTimeout(() => {
16579
+ el.focus();
16580
+ el.setSelectionRange(start + token.length, start + token.length);
16581
+ }, 0);
16582
+ } else {
16583
+ setBody((prev) => prev + token);
16584
+ }
16585
+ }, "insertVariable");
16586
+ const handleSave = /* @__PURE__ */ __name(async () => {
16587
+ if (!selectedKey) return;
16588
+ setSaving(true);
16589
+ try {
16590
+ let tplId = activeBindingTplId;
16591
+ if (tplId) {
16592
+ const res = await jsonFetch(`/api/message-templates/email/${tplId}`, {
16593
+ method: "PATCH",
16594
+ headers: {
16595
+ "Content-Type": "application/json"
16596
+ },
16597
+ body: JSON.stringify({
16598
+ name: `${audienceType}_push_${selectedKey}`,
16599
+ subject: title,
16600
+ body
16601
+ })
16602
+ });
16603
+ if (res.item) {
16604
+ setTemplates((prev) => prev.map((t) => t.id === res.item.id ? res.item : t));
16605
+ }
16606
+ } else {
16607
+ const res = await jsonFetch("/api/message-templates/email", {
16608
+ method: "POST",
16609
+ headers: {
16610
+ "Content-Type": "application/json"
16611
+ },
16612
+ body: JSON.stringify({
16613
+ name: `${audienceType}_push_${selectedKey}`,
16614
+ subject: title,
16615
+ body
16616
+ })
16617
+ });
16618
+ const createdItem = res.item || res;
16619
+ tplId = createdItem.id;
16620
+ if (createdItem) {
16621
+ setTemplates((prev) => [
16622
+ createdItem,
16623
+ ...prev
16624
+ ]);
16625
+ }
16626
+ }
16627
+ await rebind(selectedKey, tplId, `${audienceType}_push_${selectedKey}`);
16628
+ toast.success("Push notification template saved");
16629
+ await reload();
16630
+ } catch (e) {
16631
+ const msg = e instanceof Error ? e.message : "Save failed";
16632
+ toast.error(msg);
16633
+ } finally {
16634
+ setSaving(false);
16635
+ }
16636
+ }, "handleSave");
16637
+ if (loading || loadingTpl) {
16638
+ return /* @__PURE__ */ React.createElement("div", {
16639
+ className: "flex items-center gap-2 p-6 text-sm text-gray-500"
16640
+ }, /* @__PURE__ */ React.createElement(Loader2, {
16641
+ className: "h-4 w-4 animate-spin"
16642
+ }), " Loading push notification triggers\u2026");
16643
+ }
16644
+ return /* @__PURE__ */ React.createElement("div", {
16645
+ className: "space-y-6"
16646
+ }, /* @__PURE__ */ React.createElement("div", {
16647
+ className: "flex flex-col sm:flex-row sm:items-center justify-between gap-4"
16648
+ }, /* @__PURE__ */ React.createElement("div", {
16649
+ className: "space-y-1"
16650
+ }, /* @__PURE__ */ React.createElement("h3", {
16651
+ className: "text-base font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2"
16652
+ }, /* @__PURE__ */ React.createElement(Smartphone, {
16653
+ className: "h-5 w-5 text-gray-700 dark:text-gray-300"
16654
+ }), "Mobile Push Notification Templates"), /* @__PURE__ */ React.createElement("p", {
16655
+ className: "text-xs text-gray-500"
16656
+ }, "Configure push notification title and message body per trigger for", " ", /* @__PURE__ */ React.createElement("span", {
16657
+ className: "font-semibold capitalize text-gray-700 dark:text-gray-300"
16658
+ }, audience, "s"), ".")), /* @__PURE__ */ React.createElement(Button, {
16659
+ type: "button",
16660
+ onClick: handleSave,
16661
+ disabled: saving || !selectedKey,
16662
+ className: "gap-2 shrink-0"
16663
+ }, saving ? /* @__PURE__ */ React.createElement(Loader2, {
16664
+ className: "h-4 w-4 animate-spin"
16665
+ }) : /* @__PURE__ */ React.createElement(Save, {
16666
+ className: "h-4 w-4"
16667
+ }), "Save Template")), /* @__PURE__ */ React.createElement("div", {
16668
+ className: "grid grid-cols-1 md:grid-cols-4 gap-6"
16669
+ }, /* @__PURE__ */ React.createElement("div", {
16670
+ className: "md:col-span-1 space-y-2"
16671
+ }, /* @__PURE__ */ React.createElement(Label3, {
16672
+ className: "text-xs font-semibold text-gray-500 uppercase tracking-wider"
16673
+ }, "Triggers"), /* @__PURE__ */ React.createElement("div", {
16674
+ className: "flex flex-col gap-1"
16675
+ }, triggers.map((t) => {
16676
+ const isBound = bindingMap.has(t.triggerKey);
16677
+ const isSelected = t.triggerKey === selectedKey;
16678
+ return /* @__PURE__ */ React.createElement("button", {
16679
+ key: t.triggerKey,
16680
+ type: "button",
16681
+ onClick: /* @__PURE__ */ __name(() => setSelectedKey(t.triggerKey), "onClick"),
16682
+ 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"}`
16683
+ }, /* @__PURE__ */ React.createElement("div", {
16684
+ className: "flex items-center justify-between w-full"
16685
+ }, /* @__PURE__ */ React.createElement("span", {
16686
+ className: "font-medium text-sm"
16687
+ }, t.label), isBound && /* @__PURE__ */ React.createElement("span", {
16688
+ 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"}`
16689
+ }, "Configured")), /* @__PURE__ */ React.createElement("span", {
16690
+ className: `text-xs mt-1 ${isSelected ? "text-gray-300 dark:text-gray-600" : "text-gray-500 dark:text-gray-400"}`
16691
+ }, t.triggerKey));
16692
+ }))), /* @__PURE__ */ React.createElement("div", {
16693
+ 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"
16694
+ }, activeTrigger ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h4", {
16695
+ className: "text-sm font-semibold text-gray-900 dark:text-gray-100"
16696
+ }, activeTrigger.label, " (", activeTrigger.triggerKey, ")"), activeTrigger.description && /* @__PURE__ */ React.createElement("p", {
16697
+ className: "text-xs text-gray-500 mt-0.5"
16698
+ }, activeTrigger.description)), /* @__PURE__ */ React.createElement("div", {
16699
+ className: "space-y-1.5"
16700
+ }, /* @__PURE__ */ React.createElement(Label3, {
16701
+ className: "text-xs font-medium text-gray-700 dark:text-gray-300"
16702
+ }, "Insert Variables"), /* @__PURE__ */ React.createElement("div", {
16703
+ className: "flex flex-wrap gap-1.5"
16704
+ }, KNOWN_VARS.map((v) => /* @__PURE__ */ React.createElement("button", {
16705
+ key: v,
16706
+ type: "button",
16707
+ onClick: /* @__PURE__ */ __name(() => insertVariable(v), "onClick"),
16708
+ title: VAR_HINTS2[v],
16709
+ 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"
16710
+ }, `{{${v}}}`)))), /* @__PURE__ */ React.createElement("div", {
16711
+ className: "space-y-1.5"
16712
+ }, /* @__PURE__ */ React.createElement(Label3, {
16713
+ htmlFor: "push-title",
16714
+ className: "text-xs font-medium"
16715
+ }, "Notification Title"), /* @__PURE__ */ React.createElement(Input, {
16716
+ id: "push-title",
16717
+ ref: subjectRef,
16718
+ value: title,
16719
+ onFocus: /* @__PURE__ */ __name(() => {
16720
+ lastFocused.current = "subject";
16721
+ }, "onFocus"),
16722
+ onChange: /* @__PURE__ */ __name((e) => setTitle(e.target.value), "onChange"),
16723
+ placeholder: defaults.subjectPlaceholder
16724
+ })), /* @__PURE__ */ React.createElement("div", {
16725
+ className: "space-y-1.5"
16726
+ }, /* @__PURE__ */ React.createElement(Label3, {
16727
+ htmlFor: "push-body",
16728
+ className: "text-xs font-medium"
16729
+ }, "Notification Body"), /* @__PURE__ */ React.createElement(Textarea, {
16730
+ id: "push-body",
16731
+ ref: bodyRef,
16732
+ value: body,
16733
+ onFocus: /* @__PURE__ */ __name(() => {
16734
+ lastFocused.current = "body";
16735
+ }, "onFocus"),
16736
+ onChange: /* @__PURE__ */ __name((e) => setBody(e.target.value), "onChange"),
16737
+ placeholder: defaults.bodyPlaceholder,
16738
+ className: "h-36 font-mono text-xs"
16739
+ }))) : /* @__PURE__ */ React.createElement("div", {
16740
+ className: "p-12 text-center text-sm text-gray-500"
16741
+ }, "Select a trigger from the left to configure push notification template."))));
16742
+ }
16743
+ var VAR_HINTS2;
16744
+ var init_pushTriggersBindingsPage = __esm({
16745
+ "src/admin/pages/pushTriggersBindingsPage.tsx"() {
16746
+ "use client";
16747
+ init_button();
16748
+ init_input();
16749
+ init_textarea();
16750
+ init_label();
16751
+ init_order_notification_bindings_shared();
16752
+ VAR_HINTS2 = {
16753
+ orderId: "Internal order ID",
16754
+ orderNumber: "Display order number",
16755
+ customerName: "Customer full name",
16756
+ customerPhone: "Customer phone",
16757
+ eventName: "Event title / product name",
16758
+ vendorName: "Vendor / store name",
16759
+ total: "Order total amount",
16760
+ currency: "Currency code (e.g. INR)",
16761
+ vendorId: "Vendor / store ID",
16762
+ refundAmount: "Refund amount (refund triggers)",
16763
+ invoiceNumber: "Generated invoice number",
16764
+ invoiceUrl: "Link to download invoice PDF"
16765
+ };
16766
+ __name(PushTriggerBindings, "PushTriggerBindings");
16767
+ }
16768
+ });
16769
+ function PushConfigModal({ open, onOpenChange }) {
16770
+ const [loading, setLoading] = useState(false);
16771
+ const [saving, setSaving] = useState(false);
16772
+ const [testing, setTesting] = useState(false);
16773
+ const [serviceAccountJson, setServiceAccountJson] = useState("");
16774
+ const [projectId, setProjectId] = useState("");
16775
+ const [clientEmail, setClientEmail] = useState("");
16776
+ const [privateKey, setPrivateKey] = useState("");
16777
+ const [isValidJson, setIsValidJson] = useState(false);
16778
+ const [testToken, setTestToken] = useState("");
16779
+ const [testTitle, setTestTitle] = useState("Test Push Notification");
16780
+ const [testBody, setTestBody] = useState("Firebase Push Notification setup is working!");
16781
+ useEffect(() => {
16782
+ if (!open) return;
16783
+ let cancelled = false;
16784
+ (async () => {
16785
+ try {
16786
+ setLoading(true);
16787
+ const res = await fetch("/api/settings/push", {
16788
+ cache: "no-store"
16789
+ });
16790
+ if (res.ok) {
16791
+ const data = await res.json();
16792
+ if (!cancelled && data) {
16793
+ const rawJson = data.firebase_service_account_json || "";
16794
+ setServiceAccountJson(rawJson);
16795
+ if (rawJson.trim()) {
16796
+ parseAndExtract(rawJson, false);
16797
+ } else {
16798
+ setProjectId(data.firebase_project_id || "");
16799
+ setClientEmail(data.firebase_client_email || "");
16800
+ setPrivateKey(data.firebase_private_key || "");
16801
+ }
16802
+ }
16803
+ }
16804
+ } catch {
16805
+ toast.error("Failed to load push configuration");
16806
+ } finally {
16807
+ if (!cancelled) setLoading(false);
16808
+ }
16809
+ })();
16810
+ return () => {
16811
+ cancelled = true;
16812
+ };
16813
+ }, [
16814
+ open
16815
+ ]);
16816
+ const parseAndExtract = /* @__PURE__ */ __name((input, showToast = true) => {
16817
+ if (!input.trim()) {
16818
+ setIsValidJson(false);
16819
+ return;
16820
+ }
16821
+ try {
16822
+ const parsed = JSON.parse(input);
16823
+ if (parsed.project_id) setProjectId(parsed.project_id);
16824
+ if (parsed.client_email) setClientEmail(parsed.client_email);
16825
+ if (parsed.private_key) setPrivateKey(parsed.private_key);
16826
+ setIsValidJson(true);
16827
+ if (showToast) {
16828
+ toast.success("Valid Service Account JSON loaded");
16829
+ }
16830
+ } catch {
16831
+ setIsValidJson(false);
16832
+ if (showToast) {
16833
+ toast.error("Invalid Service Account JSON format");
16834
+ }
16835
+ }
16836
+ }, "parseAndExtract");
16837
+ const handleJsonChange = /* @__PURE__ */ __name((val) => {
16838
+ setServiceAccountJson(val);
16839
+ if (val.trim()) {
16840
+ parseAndExtract(val, false);
16841
+ } else {
16842
+ setIsValidJson(false);
16843
+ }
16844
+ }, "handleJsonChange");
16845
+ const handleSaveConfig = /* @__PURE__ */ __name(async () => {
16846
+ if (serviceAccountJson.trim() && !isValidJson) {
16847
+ toast.error("Please enter a valid Firebase Service Account JSON file");
16848
+ return;
16849
+ }
16850
+ setSaving(true);
16851
+ try {
16852
+ const res = await fetch("/api/settings/push", {
16853
+ method: "PUT",
16854
+ headers: {
16855
+ "Content-Type": "application/json"
16856
+ },
16857
+ body: JSON.stringify({
16858
+ firebase_service_account_json: {
16859
+ value: serviceAccountJson.trim(),
16860
+ type: "secret"
16861
+ },
16862
+ firebase_project_id: {
16863
+ value: projectId.trim(),
16864
+ type: "secret"
16865
+ },
16866
+ firebase_client_email: {
16867
+ value: clientEmail.trim(),
16868
+ type: "secret"
16869
+ },
16870
+ firebase_private_key: {
16871
+ value: privateKey.trim(),
16872
+ type: "secret"
16873
+ }
16874
+ })
16875
+ });
16876
+ if (!res.ok) {
16877
+ toast.error("Failed to save push configuration");
16878
+ return;
16879
+ }
16880
+ toast.success("Firebase Service Account JSON saved successfully");
16881
+ onOpenChange(false);
16882
+ } catch {
16883
+ toast.error("Failed to save push configuration");
16884
+ } finally {
16885
+ setSaving(false);
16886
+ }
16887
+ }, "handleSaveConfig");
16888
+ const handleSendTestPush = /* @__PURE__ */ __name(async () => {
16889
+ if (!testToken.trim()) {
16890
+ toast.error("Please enter a target FCM device token to test");
16891
+ return;
16892
+ }
16893
+ setTesting(true);
16894
+ try {
16895
+ const res = await fetch("/api/settings/push/test", {
16896
+ method: "POST",
16897
+ headers: {
16898
+ "Content-Type": "application/json"
16899
+ },
16900
+ body: JSON.stringify({
16901
+ token: testToken.trim(),
16902
+ title: testTitle.trim(),
16903
+ body: testBody.trim()
16904
+ })
16905
+ });
16906
+ const data = await res.json();
16907
+ if (!res.ok || !data.success) {
16908
+ toast.error(data.error || "Failed to send test push notification");
16909
+ return;
16910
+ }
16911
+ toast.success("Test push notification sent successfully!");
16912
+ } catch {
16913
+ toast.error("Failed to send test push notification");
16914
+ } finally {
16915
+ setTesting(false);
16916
+ }
16917
+ }, "handleSendTestPush");
16918
+ if (!open) return null;
16919
+ return /* @__PURE__ */ React.createElement("div", {
16920
+ 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"
16921
+ }, /* @__PURE__ */ React.createElement("div", {
16922
+ 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"
16923
+ }, /* @__PURE__ */ React.createElement("div", {
16924
+ className: "flex items-center justify-between border-b border-gray-200 pb-4 dark:border-gray-800"
16925
+ }, /* @__PURE__ */ React.createElement("div", {
16926
+ className: "flex items-center gap-2"
16927
+ }, /* @__PURE__ */ React.createElement(Smartphone, {
16928
+ className: "h-5 w-5 text-gray-800 dark:text-gray-200"
16929
+ }), /* @__PURE__ */ React.createElement("h2", {
16930
+ className: "text-lg font-semibold text-gray-900 dark:text-gray-100"
16931
+ }, "Firebase Service Account JSON Configuration")), /* @__PURE__ */ React.createElement("button", {
16932
+ type: "button",
16933
+ onClick: /* @__PURE__ */ __name(() => onOpenChange(false), "onClick"),
16934
+ className: "text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
16935
+ }, "\u2715")), loading ? /* @__PURE__ */ React.createElement("div", {
16936
+ className: "flex items-center justify-center py-12"
16937
+ }, /* @__PURE__ */ React.createElement(Loader2, {
16938
+ className: "h-6 w-6 animate-spin text-gray-500"
16939
+ })) : /* @__PURE__ */ React.createElement("div", {
16940
+ className: "space-y-6"
16941
+ }, /* @__PURE__ */ React.createElement("div", {
16942
+ className: "space-y-2"
16943
+ }, /* @__PURE__ */ React.createElement(Label3, {
16944
+ htmlFor: "service-account-json",
16945
+ className: "text-xs font-semibold text-gray-800 dark:text-gray-200 flex items-center justify-between"
16946
+ }, /* @__PURE__ */ React.createElement("span", {
16947
+ className: "flex items-center gap-1.5"
16948
+ }, /* @__PURE__ */ React.createElement(KeyRound, {
16949
+ className: "h-4 w-4 text-blue-500"
16950
+ }), "Firebase Service Account JSON File"), isValidJson && /* @__PURE__ */ React.createElement("span", {
16951
+ className: "text-xs font-normal text-green-600 flex items-center gap-1"
16952
+ }, /* @__PURE__ */ React.createElement(CheckCircle2, {
16953
+ className: "h-3.5 w-3.5"
16954
+ }), " Valid JSON")), /* @__PURE__ */ React.createElement(Textarea, {
16955
+ id: "service-account-json",
16956
+ value: serviceAccountJson,
16957
+ onChange: /* @__PURE__ */ __name((e) => handleJsonChange(e.target.value), "onChange"),
16958
+ placeholder: `Paste your entire service-account.json content here...
16959
+ {
16960
+ "type": "service_account",
16961
+ "project_id": "...",
16962
+ "private_key": "...",
16963
+ "client_email": "..."
16964
+ }`,
16965
+ className: "h-44 font-mono text-xs border-gray-300 dark:border-gray-700"
16966
+ })), isValidJson && /* @__PURE__ */ React.createElement("div", {
16967
+ 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"
16968
+ }, /* @__PURE__ */ React.createElement(Label3, {
16969
+ className: "text-xs font-semibold text-gray-700 dark:text-gray-300"
16970
+ }, "Extracted Firebase Credentials Preview"), /* @__PURE__ */ React.createElement("div", {
16971
+ className: "grid grid-cols-1 sm:grid-cols-2 gap-3 text-xs"
16972
+ }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("span", {
16973
+ className: "font-medium text-gray-500"
16974
+ }, "Project ID:"), /* @__PURE__ */ React.createElement("p", {
16975
+ className: "font-mono text-gray-900 dark:text-gray-100 truncate"
16976
+ }, projectId || "\u2014")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("span", {
16977
+ className: "font-medium text-gray-500"
16978
+ }, "Client Email:"), /* @__PURE__ */ React.createElement("p", {
16979
+ className: "font-mono text-gray-900 dark:text-gray-100 truncate"
16980
+ }, clientEmail || "\u2014")))), /* @__PURE__ */ React.createElement("div", {
16981
+ 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"
16982
+ }, /* @__PURE__ */ React.createElement(Label3, {
16983
+ className: "text-xs font-semibold text-gray-800 dark:text-gray-200 flex items-center gap-1.5"
16984
+ }, /* @__PURE__ */ React.createElement(Send, {
16985
+ className: "h-3.5 w-3.5 text-green-500"
16986
+ }), "Test Push Notification (Target Device Token)"), /* @__PURE__ */ React.createElement("div", {
16987
+ className: "grid grid-cols-1 sm:grid-cols-2 gap-2"
16988
+ }, /* @__PURE__ */ React.createElement(Input, {
16989
+ value: testTitle,
16990
+ onChange: /* @__PURE__ */ __name((e) => setTestTitle(e.target.value), "onChange"),
16991
+ placeholder: "Test Notification Title",
16992
+ className: "text-xs"
16993
+ }), /* @__PURE__ */ React.createElement(Input, {
16994
+ value: testBody,
16995
+ onChange: /* @__PURE__ */ __name((e) => setTestBody(e.target.value), "onChange"),
16996
+ placeholder: "Test Notification Body",
16997
+ className: "text-xs"
16998
+ })), /* @__PURE__ */ React.createElement("div", {
16999
+ className: "flex gap-2"
17000
+ }, /* @__PURE__ */ React.createElement(Input, {
17001
+ value: testToken,
17002
+ onChange: /* @__PURE__ */ __name((e) => setTestToken(e.target.value), "onChange"),
17003
+ placeholder: "Target FCM Device Token",
17004
+ className: "text-xs font-mono"
17005
+ }), /* @__PURE__ */ React.createElement(Button, {
17006
+ type: "button",
17007
+ variant: "secondary",
17008
+ size: "sm",
17009
+ disabled: testing,
17010
+ onClick: handleSendTestPush,
17011
+ className: "shrink-0 gap-1.5"
17012
+ }, testing ? /* @__PURE__ */ React.createElement(Loader2, {
17013
+ className: "h-3.5 w-3.5 animate-spin"
17014
+ }) : /* @__PURE__ */ React.createElement(Send, {
17015
+ className: "h-3.5 w-3.5"
17016
+ }), "Send Test"))), /* @__PURE__ */ React.createElement("div", {
17017
+ className: "flex justify-end gap-2 border-t border-gray-200 pt-4 dark:border-gray-800"
17018
+ }, /* @__PURE__ */ React.createElement(Button, {
17019
+ type: "button",
17020
+ variant: "outline",
17021
+ onClick: /* @__PURE__ */ __name(() => onOpenChange(false), "onClick")
17022
+ }, "Cancel"), /* @__PURE__ */ React.createElement(Button, {
17023
+ type: "button",
17024
+ disabled: saving,
17025
+ onClick: handleSaveConfig,
17026
+ className: "gap-1.5"
17027
+ }, saving && /* @__PURE__ */ React.createElement(Loader2, {
17028
+ className: "h-4 w-4 animate-spin"
17029
+ }), "Save Service Account JSON")))));
17030
+ }
17031
+ var init_PushConfigModal = __esm({
17032
+ "src/admin/components/PushConfigModal.tsx"() {
17033
+ "use client";
17034
+ init_button();
17035
+ init_input();
17036
+ init_textarea();
17037
+ init_label();
17038
+ __name(PushConfigModal, "PushConfigModal");
17039
+ }
17040
+ });
16272
17041
  function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledChange, onSaveSettings, saving }) {
16273
17042
  const [mainTab, setMainTab] = useState("email");
16274
17043
  const [audienceTab, setAudienceTab] = useState("customer");
@@ -16276,6 +17045,10 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
16276
17045
  const [emailVendorEnabled, setEmailVendorEnabled] = useState(true);
16277
17046
  const [emailAdminEnabled, setEmailAdminEnabled] = useState(true);
16278
17047
  const [whatsappEnabled, setWhatsappEnabled] = useState(true);
17048
+ const [pushCustomerEnabled, setPushCustomerEnabled] = useState(true);
17049
+ const [pushVendorEnabled, setPushVendorEnabled] = useState(true);
17050
+ const [pushAdminEnabled, setPushAdminEnabled] = useState(true);
17051
+ const [showPushConfigModal, setShowPushConfigModal] = useState(false);
16279
17052
  const [multiVendorOn, setMultiVendorOn] = useState(true);
16280
17053
  const [loading, setLoading] = useState(true);
16281
17054
  const [savingChannel, setSavingChannel] = useState(false);
@@ -16306,6 +17079,9 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
16306
17079
  setEmailVendorEnabled(data.email_vendor_enabled !== "false" && data.email_vendor_enabled !== "0");
16307
17080
  setEmailAdminEnabled(data.email_admin_enabled !== "false" && data.email_admin_enabled !== "0");
16308
17081
  setWhatsappEnabled(data.whatsapp_enabled !== "false" && data.whatsapp_enabled !== "0");
17082
+ setPushCustomerEnabled(data.push_customer_enabled !== "false" && data.push_customer_enabled !== "0");
17083
+ setPushVendorEnabled(data.push_vendor_enabled !== "false" && data.push_vendor_enabled !== "0");
17084
+ setPushAdminEnabled(data.push_admin_enabled !== "false" && data.push_admin_enabled !== "0");
16309
17085
  }
16310
17086
  }
16311
17087
  } catch {
@@ -16323,7 +17099,8 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
16323
17099
  setSavingChannel(true);
16324
17100
  try {
16325
17101
  const isAnyEmailEnabled = emailCustomerEnabled || emailVendorEnabled || emailAdminEnabled;
16326
- const isAnyChannelEnabled = isAnyEmailEnabled || whatsappEnabled;
17102
+ const isAnyPushEnabled = pushCustomerEnabled || pushVendorEnabled || pushAdminEnabled;
17103
+ const isAnyChannelEnabled = isAnyEmailEnabled || whatsappEnabled || isAnyPushEnabled;
16327
17104
  const res = await fetch(`/api/settings/${settingsGroup}`, {
16328
17105
  method: "PUT",
16329
17106
  headers: {
@@ -16350,6 +17127,22 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
16350
17127
  value: whatsappEnabled ? "true" : "false",
16351
17128
  type: "public"
16352
17129
  },
17130
+ push_enabled: {
17131
+ value: isAnyPushEnabled ? "true" : "false",
17132
+ type: "public"
17133
+ },
17134
+ push_customer_enabled: {
17135
+ value: pushCustomerEnabled ? "true" : "false",
17136
+ type: "public"
17137
+ },
17138
+ push_vendor_enabled: {
17139
+ value: pushVendorEnabled ? "true" : "false",
17140
+ type: "public"
17141
+ },
17142
+ push_admin_enabled: {
17143
+ value: pushAdminEnabled ? "true" : "false",
17144
+ type: "public"
17145
+ },
16353
17146
  enabled: {
16354
17147
  value: isAnyChannelEnabled ? "true" : "false",
16355
17148
  type: "public"
@@ -16371,7 +17164,10 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
16371
17164
  }, "handleSaveChannels");
16372
17165
  return /* @__PURE__ */ React.createElement("div", {
16373
17166
  className: "space-y-5"
16374
- }, /* @__PURE__ */ React.createElement("div", {
17167
+ }, /* @__PURE__ */ React.createElement(PushConfigModal, {
17168
+ open: showPushConfigModal,
17169
+ onOpenChange: setShowPushConfigModal
17170
+ }), /* @__PURE__ */ React.createElement("div", {
16375
17171
  className: "border-b border-gray-200 dark:border-gray-700"
16376
17172
  }, /* @__PURE__ */ React.createElement("div", {
16377
17173
  className: "flex gap-1"
@@ -16387,7 +17183,13 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
16387
17183
  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"}`
16388
17184
  }, /* @__PURE__ */ React.createElement(MessageCircle, {
16389
17185
  className: "h-4 w-4"
16390
- }), "WhatsApp template"))), mainTab === "email" && /* @__PURE__ */ React.createElement("div", {
17186
+ }), "WhatsApp template"), /* @__PURE__ */ React.createElement("button", {
17187
+ type: "button",
17188
+ onClick: /* @__PURE__ */ __name(() => setMainTab("push"), "onClick"),
17189
+ 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"}`
17190
+ }, /* @__PURE__ */ React.createElement(Smartphone, {
17191
+ className: "h-4 w-4"
17192
+ }), "Mobile notification template"))), mainTab === "email" && /* @__PURE__ */ React.createElement("div", {
16391
17193
  className: "space-y-5"
16392
17194
  }, /* @__PURE__ */ React.createElement("div", {
16393
17195
  className: "flex rounded-md bg-gray-100 p-1 dark:bg-gray-800/60 w-fit"
@@ -16421,7 +17223,7 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
16421
17223
  className: "h-4 w-4 text-blue-600 dark:text-blue-400"
16422
17224
  }), "Enable Customer Email Notifications"), /* @__PURE__ */ React.createElement("p", {
16423
17225
  className: "text-xs text-gray-500 dark:text-gray-400"
16424
- }, "When enabled, order confirmations, tickets, and status updates are emailed to customers.")), /* @__PURE__ */ React.createElement(Switch, {
17226
+ }, "When enabled, order updates, invoices, and ticket details are emailed to customers.")), /* @__PURE__ */ React.createElement(Switch, {
16425
17227
  id: "email-customer-notifications-enabled",
16426
17228
  checked: emailCustomerEnabled,
16427
17229
  onCheckedChange: setEmailCustomerEnabled,
@@ -16437,10 +17239,10 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
16437
17239
  }, /* @__PURE__ */ React.createElement("div", {
16438
17240
  className: "text-sm font-medium text-gray-900 dark:text-gray-100 flex items-center gap-2"
16439
17241
  }, /* @__PURE__ */ React.createElement(Store, {
16440
- className: "h-4 w-4 text-purple-600 dark:text-purple-400"
17242
+ className: "h-4 w-4 text-purple-600 dark:purple-400"
16441
17243
  }), "Enable Vendor Email Notifications"), /* @__PURE__ */ React.createElement("p", {
16442
17244
  className: "text-xs text-gray-500 dark:text-gray-400"
16443
- }, "When enabled, vendors are emailed automatically when order is placed/cancelled for their products.")), /* @__PURE__ */ React.createElement(Switch, {
17245
+ }, "When enabled, vendors receive email notifications for new orders assigned to them.")), /* @__PURE__ */ React.createElement(Switch, {
16444
17246
  id: "email-vendor-notifications-enabled",
16445
17247
  checked: emailVendorEnabled,
16446
17248
  onCheckedChange: setEmailVendorEnabled,
@@ -16483,7 +17285,104 @@ function EventNotificationsPluginSettings({ settingsGroup, enabled, onEnabledCha
16483
17285
  checked: whatsappEnabled,
16484
17286
  onCheckedChange: setWhatsappEnabled,
16485
17287
  disabled: loading
16486
- })), /* @__PURE__ */ React.createElement(WhatsAppTriggerBindings, null)), /* @__PURE__ */ React.createElement(Button, {
17288
+ })), /* @__PURE__ */ React.createElement(WhatsAppTriggerBindings, null)), mainTab === "push" && /* @__PURE__ */ React.createElement("div", {
17289
+ className: "space-y-5"
17290
+ }, /* @__PURE__ */ React.createElement("div", {
17291
+ 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"
17292
+ }, /* @__PURE__ */ React.createElement("div", {
17293
+ className: "space-y-0.5"
17294
+ }, /* @__PURE__ */ React.createElement("div", {
17295
+ className: "text-sm font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2"
17296
+ }, /* @__PURE__ */ React.createElement(Smartphone, {
17297
+ className: "h-4 w-4 text-blue-600 dark:text-blue-400"
17298
+ }), "Firebase Cloud Messaging (FCM) Setup"), /* @__PURE__ */ React.createElement("p", {
17299
+ className: "text-xs text-gray-500 dark:text-gray-400"
17300
+ }, "Set up your Firebase project credentials to send mobile push notifications to iOS and Android apps.")), /* @__PURE__ */ React.createElement(Button, {
17301
+ type: "button",
17302
+ variant: "outline",
17303
+ size: "sm",
17304
+ onClick: /* @__PURE__ */ __name(() => setShowPushConfigModal(true), "onClick"),
17305
+ className: "gap-1.5 bg-white dark:bg-gray-900 shrink-0"
17306
+ }, /* @__PURE__ */ React.createElement(Settings, {
17307
+ className: "h-4 w-4 text-gray-600"
17308
+ }), "Set Configuration")), /* @__PURE__ */ React.createElement("div", {
17309
+ className: "flex rounded-md bg-gray-100 p-1 dark:bg-gray-800/60 w-fit"
17310
+ }, /* @__PURE__ */ React.createElement("button", {
17311
+ type: "button",
17312
+ onClick: /* @__PURE__ */ __name(() => setAudienceTab("customer"), "onClick"),
17313
+ 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"}`
17314
+ }, /* @__PURE__ */ React.createElement(User, {
17315
+ className: "h-3.5 w-3.5 text-blue-500"
17316
+ }), "Customers"), multiVendorOn && /* @__PURE__ */ React.createElement("button", {
17317
+ type: "button",
17318
+ onClick: /* @__PURE__ */ __name(() => setAudienceTab("vendor"), "onClick"),
17319
+ 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"}`
17320
+ }, /* @__PURE__ */ React.createElement(Store, {
17321
+ className: "h-3.5 w-3.5 text-purple-500"
17322
+ }), "Vendors"), /* @__PURE__ */ React.createElement("button", {
17323
+ type: "button",
17324
+ onClick: /* @__PURE__ */ __name(() => setAudienceTab("admin"), "onClick"),
17325
+ 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"}`
17326
+ }, /* @__PURE__ */ React.createElement(ShieldCheck, {
17327
+ className: "h-3.5 w-3.5 text-amber-500"
17328
+ }), "Admin")), audienceTab === "customer" && /* @__PURE__ */ React.createElement("div", {
17329
+ className: "space-y-4"
17330
+ }, /* @__PURE__ */ React.createElement("div", {
17331
+ 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"
17332
+ }, /* @__PURE__ */ React.createElement("div", {
17333
+ className: "space-y-0.5"
17334
+ }, /* @__PURE__ */ React.createElement("div", {
17335
+ className: "text-sm font-medium text-gray-900 dark:text-gray-100 flex items-center gap-2"
17336
+ }, /* @__PURE__ */ React.createElement(User, {
17337
+ className: "h-4 w-4 text-blue-600 dark:text-blue-400"
17338
+ }), "Enable Customer Mobile Push Notifications"), /* @__PURE__ */ React.createElement("p", {
17339
+ className: "text-xs text-gray-500 dark:text-gray-400"
17340
+ }, "When enabled, order confirmation alerts and updates are pushed directly to customer mobile apps.")), /* @__PURE__ */ React.createElement(Switch, {
17341
+ id: "push-customer-notifications-enabled",
17342
+ checked: pushCustomerEnabled,
17343
+ onCheckedChange: setPushCustomerEnabled,
17344
+ disabled: loading
17345
+ })), /* @__PURE__ */ React.createElement(PushTriggerBindings, {
17346
+ audience: "customer"
17347
+ })), audienceTab === "vendor" && multiVendorOn && /* @__PURE__ */ React.createElement("div", {
17348
+ className: "space-y-4"
17349
+ }, /* @__PURE__ */ React.createElement("div", {
17350
+ 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"
17351
+ }, /* @__PURE__ */ React.createElement("div", {
17352
+ className: "space-y-0.5"
17353
+ }, /* @__PURE__ */ React.createElement("div", {
17354
+ className: "text-sm font-medium text-gray-900 dark:text-gray-100 flex items-center gap-2"
17355
+ }, /* @__PURE__ */ React.createElement(Store, {
17356
+ className: "h-4 w-4 text-purple-600 dark:text-purple-400"
17357
+ }), "Enable Vendor Mobile Push Notifications"), /* @__PURE__ */ React.createElement("p", {
17358
+ className: "text-xs text-gray-500 dark:text-gray-400"
17359
+ }, "When enabled, vendor mobile app receives instant push alerts for assigned orders.")), /* @__PURE__ */ React.createElement(Switch, {
17360
+ id: "push-vendor-notifications-enabled",
17361
+ checked: pushVendorEnabled,
17362
+ onCheckedChange: setPushVendorEnabled,
17363
+ disabled: loading
17364
+ })), /* @__PURE__ */ React.createElement(PushTriggerBindings, {
17365
+ audience: "vendor"
17366
+ })), audienceTab === "admin" && /* @__PURE__ */ React.createElement("div", {
17367
+ className: "space-y-4"
17368
+ }, /* @__PURE__ */ React.createElement("div", {
17369
+ 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"
17370
+ }, /* @__PURE__ */ React.createElement("div", {
17371
+ className: "space-y-0.5"
17372
+ }, /* @__PURE__ */ React.createElement("div", {
17373
+ className: "text-sm font-medium text-gray-900 dark:text-gray-100 flex items-center gap-2"
17374
+ }, /* @__PURE__ */ React.createElement(ShieldCheck, {
17375
+ className: "h-4 w-4 text-amber-600 dark:text-amber-400"
17376
+ }), "Enable Admin Mobile Push Notifications"), /* @__PURE__ */ React.createElement("p", {
17377
+ className: "text-xs text-gray-500 dark:text-gray-400"
17378
+ }, "When enabled, store administrators receive mobile push alerts for all orders placed/cancelled.")), /* @__PURE__ */ React.createElement(Switch, {
17379
+ id: "push-admin-notifications-enabled",
17380
+ checked: pushAdminEnabled,
17381
+ onCheckedChange: setPushAdminEnabled,
17382
+ disabled: loading
17383
+ })), /* @__PURE__ */ React.createElement(PushTriggerBindings, {
17384
+ audience: "admin"
17385
+ }))), /* @__PURE__ */ React.createElement(Button, {
16487
17386
  size: "sm",
16488
17387
  onClick: /* @__PURE__ */ __name(() => void handleSaveChannels(), "onClick"),
16489
17388
  disabled: saving || savingChannel || loading,
@@ -16499,9 +17398,207 @@ var init_EventNotificationsPluginSettings = __esm({
16499
17398
  init_switch();
16500
17399
  init_whatsappTriggerBindingsPage();
16501
17400
  init_emailTriggersBindingsPage();
17401
+ init_pushTriggersBindingsPage();
17402
+ init_PushConfigModal();
16502
17403
  __name(EventNotificationsPluginSettings, "EventNotificationsPluginSettings");
16503
17404
  }
16504
17405
  });
17406
+ function googleRedirectUri() {
17407
+ if (typeof window === "undefined") {
17408
+ const path2 = typeof process !== "undefined" && process.env.NEXT_PUBLIC_GOOGLE_OAUTH_CALLBACK_PATH?.trim() || "/api/auth/callback/google";
17409
+ return path2.startsWith("http") ? path2 : path2;
17410
+ }
17411
+ const configured = process.env.NEXT_PUBLIC_GOOGLE_OAUTH_CALLBACK_PATH?.trim();
17412
+ if (configured?.startsWith("http")) return configured;
17413
+ const path = configured || "/api/auth/callback/google";
17414
+ return `${window.location.origin}${path.startsWith("/") ? path : `/${path}`}`;
17415
+ }
17416
+ function AuthProvidersPluginSettings({ settingsGroup, onEnabledChange }) {
17417
+ const [loading, setLoading] = useState(true);
17418
+ const [saving, setSaving] = useState(false);
17419
+ const [googleEnabled, setGoogleEnabled] = useState(false);
17420
+ const [googleClientId, setGoogleClientId] = useState("");
17421
+ const [googleClientSecret, setGoogleClientSecret] = useState("");
17422
+ const [hasSavedSecret, setHasSavedSecret] = useState(false);
17423
+ const [copied, setCopied] = useState(false);
17424
+ const onEnabledChangeRef = useRef(onEnabledChange);
17425
+ onEnabledChangeRef.current = onEnabledChange;
17426
+ const redirectUri = useMemo(() => googleRedirectUri(), []);
17427
+ useEffect(() => {
17428
+ let cancelled = false;
17429
+ (async () => {
17430
+ setLoading(true);
17431
+ try {
17432
+ const res = await fetch(`/api/settings/${settingsGroup}`, {
17433
+ cache: "no-store"
17434
+ });
17435
+ const data = res.ok ? await res.json() : {};
17436
+ if (cancelled) return;
17437
+ const enabled = data.googleEnabled === "true" || data.googleEnabled === "1" || data.enabled === "true" && data.googleEnabled !== "false";
17438
+ setGoogleEnabled(enabled);
17439
+ setGoogleClientId(String(data.googleClientId ?? "").trim());
17440
+ const secret = String(data.googleClientSecret ?? "").trim();
17441
+ setGoogleClientSecret("");
17442
+ setHasSavedSecret(Boolean(secret));
17443
+ } catch {
17444
+ } finally {
17445
+ if (!cancelled) setLoading(false);
17446
+ }
17447
+ })();
17448
+ return () => {
17449
+ cancelled = true;
17450
+ };
17451
+ }, [
17452
+ settingsGroup
17453
+ ]);
17454
+ const handleCopyRedirect = /* @__PURE__ */ __name(async () => {
17455
+ try {
17456
+ await navigator.clipboard.writeText(redirectUri);
17457
+ setCopied(true);
17458
+ toast.success("Redirect URI copied");
17459
+ setTimeout(() => setCopied(false), 2e3);
17460
+ } catch {
17461
+ toast.error("Could not copy redirect URI");
17462
+ }
17463
+ }, "handleCopyRedirect");
17464
+ const handleSave = /* @__PURE__ */ __name(async () => {
17465
+ setSaving(true);
17466
+ try {
17467
+ const payload = {
17468
+ enabled: {
17469
+ value: googleEnabled ? "true" : "false",
17470
+ type: "public"
17471
+ },
17472
+ googleEnabled: {
17473
+ value: googleEnabled ? "true" : "false",
17474
+ type: "public"
17475
+ },
17476
+ googleClientId: {
17477
+ value: googleClientId.trim(),
17478
+ type: "private"
17479
+ }
17480
+ };
17481
+ if (googleClientSecret.trim()) {
17482
+ payload.googleClientSecret = {
17483
+ value: googleClientSecret.trim(),
17484
+ type: "private",
17485
+ encrypted: true
17486
+ };
17487
+ }
17488
+ const res = await fetch(`/api/settings/${settingsGroup}`, {
17489
+ method: "PUT",
17490
+ headers: {
17491
+ "Content-Type": "application/json"
17492
+ },
17493
+ body: JSON.stringify(payload)
17494
+ });
17495
+ if (!res.ok) {
17496
+ toast.error("Failed to save Google auth settings");
17497
+ return;
17498
+ }
17499
+ if (googleClientSecret.trim()) {
17500
+ setHasSavedSecret(true);
17501
+ setGoogleClientSecret("");
17502
+ }
17503
+ onEnabledChangeRef.current?.(googleEnabled);
17504
+ toast.success("Authentication provider settings saved");
17505
+ } catch {
17506
+ toast.error("Failed to save Google auth settings");
17507
+ } finally {
17508
+ setSaving(false);
17509
+ }
17510
+ }, "handleSave");
17511
+ if (loading) {
17512
+ return /* @__PURE__ */ React.createElement("p", {
17513
+ className: "text-sm text-gray-500 dark:text-gray-400"
17514
+ }, "Loading\u2026");
17515
+ }
17516
+ return /* @__PURE__ */ React.createElement("div", {
17517
+ className: "space-y-4"
17518
+ }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", {
17519
+ className: "text-sm font-semibold text-gray-900 dark:text-white"
17520
+ }, "Google (Web)"), /* @__PURE__ */ React.createElement("p", {
17521
+ className: "mt-1 text-xs text-gray-500 dark:text-gray-400"
17522
+ }, "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", {
17523
+ className: "flex items-center justify-between gap-2"
17524
+ }, /* @__PURE__ */ React.createElement(Label3, {
17525
+ htmlFor: `${settingsGroup}-googleEnabled`,
17526
+ className: "text-sm"
17527
+ }, "Enable Google sign-in"), /* @__PURE__ */ React.createElement(Switch, {
17528
+ id: `${settingsGroup}-googleEnabled`,
17529
+ checked: googleEnabled,
17530
+ onCheckedChange: setGoogleEnabled
17531
+ })), /* @__PURE__ */ React.createElement("div", {
17532
+ className: "space-y-1"
17533
+ }, /* @__PURE__ */ React.createElement(Label3, {
17534
+ htmlFor: `${settingsGroup}-redirectUri`,
17535
+ className: "text-sm"
17536
+ }, "Redirect URI (add in Google Console)"), /* @__PURE__ */ React.createElement("div", {
17537
+ className: "flex gap-2"
17538
+ }, /* @__PURE__ */ React.createElement(Input, {
17539
+ id: `${settingsGroup}-redirectUri`,
17540
+ value: redirectUri,
17541
+ readOnly: true,
17542
+ className: "h-9 flex-1 font-mono text-xs"
17543
+ }), /* @__PURE__ */ React.createElement(Button, {
17544
+ type: "button",
17545
+ size: "sm",
17546
+ variant: "outline",
17547
+ onClick: handleCopyRedirect,
17548
+ className: "shrink-0 gap-1"
17549
+ }, copied ? /* @__PURE__ */ React.createElement(Check, {
17550
+ className: "h-3.5 w-3.5"
17551
+ }) : /* @__PURE__ */ React.createElement(Copy, {
17552
+ className: "h-3.5 w-3.5"
17553
+ }), copied ? "Copied" : "Copy")), /* @__PURE__ */ React.createElement("p", {
17554
+ className: "text-xs text-gray-500 dark:text-gray-400"
17555
+ }, "Must match ", /* @__PURE__ */ React.createElement("code", {
17556
+ className: "text-[11px]"
17557
+ }, "NEXTAUTH_URL"), " for this environment.")), /* @__PURE__ */ React.createElement("div", {
17558
+ className: "space-y-1"
17559
+ }, /* @__PURE__ */ React.createElement(Label3, {
17560
+ htmlFor: `${settingsGroup}-googleClientId`,
17561
+ className: "text-sm"
17562
+ }, "Client ID"), /* @__PURE__ */ React.createElement(Input, {
17563
+ id: `${settingsGroup}-googleClientId`,
17564
+ value: googleClientId,
17565
+ onChange: /* @__PURE__ */ __name((e) => setGoogleClientId(e.target.value), "onChange"),
17566
+ placeholder: "xxxxx.apps.googleusercontent.com",
17567
+ className: "h-9 text-sm",
17568
+ autoComplete: "off"
17569
+ })), /* @__PURE__ */ React.createElement("div", {
17570
+ className: "space-y-1"
17571
+ }, /* @__PURE__ */ React.createElement(Label3, {
17572
+ htmlFor: `${settingsGroup}-googleClientSecret`,
17573
+ className: "text-sm"
17574
+ }, "Client Secret ", hasSavedSecret && !googleClientSecret.trim() ? "(saved)" : ""), /* @__PURE__ */ React.createElement(Input, {
17575
+ id: `${settingsGroup}-googleClientSecret`,
17576
+ type: "password",
17577
+ value: googleClientSecret,
17578
+ onChange: /* @__PURE__ */ __name((e) => setGoogleClientSecret(e.target.value), "onChange"),
17579
+ placeholder: hasSavedSecret ? "Leave blank to keep saved secret" : "Paste client secret",
17580
+ className: "h-9 text-sm",
17581
+ autoComplete: "new-password"
17582
+ })), /* @__PURE__ */ React.createElement(Button, {
17583
+ size: "sm",
17584
+ onClick: handleSave,
17585
+ disabled: saving,
17586
+ className: "gap-1"
17587
+ }, /* @__PURE__ */ React.createElement(Save, {
17588
+ className: "h-3.5 w-3.5"
17589
+ }), saving ? "Saving\u2026" : "Save"));
17590
+ }
17591
+ var init_AuthProvidersPluginSettings = __esm({
17592
+ "src/admin/components/AuthProvidersPluginSettings.tsx"() {
17593
+ "use client";
17594
+ init_button();
17595
+ init_input();
17596
+ init_label();
17597
+ init_switch();
17598
+ __name(googleRedirectUri, "googleRedirectUri");
17599
+ __name(AuthProvidersPluginSettings, "AuthProvidersPluginSettings");
17600
+ }
17601
+ });
16505
17602
 
16506
17603
  // src/plugins/llm/chat-email-intent.ts
16507
17604
  function normalizeIntentKey(raw) {
@@ -16879,6 +17976,7 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
16879
17976
  const isEventNotifications = settingsGroup === "event_notifications";
16880
17977
  const isSocialMedia = settingsGroup === "social_media";
16881
17978
  const isMultiVendor = settingsGroup === "multi_vendor";
17979
+ const isAuthProviders = settingsGroup === "auth_providers";
16882
17980
  const [enabled, setEnabled] = useState(true);
16883
17981
  const [botName, setBotName] = useState("");
16884
17982
  const [icon, setIcon] = useState("");
@@ -17228,6 +18326,10 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
17228
18326
  setWhatsappPhoneNumberId(String(data.phoneNumberId ?? data.WHATSAPP_PHONE_NUMBER_ID ?? "").trim());
17229
18327
  setWhatsappWabaId(String(data.wabaId ?? data.WHATSAPP_WABA_ID ?? "").trim());
17230
18328
  }
18329
+ if (isAuthProviders) {
18330
+ const googleOn = data.googleEnabled === "true" || data.googleEnabled === "1" || data.enabled === "true" && data.googleEnabled !== "false";
18331
+ setEnabled(googleOn);
18332
+ }
17231
18333
  if (isSocialMedia) {
17232
18334
  const savedLiToken = String(data.linkedin_access_token ?? "").trim();
17233
18335
  setLinkedinAccessToken(savedLiToken);
@@ -17265,7 +18367,8 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
17265
18367
  isWhatsapp,
17266
18368
  isEventNotifications,
17267
18369
  isSocialMedia,
17268
- isMultiVendor
18370
+ isMultiVendor,
18371
+ isAuthProviders
17269
18372
  ]);
17270
18373
  useEffect(() => {
17271
18374
  if (!isSocialMedia || loading || linkedInOrgsPrefetchedRef.current) return;
@@ -17711,6 +18814,18 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
17711
18814
  }
17712
18815
  };
17713
18816
  }
18817
+ if (isAuthProviders) {
18818
+ return {
18819
+ enabled: {
18820
+ value: enabled ? "true" : "false",
18821
+ type: "public"
18822
+ },
18823
+ googleEnabled: {
18824
+ value: enabled ? "true" : "false",
18825
+ type: "public"
18826
+ }
18827
+ };
18828
+ }
17714
18829
  if (isSocialMedia) {
17715
18830
  const payload2 = {
17716
18831
  enabled: {
@@ -18196,6 +19311,15 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
18196
19311
  toast.success("Removed from agent");
18197
19312
  await fetchAttachedKnowledge(slug);
18198
19313
  }, "handleUnlinkKbDoc");
19314
+ if (isAuthProviders) {
19315
+ return /* @__PURE__ */ React.createElement(AuthProvidersPluginSettings, {
19316
+ settingsGroup,
19317
+ onEnabledChange: /* @__PURE__ */ __name((value) => {
19318
+ setEnabled(value);
19319
+ onSaved?.();
19320
+ }, "onEnabledChange")
19321
+ });
19322
+ }
18199
19323
  if (loading) return /* @__PURE__ */ React.createElement("div", {
18200
19324
  className: "text-sm text-gray-500 dark:text-gray-400"
18201
19325
  }, "Loading...");
@@ -19532,6 +20656,10 @@ function PluginsPage() {
19532
20656
  const en = pluginDescriptors.find((p) => p.name === "event_notifications");
19533
20657
  if (en) setSelectedName(en.name);
19534
20658
  }
20659
+ if (q === "auth_providers" || q === "auth-providers" || q === "authentication") {
20660
+ const ap = pluginDescriptors.find((p) => p.name === "auth_providers");
20661
+ if (ap) setSelectedName(ap.name);
20662
+ }
19535
20663
  }, [
19536
20664
  searchParams,
19537
20665
  pluginDescriptors
@@ -19539,10 +20667,13 @@ function PluginsPage() {
19539
20667
  useEffect(() => {
19540
20668
  pluginDescriptors.forEach((p) => {
19541
20669
  if (!p.settingsGroup) return;
19542
- fetch(`/api/settings/${p.settingsGroup}`).then((r) => r.ok ? r.json() : {}).then((data) => setEnabledMap((prev) => ({
19543
- ...prev,
19544
- [p.name]: data.enabled !== "false"
19545
- }))).catch(() => {
20670
+ fetch(`/api/settings/${p.settingsGroup}`).then((r) => r.ok ? r.json() : {}).then((data) => {
20671
+ const on = p.settingsGroup === "auth_providers" ? data.googleEnabled === "true" || data.googleEnabled === "1" || data.enabled !== "false" && data.googleEnabled !== "false" && data.enabled === "true" : data.enabled !== "false";
20672
+ setEnabledMap((prev) => ({
20673
+ ...prev,
20674
+ [p.name]: on
20675
+ }));
20676
+ }).catch(() => {
19546
20677
  });
19547
20678
  });
19548
20679
  }, [
@@ -19552,10 +20683,13 @@ function PluginsPage() {
19552
20683
  const refreshEnabled = /* @__PURE__ */ __name((name) => {
19553
20684
  const d = pluginDescriptors.find((p) => p.name === name);
19554
20685
  if (!d?.settingsGroup) return;
19555
- fetch(`/api/settings/${d.settingsGroup}`).then((r) => r.ok ? r.json() : {}).then((data) => setEnabledMap((prev) => ({
19556
- ...prev,
19557
- [name]: data.enabled !== "false"
19558
- }))).catch(() => {
20686
+ fetch(`/api/settings/${d.settingsGroup}`).then((r) => r.ok ? r.json() : {}).then((data) => {
20687
+ const on = d.settingsGroup === "auth_providers" ? data.googleEnabled === "true" || data.googleEnabled === "1" || data.enabled === "true" && data.googleEnabled !== "false" : data.enabled !== "false";
20688
+ setEnabledMap((prev) => ({
20689
+ ...prev,
20690
+ [name]: on
20691
+ }));
20692
+ }).catch(() => {
19559
20693
  });
19560
20694
  }, "refreshEnabled");
19561
20695
  return /* @__PURE__ */ React.createElement("div", {
@@ -19637,6 +20771,7 @@ var init_PluginsPage = __esm({
19637
20771
  init_checkbox();
19638
20772
  init_select();
19639
20773
  init_EventNotificationsPluginSettings();
20774
+ init_AuthProvidersPluginSettings();
19640
20775
  init_ImageOrUrlField();
19641
20776
  init_chat_email_intent();
19642
20777
  init_llm_agent_scope();
@@ -19655,7 +20790,8 @@ var init_PluginsPage = __esm({
19655
20790
  pg_boss: CalendarClock,
19656
20791
  share: Share2,
19657
20792
  whatsapp: MessageCircle,
19658
- event_notifications: Bell
20793
+ event_notifications: Bell,
20794
+ auth_providers: KeyRound
19659
20795
  };
19660
20796
  __name(normalizeSmsProviderChoice, "normalizeSmsProviderChoice");
19661
20797
  __name(normalizeMsg91ApiMode, "normalizeMsg91ApiMode");
@@ -20128,9 +21264,7 @@ function VendorRolesPage() {
20128
21264
  className: "text-base font-semibold text-white"
20129
21265
  }, "Vendor Store Roles & Permissions"), /* @__PURE__ */ React25__default.createElement("p", {
20130
21266
  className: "mt-0.5 text-xs text-gray-300"
20131
- }, "Set CRUD permissions for any role (Viewer, Manager, Fulfillment, Catalog Staff, or custom roles). Permissions are stored in the ", /* @__PURE__ */ React25__default.createElement("code", {
20132
- className: "font-mono bg-gray-700 px-1 py-0.5 rounded text-gray-200"
20133
- }, "vendor_role_permissions"), " table and apply directly to team members assigned to that role.")), /* @__PURE__ */ React25__default.createElement("div", {
21267
+ }, "Set CRUD permissions for any role (Viewer, Manager, Fulfillment, Catalog Staff, or custom roles).")), /* @__PURE__ */ React25__default.createElement("div", {
20134
21268
  className: "min-w-0 p-6"
20135
21269
  }, error ? /* @__PURE__ */ React25__default.createElement("p", {
20136
21270
  className: "mb-4 text-sm text-red-600 dark:text-red-400"
@@ -20629,7 +21763,7 @@ function PlatformRolesPage() {
20629
21763
  className: "p-2"
20630
21764
  }, /* @__PURE__ */ React25__default.createElement("div", {
20631
21765
  className: "flex items-center justify-center gap-1"
20632
- }, /* @__PURE__ */ React25__default.createElement("span", null, "C"), /* @__PURE__ */ React25__default.createElement("input", {
21766
+ }, /* @__PURE__ */ React25__default.createElement("span", null, "Create"), /* @__PURE__ */ React25__default.createElement("input", {
20633
21767
  type: "checkbox",
20634
21768
  checked: isColumnAllChecked("canCreate"),
20635
21769
  onChange: /* @__PURE__ */ __name(() => toggleColumn("canCreate"), "onChange")
@@ -20637,7 +21771,7 @@ function PlatformRolesPage() {
20637
21771
  className: "p-2"
20638
21772
  }, /* @__PURE__ */ React25__default.createElement("div", {
20639
21773
  className: "flex items-center justify-center gap-1"
20640
- }, /* @__PURE__ */ React25__default.createElement("span", null, "R"), /* @__PURE__ */ React25__default.createElement("input", {
21774
+ }, /* @__PURE__ */ React25__default.createElement("span", null, "Read"), /* @__PURE__ */ React25__default.createElement("input", {
20641
21775
  type: "checkbox",
20642
21776
  checked: isColumnAllChecked("canRead"),
20643
21777
  onChange: /* @__PURE__ */ __name(() => toggleColumn("canRead"), "onChange")
@@ -20645,7 +21779,7 @@ function PlatformRolesPage() {
20645
21779
  className: "p-2"
20646
21780
  }, /* @__PURE__ */ React25__default.createElement("div", {
20647
21781
  className: "flex items-center justify-center gap-1"
20648
- }, /* @__PURE__ */ React25__default.createElement("span", null, "U"), /* @__PURE__ */ React25__default.createElement("input", {
21782
+ }, /* @__PURE__ */ React25__default.createElement("span", null, "Update"), /* @__PURE__ */ React25__default.createElement("input", {
20649
21783
  type: "checkbox",
20650
21784
  checked: isColumnAllChecked("canUpdate"),
20651
21785
  onChange: /* @__PURE__ */ __name(() => toggleColumn("canUpdate"), "onChange")
@@ -20653,7 +21787,7 @@ function PlatformRolesPage() {
20653
21787
  className: "p-2"
20654
21788
  }, /* @__PURE__ */ React25__default.createElement("div", {
20655
21789
  className: "flex items-center justify-center gap-1"
20656
- }, /* @__PURE__ */ React25__default.createElement("span", null, "D"), /* @__PURE__ */ React25__default.createElement("input", {
21790
+ }, /* @__PURE__ */ React25__default.createElement("span", null, "Delete"), /* @__PURE__ */ React25__default.createElement("input", {
20657
21791
  type: "checkbox",
20658
21792
  checked: isColumnAllChecked("canDelete"),
20659
21793
  onChange: /* @__PURE__ */ __name(() => toggleColumn("canDelete"), "onChange")
@@ -34165,7 +35299,7 @@ function VendorEditPage({ vendorId }) {
34165
35299
  }), /* @__PURE__ */ React25__default.createElement(Label3, {
34166
35300
  htmlFor: "termsAccepted",
34167
35301
  className: "font-normal cursor-pointer text-sm leading-snug"
34168
- }, "I confirm the vendor has read and accepted the terms and conditions for selling on this platform."))))),
35302
+ }, "I confirm I have read and accepted the terms and conditions for selling on this platform."))))),
34169
35303
  sidebar: /* @__PURE__ */ React25__default.createElement(React25__default.Fragment, null, !create && /* @__PURE__ */ React25__default.createElement("section", null, /* @__PURE__ */ React25__default.createElement("h2", {
34170
35304
  className: "text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2"
34171
35305
  }, "Status"), /* @__PURE__ */ React25__default.createElement("div", {