@infuro/cms-core 1.0.31 → 1.0.34

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 (41) hide show
  1. package/README.md +12 -8
  2. package/dist/admin.cjs +1907 -1144
  3. package/dist/admin.cjs.map +1 -1
  4. package/dist/admin.d.cts +7 -2
  5. package/dist/admin.d.ts +7 -2
  6. package/dist/admin.js +1955 -1191
  7. package/dist/admin.js.map +1 -1
  8. package/dist/api.cjs +2704 -914
  9. package/dist/api.cjs.map +1 -1
  10. package/dist/api.d.cts +1 -1
  11. package/dist/api.d.ts +1 -1
  12. package/dist/api.js +2664 -875
  13. package/dist/api.js.map +1 -1
  14. package/dist/auth.cjs +251 -23
  15. package/dist/auth.cjs.map +1 -1
  16. package/dist/auth.d.cts +53 -22
  17. package/dist/auth.d.ts +53 -22
  18. package/dist/auth.js +240 -23
  19. package/dist/auth.js.map +1 -1
  20. package/dist/cli.cjs +42 -2
  21. package/dist/cli.cjs.map +1 -1
  22. package/dist/cli.js +42 -2
  23. package/dist/cli.js.map +1 -1
  24. package/dist/{index-BcMRGNjS.d.cts → index-Bf5GO8fu.d.cts} +4 -3
  25. package/dist/{index-Bda8D1Rm.d.ts → index-DOiJuMQA.d.ts} +4 -3
  26. package/dist/index.cjs +3408 -1284
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +121 -35
  29. package/dist/index.d.ts +121 -35
  30. package/dist/index.js +3334 -1226
  31. package/dist/index.js.map +1 -1
  32. package/dist/migrations/1778660000003-AddQrTokenToOrders.ts +32 -0
  33. package/dist/migrations/1778660000004-CreateCustomerAndCustomerContacts.ts +99 -0
  34. package/dist/migrations/1778660000005-AddCustomerIdToVendorCustomers.ts +39 -0
  35. package/dist/migrations/1778660000006-UpdateVendorCustomersContactNullable.ts +36 -0
  36. package/dist/migrations/1778660000007-VendorOwnerCustomerContactsPermission.ts +39 -0
  37. package/dist/migrations/1779100000000-ChatConversationLeadEmailSent.ts +16 -0
  38. package/dist/migrations/1779200000000-LlmAgentScope.ts +72 -0
  39. package/dist/migrations/1779300000000-VendorOwnerContactsPermission.ts +39 -0
  40. package/dist/migrations/1779400000000-AddCustomerContactIdToOrders.ts +33 -0
  41. package/package.json +5 -3
package/dist/admin.cjs CHANGED
@@ -122,6 +122,30 @@ var init_permission_entities = __esm({
122
122
  }
123
123
  });
124
124
 
125
+ // src/auth/role-helpers.ts
126
+ function isSuperAdmin(user) {
127
+ if (!user) return false;
128
+ if (user.groupId === SUPER_ADMIN_GROUP_ID) return true;
129
+ if (user.isRBACAdmin === true) return true;
130
+ return isSuperAdminGroupName(user.groupName);
131
+ }
132
+ function isVendorAdmin(user) {
133
+ if (!user?.email || isSuperAdmin(user)) return false;
134
+ if (user.groupId === VENDOR_ADMIN_GROUP_ID) return true;
135
+ if ((user.vendorIds?.length ?? 0) > 0) return true;
136
+ return isVendorGroupName(user.groupName);
137
+ }
138
+ var SUPER_ADMIN_GROUP_ID, VENDOR_ADMIN_GROUP_ID;
139
+ var init_role_helpers = __esm({
140
+ "src/auth/role-helpers.ts"() {
141
+ "use strict";
142
+ init_permission_entities();
143
+ init_vendor_scope();
144
+ SUPER_ADMIN_GROUP_ID = 1;
145
+ VENDOR_ADMIN_GROUP_ID = 5;
146
+ }
147
+ });
148
+
125
149
  // src/auth/vendor-scope.ts
126
150
  function isVendorGroupName(name) {
127
151
  if (!name?.trim()) return false;
@@ -130,12 +154,12 @@ function isVendorGroupName(name) {
130
154
  }
131
155
  function isPlatformAdministrator(user) {
132
156
  if (!user?.email) return false;
133
- return !!(user.isRBACAdmin || isSuperAdminGroupName(user.groupName));
157
+ return isSuperAdmin(user);
134
158
  }
135
159
  function isVendorPortalUser(user) {
136
160
  if (user?.isVendorPortal === true) return true;
137
161
  if (!user?.email || isPlatformAdministrator(user)) return false;
138
- if ((user.vendorIds?.length ?? 0) > 0) return true;
162
+ if (isVendorAdmin(user)) return true;
139
163
  return isVendorGroupName(user.groupName);
140
164
  }
141
165
  function isVendorOwner(user) {
@@ -159,7 +183,7 @@ var VENDOR_SCOPED_STORE_ENTITIES, VENDOR_STORE_RBAC_ENTITIES, VENDOR_OWNER_GROUP
159
183
  var init_vendor_scope = __esm({
160
184
  "src/auth/vendor-scope.ts"() {
161
185
  "use strict";
162
- init_permission_entities();
186
+ init_role_helpers();
163
187
  VENDOR_SCOPED_STORE_ENTITIES = /* @__PURE__ */ new Set([
164
188
  "products",
165
189
  "collections",
@@ -821,8 +845,8 @@ function RequiredMark() {
821
845
  ] });
822
846
  }
823
847
  function ContactDetailPage({ contactId }) {
824
- const router = (0, import_navigation29.useRouter)();
825
- const searchParams = (0, import_navigation29.useSearchParams)();
848
+ const router = (0, import_navigation30.useRouter)();
849
+ const searchParams = (0, import_navigation30.useSearchParams)();
826
850
  const listReturnUrl = safeAdminListReturnUrl(searchParams.get("from")) ?? "/admin/contacts";
827
851
  const [contact, setContact] = (0, import_react67.useState)(null);
828
852
  const [loading, setLoading] = (0, import_react67.useState)(true);
@@ -940,7 +964,7 @@ function ContactDetailPage({ contactId }) {
940
964
  return /* @__PURE__ */ (0, import_jsx_runtime82.jsxs)("div", { className: "rounded-lg bg-white p-6 shadow-md", children: [
941
965
  /* @__PURE__ */ (0, import_jsx_runtime82.jsx)("p", { className: "text-red-600", children: error ?? "Not found" }),
942
966
  /* @__PURE__ */ (0, import_jsx_runtime82.jsx)(import_link15.default, { href: listReturnUrl, children: /* @__PURE__ */ (0, import_jsx_runtime82.jsxs)(Button, { variant: "outline", className: "mt-4", children: [
943
- /* @__PURE__ */ (0, import_jsx_runtime82.jsx)(import_lucide_react49.X, { className: "h-4 w-4 mr-2" }),
967
+ /* @__PURE__ */ (0, import_jsx_runtime82.jsx)(import_lucide_react50.X, { className: "h-4 w-4 mr-2" }),
944
968
  "Close"
945
969
  ] }) })
946
970
  ] });
@@ -1017,7 +1041,7 @@ function ContactDetailPage({ contactId }) {
1017
1041
  },
1018
1042
  className: "inline-flex items-center gap-1",
1019
1043
  children: [
1020
- /* @__PURE__ */ (0, import_jsx_runtime82.jsx)(import_lucide_react49.Plus, { className: "h-3.5 w-3.5" }),
1044
+ /* @__PURE__ */ (0, import_jsx_runtime82.jsx)(import_lucide_react50.Plus, { className: "h-3.5 w-3.5" }),
1021
1045
  " Add address"
1022
1046
  ]
1023
1047
  }
@@ -1042,7 +1066,7 @@ function ContactDetailPage({ contactId }) {
1042
1066
  title: "Delete address",
1043
1067
  "aria-label": `Delete address ${a.id}`,
1044
1068
  onClick: () => setAddressPendingDelete(a),
1045
- children: /* @__PURE__ */ (0, import_jsx_runtime82.jsx)(import_lucide_react49.Trash2, { className: "h-4 w-4" })
1069
+ children: /* @__PURE__ */ (0, import_jsx_runtime82.jsx)(import_lucide_react50.Trash2, { className: "h-4 w-4" })
1046
1070
  }
1047
1071
  ) })
1048
1072
  ] }, a.id)) })
@@ -1245,16 +1269,16 @@ function ContactDetailPage({ contactId }) {
1245
1269
  )
1246
1270
  ] });
1247
1271
  }
1248
- var import_react67, import_link15, import_navigation29, import_lucide_react49, import_sonner13, import_country_state_city3, import_jsx_runtime82, ADDRESS_TAGS, CONTACT_TYPE_LABELS;
1272
+ var import_react67, import_link15, import_navigation30, import_lucide_react50, import_sonner13, import_country_state_city3, import_jsx_runtime82, ADDRESS_TAGS, CONTACT_TYPE_LABELS;
1249
1273
  var init_ContactDetailPage = __esm({
1250
1274
  "src/admin/pages/ContactDetailPage.tsx"() {
1251
1275
  "use strict";
1252
1276
  "use client";
1253
1277
  import_react67 = require("react");
1254
1278
  import_link15 = __toESM(require("next/link"), 1);
1255
- import_navigation29 = require("next/navigation");
1279
+ import_navigation30 = require("next/navigation");
1256
1280
  init_admin_list_return_url();
1257
- import_lucide_react49 = require("lucide-react");
1281
+ import_lucide_react50 = require("lucide-react");
1258
1282
  import_sonner13 = require("sonner");
1259
1283
  init_button();
1260
1284
  init_input();
@@ -1594,7 +1618,7 @@ var defaultValue = {
1594
1618
  var AdminConfigContext = (0, import_react4.createContext)(defaultValue);
1595
1619
 
1596
1620
  // src/lib/cms-version.ts
1597
- var CMS_VERSION = true ? "1.0.31" : "0.0.0";
1621
+ var CMS_VERSION = true ? "1.0.34" : "0.0.0";
1598
1622
 
1599
1623
  // src/components/Admin/Sidebar.tsx
1600
1624
  var import_jsx_runtime4 = require("react/jsx-runtime");
@@ -1802,7 +1826,7 @@ function AdminSidebar({ variant = "sidebar" }) {
1802
1826
  className: `${linkCls} ${isActive("/admin/vendor-onboard") ? linkActive : linkInactive}`,
1803
1827
  children: [
1804
1828
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.Store, { className: `h-4 w-4 mr-2 ${isActive("/admin/vendor-onboard") ? iconActive : iconInactive}` }),
1805
- "Onboard vendor"
1829
+ "Vendors"
1806
1830
  ]
1807
1831
  }
1808
1832
  ) }),
@@ -2078,6 +2102,34 @@ var BUILTIN_PLUGIN_DESCRIPTORS = [
2078
2102
  }
2079
2103
  ];
2080
2104
 
2105
+ // src/auth/auth-debug.ts
2106
+ var LOG_PREFIX = "[cms-auth]";
2107
+ function isAuthDebugClientEnabled() {
2108
+ if (typeof window === "undefined") return false;
2109
+ const v = process.env.NEXT_PUBLIC_CMS_AUTH_DEBUG?.trim().toLowerCase();
2110
+ if (v === "1" || v === "true" || v === "yes") return true;
2111
+ if (v === "0" || v === "false" || v === "no") return false;
2112
+ return process.env.NODE_ENV === "development";
2113
+ }
2114
+ function logAuthClient(message, data) {
2115
+ if (!isAuthDebugClientEnabled()) return;
2116
+ if (data) console.info(LOG_PREFIX, message, data);
2117
+ else console.info(LOG_PREFIX, message);
2118
+ }
2119
+ function summarizeSessionUserForLog(user) {
2120
+ if (!user || typeof user !== "object") return { present: false };
2121
+ const u = user;
2122
+ return {
2123
+ present: true,
2124
+ email: u.email ?? null,
2125
+ id: u.id ?? null,
2126
+ adminAccess: u.adminAccess ?? null,
2127
+ isRBACAdmin: u.isRBACAdmin ?? null,
2128
+ isVendorPortal: u.isVendorPortal ?? null,
2129
+ groupName: u.groupName ?? null
2130
+ };
2131
+ }
2132
+
2081
2133
  // src/admin/pages/AdminLayout.tsx
2082
2134
  var import_jsx_runtime7 = require("react/jsx-runtime");
2083
2135
  var PUBLIC_ADMIN_PATHS = [
@@ -2111,6 +2163,14 @@ function AdminLayoutInner({ children }) {
2111
2163
  (0, import_react9.useEffect)(() => {
2112
2164
  if (isMobile) setMoreSheetOpen(false);
2113
2165
  }, [isMobile, pathname]);
2166
+ (0, import_react9.useEffect)(() => {
2167
+ logAuthClient("AdminLayout session state", {
2168
+ pathname,
2169
+ isPublicPath,
2170
+ status,
2171
+ sessionUser: summarizeSessionUserForLog(session?.user)
2172
+ });
2173
+ }, [pathname, isPublicPath, status, session?.user]);
2114
2174
  (0, import_react9.useEffect)(() => {
2115
2175
  if (isPublicPath) return;
2116
2176
  if (status === "loading") {
@@ -2118,6 +2178,7 @@ function AdminLayoutInner({ children }) {
2118
2178
  return () => clearTimeout(timeout);
2119
2179
  }
2120
2180
  if (status === "unauthenticated") {
2181
+ logAuthClient("AdminLayout redirect \u2192 /admin/signin (unauthenticated)", { pathname });
2121
2182
  router.replace("/admin/signin");
2122
2183
  } else {
2123
2184
  setLoadingTimeout(false);
@@ -2750,7 +2811,8 @@ function CreateEditForm({
2750
2811
  apiEndpoint,
2751
2812
  columns,
2752
2813
  existingData,
2753
- duplicateSeed
2814
+ duplicateSeed,
2815
+ resource
2754
2816
  }) {
2755
2817
  const isMobile = useIsMobile();
2756
2818
  const { currency } = (0, import_react13.useContext)(AdminConfigContext);
@@ -2758,6 +2820,9 @@ function CreateEditForm({
2758
2820
  currency: currency || "INR"
2759
2821
  });
2760
2822
  const [errors, setErrors] = (0, import_react13.useState)({});
2823
+ const [password, setPassword] = (0, import_react13.useState)("");
2824
+ const [confirmPassword, setConfirmPassword] = (0, import_react13.useState)("");
2825
+ const isCustomerCreate = resource === "vendor_customers" && !existingData;
2761
2826
  (0, import_react13.useEffect)(() => {
2762
2827
  fetch("/api/settings/store").then((res) => res.json()).then((data) => {
2763
2828
  setFormData((prev) => ({
@@ -2787,14 +2852,10 @@ function CreateEditForm({
2787
2852
  };
2788
2853
  }).filter(Boolean)
2789
2854
  );
2790
- }).catch(
2791
- (err) => console.error("Failed to load customers", err)
2792
- );
2855
+ }).catch((err) => console.error("Failed to load customers", err));
2793
2856
  }, [columns]);
2794
2857
  const handleCustomerSelect = (customerId) => {
2795
- const customer = customers.find(
2796
- (c) => String(c.id) === customerId
2797
- );
2858
+ const customer = customers.find((c) => String(c.id) === customerId);
2798
2859
  if (!customer) return;
2799
2860
  setFormData((prev) => ({
2800
2861
  ...prev,
@@ -2808,23 +2869,14 @@ function CreateEditForm({
2808
2869
  const [totalLoading, setTotalLoading] = (0, import_react13.useState)(false);
2809
2870
  (0, import_react13.useEffect)(() => {
2810
2871
  return () => {
2811
- if (debounceRef.current) {
2812
- clearTimeout(debounceRef.current);
2813
- }
2872
+ if (debounceRef.current) clearTimeout(debounceRef.current);
2814
2873
  };
2815
2874
  }, []);
2816
2875
  const recalculateTotal = (itemsSummaryValue) => {
2817
- if (debounceRef.current) {
2818
- clearTimeout(debounceRef.current);
2819
- }
2876
+ if (debounceRef.current) clearTimeout(debounceRef.current);
2820
2877
  const names = itemsSummaryValue.split(",").map((s) => s.trim()).filter(Boolean);
2821
2878
  if (names.length === 0) {
2822
- setFormData((prev) => ({
2823
- ...prev,
2824
- subtotal: "",
2825
- tax: "",
2826
- total: ""
2827
- }));
2879
+ setFormData((prev) => ({ ...prev, subtotal: "", tax: "", total: "" }));
2828
2880
  return;
2829
2881
  }
2830
2882
  debounceRef.current = setTimeout(async () => {
@@ -2832,12 +2884,8 @@ function CreateEditForm({
2832
2884
  try {
2833
2885
  const res = await fetch("/api/orders/calculate", {
2834
2886
  method: "POST",
2835
- headers: {
2836
- "Content-Type": "application/json"
2837
- },
2838
- body: JSON.stringify({
2839
- itemsSummary: itemsSummaryValue
2840
- })
2887
+ headers: { "Content-Type": "application/json" },
2888
+ body: JSON.stringify({ itemsSummary: itemsSummaryValue })
2841
2889
  });
2842
2890
  if (!res.ok) return;
2843
2891
  const data = await res.json();
@@ -2862,13 +2910,9 @@ function CreateEditForm({
2862
2910
  const path = col.field || col.key;
2863
2911
  if (!path) return;
2864
2912
  const v = getNestedValue(ex, path);
2865
- if (v !== void 0) {
2866
- merged[path] = v === null ? "" : v;
2867
- }
2913
+ if (v !== void 0) merged[path] = v === null ? "" : v;
2868
2914
  });
2869
- columns.filter(
2870
- (col) => !col.hideInForm && isDateFormColumn(col.type)
2871
- ).forEach((col) => {
2915
+ columns.filter((col) => !col.hideInForm && isDateFormColumn(col.type)).forEach((col) => {
2872
2916
  const path = col.field || col.key;
2873
2917
  if (!path) return;
2874
2918
  const raw = merged[path];
@@ -2884,9 +2928,7 @@ function CreateEditForm({
2884
2928
  ).forEach((col) => {
2885
2929
  const path = col.field || col.key;
2886
2930
  if (!path) return;
2887
- const allowed = new Set(
2888
- col.options.map((o) => String(o.value))
2889
- );
2931
+ const allowed = new Set(col.options.map((o) => String(o.value)));
2890
2932
  const raw = merged[path];
2891
2933
  if (raw != null && raw !== "" && !allowed.has(String(raw))) {
2892
2934
  merged[path] = "";
@@ -2894,6 +2936,8 @@ function CreateEditForm({
2894
2936
  });
2895
2937
  setFormData(merged);
2896
2938
  } else {
2939
+ setPassword("");
2940
+ setConfirmPassword("");
2897
2941
  setFormData(
2898
2942
  columns.filter((col) => !col.hideInForm).reduce((acc, col) => {
2899
2943
  const path = col.field || col.key;
@@ -2902,29 +2946,18 @@ function CreateEditForm({
2902
2946
  if (isDateFormColumn(col.type) && (initial === "" || initial == null)) {
2903
2947
  initial = todayLocalDateString();
2904
2948
  }
2905
- return {
2906
- ...acc,
2907
- [path]: initial
2908
- };
2949
+ return { ...acc, [path]: initial };
2909
2950
  }, {})
2910
2951
  );
2911
2952
  }
2912
2953
  }, [existingData, duplicateSeed, columns]);
2913
2954
  const handleChange = (e, field) => {
2914
2955
  const value = e.target.value;
2915
- setFormData((prev) => ({
2916
- ...prev,
2917
- [field]: value
2918
- }));
2919
- if (field === "itemsSummary") {
2920
- recalculateTotal(value);
2921
- }
2956
+ setFormData((prev) => ({ ...prev, [field]: value }));
2957
+ if (field === "itemsSummary") recalculateTotal(value);
2922
2958
  };
2923
2959
  const handleFileUpload = (field, url) => {
2924
- setFormData({
2925
- ...formData,
2926
- [field]: url
2927
- });
2960
+ setFormData({ ...formData, [field]: url });
2928
2961
  };
2929
2962
  const isValidPhoneFieldValue = (value) => {
2930
2963
  const v = String(value ?? "").trim();
@@ -2948,6 +2981,18 @@ function CreateEditForm({
2948
2981
  }
2949
2982
  }
2950
2983
  });
2984
+ if (isCustomerCreate) {
2985
+ if (!password) {
2986
+ newErrors["_password"] = "Password is required";
2987
+ } else if (password.length < 6) {
2988
+ newErrors["_password"] = "Password must be at least 6 characters";
2989
+ }
2990
+ if (!confirmPassword) {
2991
+ newErrors["_confirmPassword"] = "Please confirm your password";
2992
+ } else if (password && confirmPassword !== password) {
2993
+ newErrors["_confirmPassword"] = "Passwords do not match";
2994
+ }
2995
+ }
2951
2996
  setErrors(newErrors);
2952
2997
  return Object.keys(newErrors).length === 0;
2953
2998
  };
@@ -2965,16 +3010,19 @@ function CreateEditForm({
2965
3010
  if (!existingData) {
2966
3011
  delete payload.id;
2967
3012
  }
3013
+ if (isCustomerCreate) {
3014
+ payload._password = password;
3015
+ }
2968
3016
  try {
2969
3017
  const res = await fetch(url, {
2970
3018
  method,
2971
- headers: {
2972
- "Content-Type": "application/json"
2973
- },
3019
+ headers: { "Content-Type": "application/json" },
2974
3020
  body: JSON.stringify(payload)
2975
3021
  });
2976
3022
  if (!res.ok) {
2977
- throw new Error("Failed to save data");
3023
+ const errData = await res.json().catch(() => ({}));
3024
+ import_sonner3.toast.error(errData.error ?? "Failed to save data");
3025
+ return;
2978
3026
  }
2979
3027
  const result = await res.json();
2980
3028
  if (result.inviteLink) {
@@ -2988,13 +3036,12 @@ Note: ${result.note}`
2988
3036
  onClose();
2989
3037
  } catch (error) {
2990
3038
  console.error("Error saving data:", error);
3039
+ import_sonner3.toast.error("An unexpected error occurred. Please try again.");
2991
3040
  }
2992
3041
  };
2993
3042
  const visibleColumns = columns.filter((col) => {
2994
3043
  if (col.hideInForm) return false;
2995
- if (!existingData && col.hideInCreate) {
2996
- return false;
2997
- }
3044
+ if (!existingData && col.hideInCreate) return false;
2998
3045
  return true;
2999
3046
  });
3000
3047
  const isFieldDisabled = (path) => {
@@ -3024,228 +3071,193 @@ Note: ${result.note}`
3024
3071
  }
3025
3072
  )
3026
3073
  ] }),
3027
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3028
- "form",
3029
- {
3030
- onSubmit: handleSubmit,
3031
- className: "flex flex-col flex-1 min-h-0",
3032
- children: [
3033
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex-1 min-h-0 overflow-y-auto p-6 space-y-4", children: visibleColumns.map((col) => {
3034
- const path = col.field || col.key;
3035
- if (!path) return null;
3036
- const disabled = isFieldDisabled(path);
3037
- const optionSet = col.type === "select" && Array.isArray(col.options) ? new Set(
3038
- col.options.map(
3039
- (o) => String(o.value)
3040
- )
3041
- ) : null;
3042
- const rawStr = formData[path] != null && formData[path] !== "" ? String(formData[path]) : "";
3043
- const selectValue = optionSet ? rawStr !== "" && optionSet.has(rawStr) ? rawStr : col.defaultValue : void 0;
3044
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_react13.default.Fragment, { children: [
3045
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
3046
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Label3, { className: "block text-sm font-medium mb-1", children: [
3047
- col.displayName,
3048
- ["subtotal", "tax", "total"].includes(path) && totalLoading && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "ml-2 text-xs text-gray-400 font-normal", children: "Calculating\u2026" })
3049
- ] }),
3050
- col.type === "customerDropdown" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3051
- Select,
3052
- {
3053
- value: (() => {
3054
- const match = customers.find(
3055
- (c) => String(c.id) === String(
3056
- formData["contactId"]
3057
- ) || c.name === formData["contact.name"]
3058
- );
3059
- return match ? String(match.id) : void 0;
3060
- })(),
3061
- onValueChange: handleCustomerSelect,
3062
- children: [
3063
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SelectTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SelectValue, { placeholder: "Select a customer" }) }),
3064
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SelectContent, { children: customers.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3065
- SelectItem,
3066
- {
3067
- value: "__loading__",
3068
- disabled: true,
3069
- children: "Loading\u2026"
3070
- }
3071
- ) : customers.map((c) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3072
- SelectItem,
3073
- {
3074
- value: String(
3075
- c.id
3076
- ),
3077
- children: c.name
3078
- },
3079
- c.id
3080
- )) })
3081
- ]
3082
- }
3083
- ) : col.relationApi ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3084
- RelationAutocomplete,
3085
- {
3086
- apiEndpoint: col.relationApi,
3087
- value: formData[path] ?? "",
3088
- onChange: (v) => setFormData({
3089
- ...formData,
3090
- [path]: v
3091
- }),
3092
- labelField: col.relationLabelField ?? "name",
3093
- valueField: col.relationValueField ?? "id",
3094
- queryParams: col.relationQueryParams,
3095
- placeholder: `Select ${col.displayName}`
3096
- }
3097
- ) : col.type === "textarea" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3098
- Textarea,
3099
- {
3100
- rows: typeof col.textareaRows === "number" ? col.textareaRows : 4,
3101
- className: "min-h-[80px] font-mono text-sm",
3102
- value: formData[path] ?? "",
3103
- onChange: (e) => handleChange(e, path),
3104
- placeholder: col.placeholder,
3105
- disabled
3106
- }
3107
- ) : col.type === "select" && Array.isArray(
3108
- col.options
3109
- ) && col.options.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3110
- Select,
3111
- {
3112
- value: selectValue,
3113
- onValueChange: (value) => setFormData({
3114
- ...formData,
3115
- [path]: value
3116
- }),
3117
- disabled,
3118
- children: [
3119
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SelectTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3120
- SelectValue,
3121
- {
3122
- placeholder: selectValue ? void 0 : "Select an option"
3123
- }
3124
- ) }),
3125
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SelectContent, { children: col.options.map(
3126
- (option) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3127
- SelectItem,
3128
- {
3129
- value: option.value,
3130
- children: option.label
3131
- },
3132
- option.value
3133
- )
3134
- ) })
3135
- ]
3136
- }
3137
- ) : col.type === "boolean" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3138
- Switch,
3139
- {
3140
- checked: Boolean(
3141
- formData[path]
3142
- ),
3143
- onCheckedChange: (checked) => setFormData({
3144
- ...formData,
3145
- [path]: checked
3146
- }),
3147
- disabled
3148
- }
3149
- ) : isDateFormColumn(
3150
- col.type
3151
- ) ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3152
- Input,
3153
- {
3154
- type: "date",
3155
- value: formData[path] ?? "",
3156
- onChange: (e) => handleChange(e, path),
3157
- disabled
3158
- }
3159
- ) : col.type === "password" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3160
- Input,
3161
- {
3162
- type: "password",
3163
- value: formData[path] || "",
3164
- onChange: (e) => handleChange(e, path),
3165
- disabled
3166
- }
3167
- ) : col.type === "number" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3168
- Input,
3169
- {
3170
- type: "number",
3171
- placeholder: col.placeholder,
3172
- value: formData[path] ?? "",
3173
- onChange: (e) => handleChange(e, path),
3174
- disabled
3175
- }
3176
- ) : col.type === "phone" || col.type === "tel" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3177
- Input,
3178
- {
3179
- type: "tel",
3180
- inputMode: "tel",
3181
- autoComplete: "tel",
3182
- placeholder: col.placeholder ?? "e.g. +1 555 123 4567",
3183
- value: formData[path] ?? "",
3184
- onChange: (e) => handleChange(e, path),
3185
- disabled
3186
- }
3187
- ) : col.type === "file" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3188
- FileUpload,
3189
- {
3190
- onUploadSuccess: (url) => handleFileUpload(
3191
- path,
3192
- url
3193
- )
3194
- }
3195
- ) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3196
- Input,
3197
- {
3198
- type: col.type || "text",
3199
- placeholder: ["subtotal", "tax", "total"].includes(path) ? "Auto-calculated from items" : col.placeholder,
3200
- value: formData[path] ?? "",
3201
- onChange: (e) => handleChange(e, path),
3202
- disabled
3203
- }
3204
- ),
3205
- errors[path] && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "text-red-500 text-sm mt-1", children: errors[path] })
3074
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("form", { onSubmit: handleSubmit, className: "flex flex-col flex-1 min-h-0", children: [
3075
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex-1 min-h-0 overflow-y-auto p-6 space-y-4", children: [
3076
+ visibleColumns.map((col) => {
3077
+ const path = col.field || col.key;
3078
+ if (!path) return null;
3079
+ const disabled = isFieldDisabled(path);
3080
+ const optionSet = col.type === "select" && Array.isArray(col.options) ? new Set(col.options.map((o) => String(o.value))) : null;
3081
+ const rawStr = formData[path] != null && formData[path] !== "" ? String(formData[path]) : "";
3082
+ const selectValue = optionSet ? rawStr !== "" && optionSet.has(rawStr) ? rawStr : col.defaultValue : void 0;
3083
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_react13.default.Fragment, { children: [
3084
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
3085
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Label3, { className: "block text-sm font-medium mb-1", children: [
3086
+ col.displayName,
3087
+ ["subtotal", "tax", "total"].includes(path) && totalLoading && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "ml-2 text-xs text-gray-400 font-normal", children: "Calculating\u2026" })
3206
3088
  ] }),
3207
- col.type === "customerDropdown" && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
3208
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Label3, { className: "block text-sm font-medium mb-1", children: "Name" }),
3209
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3210
- Input,
3211
- {
3212
- type: "text",
3213
- placeholder: "Customer name",
3214
- value: formData["contact.name"] ?? "",
3215
- onChange: (e) => setFormData(
3216
- (prev) => ({
3217
- ...prev,
3218
- "contact.name": e.target.value
3219
- })
3220
- )
3221
- }
3222
- )
3223
- ] })
3224
- ] }, path);
3225
- }) }),
3226
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex gap-2 p-4 border-t border-gray-200 shrink-0 bg-white", children: [
3089
+ col.type === "customerDropdown" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3090
+ Select,
3091
+ {
3092
+ value: (() => {
3093
+ const match = customers.find(
3094
+ (c) => String(c.id) === String(formData["contactId"]) || c.name === formData["contact.name"]
3095
+ );
3096
+ return match ? String(match.id) : void 0;
3097
+ })(),
3098
+ onValueChange: handleCustomerSelect,
3099
+ children: [
3100
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SelectTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SelectValue, { placeholder: "Select a customer" }) }),
3101
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SelectContent, { children: customers.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SelectItem, { value: "__loading__", disabled: true, children: "Loading\u2026" }) : customers.map((c) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SelectItem, { value: String(c.id), children: c.name }, c.id)) })
3102
+ ]
3103
+ }
3104
+ ) : col.relationApi ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3105
+ RelationAutocomplete,
3106
+ {
3107
+ apiEndpoint: col.relationApi,
3108
+ value: formData[path] ?? "",
3109
+ onChange: (v) => setFormData({ ...formData, [path]: v }),
3110
+ labelField: col.relationLabelField ?? "name",
3111
+ valueField: col.relationValueField ?? "id",
3112
+ queryParams: col.relationQueryParams,
3113
+ placeholder: `Select ${col.displayName}`
3114
+ }
3115
+ ) : col.type === "textarea" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3116
+ Textarea,
3117
+ {
3118
+ rows: typeof col.textareaRows === "number" ? col.textareaRows : 4,
3119
+ className: "min-h-[80px] font-mono text-sm",
3120
+ value: formData[path] ?? "",
3121
+ onChange: (e) => handleChange(e, path),
3122
+ placeholder: col.placeholder,
3123
+ disabled
3124
+ }
3125
+ ) : col.type === "select" && Array.isArray(col.options) && col.options.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
3126
+ Select,
3127
+ {
3128
+ value: selectValue,
3129
+ onValueChange: (value) => setFormData({ ...formData, [path]: value }),
3130
+ disabled,
3131
+ children: [
3132
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SelectTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3133
+ SelectValue,
3134
+ {
3135
+ placeholder: selectValue ? void 0 : "Select an option"
3136
+ }
3137
+ ) }),
3138
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SelectContent, { children: col.options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SelectItem, { value: option.value, children: option.label }, option.value)) })
3139
+ ]
3140
+ }
3141
+ ) : col.type === "boolean" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3142
+ Switch,
3143
+ {
3144
+ checked: Boolean(formData[path]),
3145
+ onCheckedChange: (checked) => setFormData({ ...formData, [path]: checked }),
3146
+ disabled
3147
+ }
3148
+ ) : isDateFormColumn(col.type) ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3149
+ Input,
3150
+ {
3151
+ type: "date",
3152
+ value: formData[path] ?? "",
3153
+ onChange: (e) => handleChange(e, path),
3154
+ disabled
3155
+ }
3156
+ ) : col.type === "password" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3157
+ Input,
3158
+ {
3159
+ type: "password",
3160
+ value: formData[path] || "",
3161
+ onChange: (e) => handleChange(e, path),
3162
+ disabled
3163
+ }
3164
+ ) : col.type === "number" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3165
+ Input,
3166
+ {
3167
+ type: "number",
3168
+ placeholder: col.placeholder,
3169
+ value: formData[path] ?? "",
3170
+ onChange: (e) => handleChange(e, path),
3171
+ disabled
3172
+ }
3173
+ ) : col.type === "phone" || col.type === "tel" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3174
+ Input,
3175
+ {
3176
+ type: "tel",
3177
+ inputMode: "tel",
3178
+ autoComplete: "tel",
3179
+ placeholder: col.placeholder ?? "e.g. +1 555 123 4567",
3180
+ value: formData[path] ?? "",
3181
+ onChange: (e) => handleChange(e, path),
3182
+ disabled
3183
+ }
3184
+ ) : col.type === "file" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3185
+ FileUpload,
3186
+ {
3187
+ onUploadSuccess: (url) => handleFileUpload(path, url)
3188
+ }
3189
+ ) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3190
+ Input,
3191
+ {
3192
+ type: col.type || "text",
3193
+ placeholder: ["subtotal", "tax", "total"].includes(path) ? "Auto-calculated from items" : col.placeholder,
3194
+ value: formData[path] ?? "",
3195
+ onChange: (e) => handleChange(e, path),
3196
+ disabled
3197
+ }
3198
+ ),
3199
+ errors[path] && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "text-red-500 text-sm mt-1", children: errors[path] })
3200
+ ] }),
3201
+ col.type === "customerDropdown" && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
3202
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Label3, { className: "block text-sm font-medium mb-1", children: "Name" }),
3203
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3204
+ Input,
3205
+ {
3206
+ type: "text",
3207
+ placeholder: "Customer name",
3208
+ value: formData["contact.name"] ?? "",
3209
+ onChange: (e) => setFormData((prev) => ({
3210
+ ...prev,
3211
+ "contact.name": e.target.value
3212
+ }))
3213
+ }
3214
+ )
3215
+ ] })
3216
+ ] }, path);
3217
+ }),
3218
+ isCustomerCreate && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
3219
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
3220
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Label3, { className: "block text-sm font-medium mb-1", children: "Password" }),
3227
3221
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3228
- Button,
3222
+ Input,
3229
3223
  {
3230
- type: "submit",
3231
- className: "flex-1",
3232
- children: "Save"
3224
+ type: "password",
3225
+ value: password,
3226
+ onChange: (e) => setPassword(e.target.value),
3227
+ placeholder: "Min. 6 characters"
3233
3228
  }
3234
3229
  ),
3230
+ errors["_password"] && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "text-red-500 text-sm mt-1", children: errors["_password"] })
3231
+ ] }),
3232
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
3233
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Label3, { className: "block text-sm font-medium mb-1", children: "Confirm Password" }),
3235
3234
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3236
- Button,
3235
+ Input,
3237
3236
  {
3238
- type: "button",
3239
- variant: "outline",
3240
- onClick: onClose,
3241
- className: "flex-1",
3242
- children: "Cancel"
3237
+ type: "password",
3238
+ value: confirmPassword,
3239
+ onChange: (e) => setConfirmPassword(e.target.value),
3240
+ placeholder: "Re-enter password"
3243
3241
  }
3244
- )
3242
+ ),
3243
+ errors["_confirmPassword"] && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "text-red-500 text-sm mt-1", children: errors["_confirmPassword"] })
3245
3244
  ] })
3246
- ]
3247
- }
3248
- )
3245
+ ] })
3246
+ ] }),
3247
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex gap-2 p-4 border-t border-gray-200 shrink-0 bg-white", children: [
3248
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Button, { type: "submit", className: "flex-1", children: "Save" }),
3249
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3250
+ Button,
3251
+ {
3252
+ type: "button",
3253
+ variant: "outline",
3254
+ onClick: onClose,
3255
+ className: "flex-1",
3256
+ children: "Cancel"
3257
+ }
3258
+ )
3259
+ ] })
3260
+ ] })
3249
3261
  ]
3250
3262
  }
3251
3263
  ) }) : null;
@@ -5491,7 +5503,8 @@ function AdminCRUD({
5491
5503
  apiEndpoint,
5492
5504
  columns,
5493
5505
  existingData: editingItem,
5494
- duplicateSeed
5506
+ duplicateSeed,
5507
+ resource: resourceName
5495
5508
  }
5496
5509
  ),
5497
5510
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
@@ -9075,7 +9088,9 @@ var SigninPage = () => {
9075
9088
  const router = (0, import_navigation8.useRouter)();
9076
9089
  const { status } = (0, import_react31.useSession)();
9077
9090
  (0, import_react30.useEffect)(() => {
9091
+ logAuthClient("SignInPage session status", { status });
9078
9092
  if (status === "authenticated") {
9093
+ logAuthClient("SignInPage redirect \u2192 /admin/dashboard (already authenticated)");
9079
9094
  router.replace("/admin/dashboard");
9080
9095
  }
9081
9096
  }, [status, router]);
@@ -9084,14 +9099,22 @@ var SigninPage = () => {
9084
9099
  setLoading(true);
9085
9100
  setError("");
9086
9101
  try {
9102
+ logAuthClient("SignInPage signIn start", { email });
9087
9103
  const result = await (0, import_react31.signIn)("credentials", {
9088
9104
  email,
9089
9105
  password,
9090
9106
  redirect: false
9091
9107
  });
9108
+ logAuthClient("SignInPage signIn result", {
9109
+ ok: result?.ok ?? null,
9110
+ error: result?.error ?? null,
9111
+ status: result?.status ?? null,
9112
+ url: result?.url ?? null
9113
+ });
9092
9114
  if (result?.error) {
9093
9115
  setError("Invalid email or password");
9094
9116
  } else {
9117
+ logAuthClient("SignInPage navigate \u2192 /admin/dashboard");
9095
9118
  window.location.assign("/admin/dashboard");
9096
9119
  }
9097
9120
  } catch (error2) {
@@ -10074,7 +10097,7 @@ function DashboardPage() {
10074
10097
  // src/admin/pages/AdminPageResolver.tsx
10075
10098
  var import_react68 = require("react");
10076
10099
  var import_dynamic2 = __toESM(require("next/dynamic"), 1);
10077
- var import_navigation30 = require("next/navigation");
10100
+ var import_navigation31 = require("next/navigation");
10078
10101
 
10079
10102
  // src/admin/pages/SubmissionDetailPage.tsx
10080
10103
  var import_react37 = require("react");
@@ -10210,6 +10233,7 @@ init_utils();
10210
10233
  init_table();
10211
10234
  init_DetailPageLayout();
10212
10235
  init_DetailPageHeader();
10236
+ var import_react_qr_code = require("react-qr-code");
10213
10237
  var import_jsx_runtime52 = require("react/jsx-runtime");
10214
10238
  function formatMoney(amount, currency = "INR") {
10215
10239
  return new Intl.NumberFormat("en-IN", {
@@ -10567,6 +10591,19 @@ function OrderDetailPage({ orderId }) {
10567
10591
  },
10568
10592
  p.id
10569
10593
  )) })
10594
+ ] }),
10595
+ order.qrToken && /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("section", { children: [
10596
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("h2", { className: "text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2", children: "Order QR Code" }),
10597
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("div", { className: "min-w-0 border border-gray-200 rounded-lg p-4 bg-gray-50/50 flex flex-col items-center gap-2", children: [
10598
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
10599
+ import_react_qr_code.QRCode,
10600
+ {
10601
+ value: `${window.location.origin}/track/${order.qrToken}`,
10602
+ size: 180
10603
+ }
10604
+ ),
10605
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("p", { className: "text-xs text-gray-400 break-all text-center", children: order.qrToken })
10606
+ ] })
10570
10607
  ] })
10571
10608
  ] })
10572
10609
  }
@@ -11024,6 +11061,7 @@ function formatMoney3(amount, currency) {
11024
11061
  }).format(Number(amount));
11025
11062
  }
11026
11063
  var MANUAL_CUSTOMER = "__manual__";
11064
+ var MANUAL_CONTACT = "__manual__";
11027
11065
  function OrderPlacementPage({ editOrderId }) {
11028
11066
  const router = (0, import_navigation15.useRouter)();
11029
11067
  const searchParams = (0, import_navigation15.useSearchParams)();
@@ -11031,25 +11069,23 @@ function OrderPlacementPage({ editOrderId }) {
11031
11069
  const [customers, setCustomers] = (0, import_react40.useState)([]);
11032
11070
  const [customersLoading, setCustomersLoading] = (0, import_react40.useState)(true);
11033
11071
  const [contactId, setContactId] = (0, import_react40.useState)(null);
11072
+ const [selectedCustomerId, setSelectedCustomerId] = (0, import_react40.useState)(null);
11034
11073
  const [contactName, setContactName] = (0, import_react40.useState)("");
11035
11074
  const [contactEmail, setContactEmail] = (0, import_react40.useState)("");
11036
11075
  const [contactPhone, setContactPhone] = (0, import_react40.useState)("");
11076
+ const [placeForSomeoneElse, setPlaceForSomeoneElse] = (0, import_react40.useState)(false);
11077
+ const [customerContacts, setCustomerContacts] = (0, import_react40.useState)([]);
11078
+ const [customerContactsLoading, setCustomerContactsLoading] = (0, import_react40.useState)(false);
11079
+ const [subContactId, setSubContactId] = (0, import_react40.useState)(null);
11080
+ const [subContactName, setSubContactName] = (0, import_react40.useState)("");
11081
+ const [subContactEmail, setSubContactEmail] = (0, import_react40.useState)("");
11082
+ const [subContactPhone, setSubContactPhone] = (0, import_react40.useState)("");
11037
11083
  const [status, setStatus] = (0, import_react40.useState)("pending");
11038
11084
  const [currency, setCurrency] = (0, import_react40.useState)("INR");
11039
11085
  const [loadingOrder, setLoadingOrder] = (0, import_react40.useState)(Boolean(editOrderId));
11040
11086
  const [orderLoadError, setOrderLoadError] = (0, import_react40.useState)(null);
11041
- const [couponError, setCouponError] = (0, import_react40.useState)(null);
11042
- const [couponLoading, setCouponLoading] = (0, import_react40.useState)(false);
11043
11087
  const [coupons, setCoupons] = (0, import_react40.useState)([]);
11044
- const [couponInput, setCouponInput] = (0, import_react40.useState)("");
11045
- const emptyAddress = {
11046
- line1: "",
11047
- line2: "",
11048
- city: "",
11049
- state: "",
11050
- country: "",
11051
- postalCode: ""
11052
- };
11088
+ const emptyAddress = { line1: "", line2: "", city: "", state: "", country: "", postalCode: "" };
11053
11089
  const [billingAddress, setBillingAddress] = (0, import_react40.useState)(emptyAddress);
11054
11090
  const [shippingAddress, setShippingAddress] = (0, import_react40.useState)(emptyAddress);
11055
11091
  const [sameAsBilling, setSameAsBilling] = (0, import_react40.useState)(true);
@@ -11112,9 +11148,7 @@ function OrderPlacementPage({ editOrderId }) {
11112
11148
  setSameAsBilling(true);
11113
11149
  }
11114
11150
  const loadedCid = typeof cid === "number" && Number.isFinite(cid) ? cid : null;
11115
- if (loadedCid && !o.billingAddress && !o.shippingAddress) {
11116
- void loadCustomerAddresses(loadedCid);
11117
- }
11151
+ if (loadedCid && !o.billingAddress && !o.shippingAddress) void loadContactAddresses(loadedCid);
11118
11152
  setStatus(String(o.status ?? "pending"));
11119
11153
  const cur = String(o.currency ?? "INR").trim().toUpperCase().slice(0, 8);
11120
11154
  if (cur) setCurrency(cur);
@@ -11124,13 +11158,7 @@ function OrderPlacementPage({ editOrderId }) {
11124
11158
  const coll = p?.collection?.name;
11125
11159
  const nm = p?.name;
11126
11160
  const label = [coll, nm].filter(Boolean).join(" \u2014 ") || nm || `Product #${pid}`;
11127
- return {
11128
- key: `line-${it.id}`,
11129
- productId: pid,
11130
- label,
11131
- sku: p?.sku ?? null,
11132
- quantity: Math.max(1, Math.floor(Number(it.quantity) || 1))
11133
- };
11161
+ return { key: `line-${it.id}`, productId: pid, label, sku: p?.sku ?? null, quantity: Math.max(1, Math.floor(Number(it.quantity) || 1)) };
11134
11162
  });
11135
11163
  setLines(draft);
11136
11164
  try {
@@ -11143,18 +11171,7 @@ function OrderPlacementPage({ editOrderId }) {
11143
11171
  if (dr.ok) {
11144
11172
  const d = await dr.json();
11145
11173
  if (d?.id) {
11146
- setCoupons([{
11147
- couponCode: d.couponCode ?? "",
11148
- discountId: d.id,
11149
- name: d.name,
11150
- value: d.value,
11151
- type: d.discountType,
11152
- canCombine: !!d.canCombineWithOtherDiscounts,
11153
- calculatedDiscount: 0,
11154
- rewardProductIds: [],
11155
- rewardLines: [],
11156
- isAutomatic: d.isAutomatic ?? false
11157
- }]);
11174
+ setCoupons([{ couponCode: d.couponCode ?? "", discountId: d.id, name: d.name, value: d.value, type: d.discountType, canCombine: !!d.canCombineWithOtherDiscounts, calculatedDiscount: 0, rewardProductIds: [], rewardLines: [], isAutomatic: d.isAutomatic ?? false }]);
11158
11175
  }
11159
11176
  }
11160
11177
  }
@@ -11165,12 +11182,7 @@ function OrderPlacementPage({ editOrderId }) {
11165
11182
  const pid = Number(it.productId);
11166
11183
  const p = it.product;
11167
11184
  const unit = Number(it.unitPrice ?? 0);
11168
- productCache.current.set(pid, {
11169
- id: pid,
11170
- name: p?.name ?? null,
11171
- sku: p?.sku ?? null,
11172
- price: Number.isFinite(unit) ? unit : 0
11173
- });
11185
+ productCache.current.set(pid, { id: pid, name: p?.name ?? null, sku: p?.sku ?? null, price: Number.isFinite(unit) ? unit : 0 });
11174
11186
  }
11175
11187
  } catch {
11176
11188
  if (!cancelled) setOrderLoadError("Failed to load order");
@@ -11187,23 +11199,17 @@ function OrderPlacementPage({ editOrderId }) {
11187
11199
  (async () => {
11188
11200
  setCustomersLoading(true);
11189
11201
  try {
11190
- const res = await fetch(
11191
- "/api/vendor_customers?limit=500&sortField=createdAt&sortOrder=asc"
11192
- );
11202
+ const res = await fetch("/api/vendor_customers?limit=500&sortField=createdAt&sortOrder=asc");
11193
11203
  if (!res.ok) {
11194
11204
  if (!cancelled) setCustomers([]);
11195
11205
  return;
11196
11206
  }
11197
11207
  const data = await res.json();
11198
11208
  const rows = (Array.isArray(data?.data) ? data.data : []).map((row) => {
11199
- const id = Number(row.contactId ?? row.contact?.id);
11209
+ const id = Number(row.id);
11200
11210
  if (!Number.isFinite(id)) return null;
11201
- return {
11202
- id,
11203
- name: row.name ?? row.contact?.name ?? null,
11204
- email: row.email ?? row.contact?.email ?? null,
11205
- phone: row.phone ?? row.contact?.phone ?? null
11206
- };
11211
+ const rawCustomerId = Number(row.customerId);
11212
+ return { id, customerId: Number.isFinite(rawCustomerId) && rawCustomerId > 0 ? rawCustomerId : null, name: row.name ?? null, email: row.email ?? null, phone: row.phone ?? null };
11207
11213
  }).filter((r) => r != null);
11208
11214
  if (!cancelled) setCustomers(rows);
11209
11215
  } catch {
@@ -11216,6 +11222,140 @@ function OrderPlacementPage({ editOrderId }) {
11216
11222
  cancelled = true;
11217
11223
  };
11218
11224
  }, []);
11225
+ async function loadCustomerContacts(customerId) {
11226
+ setCustomerContactsLoading(true);
11227
+ setCustomerContacts([]);
11228
+ setSubContactId(null);
11229
+ setSubContactName("");
11230
+ setSubContactEmail("");
11231
+ setSubContactPhone("");
11232
+ try {
11233
+ const res = await fetch(`/api/customer_contacts?customerId=${customerId}&limit=200&sortField=id&sortOrder=asc`);
11234
+ if (!res.ok) return;
11235
+ const data = await res.json();
11236
+ const rows = (Array.isArray(data?.data) ? data.data : Array.isArray(data) ? data : []).filter((r) => r.contact && Number.isFinite(Number(r.contactId)));
11237
+ setCustomerContacts(rows);
11238
+ } catch {
11239
+ setCustomerContacts([]);
11240
+ } finally {
11241
+ setCustomerContactsLoading(false);
11242
+ }
11243
+ }
11244
+ async function loadContactAddressesByEmail(email) {
11245
+ try {
11246
+ const res = await fetch(`/api/contacts?search=${encodeURIComponent(email)}&limit=5`);
11247
+ if (!res.ok) {
11248
+ setBillingAddress({ ...emptyAddress });
11249
+ setShippingAddress({ ...emptyAddress });
11250
+ return;
11251
+ }
11252
+ const data = await res.json();
11253
+ const match = data.data?.find((c) => c.email?.toLowerCase() === email.toLowerCase());
11254
+ if (!match) {
11255
+ setBillingAddress({ ...emptyAddress });
11256
+ setShippingAddress({ ...emptyAddress });
11257
+ return;
11258
+ }
11259
+ await loadContactAddresses(match.id);
11260
+ } catch {
11261
+ setBillingAddress({ ...emptyAddress });
11262
+ setShippingAddress({ ...emptyAddress });
11263
+ }
11264
+ }
11265
+ async function loadContactAddresses(cid) {
11266
+ try {
11267
+ const res = await fetch(`/api/order_addresses?contactId=${cid}&limit=100&sortField=createdAt&sortOrder=desc`);
11268
+ if (!res.ok) return;
11269
+ const body = await res.json();
11270
+ const rows = Array.isArray(body) ? body : Array.isArray(body?.data) ? body.data : [];
11271
+ const billing = rows.find((a) => a.addressType === "billing_address");
11272
+ const shipping = rows.find((a) => a.addressType === "shipping_address");
11273
+ if (billing) setBillingAddress(mapAddressToForm(billing));
11274
+ else setBillingAddress({ ...emptyAddress });
11275
+ if (sameAsBilling) setShippingAddress(billing ? mapAddressToForm(billing) : { ...emptyAddress });
11276
+ else setShippingAddress(shipping ? mapAddressToForm(shipping) : { ...emptyAddress });
11277
+ } catch {
11278
+ setBillingAddress(emptyAddress);
11279
+ setShippingAddress(emptyAddress);
11280
+ }
11281
+ }
11282
+ function handleCustomerSelect(value) {
11283
+ if (value === MANUAL_CUSTOMER) {
11284
+ setContactId(null);
11285
+ setSelectedCustomerId(null);
11286
+ setCustomerContacts([]);
11287
+ setSubContactId(null);
11288
+ setSubContactName("");
11289
+ setSubContactEmail("");
11290
+ setSubContactPhone("");
11291
+ setBillingAddress({ ...emptyAddress });
11292
+ setShippingAddress({ ...emptyAddress });
11293
+ return;
11294
+ }
11295
+ const id = Number(value);
11296
+ if (!Number.isFinite(id)) return;
11297
+ const customer = customers.find((c) => c.id === id);
11298
+ if (!customer) return;
11299
+ setContactId(id);
11300
+ setSelectedCustomerId(customer.customerId ?? null);
11301
+ setContactName(customer.name ?? "");
11302
+ setContactEmail(customer.email ?? "");
11303
+ setContactPhone(customer.phone ?? "");
11304
+ setSubContactId(null);
11305
+ setSubContactName("");
11306
+ setSubContactEmail("");
11307
+ setSubContactPhone("");
11308
+ setPlaceForSomeoneElse(false);
11309
+ if (customer.customerId != null) loadCustomerContacts(customer.customerId);
11310
+ if (customer.email) {
11311
+ loadContactAddressesByEmail(customer.email);
11312
+ } else {
11313
+ setBillingAddress({ ...emptyAddress });
11314
+ setShippingAddress({ ...emptyAddress });
11315
+ }
11316
+ }
11317
+ function handleSubContactSelect(value) {
11318
+ if (value === MANUAL_CONTACT) {
11319
+ setSubContactId(null);
11320
+ setSubContactName("");
11321
+ setSubContactEmail("");
11322
+ setSubContactPhone("");
11323
+ setBillingAddress({ ...emptyAddress });
11324
+ setShippingAddress({ ...emptyAddress });
11325
+ return;
11326
+ }
11327
+ const cid = Number(value);
11328
+ if (!Number.isFinite(cid)) return;
11329
+ const row = customerContacts.find((r) => r.contactId === cid);
11330
+ if (!row) return;
11331
+ setSubContactId(cid);
11332
+ setSubContactName(row.contact.name ?? "");
11333
+ setSubContactEmail(row.contact.email ?? "");
11334
+ setSubContactPhone(row.contact.phone ?? "");
11335
+ loadContactAddresses(cid);
11336
+ }
11337
+ function handlePlaceForSomeoneElseToggle(checked) {
11338
+ setPlaceForSomeoneElse(checked);
11339
+ if (checked) {
11340
+ setBillingAddress({ ...emptyAddress });
11341
+ setShippingAddress({ ...emptyAddress });
11342
+ setSubContactId(null);
11343
+ setSubContactName("");
11344
+ setSubContactEmail("");
11345
+ setSubContactPhone("");
11346
+ return;
11347
+ }
11348
+ setSubContactId(null);
11349
+ setSubContactName("");
11350
+ setSubContactEmail("");
11351
+ setSubContactPhone("");
11352
+ if (contactEmail) {
11353
+ loadContactAddressesByEmail(contactEmail);
11354
+ } else {
11355
+ setBillingAddress({ ...emptyAddress });
11356
+ setShippingAddress({ ...emptyAddress });
11357
+ }
11358
+ }
11219
11359
  const cartProductIds = (0, import_react40.useMemo)(() => new Set(lines.map((l) => l.productId)), [lines]);
11220
11360
  (0, import_react40.useEffect)(() => {
11221
11361
  const q = productQuery.trim();
@@ -11224,13 +11364,7 @@ function OrderPlacementPage({ editOrderId }) {
11224
11364
  (async () => {
11225
11365
  setHitsLoading(true);
11226
11366
  try {
11227
- const params = new URLSearchParams({
11228
- limit: "50",
11229
- page: "1",
11230
- sortField: "name",
11231
- sortOrder: "asc",
11232
- status: "available"
11233
- });
11367
+ const params = new URLSearchParams({ limit: "50", page: "1", sortField: "name", sortOrder: "asc", status: "available" });
11234
11368
  if (q.length >= 2) params.set("search", q);
11235
11369
  const res = await fetch(`/api/products?${params}`);
11236
11370
  if (!res.ok) {
@@ -11311,53 +11445,34 @@ function OrderPlacementPage({ editOrderId }) {
11311
11445
  const res = await fetch("/api/discounts/validate-coupon", {
11312
11446
  method: "POST",
11313
11447
  headers: { "Content-Type": "application/json" },
11314
- body: JSON.stringify({
11315
- discountId: coupon.discountId,
11316
- isAutomatic: true,
11317
- orderLines: nonRewardLines.map((l) => ({ productId: l.productId, quantity: l.quantity })),
11318
- currency,
11319
- contactId
11320
- })
11448
+ body: JSON.stringify({ discountId: coupon.discountId, isAutomatic: true, orderLines: nonRewardLines.map((l) => ({ productId: l.productId, quantity: l.quantity })), currency, contactId })
11321
11449
  });
11322
11450
  if (cancelled) return;
11323
11451
  const data = await res.json();
11324
11452
  if (!data.valid) {
11325
11453
  const rewardPids = new Set(coupon.rewardLines.map((r) => r.productId));
11326
11454
  setCoupons((prev) => prev.filter((c) => c.discountId !== coupon.discountId));
11327
- setLines(
11328
- (prev) => prev.map((l) => {
11329
- if (!rewardPids.has(l.productId)) return l;
11330
- const rewardLine = coupon.rewardLines.find((r) => r.productId === l.productId);
11331
- if (!rewardLine) return l;
11332
- const newQty = l.quantity - rewardLine.quantity;
11333
- return newQty > 0 ? { ...l, quantity: newQty } : null;
11334
- }).filter((l) => l !== null)
11335
- );
11455
+ setLines((prev) => prev.map((l) => {
11456
+ if (!rewardPids.has(l.productId)) return l;
11457
+ const rewardLine = coupon.rewardLines.find((r) => r.productId === l.productId);
11458
+ if (!rewardLine) return l;
11459
+ const newQty = l.quantity - rewardLine.quantity;
11460
+ return newQty > 0 ? { ...l, quantity: newQty } : null;
11461
+ }).filter((l) => l !== null));
11336
11462
  rewardPids.forEach((pid) => rewardProductIds.current.delete(pid));
11337
11463
  rewardInjectedForDiscount.current.delete(coupon.discountId);
11338
11464
  } else {
11339
11465
  const newRewardLines = data.discount.rewardLines ?? [];
11340
- setCoupons((prev) => prev.map(
11341
- (c) => c.discountId !== coupon.discountId ? c : {
11342
- ...c,
11343
- calculatedDiscount: data.discount.calculatedDiscount,
11344
- rewardProductIds: data.discount.rewardProductIds ?? [],
11345
- rewardLines: newRewardLines
11346
- }
11347
- ));
11466
+ setCoupons((prev) => prev.map((c) => c.discountId !== coupon.discountId ? c : { ...c, calculatedDiscount: data.discount.calculatedDiscount, rewardProductIds: data.discount.rewardProductIds ?? [], rewardLines: newRewardLines }));
11348
11467
  setLines((prev) => {
11349
11468
  let next = [...prev];
11350
11469
  for (const rewardLine of newRewardLines) {
11351
11470
  const idx = next.findIndex((l) => l.productId === rewardLine.productId);
11352
- if (idx >= 0 && rewardProductIds.current.has(rewardLine.productId)) {
11353
- next[idx] = { ...next[idx], quantity: rewardLine.quantity };
11354
- }
11471
+ if (idx >= 0 && rewardProductIds.current.has(rewardLine.productId)) next[idx] = { ...next[idx], quantity: rewardLine.quantity };
11355
11472
  }
11356
11473
  const newRewardPids = new Set(newRewardLines.map((r) => r.productId));
11357
11474
  const oldRewardPids = new Set(coupon.rewardLines.map((r) => r.productId));
11358
- next = next.filter(
11359
- (l) => !oldRewardPids.has(l.productId) || newRewardPids.has(l.productId)
11360
- );
11475
+ next = next.filter((l) => !oldRewardPids.has(l.productId) || newRewardPids.has(l.productId));
11361
11476
  return next;
11362
11477
  });
11363
11478
  }
@@ -11372,126 +11487,28 @@ function OrderPlacementPage({ editOrderId }) {
11372
11487
  lines.filter((l) => !rewardProductIds.current.has(l.productId)).map((l) => `${l.productId}:${l.quantity}`).join(","),
11373
11488
  coupons.filter((c) => c.isAutomatic).map((c) => c.discountId).join(",")
11374
11489
  ]);
11375
- async function handleApplyCoupon() {
11376
- const code = couponInput.trim().toUpperCase();
11377
- if (!code) return;
11378
- if (coupons.some((c) => c.couponCode === code)) {
11379
- setCouponError("Coupon already applied");
11380
- return;
11381
- }
11382
- if (coupons.length > 0 && !coupons[0].canCombine) {
11383
- setCouponError("Applied coupon cannot be combined with other discounts");
11384
- return;
11385
- }
11386
- setCouponError(null);
11387
- setCouponLoading(true);
11388
- try {
11389
- const orderLines = lines.map((l) => ({ productId: l.productId, quantity: l.quantity }));
11390
- const res = await fetch("/api/discounts/validate-coupon", {
11391
- method: "POST",
11392
- headers: { "Content-Type": "application/json" },
11393
- body: JSON.stringify({ couponCode: code, orderLines, currency, contactId })
11394
- });
11395
- const data = await res.json();
11396
- if (!res.ok || !data.valid) {
11397
- setCouponError(data.error ?? "Invalid coupon");
11398
- return;
11399
- }
11400
- const d = data.discount;
11401
- if (!d.canCombineWithOtherDiscounts && coupons.length > 0) {
11402
- setCouponError("This coupon cannot be combined with other discounts");
11403
- return;
11404
- }
11405
- setCoupons((prev) => [...prev, {
11406
- couponCode: code,
11407
- discountId: d.id,
11408
- name: d.name,
11409
- value: d.value,
11410
- type: d.discountType,
11411
- canCombine: d.canCombineWithOtherDiscounts,
11412
- calculatedDiscount: d.calculatedDiscount,
11413
- rewardProductIds: d.rewardProductIds ?? [],
11414
- rewardLines: d.rewardLines ?? [],
11415
- isAutomatic: false
11416
- }]);
11417
- setCouponInput("");
11418
- import_sonner7.toast.success(`Coupon "${code}" applied`);
11419
- } catch {
11420
- setCouponError("Failed to validate coupon");
11421
- } finally {
11422
- setCouponLoading(false);
11423
- }
11424
- }
11425
11490
  function handleRemoveCoupon(couponCode) {
11426
11491
  const coupon = coupons.find((c) => c.couponCode === couponCode);
11427
11492
  if (coupon) {
11428
- const rewardKeyPrefix = `reward-`;
11429
- const rewardKeyForDiscount = new Set(
11430
- (coupon.rewardLines ?? []).map((r) => `reward-${r.productId}-${coupon.discountId}`)
11431
- );
11493
+ const rewardKeyForDiscount = new Set((coupon.rewardLines ?? []).map((r) => `reward-${r.productId}-${coupon.discountId}`));
11432
11494
  if (rewardKeyForDiscount.size > 0) {
11433
- setLines(
11434
- (prev) => prev.filter((l) => !rewardKeyForDiscount.has(l.key))
11435
- );
11495
+ setLines((prev) => prev.filter((l) => !rewardKeyForDiscount.has(l.key)));
11436
11496
  coupon.rewardLines.forEach((r) => {
11437
- const stillRewardByOther = coupons.some(
11438
- (c) => c.couponCode !== couponCode && c.rewardLines.some((rl) => rl.productId === r.productId)
11439
- );
11497
+ const stillRewardByOther = coupons.some((c) => c.couponCode !== couponCode && c.rewardLines.some((rl) => rl.productId === r.productId));
11440
11498
  if (!stillRewardByOther) rewardProductIds.current.delete(r.productId);
11441
11499
  });
11442
11500
  }
11443
11501
  rewardInjectedForDiscount.current.delete(coupon.discountId);
11444
11502
  }
11445
11503
  setCoupons((prev) => prev.filter((c) => c.couponCode !== couponCode));
11446
- setCouponError(null);
11447
11504
  }
11448
11505
  function mapAddressToForm(addr) {
11449
- return {
11450
- line1: addr.line1 ?? "",
11451
- line2: addr.line2 ?? "",
11452
- city: addr.city ?? "",
11453
- state: addr.state ?? "",
11454
- country: addr.country ?? "",
11455
- postalCode: addr.postalCode ?? ""
11456
- };
11506
+ return { line1: addr.line1 ?? "", line2: addr.line2 ?? "", city: addr.city ?? "", state: addr.state ?? "", country: addr.country ?? "", postalCode: addr.postalCode ?? "" };
11457
11507
  }
11458
11508
  function updateAddress(type, field, value) {
11459
11509
  if (type === "billing") setBillingAddress((prev) => ({ ...prev, [field]: value }));
11460
11510
  else setShippingAddress((prev) => ({ ...prev, [field]: value }));
11461
11511
  }
11462
- function handleCustomerSelect(value) {
11463
- if (value === MANUAL_CUSTOMER) {
11464
- setContactId(null);
11465
- return;
11466
- }
11467
- const id = Number(value);
11468
- if (!Number.isFinite(id)) return;
11469
- const customer = customers.find((c) => c.id === id);
11470
- if (!customer) return;
11471
- setContactId(id);
11472
- setContactName(customer.name ?? "");
11473
- setContactEmail(customer.email ?? "");
11474
- setContactPhone(customer.phone ?? "");
11475
- loadCustomerAddresses(id);
11476
- }
11477
- async function loadCustomerAddresses(customerId) {
11478
- try {
11479
- const res = await fetch(`/api/order_addresses?contactId=${customerId}&limit=100&sortField=createdAt&sortOrder=desc`);
11480
- if (!res.ok) return;
11481
- const body = await res.json();
11482
- const rows = Array.isArray(body) ? body : Array.isArray(body?.data) ? body.data : [];
11483
- const billing = rows.find((a) => a.addressType === "billing_address");
11484
- const shipping = rows.find((a) => a.addressType === "shipping_address");
11485
- if (billing) setBillingAddress(mapAddressToForm(billing));
11486
- else setBillingAddress({ ...emptyAddress });
11487
- if (sameAsBilling) setShippingAddress(billing ? mapAddressToForm(billing) : { ...emptyAddress });
11488
- else setShippingAddress(shipping ? mapAddressToForm(shipping) : { ...emptyAddress });
11489
- } catch {
11490
- setBillingAddress(emptyAddress);
11491
- setShippingAddress(emptyAddress);
11492
- }
11493
- }
11494
- const customerSelectValue = contactId != null ? String(contactId) : MANUAL_CUSTOMER;
11495
11512
  function toggleStaged(id) {
11496
11513
  setStagedIds((prev) => {
11497
11514
  const next = new Set(prev);
@@ -11528,9 +11545,7 @@ function OrderPlacementPage({ editOrderId }) {
11528
11545
  if (idx >= 0) {
11529
11546
  const row = next[idx];
11530
11547
  next[idx] = { ...row, quantity: row.quantity + 1 };
11531
- } else {
11532
- next.push({ key: String(id), productId: id, label: hit.name ?? `Product #${id}`, sku: hit.sku, quantity: 1 });
11533
- }
11548
+ } else next.push({ key: String(id), productId: id, label: hit.name ?? `Product #${id}`, sku: hit.sku, quantity: 1 });
11534
11549
  }
11535
11550
  return next;
11536
11551
  });
@@ -11559,9 +11574,12 @@ function OrderPlacementPage({ editOrderId }) {
11559
11574
  }, [preview]);
11560
11575
  async function handleSubmit(e) {
11561
11576
  e.preventDefault();
11562
- const email = contactEmail.trim();
11563
- if (contactId == null && !email) {
11564
- import_sonner7.toast.error("Select a customer or enter an email address");
11577
+ const effectiveEmail = placeForSomeoneElse ? subContactEmail.trim() : contactEmail.trim();
11578
+ const effectiveName = placeForSomeoneElse ? subContactName.trim() : contactName.trim();
11579
+ const effectivePhone = placeForSomeoneElse ? subContactPhone.trim() : contactPhone.trim();
11580
+ const effectiveContactId = placeForSomeoneElse ? subContactId : contactId;
11581
+ if (effectiveContactId == null && !effectiveEmail) {
11582
+ import_sonner7.toast.error(placeForSomeoneElse ? "Enter the contact's email address" : "Select a customer or enter an email address");
11565
11583
  return;
11566
11584
  }
11567
11585
  if (lines.length === 0) {
@@ -11575,46 +11593,67 @@ function OrderPlacementPage({ editOrderId }) {
11575
11593
  }
11576
11594
  setSubmitting(true);
11577
11595
  try {
11596
+ let resolvedContactId = placeForSomeoneElse ? subContactId : null;
11597
+ if (!resolvedContactId && effectiveEmail) {
11598
+ try {
11599
+ const res2 = await fetch(`/api/contacts?search=${encodeURIComponent(effectiveEmail)}&limit=1`);
11600
+ if (res2.ok) {
11601
+ const data = await res2.json();
11602
+ const match = data.data?.find((c) => c.email?.toLowerCase() === effectiveEmail.toLowerCase());
11603
+ if (match) resolvedContactId = match.id;
11604
+ }
11605
+ } catch {
11606
+ }
11607
+ }
11608
+ let orderContactId = resolvedContactId;
11609
+ if (placeForSomeoneElse) {
11610
+ if (contactEmail) {
11611
+ try {
11612
+ const res2 = await fetch(`/api/contacts?search=${encodeURIComponent(contactEmail)}&limit=5`);
11613
+ if (res2.ok) {
11614
+ const data = await res2.json();
11615
+ const match = data.data?.find((c) => c.email?.toLowerCase() === contactEmail.toLowerCase());
11616
+ if (match) orderContactId = match.id;
11617
+ }
11618
+ } catch {
11619
+ }
11620
+ }
11621
+ }
11578
11622
  const payload = {
11579
11623
  status,
11580
11624
  currency,
11581
- "contact.name": contactName.trim() || email.split("@")[0] || "Customer",
11582
- "contact.email": email || (contactId != null ? customers.find((c) => c.id === contactId)?.email : "") || "",
11583
- "contact.phone": contactPhone.trim() || null,
11625
+ "contact.name": effectiveName || effectiveEmail.split("@")[0] || "Customer",
11626
+ "contact.email": effectiveEmail || "",
11627
+ "contact.phone": effectivePhone || null,
11584
11628
  billingAddress,
11585
11629
  shippingAddress: sameAsBilling ? billingAddress : shippingAddress,
11586
11630
  orderLines: lines.map((l) => {
11587
11631
  const calc = preview?.lines.find((pl) => pl.productId === l.productId);
11588
- return {
11589
- productId: l.productId,
11590
- quantity: l.quantity,
11591
- unitPrice: calc?.unitPrice ?? productCache.current.get(l.productId)?.price ?? 0
11592
- };
11632
+ return { productId: l.productId, quantity: l.quantity, unitPrice: calc?.unitPrice ?? productCache.current.get(l.productId)?.price ?? 0 };
11593
11633
  })
11594
11634
  };
11595
- if (contactId != null) payload.contactId = contactId;
11635
+ if (orderContactId != null) payload.contactId = orderContactId;
11636
+ if (!placeForSomeoneElse && contactId != null) payload.customerId = contactId;
11596
11637
  if (coupons.length > 0) payload.discountId = coupons[0].discountId;
11597
11638
  const url = editOrderId ? `/api/orders/${editOrderId}` : "/api/orders";
11598
11639
  const method = editOrderId ? "PUT" : "POST";
11599
- const res = await fetch(url, {
11600
- method,
11601
- headers: { "Content-Type": "application/json" },
11602
- body: JSON.stringify(payload)
11603
- });
11640
+ const res = await fetch(url, { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) });
11604
11641
  const body = await res.json().catch(() => ({}));
11605
11642
  if (!res.ok) {
11606
11643
  import_sonner7.toast.error(body.error ?? (editOrderId ? "Failed to update order" : "Failed to create order"));
11607
11644
  return;
11608
11645
  }
11609
- if (editOrderId && contactId != null) {
11610
- const cr = await fetch(`/api/contacts/${contactId}`, {
11646
+ if (resolvedContactId == null) {
11647
+ const serverContactId = body.contactId;
11648
+ if (serverContactId != null && Number.isFinite(serverContactId)) {
11649
+ resolvedContactId = serverContactId;
11650
+ }
11651
+ }
11652
+ if (editOrderId && effectiveContactId != null) {
11653
+ const cr = await fetch(`/api/contacts/${effectiveContactId}`, {
11611
11654
  method: "PUT",
11612
11655
  headers: { "Content-Type": "application/json" },
11613
- body: JSON.stringify({
11614
- name: contactName.trim() || email.split("@")[0] || "Customer",
11615
- email: email || void 0,
11616
- phone: contactPhone.trim() || null
11617
- })
11656
+ body: JSON.stringify({ name: effectiveName || effectiveEmail.split("@")[0] || "Customer", email: effectiveEmail || void 0, phone: effectivePhone || null })
11618
11657
  });
11619
11658
  if (!cr.ok) {
11620
11659
  const cb = await cr.json().catch(() => ({}));
@@ -11626,6 +11665,25 @@ function OrderPlacementPage({ editOrderId }) {
11626
11665
  router.push(withAdminListFromParam(listReturnUrl, `/admin/orders/${editOrderId}/view`));
11627
11666
  return;
11628
11667
  }
11668
+ if (selectedCustomerId != null && resolvedContactId != null) {
11669
+ try {
11670
+ const linkCheck = await fetch(`/api/customer_contacts?customerId=${selectedCustomerId}&limit=200`);
11671
+ if (linkCheck.ok) {
11672
+ const linkData = await linkCheck.json();
11673
+ const existingRows = Array.isArray(linkData?.data) ? linkData.data : Array.isArray(linkData) ? linkData : [];
11674
+ const alreadyLinked = existingRows.some((r) => Number(r.contactId) === resolvedContactId);
11675
+ if (!alreadyLinked) {
11676
+ await fetch("/api/customer_contacts", {
11677
+ method: "POST",
11678
+ headers: { "Content-Type": "application/json" },
11679
+ body: JSON.stringify({ customerId: selectedCustomerId, contactId: resolvedContactId })
11680
+ });
11681
+ }
11682
+ }
11683
+ } catch {
11684
+ console.warn("[OrderPlacement] Could not link contact to customer");
11685
+ }
11686
+ }
11629
11687
  const id = body.id;
11630
11688
  import_sonner7.toast.success("Order created");
11631
11689
  if (id != null) router.push(withAdminListFromParam(listReturnUrl, `/admin/orders/${id}/view`));
@@ -11637,6 +11695,8 @@ function OrderPlacementPage({ editOrderId }) {
11637
11695
  }
11638
11696
  }
11639
11697
  const allVisibleStaged = productHits.length > 0 && productHits.every((p) => stagedIds.has(p.id));
11698
+ const customerSelectValue = contactId != null ? String(contactId) : MANUAL_CUSTOMER;
11699
+ const subContactSelectValue = subContactId != null ? String(subContactId) : MANUAL_CONTACT;
11640
11700
  if (editOrderId && loadingOrder) {
11641
11701
  return /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("div", { className: "rounded-lg bg-white shadow-md min-w-0", children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("div", { className: "flex justify-center items-center py-16 text-gray-500 text-sm", children: "Loading order\u2026" }) });
11642
11702
  }
@@ -11681,74 +11741,168 @@ function OrderPlacementPage({ editOrderId }) {
11681
11741
  ] }),
11682
11742
  /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 sm:col-span-2", children: [
11683
11743
  /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { htmlFor: "c-email", children: "Email" }),
11684
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { id: "c-email", type: "email", value: contactEmail, onChange: (e) => setContactEmail(e.target.value), placeholder: "name@example.com", required: contactId == null })
11744
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { id: "c-email", type: "email", value: contactEmail, onChange: (e) => setContactEmail(e.target.value), placeholder: "name@example.com", required: contactId == null && !placeForSomeoneElse })
11685
11745
  ] }),
11686
11746
  /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 sm:col-span-2", children: [
11687
11747
  /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { htmlFor: "c-phone", children: "Phone" }),
11688
11748
  /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { id: "c-phone", value: contactPhone, onChange: (e) => setContactPhone(e.target.value), placeholder: "phone number" })
11749
+ ] })
11750
+ ] }),
11751
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "flex items-start gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/60 px-4 py-3 mt-1", children: [
11752
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
11753
+ Checkbox,
11754
+ {
11755
+ id: "place-for-someone-else",
11756
+ checked: placeForSomeoneElse,
11757
+ onCheckedChange: (checked) => handlePlaceForSomeoneElseToggle(Boolean(checked)),
11758
+ disabled: contactId == null
11759
+ }
11760
+ ),
11761
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-0.5", children: [
11762
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
11763
+ "label",
11764
+ {
11765
+ htmlFor: "place-for-someone-else",
11766
+ className: `text-sm font-medium leading-none cursor-pointer ${contactId == null ? "text-gray-400" : "text-gray-700"}`,
11767
+ children: "Place this order for someone else"
11768
+ }
11769
+ ),
11770
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("p", { className: "text-xs text-gray-500", children: contactId == null ? "Select a customer above to enable this option." : "Select a linked contact or enter their details below. Addresses will be filled from the selected contact." })
11771
+ ] })
11772
+ ] }),
11773
+ placeForSomeoneElse && contactId != null && /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "rounded-lg border border-blue-200 bg-blue-50/40 p-4 space-y-3", children: [
11774
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "flex items-center gap-2", children: [
11775
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(import_lucide_react30.Users, { className: "h-4 w-4 text-blue-600" }),
11776
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("h3", { className: "text-sm font-semibold text-blue-800", children: "Order recipient" })
11689
11777
  ] }),
11690
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-4 sm:col-span-2 border rounded-lg p-4", children: [
11691
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("h3", { className: "text-sm font-semibold", children: "Billing Address" }),
11692
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [
11693
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 md:col-span-2", children: [
11694
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "Address Line 1" }),
11695
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: billingAddress.line1, onChange: (e) => updateAddress("billing", "line1", e.target.value), placeholder: "Street Address" })
11696
- ] }),
11697
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 md:col-span-2", children: [
11698
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "Address Line 2" }),
11699
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: billingAddress.line2, onChange: (e) => updateAddress("billing", "line2", e.target.value), placeholder: "Apartment, Suite, floor, etc." })
11700
- ] }),
11701
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2", children: [
11702
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "City" }),
11703
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: billingAddress.city, onChange: (e) => updateAddress("billing", "city", e.target.value), placeholder: "City" })
11704
- ] }),
11705
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2", children: [
11706
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "State" }),
11707
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: billingAddress.state, onChange: (e) => updateAddress("billing", "state", e.target.value), placeholder: "State" })
11708
- ] }),
11709
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2", children: [
11710
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "Country" }),
11711
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: billingAddress.country, onChange: (e) => updateAddress("billing", "country", e.target.value), placeholder: "Country" })
11712
- ] }),
11713
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2", children: [
11714
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "Postal Code" }),
11715
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: billingAddress.postalCode, onChange: (e) => updateAddress("billing", "postalCode", e.target.value), placeholder: "Postal Code" })
11716
- ] })
11778
+ customerContactsLoading ? /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("p", { className: "text-xs text-gray-500", children: "Loading contacts\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2", children: [
11779
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { htmlFor: "sc-select", children: "Select contact" }),
11780
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(
11781
+ "select",
11782
+ {
11783
+ id: "sc-select",
11784
+ value: subContactSelectValue,
11785
+ onChange: (e) => handleSubContactSelect(e.target.value),
11786
+ className: "w-full h-10 rounded-md border border-gray-300 bg-white px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
11787
+ children: [
11788
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("option", { value: MANUAL_CONTACT, children: "Enter manually (new contact)" }),
11789
+ customerContacts.map((r) => /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("option", { value: String(r.contactId), children: [
11790
+ r.contact.name,
11791
+ r.contact.email ? ` \u2014 ${r.contact.email}` : ""
11792
+ ] }, r.contactId))
11793
+ ]
11794
+ }
11795
+ ),
11796
+ customerContacts.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("p", { className: "text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded px-3 py-2", children: "No linked contacts found for this customer. Fill in the details below." })
11797
+ ] }),
11798
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3", children: [
11799
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 sm:col-span-2", children: [
11800
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { htmlFor: "sc-name", children: "Recipient name" }),
11801
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
11802
+ Input,
11803
+ {
11804
+ id: "sc-name",
11805
+ value: subContactName,
11806
+ onChange: (e) => setSubContactName(e.target.value),
11807
+ placeholder: "Full name",
11808
+ readOnly: subContactId != null,
11809
+ className: subContactId != null ? "bg-gray-100 cursor-not-allowed" : ""
11810
+ }
11811
+ )
11812
+ ] }),
11813
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 sm:col-span-2", children: [
11814
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { htmlFor: "sc-email", children: "Recipient email" }),
11815
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
11816
+ Input,
11817
+ {
11818
+ id: "sc-email",
11819
+ type: "email",
11820
+ value: subContactEmail,
11821
+ onChange: (e) => setSubContactEmail(e.target.value),
11822
+ placeholder: "email@example.com",
11823
+ required: placeForSomeoneElse && subContactId == null,
11824
+ readOnly: subContactId != null,
11825
+ className: subContactId != null ? "bg-gray-100 cursor-not-allowed" : ""
11826
+ }
11827
+ )
11828
+ ] }),
11829
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 sm:col-span-2", children: [
11830
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { htmlFor: "sc-phone", children: "Recipient phone" }),
11831
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
11832
+ Input,
11833
+ {
11834
+ id: "sc-phone",
11835
+ value: subContactPhone,
11836
+ onChange: (e) => setSubContactPhone(e.target.value),
11837
+ placeholder: "phone number",
11838
+ readOnly: subContactId != null,
11839
+ className: subContactId != null ? "bg-gray-100 cursor-not-allowed" : ""
11840
+ }
11841
+ )
11842
+ ] })
11843
+ ] })
11844
+ ] }),
11845
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-4 border rounded-lg p-4", children: [
11846
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("h3", { className: "text-sm font-semibold", children: "Billing Address" }),
11847
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [
11848
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 md:col-span-2", children: [
11849
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "Address Line 1" }),
11850
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: billingAddress.line1, onChange: (e) => updateAddress("billing", "line1", e.target.value), placeholder: "Street Address" })
11851
+ ] }),
11852
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 md:col-span-2", children: [
11853
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "Address Line 2" }),
11854
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: billingAddress.line2, onChange: (e) => updateAddress("billing", "line2", e.target.value), placeholder: "Apartment, Suite, Floor, etc." })
11855
+ ] }),
11856
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2", children: [
11857
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "City" }),
11858
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: billingAddress.city, onChange: (e) => updateAddress("billing", "city", e.target.value), placeholder: "City" })
11859
+ ] }),
11860
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2", children: [
11861
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "State" }),
11862
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: billingAddress.state, onChange: (e) => updateAddress("billing", "state", e.target.value), placeholder: "State" })
11863
+ ] }),
11864
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2", children: [
11865
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "Country" }),
11866
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: billingAddress.country, onChange: (e) => updateAddress("billing", "country", e.target.value), placeholder: "Country" })
11867
+ ] }),
11868
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2", children: [
11869
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "Postal Code" }),
11870
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: billingAddress.postalCode, onChange: (e) => updateAddress("billing", "postalCode", e.target.value), placeholder: "Postal Code" })
11871
+ ] })
11872
+ ] })
11873
+ ] }),
11874
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-4 border rounded-lg p-4", children: [
11875
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "flex items-center justify-between", children: [
11876
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("h3", { className: "text-sm font-semibold", children: "Shipping Address" }),
11877
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "flex items-center gap-2", children: [
11878
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Checkbox, { checked: sameAsBilling, onCheckedChange: (checked) => setSameAsBilling(Boolean(checked)) }),
11879
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("span", { className: "text-sm text-gray-600", children: "Same as Billing" })
11717
11880
  ] })
11718
11881
  ] }),
11719
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-4 sm:col-span-2 border rounded-lg p-4", children: [
11720
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "flex items-center justify-between", children: [
11721
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("h3", { className: "text-sm font-semibold", children: "Shipping Address" }),
11722
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "flex items-center gap-2", children: [
11723
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Checkbox, { checked: sameAsBilling, onCheckedChange: (checked) => setSameAsBilling(Boolean(checked)) }),
11724
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("span", { className: "text-sm text-gray-600", children: "Same as Billing" })
11725
- ] })
11882
+ !sameAsBilling && /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [
11883
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 md:col-span-2", children: [
11884
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "Address Line 1" }),
11885
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: shippingAddress.line1, onChange: (e) => updateAddress("shipping", "line1", e.target.value), placeholder: "Street Address" })
11726
11886
  ] }),
11727
- !sameAsBilling && /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [
11728
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 md:col-span-2", children: [
11729
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "Address Line 1" }),
11730
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: shippingAddress.line1, onChange: (e) => updateAddress("shipping", "line1", e.target.value), placeholder: "Street Address" })
11731
- ] }),
11732
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 md:col-span-2", children: [
11733
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "Address Line 2" }),
11734
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: shippingAddress.line2, onChange: (e) => updateAddress("shipping", "line2", e.target.value), placeholder: "Apartment, Suite, Floor, etc." })
11735
- ] }),
11736
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 md:col-span-2", children: [
11737
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "City" }),
11738
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: shippingAddress.city, onChange: (e) => updateAddress("shipping", "city", e.target.value), placeholder: "City" })
11739
- ] }),
11740
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 md:col-span-2", children: [
11741
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "State" }),
11742
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: shippingAddress.state, onChange: (e) => updateAddress("shipping", "state", e.target.value), placeholder: "State" })
11743
- ] }),
11744
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 md:col-span-2", children: [
11745
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "Country" }),
11746
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: shippingAddress.country, onChange: (e) => updateAddress("shipping", "country", e.target.value), placeholder: "Country" })
11747
- ] }),
11748
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 md:col-span-2", children: [
11749
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "Postal Code" }),
11750
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: shippingAddress.postalCode, onChange: (e) => updateAddress("shipping", "postalCode", e.target.value), placeholder: "Postal Code" })
11751
- ] })
11887
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 md:col-span-2", children: [
11888
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "Address Line 2" }),
11889
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: shippingAddress.line2, onChange: (e) => updateAddress("shipping", "line2", e.target.value), placeholder: "Apartment, Suite, Floor, etc." })
11890
+ ] }),
11891
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 md:col-span-2", children: [
11892
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "City" }),
11893
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: shippingAddress.city, onChange: (e) => updateAddress("shipping", "city", e.target.value), placeholder: "City" })
11894
+ ] }),
11895
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 md:col-span-2", children: [
11896
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "State" }),
11897
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: shippingAddress.state, onChange: (e) => updateAddress("shipping", "state", e.target.value), placeholder: "State" })
11898
+ ] }),
11899
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 md:col-span-2", children: [
11900
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "Country" }),
11901
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: shippingAddress.country, onChange: (e) => updateAddress("shipping", "country", e.target.value), placeholder: "Country" })
11902
+ ] }),
11903
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "space-y-2 md:col-span-2", children: [
11904
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Label3, { children: "Postal Code" }),
11905
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { value: shippingAddress.postalCode, onChange: (e) => updateAddress("shipping", "postalCode", e.target.value), placeholder: "Postal Code" })
11752
11906
  ] })
11753
11907
  ] })
11754
11908
  ] })
@@ -11846,51 +12000,21 @@ function OrderPlacementPage({ editOrderId }) {
11846
12000
  const active = selectedKey === line.key;
11847
12001
  const calc = calcByProductId.get(line.productId);
11848
12002
  const isReward = line.key.startsWith("reward-");
11849
- return /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(
11850
- TableRow,
11851
- {
11852
- className: active ? "bg-blue-50/80 cursor-pointer" : "cursor-pointer",
11853
- onClick: () => setSelectedKey(line.key),
11854
- children: [
11855
- /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(TableCell, { className: "align-top min-w-0", children: [
11856
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("p", { className: "font-medium text-gray-900 break-words", children: line.label }),
11857
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("p", { className: "text-xs text-gray-500 font-mono", children: line.sku ?? "\u2014" }),
11858
- isReward && /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("span", { className: "text-[10px] text-purple-600 font-medium", children: "\u{1F381} Reward" })
11859
- ] }),
11860
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(TableCell, { className: "align-top text-right", children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
11861
- Input,
11862
- {
11863
- type: "number",
11864
- min: 1,
11865
- className: "h-8 w-14 ml-auto text-right",
11866
- value: line.quantity,
11867
- onChange: (e) => updateQty(line.key, Number(e.target.value)),
11868
- onClick: (e) => e.stopPropagation(),
11869
- readOnly: isReward,
11870
- disabled: isReward
11871
- }
11872
- ) }),
11873
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(TableCell, { className: "align-top text-right text-sm hidden sm:table-cell whitespace-nowrap", children: calc ? formatMoney3(calc.unitPrice, currency) : previewLoading ? "\u2026" : "\u2014" }),
11874
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(TableCell, { className: "align-top text-right text-sm hidden md:table-cell whitespace-nowrap", children: calc ? formatMoney3(calc.tax, currency) : previewLoading ? "\u2026" : "\u2014" }),
11875
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(TableCell, { className: "align-top text-right font-medium text-sm whitespace-nowrap", children: calc ? formatMoney3(calc.total, currency) : previewLoading ? "\u2026" : "\u2014" }),
11876
- /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(TableCell, { className: "align-top", children: !isReward && /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
11877
- Button,
11878
- {
11879
- type: "button",
11880
- variant: "ghost",
11881
- size: "icon",
11882
- className: "h-8 w-8 text-red-600",
11883
- onClick: (e) => {
11884
- e.stopPropagation();
11885
- removeLine(line.key);
11886
- },
11887
- children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(import_lucide_react30.Trash2, { className: "h-4 w-4" })
11888
- }
11889
- ) })
11890
- ]
11891
- },
11892
- line.key
11893
- );
12003
+ return /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(TableRow, { className: active ? "bg-blue-50/80 cursor-pointer" : "cursor-pointer", onClick: () => setSelectedKey(line.key), children: [
12004
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(TableCell, { className: "align-top min-w-0", children: [
12005
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("p", { className: "font-medium text-gray-900 break-words", children: line.label }),
12006
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("p", { className: "text-xs text-gray-500 font-mono", children: line.sku ?? "\u2014" }),
12007
+ isReward && /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("span", { className: "text-[10px] text-purple-600 font-medium", children: "\u{1F381} Reward" })
12008
+ ] }),
12009
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(TableCell, { className: "align-top text-right", children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Input, { type: "number", min: 1, className: "h-8 w-14 ml-auto text-right", value: line.quantity, onChange: (e) => updateQty(line.key, Number(e.target.value)), onClick: (e) => e.stopPropagation(), readOnly: isReward, disabled: isReward }) }),
12010
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(TableCell, { className: "align-top text-right text-sm hidden sm:table-cell whitespace-nowrap", children: calc ? formatMoney3(calc.unitPrice, currency) : previewLoading ? "\u2026" : "\u2014" }),
12011
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(TableCell, { className: "align-top text-right text-sm hidden md:table-cell whitespace-nowrap", children: calc ? formatMoney3(calc.tax, currency) : previewLoading ? "\u2026" : "\u2014" }),
12012
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(TableCell, { className: "align-top text-right font-medium text-sm whitespace-nowrap", children: calc ? formatMoney3(calc.total, currency) : previewLoading ? "\u2026" : "\u2014" }),
12013
+ /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(TableCell, { className: "align-top", children: !isReward && /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(Button, { type: "button", variant: "ghost", size: "icon", className: "h-8 w-8 text-red-600", onClick: (e) => {
12014
+ e.stopPropagation();
12015
+ removeLine(line.key);
12016
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(import_lucide_react30.Trash2, { className: "h-4 w-4" }) }) })
12017
+ ] }, line.key);
11894
12018
  }) })
11895
12019
  ] }) }),
11896
12020
  /* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "flex flex-wrap items-center gap-4 justify-between border border-gray-200 rounded-lg p-4 bg-gray-50/70 text-sm", children: [
@@ -11957,21 +12081,13 @@ function OrderPlacementPage({ editOrderId }) {
11957
12081
  if (!hit) return;
11958
12082
  const productHit = hit;
11959
12083
  setLines((prev) => {
11960
- const existingRewardIdx = prev.findIndex(
11961
- (l) => l.productId === pid && rewardProductIds.current.has(pid)
11962
- );
12084
+ const existingRewardIdx = prev.findIndex((l) => l.productId === pid && rewardProductIds.current.has(pid));
11963
12085
  if (existingRewardIdx >= 0) {
11964
12086
  const next = [...prev];
11965
12087
  next[existingRewardIdx] = { ...next[existingRewardIdx], quantity: qty };
11966
12088
  return next;
11967
12089
  }
11968
- return [...prev, {
11969
- key: `reward-${pid}-${coupon.discountId}`,
11970
- productId: pid,
11971
- label: productHit.name ?? `Product #${pid}`,
11972
- sku: productHit.sku,
11973
- quantity: qty
11974
- }];
12090
+ return [...prev, { key: `reward-${pid}-${coupon.discountId}`, productId: pid, label: productHit.name ?? `Product #${pid}`, sku: productHit.sku, quantity: qty }];
11975
12091
  });
11976
12092
  });
11977
12093
  }
@@ -16115,6 +16231,155 @@ function serializeEmailRecipients(emails) {
16115
16231
  init_button();
16116
16232
  init_input();
16117
16233
  var import_sonner12 = require("sonner");
16234
+
16235
+ // src/plugins/llm/chat-email-intent.ts
16236
+ function normalizeIntentKey(raw) {
16237
+ return raw.trim().toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
16238
+ }
16239
+ function parseIntentsJson(raw) {
16240
+ if (!raw?.trim()) return null;
16241
+ try {
16242
+ const parsed = JSON.parse(raw);
16243
+ if (!Array.isArray(parsed)) return null;
16244
+ const out = [];
16245
+ for (const row of parsed) {
16246
+ if (!row || typeof row !== "object") continue;
16247
+ const o = row;
16248
+ const intent = normalizeIntentKey(String(o.intent ?? o.id ?? ""));
16249
+ const description = String(o.description ?? "").trim();
16250
+ const emailTo = String(o.emailTo ?? o.email ?? "").trim();
16251
+ if (!intent || !description || !emailTo) continue;
16252
+ out.push({ intent, description, emailTo });
16253
+ }
16254
+ return out.length > 0 ? out : null;
16255
+ } catch {
16256
+ return null;
16257
+ }
16258
+ }
16259
+ function dedupeIntents(intents) {
16260
+ const seen = /* @__PURE__ */ new Set();
16261
+ const out = [];
16262
+ for (const row of intents) {
16263
+ const key = normalizeIntentKey(row.intent);
16264
+ if (!key || seen.has(key)) continue;
16265
+ seen.add(key);
16266
+ out.push({
16267
+ intent: key,
16268
+ description: row.description.trim(),
16269
+ emailTo: row.emailTo.trim()
16270
+ });
16271
+ }
16272
+ return out;
16273
+ }
16274
+ function parseEmailIntentsFromSettings(raw) {
16275
+ return dedupeIntents(parseIntentsJson(raw) ?? []);
16276
+ }
16277
+ function serializeChatLeadIntents(intents) {
16278
+ return JSON.stringify(
16279
+ dedupeIntents(intents).map((r) => ({
16280
+ intent: r.intent,
16281
+ description: r.description,
16282
+ emailTo: r.emailTo
16283
+ }))
16284
+ );
16285
+ }
16286
+ var EMAIL_TOOL_VALIDATION_KEY = "emailTool";
16287
+ function parseEmailToolObject(raw) {
16288
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
16289
+ const o = raw;
16290
+ const intentsRaw = o.intents;
16291
+ let intents;
16292
+ if (Array.isArray(intentsRaw)) {
16293
+ const parsed = parseIntentsJson(JSON.stringify(intentsRaw));
16294
+ if (parsed?.length) intents = parsed;
16295
+ }
16296
+ return {
16297
+ enabled: o.enabled === true,
16298
+ classifierInstructions: typeof o.classifierInstructions === "string" ? o.classifierInstructions.trim() : void 0,
16299
+ toolPrompt: typeof o.toolPrompt === "string" ? o.toolPrompt.trim() : void 0,
16300
+ intents
16301
+ };
16302
+ }
16303
+ function splitValidationRulesForAdmin(validationRulesText) {
16304
+ const raw = validationRulesText?.trim() ?? "";
16305
+ if (!raw) return { guardrailsJson: "", emailTool: null };
16306
+ try {
16307
+ const parsed = JSON.parse(raw);
16308
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
16309
+ return { guardrailsJson: raw, emailTool: null };
16310
+ }
16311
+ const emailTool = parseEmailToolObject(parsed[EMAIL_TOOL_VALIDATION_KEY]);
16312
+ const rest = { ...parsed };
16313
+ delete rest[EMAIL_TOOL_VALIDATION_KEY];
16314
+ const guardrailsJson = Object.keys(rest).length > 0 ? JSON.stringify(rest, null, 2) : "";
16315
+ return { guardrailsJson, emailTool };
16316
+ } catch {
16317
+ return { guardrailsJson: raw, emailTool: null };
16318
+ }
16319
+ }
16320
+ function mergeEmailToolIntoValidationRules(guardrailsJson, emailTool) {
16321
+ const intents = dedupeIntents(emailTool.intents);
16322
+ const hasEmail = intents.length > 0 || emailTool.classifierInstructions.trim() !== "" || emailTool.toolPrompt.trim() !== "";
16323
+ let base = {};
16324
+ const raw = guardrailsJson?.trim();
16325
+ if (raw) {
16326
+ try {
16327
+ const parsed = JSON.parse(raw);
16328
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
16329
+ base = { ...parsed };
16330
+ delete base[EMAIL_TOOL_VALIDATION_KEY];
16331
+ } else {
16332
+ return raw;
16333
+ }
16334
+ } catch {
16335
+ return raw;
16336
+ }
16337
+ }
16338
+ if (!hasEmail) {
16339
+ return Object.keys(base).length > 0 ? JSON.stringify(base, null, 2) : null;
16340
+ }
16341
+ base[EMAIL_TOOL_VALIDATION_KEY] = {
16342
+ enabled: true,
16343
+ classifierInstructions: emailTool.classifierInstructions.trim(),
16344
+ toolPrompt: emailTool.toolPrompt.trim(),
16345
+ intents: intents.map((r) => ({
16346
+ intent: r.intent,
16347
+ description: r.description,
16348
+ emailTo: r.emailTo
16349
+ }))
16350
+ };
16351
+ return JSON.stringify(base, null, 2);
16352
+ }
16353
+
16354
+ // src/plugins/llm/llm-agent-scope.ts
16355
+ var LLM_AGENT_SCOPE_CHATBOT = "chatbot";
16356
+ var LLM_AGENT_SCOPE_BLOG_CREATION = "blog_creation";
16357
+ var LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST = "social_media_post";
16358
+ var LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT = "email_intent_chatbot";
16359
+ var LLM_AGENT_SCOPE_BLOG_METADATA = "blog_metadata";
16360
+ var LLM_AGENT_DEFAULT_SLUG_BY_SCOPE = {
16361
+ [LLM_AGENT_SCOPE_CHATBOT]: "site-chat-assistant",
16362
+ [LLM_AGENT_SCOPE_BLOG_CREATION]: "blog-generator",
16363
+ [LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST]: "blog-generator-social",
16364
+ [LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT]: "email-intent-chatbot",
16365
+ [LLM_AGENT_SCOPE_BLOG_METADATA]: "blog-generator-metadata"
16366
+ };
16367
+ var LLM_AGENT_DEFAULT_NAME_BY_SCOPE = {
16368
+ [LLM_AGENT_SCOPE_CHATBOT]: "Site Chat Assistant",
16369
+ [LLM_AGENT_SCOPE_BLOG_CREATION]: "Blog Generator",
16370
+ [LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST]: "Blog Generator (Social)",
16371
+ [LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT]: "Chat Lead Intent Classifier",
16372
+ [LLM_AGENT_SCOPE_BLOG_METADATA]: "Blog Generator (Metadata)"
16373
+ };
16374
+ var SITE_CHAT_ASSISTANT_SLUG = LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
16375
+ var SITE_CHAT_ASSISTANT_NAME = LLM_AGENT_DEFAULT_NAME_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
16376
+ var BLOG_LLM_AGENT_SLUGS = [
16377
+ LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_BLOG_CREATION],
16378
+ LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_BLOG_METADATA],
16379
+ LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST]
16380
+ ];
16381
+
16382
+ // src/admin/pages/PluginsPage.tsx
16118
16383
  var import_jsx_runtime66 = require("react/jsx-runtime");
16119
16384
  function normalizeLinkedInOrganizations(payload) {
16120
16385
  if (!payload || typeof payload !== "object") return [];
@@ -16154,10 +16419,6 @@ function normalizeFacebookGraphPages(graph) {
16154
16419
  }
16155
16420
  return out;
16156
16421
  }
16157
- function slugifyAgentKey(name) {
16158
- const s = name.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
16159
- return s || "agent";
16160
- }
16161
16422
  function normalizeChatMode(raw) {
16162
16423
  if (raw === "external" || raw === "llm") return raw;
16163
16424
  return "whatsapp";
@@ -16296,6 +16557,8 @@ function PluginSettingsPanel({
16296
16557
  descriptor,
16297
16558
  onSaved
16298
16559
  }) {
16560
+ const { pluginDescriptors = [] } = (0, import_react51.useContext)(AdminConfigContext);
16561
+ const emailPluginAvailable = pluginDescriptors.some((p) => p.name === "email" && p.enabled !== false);
16299
16562
  const settingsGroup = descriptor.settingsGroup;
16300
16563
  const isLlm = settingsGroup === "llm";
16301
16564
  const isEmail = settingsGroup === "email";
@@ -16331,9 +16594,19 @@ function PluginSettingsPanel({
16331
16594
  const [agentTemp, setAgentTemp] = (0, import_react51.useState)("");
16332
16595
  const [agentMaxTokens, setAgentMaxTokens] = (0, import_react51.useState)("");
16333
16596
  const [agentValidationJson, setAgentValidationJson] = (0, import_react51.useState)("");
16597
+ const [emailToolEnabled, setEmailToolEnabled] = (0, import_react51.useState)(false);
16598
+ const [emailIntents, setEmailIntents] = (0, import_react51.useState)([]);
16599
+ const [emailToolPrompt, setEmailToolPrompt] = (0, import_react51.useState)("");
16334
16600
  const [agentLoading, setAgentLoading] = (0, import_react51.useState)(false);
16335
16601
  const [agentSaving, setAgentSaving] = (0, import_react51.useState)(false);
16336
16602
  const [agentProvisionError, setAgentProvisionError] = (0, import_react51.useState)(null);
16603
+ const [llmConfigTab, setLlmConfigTab] = (0, import_react51.useState)("chat");
16604
+ const [notifyEmailAgentId, setNotifyEmailAgentId] = (0, import_react51.useState)(null);
16605
+ const [notifyEmailName, setNotifyEmailName] = (0, import_react51.useState)("");
16606
+ const [notifyEmailSystem, setNotifyEmailSystem] = (0, import_react51.useState)("");
16607
+ const [notifyEmailLoading, setNotifyEmailLoading] = (0, import_react51.useState)(false);
16608
+ const [notifyEmailSaving, setNotifyEmailSaving] = (0, import_react51.useState)(false);
16609
+ const [notifyEmailProvisionError, setNotifyEmailProvisionError] = (0, import_react51.useState)(null);
16337
16610
  const [kbCatalog, setKbCatalog] = (0, import_react51.useState)([]);
16338
16611
  const [attachedAgentKnowledge, setAttachedAgentKnowledge] = (0, import_react51.useState)([]);
16339
16612
  const [attachedKbLoading, setAttachedKbLoading] = (0, import_react51.useState)(false);
@@ -16555,7 +16828,12 @@ function PluginSettingsPanel({
16555
16828
  setIconImageUrl(data.iconImageUrl ?? "");
16556
16829
  setIconBackgroundColor(data.iconBackgroundColor ?? "#6366f1");
16557
16830
  setHeaderColor(data.headerColor ?? "#6366f1");
16558
- setAttachedAgentSlug(data.attachedAgentSlug ?? "");
16831
+ setAttachedAgentSlug((data.attachedAgentSlug ?? "").trim());
16832
+ setEmailToolEnabled(data.emailToolEnabled === "true");
16833
+ setEmailIntents(parseEmailIntentsFromSettings(data.emailIntents));
16834
+ const legacyClassifier = data.emailClassifierInstructions ?? [data.emailIntentPrompt, data.emailNegativeIntentPrompt].filter(Boolean).join("\n\n") ?? "";
16835
+ if (legacyClassifier) setNotifyEmailSystem(legacyClassifier);
16836
+ setEmailToolPrompt(data.emailToolPrompt ?? "");
16559
16837
  }
16560
16838
  if (isErp) {
16561
16839
  setErpPipelineName(data.pipelineName ?? data.pipelineId ?? "");
@@ -16641,33 +16919,116 @@ function PluginSettingsPanel({
16641
16919
  }).catch(() => setErpFormsCatalog([]));
16642
16920
  }, [isErp, loading]);
16643
16921
  const fetchLlmAgents = (0, import_react51.useCallback)(async () => {
16644
- const res = await fetch("/api/llm_agents?limit=100&sortField=name&sortOrder=asc");
16922
+ const res = await fetch(
16923
+ `/api/llm_agents?scope=${LLM_AGENT_SCOPE_CHATBOT}&limit=5&sortField=name&sortOrder=asc`
16924
+ );
16645
16925
  if (!res.ok) {
16646
16926
  setLlmAgents([]);
16927
+ setAgentId(null);
16647
16928
  return;
16648
16929
  }
16649
16930
  const j = await res.json();
16650
- const list = (j.data ?? []).map((r) => ({
16931
+ const chatRows = (j.data ?? []).filter(
16932
+ (r) => String(r.scope ?? "").trim() === LLM_AGENT_SCOPE_CHATBOT || String(r.slug ?? "").trim() === LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT]
16933
+ );
16934
+ const list = chatRows.map((r) => ({
16651
16935
  id: r.id,
16652
16936
  name: String(r.name ?? ""),
16653
16937
  slug: String(r.slug ?? ""),
16938
+ scope: LLM_AGENT_SCOPE_CHATBOT,
16654
16939
  enabled: r.enabled !== false
16655
16940
  }));
16656
16941
  setLlmAgents(list);
16657
16942
  const pick = list.find((a) => a.enabled) ?? list[0];
16658
- const fullRow = (j.data ?? []).find((r) => r.id === pick?.id);
16943
+ const fullRow = pick ? chatRows.find((r) => r.id === pick.id) : void 0;
16659
16944
  if (pick && fullRow) {
16660
16945
  setAgentId(pick.id);
16661
16946
  setAgentName(pick.name);
16662
16947
  setAgentSlug(pick.slug);
16948
+ setAttachedAgentSlug(pick.slug);
16663
16949
  setAgentSystem(String(fullRow.systemInstruction ?? ""));
16664
16950
  setAgentModel(String(fullRow.model ?? ""));
16665
16951
  setAgentTemp(fullRow.temperature != null ? String(fullRow.temperature) : "");
16666
16952
  setAgentMaxTokens(fullRow.maxTokens != null ? String(fullRow.maxTokens) : "");
16667
- setAgentValidationJson(String(fullRow.validationRules ?? ""));
16668
- setAttachedAgentSlug(pick.slug);
16953
+ const { guardrailsJson } = splitValidationRulesForAdmin(fullRow.validationRules ?? null);
16954
+ setAgentValidationJson(guardrailsJson);
16955
+ } else {
16956
+ setAgentId(null);
16957
+ setAgentName(botName.trim() || LLM_AGENT_DEFAULT_NAME_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT]);
16958
+ setAgentSlug(LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT]);
16959
+ setAgentSystem("");
16960
+ setAgentModel("");
16961
+ setAgentTemp("");
16962
+ setAgentMaxTokens("");
16963
+ setAgentValidationJson("");
16964
+ }
16965
+ }, [botName]);
16966
+ const fetchNotifyEmailAgent = (0, import_react51.useCallback)(async () => {
16967
+ const res = await fetch(
16968
+ `/api/llm_agents?scope=${LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT}&limit=1&sortField=name&sortOrder=asc`
16969
+ );
16970
+ if (!res.ok) {
16971
+ setNotifyEmailAgentId(null);
16972
+ return;
16973
+ }
16974
+ const j = await res.json();
16975
+ const row = j.data?.find((r) => String(r.scope ?? "").trim() === LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT) ?? j.data?.find(
16976
+ (r) => String(r.slug ?? "").trim() === LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT]
16977
+ ) ?? j.data?.[0];
16978
+ if (!row || String(row.scope ?? "").trim() !== LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT) {
16979
+ setNotifyEmailAgentId(null);
16980
+ setNotifyEmailName(LLM_AGENT_DEFAULT_NAME_BY_SCOPE[LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT]);
16981
+ setNotifyEmailSystem("");
16982
+ return;
16983
+ }
16984
+ setNotifyEmailAgentId(row.id);
16985
+ setNotifyEmailName(String(row.name ?? ""));
16986
+ setNotifyEmailSystem(String(row.systemInstruction ?? ""));
16987
+ const { emailTool } = splitValidationRulesForAdmin(row.validationRules ?? null);
16988
+ if (emailTool?.intents?.length) setEmailIntents(emailTool.intents);
16989
+ if (emailTool?.toolPrompt) setEmailToolPrompt(emailTool.toolPrompt);
16990
+ if (!row.systemInstruction?.trim() && emailTool?.classifierInstructions) {
16991
+ setNotifyEmailSystem(emailTool.classifierInstructions);
16669
16992
  }
16670
16993
  }, []);
16994
+ const bootstrapNotifyEmailAgent = (0, import_react51.useCallback)(async () => {
16995
+ setNotifyEmailProvisionError(null);
16996
+ const listRes = await fetch(
16997
+ `/api/llm_agents?scope=${LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT}&limit=1`
16998
+ );
16999
+ if (!listRes.ok) {
17000
+ const message = `Could not load notify email agent (HTTP ${listRes.status}).`;
17001
+ setNotifyEmailProvisionError(message);
17002
+ await fetchNotifyEmailAgent();
17003
+ return { ok: false, message };
17004
+ }
17005
+ const listJ = await listRes.json();
17006
+ const notifyRow = listJ.data?.find((a) => a.scope === LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT);
17007
+ if (notifyRow) {
17008
+ await fetchNotifyEmailAgent();
17009
+ return { ok: true };
17010
+ }
17011
+ const createRes = await fetch("/api/llm_agents", {
17012
+ method: "POST",
17013
+ headers: { "Content-Type": "application/json" },
17014
+ body: JSON.stringify({
17015
+ name: LLM_AGENT_DEFAULT_NAME_BY_SCOPE[LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT],
17016
+ slug: LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT],
17017
+ scope: LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT,
17018
+ systemInstruction: "",
17019
+ enabled: true
17020
+ })
17021
+ });
17022
+ if (!createRes.ok) {
17023
+ const errBody = await createRes.json().catch(() => ({}));
17024
+ const message = errBody.error ?? `Could not create notify email agent (HTTP ${createRes.status}).`;
17025
+ setNotifyEmailProvisionError(message);
17026
+ await fetchNotifyEmailAgent();
17027
+ return { ok: false, message };
17028
+ }
17029
+ await fetchNotifyEmailAgent();
17030
+ return { ok: true };
17031
+ }, [fetchNotifyEmailAgent]);
16671
17032
  const fetchKbCatalog = (0, import_react51.useCallback)(async () => {
16672
17033
  try {
16673
17034
  const res = await fetch("/api/knowledge_base_documents?limit=300&sortField=name&sortOrder=asc");
@@ -16713,7 +17074,9 @@ function PluginSettingsPanel({
16713
17074
  }, []);
16714
17075
  const bootstrapLlmAgentForPlugins = (0, import_react51.useCallback)(async () => {
16715
17076
  setAgentProvisionError(null);
16716
- const listRes = await fetch("/api/llm_agents?limit=100&sortField=name&sortOrder=asc");
17077
+ const listRes = await fetch(
17078
+ `/api/llm_agents?scope=${LLM_AGENT_SCOPE_CHATBOT}&limit=5&sortField=name&sortOrder=asc`
17079
+ );
16717
17080
  if (!listRes.ok) {
16718
17081
  const errBody = await listRes.json().catch(() => ({}));
16719
17082
  const message = errBody.error ?? `Could not load agents (HTTP ${listRes.status}). Ensure your app uses the latest @infuro/cms-core and your admin user can read entity "llm_agents".`;
@@ -16722,16 +17085,25 @@ function PluginSettingsPanel({
16722
17085
  return { ok: false, message };
16723
17086
  }
16724
17087
  const listJ = await listRes.json();
16725
- if (listJ.data?.length) {
17088
+ const chatRow = listJ.data?.find(
17089
+ (a) => String(a.scope ?? "").trim() === LLM_AGENT_SCOPE_CHATBOT || String(a.slug ?? "").trim() === LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT]
17090
+ );
17091
+ if (chatRow) {
16726
17092
  await fetchLlmAgents();
16727
17093
  return { ok: true };
16728
17094
  }
16729
- const defaultName = botName.trim() || "Assistant";
16730
- const defaultSlug = slugifyAgentKey(defaultName);
17095
+ const defaultName = botName.trim() || LLM_AGENT_DEFAULT_NAME_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
17096
+ const defaultSlug = LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
16731
17097
  const createRes = await fetch("/api/llm_agents", {
16732
17098
  method: "POST",
16733
17099
  headers: { "Content-Type": "application/json" },
16734
- body: JSON.stringify({ name: defaultName, slug: defaultSlug, systemInstruction: "", enabled: true })
17100
+ body: JSON.stringify({
17101
+ name: defaultName,
17102
+ slug: defaultSlug,
17103
+ scope: LLM_AGENT_SCOPE_CHATBOT,
17104
+ systemInstruction: "",
17105
+ enabled: true
17106
+ })
16735
17107
  });
16736
17108
  if (!createRes.ok) {
16737
17109
  const errBody = await createRes.json().catch(() => ({}));
@@ -16757,11 +17129,7 @@ function PluginSettingsPanel({
16757
17129
  }
16758
17130
  };
16759
17131
  const handleCreateAssistantFromForm = async () => {
16760
- const name = agentName.trim() || botName.trim();
16761
- if (!name) {
16762
- import_sonner12.toast.error("Enter an assistant name");
16763
- return;
16764
- }
17132
+ const name = agentName.trim() || botName.trim() || LLM_AGENT_DEFAULT_NAME_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
16765
17133
  const tempRaw = agentTemp.trim();
16766
17134
  const maxRaw = agentMaxTokens.trim();
16767
17135
  const temperature = tempRaw === "" ? null : Number(tempRaw);
@@ -16774,7 +17142,7 @@ function PluginSettingsPanel({
16774
17142
  import_sonner12.toast.error("Max tokens must be a positive integer");
16775
17143
  return;
16776
17144
  }
16777
- const slug = slugifyAgentKey(name);
17145
+ const slug = LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
16778
17146
  setAgentSaving(true);
16779
17147
  try {
16780
17148
  const res = await fetch("/api/llm_agents", {
@@ -16783,6 +17151,7 @@ function PluginSettingsPanel({
16783
17151
  body: JSON.stringify({
16784
17152
  name,
16785
17153
  slug,
17154
+ scope: LLM_AGENT_SCOPE_CHATBOT,
16786
17155
  systemInstruction: agentSystem.trim(),
16787
17156
  model: agentModel.trim() || null,
16788
17157
  temperature,
@@ -16809,18 +17178,19 @@ function PluginSettingsPanel({
16809
17178
  (0, import_react51.useEffect)(() => {
16810
17179
  if (!isLlm || loading || chatMode !== "llm") return;
16811
17180
  setAgentLoading(true);
17181
+ setNotifyEmailLoading(true);
16812
17182
  void (async () => {
16813
17183
  try {
16814
17184
  await bootstrapLlmAgentForPlugins();
17185
+ await bootstrapNotifyEmailAgent();
17186
+ await fetchNotifyEmailAgent();
17187
+ await fetchKbCatalog();
16815
17188
  } finally {
16816
17189
  setAgentLoading(false);
17190
+ setNotifyEmailLoading(false);
16817
17191
  }
16818
17192
  })();
16819
- }, [isLlm, loading, chatMode, bootstrapLlmAgentForPlugins]);
16820
- (0, import_react51.useEffect)(() => {
16821
- if (!isLlm || loading || chatMode !== "llm") return;
16822
- void fetchKbCatalog();
16823
- }, [isLlm, loading, chatMode, fetchKbCatalog]);
17193
+ }, [isLlm, loading, chatMode, bootstrapLlmAgentForPlugins, bootstrapNotifyEmailAgent, fetchNotifyEmailAgent, fetchKbCatalog]);
16824
17194
  (0, import_react51.useEffect)(() => {
16825
17195
  if (!isLlm || loading || chatMode !== "llm" || !attachedAgentSlug.trim()) {
16826
17196
  setAttachedAgentKnowledge([]);
@@ -16911,7 +17281,13 @@ function PluginSettingsPanel({
16911
17281
  payload.iconImageUrl = { value: iconImageUrl, type: "public" };
16912
17282
  payload.iconBackgroundColor = { value: iconBackgroundColor, type: "public" };
16913
17283
  payload.headerColor = { value: headerColor, type: "public" };
16914
- payload.attachedAgentSlug = { value: attachedAgentSlug.trim(), type: "public" };
17284
+ payload.attachedAgentSlug = {
17285
+ value: agentSlug.trim() || attachedAgentSlug.trim() || LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT],
17286
+ type: "public"
17287
+ };
17288
+ payload.emailToolEnabled = { value: emailToolEnabled ? "true" : "false", type: "public" };
17289
+ payload.emailIntents = { value: serializeChatLeadIntents(emailIntents), type: "public" };
17290
+ payload.emailToolPrompt = { value: emailToolPrompt, type: "public" };
16915
17291
  }
16916
17292
  if (isEmail) {
16917
17293
  payload.salesTeamEmails = { value: serializeEmailRecipients(salesTeamEmails), type: "public" };
@@ -16927,65 +17303,169 @@ function PluginSettingsPanel({
16927
17303
  }
16928
17304
  return payload;
16929
17305
  };
16930
- const handleSaveAgent = async () => {
16931
- if (!agentId) {
16932
- import_sonner12.toast.error("No agent to save");
16933
- return;
16934
- }
16935
- const name = agentName.trim();
16936
- if (!name) {
16937
- import_sonner12.toast.error("Agent name is required");
16938
- return;
16939
- }
17306
+ const savePluginSettings = async () => {
17307
+ const res = await fetch(`/api/settings/${settingsGroup}`, {
17308
+ method: "PUT",
17309
+ headers: { "Content-Type": "application/json" },
17310
+ body: JSON.stringify(buildPayload())
17311
+ });
17312
+ return res.ok;
17313
+ };
17314
+ const validateChatAgentNumbers = () => {
16940
17315
  const tempRaw = agentTemp.trim();
16941
17316
  const maxRaw = agentMaxTokens.trim();
16942
17317
  const temperature = tempRaw === "" ? null : Number(tempRaw);
16943
17318
  const maxTokens = maxRaw === "" ? null : parseInt(maxRaw, 10);
16944
17319
  if (tempRaw !== "" && !Number.isFinite(temperature)) {
16945
17320
  import_sonner12.toast.error("Temperature must be a number");
16946
- return;
17321
+ return null;
16947
17322
  }
16948
17323
  if (maxRaw !== "" && (!Number.isFinite(maxTokens) || maxTokens < 1)) {
16949
17324
  import_sonner12.toast.error("Max tokens must be a positive integer");
17325
+ return null;
17326
+ }
17327
+ return { temperature, maxTokens };
17328
+ };
17329
+ const persistChatAgent = async () => {
17330
+ if (!agentId) return false;
17331
+ const nums = validateChatAgentNumbers();
17332
+ if (!nums) return false;
17333
+ const name = agentName.trim() || botName.trim() || LLM_AGENT_DEFAULT_NAME_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
17334
+ const res = await fetch(`/api/llm_agents/${agentId}`, {
17335
+ method: "PUT",
17336
+ headers: { "Content-Type": "application/json" },
17337
+ body: JSON.stringify({
17338
+ name,
17339
+ systemInstruction: agentSystem.trim(),
17340
+ model: agentModel.trim() || null,
17341
+ temperature: nums.temperature,
17342
+ maxTokens: nums.maxTokens,
17343
+ validationRules: agentValidationJson.trim() || null,
17344
+ enabled: true
17345
+ })
17346
+ });
17347
+ if (!res.ok) return false;
17348
+ setAttachedAgentSlug(agentSlug);
17349
+ return true;
17350
+ };
17351
+ const persistNotifyEmailAgent = async (agentIdOverride) => {
17352
+ const id = agentIdOverride ?? notifyEmailAgentId;
17353
+ if (!id) return false;
17354
+ const validationRules = mergeEmailToolIntoValidationRules(null, {
17355
+ classifierInstructions: "",
17356
+ toolPrompt: emailToolPrompt,
17357
+ intents: emailIntents
17358
+ });
17359
+ const res = await fetch(`/api/llm_agents/${id}`, {
17360
+ method: "PUT",
17361
+ headers: { "Content-Type": "application/json" },
17362
+ body: JSON.stringify({
17363
+ name: notifyEmailName.trim() || LLM_AGENT_DEFAULT_NAME_BY_SCOPE[LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT],
17364
+ systemInstruction: notifyEmailSystem.trim(),
17365
+ validationRules,
17366
+ enabled: true
17367
+ })
17368
+ });
17369
+ return res.ok;
17370
+ };
17371
+ const saveLlmPluginSettingsOnly = async () => {
17372
+ const res = await fetch(`/api/settings/${settingsGroup}`, {
17373
+ method: "PUT",
17374
+ headers: { "Content-Type": "application/json" },
17375
+ body: JSON.stringify(buildPayload())
17376
+ });
17377
+ return res.ok;
17378
+ };
17379
+ const handleSaveLlmChatTab = async () => {
17380
+ if (!agentId) {
17381
+ import_sonner12.toast.error("Wait for the assistant to load, or click Create assistant");
16950
17382
  return;
16951
17383
  }
16952
- setAgentSaving(true);
17384
+ if (!validateChatAgentNumbers()) return;
17385
+ setSaving(true);
16953
17386
  try {
16954
- const res = await fetch(`/api/llm_agents/${agentId}`, {
16955
- method: "PUT",
16956
- headers: { "Content-Type": "application/json" },
16957
- body: JSON.stringify({
16958
- name,
16959
- systemInstruction: agentSystem.trim(),
16960
- model: agentModel.trim() || null,
16961
- temperature,
16962
- maxTokens,
16963
- validationRules: agentValidationJson.trim() || null,
16964
- enabled: true
16965
- })
16966
- });
16967
- if (!res.ok) {
16968
- const err = await res.json().catch(() => ({}));
16969
- import_sonner12.toast.error(err.error || "Failed to update agent");
17387
+ const chatOk = await persistChatAgent();
17388
+ if (!chatOk) {
17389
+ import_sonner12.toast.error("Failed to save assistant");
17390
+ return;
17391
+ }
17392
+ const settingsOk = await saveLlmPluginSettingsOnly();
17393
+ if (!settingsOk) {
17394
+ import_sonner12.toast.error("Failed to save widget settings");
16970
17395
  return;
16971
17396
  }
16972
- setAttachedAgentSlug(agentSlug);
16973
- import_sonner12.toast.success("Agent saved");
17397
+ import_sonner12.toast.success("Chat settings saved");
16974
17398
  } catch {
16975
- import_sonner12.toast.error("Failed to update agent");
17399
+ import_sonner12.toast.error("Failed to save");
16976
17400
  } finally {
16977
- setAgentSaving(false);
17401
+ setSaving(false);
17402
+ }
17403
+ };
17404
+ const handleSaveLlmNotifyTab = async () => {
17405
+ if (!emailPluginAvailable) {
17406
+ import_sonner12.toast.error("Enable the Email plugin (SMTP) first");
17407
+ return;
17408
+ }
17409
+ const filledIntents = emailIntents.filter(
17410
+ (r) => r.intent.trim() && r.description.trim() && r.emailTo.trim()
17411
+ );
17412
+ if (filledIntents.length === 0) {
17413
+ import_sonner12.toast.error("Add at least one intent with Intent, Description, and Email To");
17414
+ return;
17415
+ }
17416
+ setEmailIntents(filledIntents);
17417
+ setNotifyEmailSaving(true);
17418
+ try {
17419
+ let notifyId = notifyEmailAgentId;
17420
+ if (!notifyId) {
17421
+ await bootstrapNotifyEmailAgent();
17422
+ const nr = await fetch(
17423
+ `/api/llm_agents?scope=${LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT}&limit=1`
17424
+ );
17425
+ if (nr.ok) {
17426
+ const nj = await nr.json();
17427
+ const row = nj.data?.find(
17428
+ (r) => String(r.scope ?? "").trim() === LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT
17429
+ );
17430
+ if (row) {
17431
+ notifyId = row.id;
17432
+ setNotifyEmailAgentId(row.id);
17433
+ }
17434
+ }
17435
+ }
17436
+ if (!notifyId) {
17437
+ import_sonner12.toast.error("Could not set up lead email \u2014 try again");
17438
+ return;
17439
+ }
17440
+ setEmailToolEnabled(true);
17441
+ const notifyOk = await persistNotifyEmailAgent(notifyId);
17442
+ if (!notifyOk) {
17443
+ import_sonner12.toast.error("Failed to save lead email settings");
17444
+ return;
17445
+ }
17446
+ const settingsOk = await saveLlmPluginSettingsOnly();
17447
+ if (!settingsOk) {
17448
+ import_sonner12.toast.warning("Routing saved, but enable flag failed to save in settings");
17449
+ return;
17450
+ }
17451
+ import_sonner12.toast.success("Lead email routing saved");
17452
+ await fetchNotifyEmailAgent();
17453
+ } catch {
17454
+ import_sonner12.toast.error("Failed to save");
17455
+ } finally {
17456
+ setNotifyEmailSaving(false);
16978
17457
  }
16979
17458
  };
16980
17459
  const handleSave = async () => {
17460
+ if (isLlm && chatMode === "llm") {
17461
+ if (llmConfigTab === "notify") await handleSaveLlmNotifyTab();
17462
+ else await handleSaveLlmChatTab();
17463
+ return;
17464
+ }
16981
17465
  setSaving(true);
16982
17466
  try {
16983
- const res = await fetch(`/api/settings/${settingsGroup}`, {
16984
- method: "PUT",
16985
- headers: { "Content-Type": "application/json" },
16986
- body: JSON.stringify(buildPayload())
16987
- });
16988
- if (!res.ok) throw new Error();
17467
+ const ok = await savePluginSettings();
17468
+ if (!ok) throw new Error();
16989
17469
  if (isSocialMedia && (linkedinAccessToken.trim() || linkedinHasSavedToken)) {
16990
17470
  if (linkedinAccessToken.trim()) {
16991
17471
  setLinkedinHasSavedToken(true);
@@ -17907,328 +18387,458 @@ function PluginSettingsPanel({
17907
18387
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Only paste code from sources you trust." })
17908
18388
  ] }),
17909
18389
  chatMode === "llm" && /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [
17910
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "rounded-lg border border-gray-200 dark:border-gray-600 bg-gray-50/80 dark:bg-gray-800/40 p-3 space-y-3", children: [
17911
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
17912
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2 text-sm font-medium text-gray-900 dark:text-white", children: [
17913
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Bot, { className: "h-4 w-4" }),
17914
- "Chat assistant (single agent)"
17915
- ] }),
17916
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-[11px] text-gray-500 dark:text-gray-400", children: "Configure one assistant for this site here. After it exists, attach knowledge files in the section below." })
17917
- ] }),
17918
- agentLoading ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2 text-sm text-gray-500", children: [
17919
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-4 w-4 animate-spin" }),
17920
- "Loading assistant\u2026"
17921
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [
17922
- agentProvisionError ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "rounded border border-amber-200 bg-amber-50 p-2 text-xs text-amber-900 dark:border-amber-800 dark:bg-amber-950/40 dark:text-amber-100", children: agentProvisionError }) : null,
18390
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "inline-flex rounded-lg border border-gray-200 bg-gray-50 p-0.5 dark:border-gray-600 dark:bg-gray-800/80", children: [
18391
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
18392
+ "button",
18393
+ {
18394
+ type: "button",
18395
+ className: `flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors ${llmConfigTab === "chat" ? "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"}`,
18396
+ onClick: () => setLlmConfigTab("chat"),
18397
+ children: [
18398
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.MessageCircle, { className: "h-3.5 w-3.5 shrink-0" }),
18399
+ "Chat"
18400
+ ]
18401
+ }
18402
+ ),
18403
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
18404
+ "button",
18405
+ {
18406
+ type: "button",
18407
+ className: `flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors ${llmConfigTab === "notify" ? "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"}`,
18408
+ onClick: () => setLlmConfigTab("notify"),
18409
+ children: [
18410
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Mail, { className: "h-3.5 w-3.5 shrink-0" }),
18411
+ "Notify"
18412
+ ]
18413
+ }
18414
+ )
18415
+ ] }),
18416
+ llmConfigTab === "chat" ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [
18417
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "rounded-lg border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-900/40 p-3 space-y-3", children: [
18418
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-sm font-medium text-gray-900 dark:text-white", children: "Widget on your site" }),
18419
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-[11px] text-gray-500 dark:text-gray-400", children: "Title, icon, and colors visitors see on the chat bubble." }),
17923
18420
  /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
17924
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-name", className: "text-sm", children: "Assistant name" }),
18421
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-botName`, className: "text-sm", children: "Widget title" }),
17925
18422
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
17926
18423
  Input,
17927
18424
  {
17928
- id: "agent-name",
17929
- value: agentName,
17930
- onChange: (e) => setAgentName(e.target.value),
18425
+ id: `${settingsGroup}-botName`,
18426
+ value: botName,
18427
+ onChange: (e) => setBotName(e.target.value),
17931
18428
  placeholder: "e.g. JM Buddy",
17932
18429
  className: "h-8 text-sm"
17933
18430
  }
17934
18431
  )
17935
18432
  ] }),
17936
18433
  /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
17937
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-slug", className: "text-sm", children: "Slug" }),
18434
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-iconImageUrl`, className: "text-sm", children: "Icon image URL" }),
17938
18435
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
17939
18436
  Input,
17940
18437
  {
17941
- id: "agent-slug",
17942
- value: agentId ? agentSlug : slugifyAgentKey((agentName || botName).trim() || "assistant"),
17943
- disabled: true,
17944
- className: "h-8 text-sm font-mono bg-gray-100 dark:bg-gray-700"
18438
+ id: `${settingsGroup}-iconImageUrl`,
18439
+ value: iconImageUrl,
18440
+ onChange: (e) => setIconImageUrl(e.target.value),
18441
+ placeholder: "https://\u2026 or /images/chat-icon.png",
18442
+ className: "h-8 text-sm"
17945
18443
  }
17946
18444
  ),
17947
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-[11px] text-gray-500", children: "Derived from the name. Used in API routes." })
18445
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "PNG or image URL. Leave empty to use emoji below." })
17948
18446
  ] }),
17949
18447
  /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
17950
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-system", className: "text-sm", children: "System instruction" }),
18448
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-icon`, className: "text-sm", children: "Icon fallback (emoji)" }),
17951
18449
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
17952
- Textarea,
18450
+ Input,
17953
18451
  {
17954
- id: "agent-system",
17955
- value: agentSystem,
17956
- onChange: (e) => setAgentSystem(e.target.value),
17957
- rows: 5,
17958
- placeholder: "How the model should behave\u2026",
17959
- className: "text-sm"
18452
+ id: `${settingsGroup}-icon`,
18453
+ value: icon,
18454
+ onChange: (e) => setIcon(e.target.value),
18455
+ placeholder: "e.g. \u{1F4AC}",
18456
+ className: "h-8 text-sm w-20"
17960
18457
  }
17961
18458
  )
17962
18459
  ] }),
17963
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
18460
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex gap-4", children: [
18461
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18462
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-iconBg`, className: "text-sm", children: "Icon background" }),
18463
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2", children: [
18464
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18465
+ "input",
18466
+ {
18467
+ type: "color",
18468
+ id: `${settingsGroup}-iconBg`,
18469
+ value: iconBackgroundColor,
18470
+ onChange: (e) => setIconBackgroundColor(e.target.value),
18471
+ className: "h-8 w-10 cursor-pointer rounded border border-gray-300 dark:border-gray-600"
18472
+ }
18473
+ ),
18474
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18475
+ Input,
18476
+ {
18477
+ value: iconBackgroundColor,
18478
+ onChange: (e) => setIconBackgroundColor(e.target.value),
18479
+ className: "h-8 w-24 text-sm font-mono"
18480
+ }
18481
+ )
18482
+ ] })
18483
+ ] }),
17964
18484
  /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
17965
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-model", className: "text-sm", children: "Model (optional)" }),
18485
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-headerColor`, className: "text-sm", children: "Header color" }),
18486
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2", children: [
18487
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18488
+ "input",
18489
+ {
18490
+ type: "color",
18491
+ id: `${settingsGroup}-headerColor`,
18492
+ value: headerColor,
18493
+ onChange: (e) => setHeaderColor(e.target.value),
18494
+ className: "h-8 w-10 cursor-pointer rounded border border-gray-300 dark:border-gray-600"
18495
+ }
18496
+ ),
18497
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18498
+ Input,
18499
+ {
18500
+ value: headerColor,
18501
+ onChange: (e) => setHeaderColor(e.target.value),
18502
+ className: "h-8 w-24 text-sm font-mono"
18503
+ }
18504
+ )
18505
+ ] })
18506
+ ] })
18507
+ ] })
18508
+ ] }),
18509
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "rounded-lg border border-gray-200 dark:border-gray-600 bg-gray-50/80 dark:bg-gray-800/40 p-3 space-y-4", children: [
18510
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18511
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2 text-sm font-medium text-gray-900 dark:text-white", children: [
18512
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Bot, { className: "h-4 w-4" }),
18513
+ "Assistant"
18514
+ ] }),
18515
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-[11px] text-gray-500 dark:text-gray-400", children: "Storefront assistant \u2014 model, instructions, and guardrails." })
18516
+ ] }),
18517
+ agentLoading ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2 text-sm text-gray-500", children: [
18518
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-4 w-4 animate-spin" }),
18519
+ "Loading\u2026"
18520
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [
18521
+ agentProvisionError ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "rounded border border-amber-200 bg-amber-50 p-2 text-xs text-amber-900 dark:border-amber-800 dark:bg-amber-950/40 dark:text-amber-100", children: agentProvisionError }) : null,
18522
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18523
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-system", className: "text-sm", children: "System instruction" }),
17966
18524
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
17967
- Input,
18525
+ Textarea,
17968
18526
  {
17969
- id: "agent-model",
17970
- value: agentModel,
17971
- onChange: (e) => setAgentModel(e.target.value),
17972
- placeholder: "Gateway model id",
17973
- className: "h-8 text-sm font-mono"
18527
+ id: "agent-system",
18528
+ value: agentSystem,
18529
+ onChange: (e) => setAgentSystem(e.target.value),
18530
+ rows: 5,
18531
+ placeholder: "How the model should behave\u2026",
18532
+ className: "text-sm"
17974
18533
  }
17975
18534
  )
17976
18535
  ] }),
18536
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
18537
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18538
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-model", className: "text-sm", children: "Model (optional)" }),
18539
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18540
+ Input,
18541
+ {
18542
+ id: "agent-model",
18543
+ value: agentModel,
18544
+ onChange: (e) => setAgentModel(e.target.value),
18545
+ placeholder: "Gateway model id",
18546
+ className: "h-8 text-sm font-mono"
18547
+ }
18548
+ )
18549
+ ] }),
18550
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18551
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-temp", className: "text-sm", children: "Temperature" }),
18552
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18553
+ Input,
18554
+ {
18555
+ id: "agent-temp",
18556
+ value: agentTemp,
18557
+ onChange: (e) => setAgentTemp(e.target.value),
18558
+ placeholder: "e.g. 0.7",
18559
+ className: "h-8 text-sm"
18560
+ }
18561
+ )
18562
+ ] })
18563
+ ] }),
17977
18564
  /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
17978
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-temp", className: "text-sm", children: "Temperature" }),
18565
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-max", className: "text-sm", children: "Max tokens" }),
17979
18566
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
17980
18567
  Input,
17981
18568
  {
17982
- id: "agent-temp",
17983
- value: agentTemp,
17984
- onChange: (e) => setAgentTemp(e.target.value),
17985
- placeholder: "e.g. 0.7",
18569
+ id: "agent-max",
18570
+ value: agentMaxTokens,
18571
+ onChange: (e) => setAgentMaxTokens(e.target.value.replace(/\D/g, "")),
18572
+ placeholder: "e.g. 1024",
17986
18573
  className: "h-8 text-sm"
17987
18574
  }
17988
18575
  )
17989
- ] })
17990
- ] }),
17991
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
17992
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-max", className: "text-sm", children: "Max tokens" }),
17993
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
17994
- Input,
17995
- {
17996
- id: "agent-max",
17997
- value: agentMaxTokens,
17998
- onChange: (e) => setAgentMaxTokens(e.target.value.replace(/\D/g, "")),
17999
- placeholder: "e.g. 1024",
18000
- className: "h-8 text-sm"
18001
- }
18002
- )
18576
+ ] }),
18577
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18578
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-validation", className: "text-sm", children: "Validation & output guardrails" }),
18579
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18580
+ Textarea,
18581
+ {
18582
+ id: "agent-validation",
18583
+ value: agentValidationJson,
18584
+ onChange: (e) => setAgentValidationJson(e.target.value),
18585
+ rows: 4,
18586
+ placeholder: 'Plain text or JSON: {"guardrails":"Never promise refunds.","maxUserChars":2000}',
18587
+ className: "text-xs font-mono"
18588
+ }
18589
+ )
18590
+ ] }),
18591
+ !agentId ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
18592
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18593
+ Button,
18594
+ {
18595
+ type: "button",
18596
+ size: "sm",
18597
+ className: "gap-1",
18598
+ disabled: agentSaving,
18599
+ onClick: () => void handleCreateAssistantFromForm(),
18600
+ children: agentSaving ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { className: "inline-flex items-center gap-1.5", children: [
18601
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
18602
+ "Creating\u2026"
18603
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [
18604
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Bot, { className: "h-3.5 w-3.5" }),
18605
+ "Create assistant"
18606
+ ] })
18607
+ }
18608
+ ),
18609
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18610
+ Button,
18611
+ {
18612
+ type: "button",
18613
+ size: "sm",
18614
+ variant: "secondary",
18615
+ className: "gap-1",
18616
+ disabled: agentSaving || agentLoading,
18617
+ onClick: () => void handleRetryBootstrapAgent(),
18618
+ children: "Retry auto-setup"
18619
+ }
18620
+ )
18621
+ ] }) : null
18003
18622
  ] }),
18623
+ agentId && agentSlug.trim() ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "rounded-md border border-dashed border-gray-300 dark:border-gray-600 bg-white/60 dark:bg-gray-900/30 p-3 space-y-3", children: [
18624
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2 text-xs font-medium text-gray-800 dark:text-gray-200", children: [
18625
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.FileUp, { className: "h-3.5 w-3.5 shrink-0" }),
18626
+ "Knowledge for this agent"
18627
+ ] }),
18628
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Upload text (.txt, .md, .json) or PDF (.pdf), or link documents already in the knowledge base." }),
18629
+ attachedKbLoading ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Loading linked documents\u2026" }) : attachedAgentKnowledge.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "No documents linked yet." }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("ul", { className: "max-h-32 space-y-1.5 overflow-y-auto", children: attachedAgentKnowledge.map((d) => /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("li", { className: "flex items-center justify-between gap-2 text-sm", children: [
18630
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { className: "min-w-0 truncate", title: d.name, children: d.name }),
18631
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18632
+ Button,
18633
+ {
18634
+ type: "button",
18635
+ variant: "ghost",
18636
+ size: "sm",
18637
+ className: "h-7 shrink-0 text-xs text-red-600 hover:text-red-700 dark:text-red-400",
18638
+ onClick: () => void handleUnlinkKbDoc(d.id),
18639
+ children: "Remove"
18640
+ }
18641
+ )
18642
+ ] }, d.id)) }),
18643
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "min-w-[180px] space-y-1", children: [
18644
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { className: "text-xs", children: "Upload file" }),
18645
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "relative", children: [
18646
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18647
+ Input,
18648
+ {
18649
+ type: "file",
18650
+ accept: ".txt,.md,.json,.pdf,text/plain,text/markdown,application/json,application/pdf",
18651
+ disabled: uploadingAttachedKbFile || uploadingAttachedKbLink,
18652
+ className: "h-8 cursor-pointer text-xs disabled:opacity-60",
18653
+ onChange: onAttachedKbFileChange
18654
+ },
18655
+ attachedKbInputKey
18656
+ ),
18657
+ uploadingAttachedKbFile ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
18658
+ "div",
18659
+ {
18660
+ className: "pointer-events-none absolute inset-0 flex items-center justify-center gap-2 rounded-md bg-background/85 text-xs font-medium text-gray-700 dark:text-gray-200",
18661
+ "aria-live": "polite",
18662
+ children: [
18663
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-4 w-4 shrink-0 animate-spin" }),
18664
+ "Saving & linking\u2026"
18665
+ ]
18666
+ }
18667
+ ) : null
18668
+ ] }),
18669
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-[11px] text-gray-500 dark:text-gray-400", children: "Pick a file to upload immediately (chunking, embeddings if configured, then link to this agent)." })
18670
+ ] }),
18671
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex flex-wrap items-end gap-2", children: [
18672
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "min-w-[200px] flex-1 space-y-1", children: [
18673
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { className: "text-xs", children: "Attach existing document" }),
18674
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(Select, { value: attachExistingDocId, onValueChange: setAttachExistingDocId, children: [
18675
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(SelectTrigger, { className: "h-8 text-xs", children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(SelectValue, { placeholder: "Choose a document" }) }),
18676
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(SelectContent, { children: [
18677
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(SelectItem, { value: "__none__", children: "\u2014 Select \u2014" }),
18678
+ kbCatalog.filter((d) => !attachedAgentKnowledge.some((a) => a.id === d.id)).map((d) => /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(SelectItem, { value: String(d.id), children: d.name }, d.id))
18679
+ ] })
18680
+ ] })
18681
+ ] }),
18682
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18683
+ Button,
18684
+ {
18685
+ type: "button",
18686
+ size: "sm",
18687
+ className: "h-8",
18688
+ disabled: uploadingAttachedKbFile || uploadingAttachedKbLink || attachExistingDocId === "__none__",
18689
+ onClick: () => void handleAttachExistingToAttached(),
18690
+ children: uploadingAttachedKbLink ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { className: "inline-flex items-center gap-1.5", children: [
18691
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
18692
+ "Linking\u2026"
18693
+ ] }) : "Attach"
18694
+ }
18695
+ )
18696
+ ] })
18697
+ ] }) : null
18698
+ ] }),
18699
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
18700
+ Button,
18701
+ {
18702
+ type: "button",
18703
+ size: "sm",
18704
+ onClick: () => void handleSaveLlmChatTab(),
18705
+ disabled: saving,
18706
+ className: "gap-1",
18707
+ children: [
18708
+ saving ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Save, { className: "h-3.5 w-3.5" }),
18709
+ "Save"
18710
+ ]
18711
+ }
18712
+ )
18713
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "rounded-lg border border-gray-200 dark:border-gray-600 bg-gray-50/80 dark:bg-gray-800/40 p-3 space-y-4", children: [
18714
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { children: [
18715
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-sm font-medium text-gray-900 dark:text-white", children: "Lead email routing" }),
18716
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "mt-0.5 text-[11px] text-gray-500 dark:text-gray-400", children: "Routing instructions and intents. When a message matches, your team gets one email per conversation." })
18717
+ ] }),
18718
+ !emailPluginAvailable ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "rounded border border-amber-200 bg-amber-50/80 p-2 text-[11px] text-amber-700 dark:border-amber-800 dark:bg-amber-950/40 dark:text-amber-300", children: "Enable the Email plugin (SMTP) to send lead alert emails." }) : notifyEmailLoading ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2 text-sm text-gray-500", children: [
18719
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-4 w-4 animate-spin" }),
18720
+ "Loading\u2026"
18721
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [
18722
+ notifyEmailProvisionError ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "rounded border border-amber-200 bg-amber-50 p-2 text-xs text-amber-900 dark:border-amber-800 dark:bg-amber-950/40 dark:text-amber-100", children: notifyEmailProvisionError }) : null,
18004
18723
  /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18005
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-validation", className: "text-sm", children: "Validation & output guardrails" }),
18724
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "notify-email-system", className: "text-sm", children: "Routing instructions" }),
18006
18725
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18007
18726
  Textarea,
18008
18727
  {
18009
- id: "agent-validation",
18010
- value: agentValidationJson,
18011
- onChange: (e) => setAgentValidationJson(e.target.value),
18728
+ id: "notify-email-system",
18729
+ value: notifyEmailSystem,
18730
+ onChange: (e) => setNotifyEmailSystem(e.target.value),
18012
18731
  rows: 4,
18013
- placeholder: 'Plain text or JSON: {"guardrails":"Never promise refunds.","maxUserChars":2000}',
18014
- className: "text-xs font-mono"
18015
- }
18016
- )
18017
- ] }),
18018
- !agentId ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
18019
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18020
- Button,
18021
- {
18022
- type: "button",
18023
- size: "sm",
18024
- className: "gap-1",
18025
- disabled: agentSaving,
18026
- onClick: () => void handleCreateAssistantFromForm(),
18027
- children: agentSaving ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { className: "inline-flex items-center gap-1.5", children: [
18028
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
18029
- "Creating\u2026"
18030
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [
18031
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Bot, { className: "h-3.5 w-3.5" }),
18032
- "Create assistant"
18033
- ] })
18034
- }
18035
- ),
18036
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18037
- Button,
18038
- {
18039
- type: "button",
18040
- size: "sm",
18041
- variant: "secondary",
18042
- className: "gap-1",
18043
- disabled: agentSaving || agentLoading,
18044
- onClick: () => void handleRetryBootstrapAgent(),
18045
- children: "Retry auto-setup"
18732
+ placeholder: "When to treat a message as a lead, priority, when not to email\u2026",
18733
+ className: "text-sm"
18046
18734
  }
18047
18735
  )
18048
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18049
- Button,
18050
- {
18051
- type: "button",
18052
- size: "sm",
18053
- className: "gap-1",
18054
- disabled: agentSaving,
18055
- onClick: () => void handleSaveAgent(),
18056
- children: agentSaving ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { className: "inline-flex items-center gap-1.5", children: [
18057
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
18058
- "Saving\u2026"
18059
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [
18060
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Save, { className: "h-3.5 w-3.5" }),
18061
- "Save assistant"
18062
- ] })
18063
- }
18064
- )
18065
- ] }),
18066
- agentId && agentSlug.trim() ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "rounded-md border border-dashed border-gray-300 dark:border-gray-600 bg-white/60 dark:bg-gray-900/30 p-3 space-y-3", children: [
18067
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2 text-xs font-medium text-gray-800 dark:text-gray-200", children: [
18068
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.FileUp, { className: "h-3.5 w-3.5 shrink-0" }),
18069
- "Knowledge for this agent"
18070
18736
  ] }),
18071
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Upload text (.txt, .md, .json) or PDF (.pdf), or link documents already in the knowledge base." }),
18072
- attachedKbLoading ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Loading linked documents\u2026" }) : attachedAgentKnowledge.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "No documents linked yet." }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("ul", { className: "max-h-32 space-y-1.5 overflow-y-auto", children: attachedAgentKnowledge.map((d) => /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("li", { className: "flex items-center justify-between gap-2 text-sm", children: [
18073
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { className: "min-w-0 truncate", title: d.name, children: d.name }),
18074
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18075
- Button,
18076
- {
18077
- type: "button",
18078
- variant: "ghost",
18079
- size: "sm",
18080
- className: "h-7 shrink-0 text-xs text-red-600 hover:text-red-700 dark:text-red-400",
18081
- onClick: () => void handleUnlinkKbDoc(d.id),
18082
- children: "Remove"
18083
- }
18084
- )
18085
- ] }, d.id)) }),
18086
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "min-w-[180px] space-y-1", children: [
18087
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { className: "text-xs", children: "Upload file" }),
18088
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "relative", children: [
18089
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18090
- Input,
18091
- {
18092
- type: "file",
18093
- accept: ".txt,.md,.json,.pdf,text/plain,text/markdown,application/json,application/pdf",
18094
- disabled: uploadingAttachedKbFile || uploadingAttachedKbLink,
18095
- className: "h-8 cursor-pointer text-xs disabled:opacity-60",
18096
- onChange: onAttachedKbFileChange
18097
- },
18098
- attachedKbInputKey
18099
- ),
18100
- uploadingAttachedKbFile ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
18101
- "div",
18737
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-2", children: [
18738
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
18739
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { className: "text-sm", children: "Intents" }),
18740
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
18741
+ Button,
18102
18742
  {
18103
- className: "pointer-events-none absolute inset-0 flex items-center justify-center gap-2 rounded-md bg-background/85 text-xs font-medium text-gray-700 dark:text-gray-200",
18104
- "aria-live": "polite",
18743
+ type: "button",
18744
+ size: "sm",
18745
+ variant: "outline",
18746
+ className: "h-7 gap-1 text-xs",
18747
+ onClick: () => setEmailIntents((rows) => [
18748
+ ...rows,
18749
+ { intent: "", description: "", emailTo: "" }
18750
+ ]),
18105
18751
  children: [
18106
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-4 w-4 shrink-0 animate-spin" }),
18107
- "Saving & linking\u2026"
18752
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Plus, { className: "h-3 w-3" }),
18753
+ "Add intent"
18108
18754
  ]
18109
18755
  }
18110
- ) : null
18756
+ )
18111
18757
  ] }),
18112
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-[11px] text-gray-500 dark:text-gray-400", children: "Pick a file to upload immediately (chunking, embeddings if configured, then link to this agent)." })
18113
- ] }),
18114
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex flex-wrap items-end gap-2", children: [
18115
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "min-w-[200px] flex-1 space-y-1", children: [
18116
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { className: "text-xs", children: "Attach existing document" }),
18117
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(Select, { value: attachExistingDocId, onValueChange: setAttachExistingDocId, children: [
18118
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(SelectTrigger, { className: "h-8 text-xs", children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(SelectValue, { placeholder: "Choose a document" }) }),
18119
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(SelectContent, { children: [
18120
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(SelectItem, { value: "__none__", children: "\u2014 Select \u2014" }),
18121
- kbCatalog.filter((d) => !attachedAgentKnowledge.some((a) => a.id === d.id)).map((d) => /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(SelectItem, { value: String(d.id), children: d.name }, d.id))
18122
- ] })
18758
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("div", { className: "overflow-x-auto rounded border border-gray-200 dark:border-gray-600", children: /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("table", { className: "w-full text-xs", children: [
18759
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("tr", { className: "bg-gray-100/80 text-left dark:bg-gray-900/60", children: [
18760
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("th", { className: "w-[28%] p-2 font-medium", children: "Intent" }),
18761
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("th", { className: "w-[42%] p-2 font-medium", children: "Description" }),
18762
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("th", { className: "w-[26%] p-2 font-medium", children: "Email To" }),
18763
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("th", { className: "w-8 p-2" })
18764
+ ] }) }),
18765
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("tbody", { children: [
18766
+ emailIntents.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("td", { colSpan: 4, className: "p-4 text-center text-gray-500 dark:text-gray-400", children: "Add at least one intent, then Save." }) }) : null,
18767
+ emailIntents.map((row, idx) => /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("tr", { className: "border-t border-gray-200 dark:border-gray-700", children: [
18768
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("td", { className: "p-1.5 align-top", children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18769
+ Input,
18770
+ {
18771
+ value: row.intent,
18772
+ onChange: (e) => {
18773
+ const v = e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, "");
18774
+ setEmailIntents(
18775
+ (rows) => rows.map((r, i) => i === idx ? { ...r, intent: v } : r)
18776
+ );
18777
+ },
18778
+ placeholder: "SALES_LEAD",
18779
+ className: "h-8 font-mono text-xs"
18780
+ }
18781
+ ) }),
18782
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("td", { className: "p-1.5 align-top", children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18783
+ Input,
18784
+ {
18785
+ value: row.description,
18786
+ onChange: (e) => setEmailIntents(
18787
+ (rows) => rows.map(
18788
+ (r, i) => i === idx ? { ...r, description: e.target.value } : r
18789
+ )
18790
+ ),
18791
+ placeholder: "What this intent means",
18792
+ className: "h-8 text-xs"
18793
+ }
18794
+ ) }),
18795
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("td", { className: "p-1.5 align-top", children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18796
+ Input,
18797
+ {
18798
+ value: row.emailTo,
18799
+ onChange: (e) => setEmailIntents(
18800
+ (rows) => rows.map(
18801
+ (r, i) => i === idx ? { ...r, emailTo: e.target.value } : r
18802
+ )
18803
+ ),
18804
+ placeholder: "team@company.com",
18805
+ className: "h-8 text-xs"
18806
+ }
18807
+ ) }),
18808
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("td", { className: "p-1.5 align-top", children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18809
+ Button,
18810
+ {
18811
+ type: "button",
18812
+ size: "icon",
18813
+ variant: "ghost",
18814
+ className: "h-8 w-8 text-red-600",
18815
+ onClick: () => setEmailIntents((rows) => rows.filter((_, i) => i !== idx)),
18816
+ "aria-label": "Remove intent",
18817
+ children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Trash2, { className: "h-3.5 w-3.5" })
18818
+ }
18819
+ ) })
18820
+ ] }, idx))
18123
18821
  ] })
18124
- ] }),
18125
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18126
- Button,
18127
- {
18128
- type: "button",
18129
- size: "sm",
18130
- className: "h-8",
18131
- disabled: uploadingAttachedKbFile || uploadingAttachedKbLink || attachExistingDocId === "__none__",
18132
- onClick: () => void handleAttachExistingToAttached(),
18133
- children: uploadingAttachedKbLink ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { className: "inline-flex items-center gap-1.5", children: [
18134
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
18135
- "Linking\u2026"
18136
- ] }) : "Attach"
18137
- }
18138
- )
18822
+ ] }) })
18139
18823
  ] })
18140
- ] }) : null
18141
- ] }),
18142
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18143
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-botName`, className: "text-sm", children: "Widget title" }),
18144
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18145
- Input,
18146
- {
18147
- id: `${settingsGroup}-botName`,
18148
- value: botName,
18149
- onChange: (e) => setBotName(e.target.value),
18150
- placeholder: "e.g. Support Bot",
18151
- className: "h-8 text-sm"
18152
- }
18153
- )
18154
- ] }),
18155
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18156
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-iconImageUrl`, className: "text-sm", children: "Icon image URL" }),
18157
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18158
- Input,
18159
- {
18160
- id: `${settingsGroup}-iconImageUrl`,
18161
- value: iconImageUrl,
18162
- onChange: (e) => setIconImageUrl(e.target.value),
18163
- placeholder: "https://\u2026 or /images/chat-icon.png",
18164
- className: "h-8 text-sm"
18165
- }
18166
- ),
18167
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "PNG or image URL. Leave empty to use emoji below." })
18168
- ] }),
18169
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18170
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-icon`, className: "text-sm", children: "Icon fallback (emoji)" }),
18171
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18172
- Input,
18824
+ ] }),
18825
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
18826
+ Button,
18173
18827
  {
18174
- id: `${settingsGroup}-icon`,
18175
- value: icon,
18176
- onChange: (e) => setIcon(e.target.value),
18177
- placeholder: "e.g. \u{1F4AC}",
18178
- className: "h-8 text-sm w-20"
18828
+ type: "button",
18829
+ size: "sm",
18830
+ onClick: () => void handleSaveLlmNotifyTab(),
18831
+ disabled: notifyEmailSaving || !emailPluginAvailable,
18832
+ className: "gap-1",
18833
+ children: [
18834
+ notifyEmailSaving ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Save, { className: "h-3.5 w-3.5" }),
18835
+ "Save"
18836
+ ]
18179
18837
  }
18180
18838
  )
18181
- ] }),
18182
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex gap-4", children: [
18183
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18184
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-iconBg`, className: "text-sm", children: "Icon background" }),
18185
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2", children: [
18186
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18187
- "input",
18188
- {
18189
- type: "color",
18190
- id: `${settingsGroup}-iconBg`,
18191
- value: iconBackgroundColor,
18192
- onChange: (e) => setIconBackgroundColor(e.target.value),
18193
- className: "h-8 w-10 cursor-pointer rounded border border-gray-300 dark:border-gray-600"
18194
- }
18195
- ),
18196
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18197
- Input,
18198
- {
18199
- value: iconBackgroundColor,
18200
- onChange: (e) => setIconBackgroundColor(e.target.value),
18201
- className: "h-8 w-24 text-sm font-mono"
18202
- }
18203
- )
18204
- ] })
18205
- ] }),
18206
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18207
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-headerColor`, className: "text-sm", children: "Header color" }),
18208
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2", children: [
18209
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18210
- "input",
18211
- {
18212
- type: "color",
18213
- id: `${settingsGroup}-headerColor`,
18214
- value: headerColor,
18215
- onChange: (e) => setHeaderColor(e.target.value),
18216
- className: "h-8 w-10 cursor-pointer rounded border border-gray-300 dark:border-gray-600"
18217
- }
18218
- ),
18219
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18220
- Input,
18221
- {
18222
- value: headerColor,
18223
- onChange: (e) => setHeaderColor(e.target.value),
18224
- className: "h-8 w-24 text-sm font-mono"
18225
- }
18226
- )
18227
- ] })
18228
- ] })
18229
18839
  ] })
18230
18840
  ] }),
18231
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(Button, { size: "sm", onClick: handleSave, disabled: saving, className: "gap-1", children: [
18841
+ chatMode !== "llm" && /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(Button, { size: "sm", onClick: handleSave, disabled: saving, className: "gap-1", children: [
18232
18842
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Save, { className: "h-3.5 w-3.5" }),
18233
18843
  "Save"
18234
18844
  ] })
@@ -19195,12 +19805,10 @@ function ProductEditPage({ productId }) {
19195
19805
  setSaving(true);
19196
19806
  try {
19197
19807
  const activeCurrency = String(currency || "INR");
19198
- console.log("DEBUG save:", { activeCurrency, price, compareAtPrice });
19199
19808
  let saveRate = 1;
19200
19809
  if (activeCurrency !== "INR") {
19201
19810
  const displayRate = await fetchDisplayRate(activeCurrency);
19202
19811
  saveRate = displayRate !== 0 && displayRate !== 1 ? 1 / displayRate : 1;
19203
- console.log("DEBUG rate:", { displayRate, saveRate, priceInBase: Math.round(Number(price) * saveRate * 100) / 100 });
19204
19812
  }
19205
19813
  const priceInBase = Math.round(Number(price) * saveRate * 100) / 100;
19206
19814
  const compareAtPriceInBase = compareAtPrice ? Math.round(Number(compareAtPrice) * saveRate * 100) / 100 : null;
@@ -20497,8 +21105,12 @@ function SchedulesPage() {
20497
21105
 
20498
21106
  // src/admin/pages/VendorOnboardPage.tsx
20499
21107
  var import_react59 = require("react");
21108
+ var import_navigation27 = require("next/navigation");
20500
21109
  var import_react60 = require("next-auth/react");
21110
+ var import_lucide_react46 = require("lucide-react");
20501
21111
  init_vendor_scope();
21112
+ init_DetailPageHeader();
21113
+ init_DetailPageLayout();
20502
21114
  init_button();
20503
21115
  init_input();
20504
21116
 
@@ -20537,10 +21149,39 @@ RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
20537
21149
 
20538
21150
  // src/admin/pages/VendorOnboardPage.tsx
20539
21151
  var import_jsx_runtime76 = require("react/jsx-runtime");
20540
- function VendorOnboardPage() {
20541
- const { data: session } = (0, import_react60.useSession)();
20542
- const sessionUser = session?.user;
20543
- const isAdmin = canOnboardVendors(session?.user);
21152
+ var VENDOR_LIST_COLUMNS = [
21153
+ { field: "name", displayName: "Name" },
21154
+ { field: "slug", displayName: "Slug" },
21155
+ { field: "email", displayName: "Email" },
21156
+ { field: "phone", displayName: "Phone" },
21157
+ { field: "active", displayName: "Active", type: "boolean" },
21158
+ {
21159
+ field: "inviteStatus",
21160
+ displayName: "Invite",
21161
+ type: "select",
21162
+ options: [
21163
+ { value: "none", label: "None" },
21164
+ { value: "pending", label: "Pending" },
21165
+ { value: "expired", label: "Expired" },
21166
+ { value: "accepted", label: "Accepted" }
21167
+ ]
21168
+ },
21169
+ { field: "createdAt", displayName: "Created", type: "date" }
21170
+ ];
21171
+ function AccessDenied({ groupName }) {
21172
+ return /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("div", { className: "rounded-lg bg-white p-6 shadow-md max-w-lg", children: /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("p", { className: "text-sm text-red-600", children: [
21173
+ "Only users in the ",
21174
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("strong", { children: "Administrator" }),
21175
+ " group can manage vendors.",
21176
+ groupName ? /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)(import_jsx_runtime76.Fragment, { children: [
21177
+ " ",
21178
+ "Your group is ",
21179
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("strong", { children: groupName }),
21180
+ ". Assign Administrator under System \u2192 Users, then sign out and sign in again."
21181
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(import_jsx_runtime76.Fragment, { children: " Assign the Administrator group under System \u2192 Users, then sign out and sign in again." })
21182
+ ] }) });
21183
+ }
21184
+ function VendorOnboardForm({ onSuccess }) {
20544
21185
  const [vendorName, setVendorName] = (0, import_react59.useState)("");
20545
21186
  const [vendorSlug, setVendorSlug] = (0, import_react59.useState)("");
20546
21187
  const [vendorEmail, setVendorEmail] = (0, import_react59.useState)("");
@@ -20548,44 +21189,31 @@ function VendorOnboardPage() {
20548
21189
  const [ownerName, setOwnerName] = (0, import_react59.useState)("");
20549
21190
  const [ownerEmail, setOwnerEmail] = (0, import_react59.useState)("");
20550
21191
  const [activationMode, setActivationMode] = (0, import_react59.useState)("invite");
20551
- const [sendInviteEmail, setSendInviteEmail] = (0, import_react59.useState)(true);
21192
+ const [sendOwnerEmail, setSendOwnerEmail] = (0, import_react59.useState)(true);
20552
21193
  const [ownerPassword, setOwnerPassword] = (0, import_react59.useState)("");
20553
21194
  const [ownerPasswordConfirm, setOwnerPasswordConfirm] = (0, import_react59.useState)("");
20554
21195
  const [loading, setLoading] = (0, import_react59.useState)(false);
20555
21196
  const [message, setMessage] = (0, import_react59.useState)(null);
20556
21197
  const [inviteLink, setInviteLink] = (0, import_react59.useState)(null);
20557
- const [error, setError] = (0, import_react59.useState)(null);
20558
- if (!isAdmin) {
20559
- return /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("div", { className: "p-6 max-w-lg", children: /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("p", { className: "text-sm text-red-600", children: [
20560
- "Only users in the ",
20561
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("strong", { children: "Administrator" }),
20562
- " group can onboard vendors.",
20563
- sessionUser?.groupName ? /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)(import_jsx_runtime76.Fragment, { children: [
20564
- " Your group is ",
20565
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("strong", { children: sessionUser.groupName }),
20566
- ". Assign Administrator under System \u2192 Users, then sign out and sign in again."
20567
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(import_jsx_runtime76.Fragment, { children: " Assign the Administrator group under System \u2192 Users, then sign out and sign in again." })
20568
- ] }) });
20569
- }
20570
- const handleSubmit = async (e) => {
20571
- e.preventDefault();
21198
+ const [errors, setErrors] = (0, import_react59.useState)([]);
21199
+ const handleSubmit = async () => {
20572
21200
  setLoading(true);
20573
21201
  setMessage(null);
20574
- setError(null);
21202
+ setErrors([]);
20575
21203
  setInviteLink(null);
20576
21204
  if (activationMode === "password") {
20577
21205
  if (!ownerPassword) {
20578
- setError("Enter a password for the owner account");
21206
+ setErrors(["Enter a password for the owner account"]);
20579
21207
  setLoading(false);
20580
21208
  return;
20581
21209
  }
20582
21210
  if (ownerPassword !== ownerPasswordConfirm) {
20583
- setError("Passwords do not match");
21211
+ setErrors(["Passwords do not match"]);
20584
21212
  setLoading(false);
20585
21213
  return;
20586
21214
  }
20587
21215
  if (ownerPassword.length < 6) {
20588
- setError("Password must be at least 6 characters");
21216
+ setErrors(["Password must be at least 6 characters"]);
20589
21217
  setLoading(false);
20590
21218
  return;
20591
21219
  }
@@ -20596,7 +21224,7 @@ function VendorOnboardPage() {
20596
21224
  headers: { "Content-Type": "application/json" },
20597
21225
  body: JSON.stringify({
20598
21226
  activation: activationMode,
20599
- sendInviteEmail: activationMode === "invite" ? sendInviteEmail : false,
21227
+ sendOwnerEmail,
20600
21228
  vendor: {
20601
21229
  name: vendorName,
20602
21230
  slug: vendorSlug || void 0,
@@ -20613,140 +21241,241 @@ function VendorOnboardPage() {
20613
21241
  });
20614
21242
  const data = await res.json();
20615
21243
  if (!res.ok) {
20616
- setError(data.error || "Onboarding failed");
21244
+ setErrors([data.error || "Onboarding failed"]);
20617
21245
  return;
20618
21246
  }
20619
21247
  setMessage(data.message || "Vendor created");
20620
21248
  setInviteLink(data.inviteLink ?? null);
20621
- setVendorName("");
20622
- setVendorSlug("");
20623
- setVendorEmail("");
20624
- setVendorPhone("");
20625
- setOwnerName("");
20626
- setOwnerEmail("");
20627
- setOwnerPassword("");
20628
- setOwnerPasswordConfirm("");
21249
+ if (!data.inviteLink) {
21250
+ onSuccess();
21251
+ }
20629
21252
  } catch {
20630
- setError("Request failed");
21253
+ setErrors(["Request failed"]);
20631
21254
  } finally {
20632
21255
  setLoading(false);
20633
21256
  }
20634
21257
  };
20635
- const submitLabel = activationMode === "password" ? "Create vendor & set password" : sendInviteEmail ? "Create vendor & send invite" : "Create vendor & copy invite link";
20636
- return /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "p-6 max-w-xl", children: [
20637
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("h1", { className: "text-xl font-semibold text-gray-900 dark:text-white", children: "Onboard vendor" }),
20638
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("p", { className: "mt-1 text-sm text-gray-600 dark:text-gray-400", children: "Creates a vendor store and a Vendor Owner account. Choose how the owner gets access." }),
20639
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("form", { onSubmit: handleSubmit, className: "mt-6 space-y-6", children: [
20640
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "space-y-3", children: [
20641
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("h2", { className: "text-sm font-medium text-gray-900 dark:text-white", children: "Store" }),
20642
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
20643
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "vendorName", children: "Store name" }),
20644
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Input, { id: "vendorName", required: true, value: vendorName, onChange: (e) => setVendorName(e.target.value), className: "mt-1" })
20645
- ] }),
20646
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
20647
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "vendorSlug", children: "Slug (optional)" }),
20648
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Input, { id: "vendorSlug", value: vendorSlug, onChange: (e) => setVendorSlug(e.target.value), placeholder: "auto from name", className: "mt-1" })
20649
- ] }),
20650
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
20651
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "vendorEmail", children: "Store email" }),
20652
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Input, { id: "vendorEmail", type: "email", value: vendorEmail, onChange: (e) => setVendorEmail(e.target.value), className: "mt-1" })
20653
- ] }),
20654
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
20655
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "vendorPhone", children: "Phone" }),
20656
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Input, { id: "vendorPhone", value: vendorPhone, onChange: (e) => setVendorPhone(e.target.value), className: "mt-1" })
20657
- ] })
20658
- ] }),
20659
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "space-y-3", children: [
20660
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("h2", { className: "text-sm font-medium text-gray-900 dark:text-white", children: "Owner account" }),
20661
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
20662
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "ownerName", children: "Name" }),
20663
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Input, { id: "ownerName", required: true, value: ownerName, onChange: (e) => setOwnerName(e.target.value), className: "mt-1" })
20664
- ] }),
20665
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
20666
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "ownerEmail", children: "Email" }),
20667
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Input, { id: "ownerEmail", type: "email", required: true, value: ownerEmail, onChange: (e) => setOwnerEmail(e.target.value), className: "mt-1" })
20668
- ] })
21258
+ const submitLabel = activationMode === "password" ? sendOwnerEmail ? "Create vendor & send welcome email" : "Create vendor & set password" : sendOwnerEmail ? "Create vendor & send invite" : "Create vendor & copy invite link";
21259
+ const fieldClass = "w-full rounded-md border border-gray-300 px-3 py-2 text-sm";
21260
+ return /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "rounded-lg bg-white shadow-md", children: [
21261
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
21262
+ DetailPageHeader,
21263
+ {
21264
+ title: "Create vendor",
21265
+ subtitle: "Creates a vendor store and a Vendor Owner account",
21266
+ closeHref: "/admin/vendor-onboard",
21267
+ menuItems: [{ label: loading ? "Creating\u2026" : submitLabel, icon: import_lucide_react46.Save, onClick: handleSubmit }]
21268
+ }
21269
+ ),
21270
+ errors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("div", { className: "bg-red-50 border-l-4 border-red-400 p-4 mx-6 mt-4", children: /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "flex", children: [
21271
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(import_lucide_react46.AlertCircle, { className: "h-5 w-5 text-red-400 flex-shrink-0" }),
21272
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "ml-3", children: [
21273
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("h3", { className: "text-sm font-medium text-red-800", children: "Please fix the following errors:" }),
21274
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("ul", { className: "mt-2 text-sm text-red-700 list-disc pl-5 space-y-1", children: errors.map((e, i) => /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("li", { children: e }, i)) })
21275
+ ] })
21276
+ ] }) }),
21277
+ message && /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "mx-6 mt-4 rounded-lg border border-green-200 bg-green-50 p-4 text-sm text-green-800", children: [
21278
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("p", { children: message }),
21279
+ inviteLink && /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("p", { className: "mt-2 text-xs break-all", children: [
21280
+ "Invite link:",
21281
+ " ",
21282
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("a", { href: inviteLink, className: "text-blue-600 underline", children: inviteLink })
20669
21283
  ] }),
20670
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "space-y-3 rounded-lg border border-gray-200 p-4", children: [
20671
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("h2", { className: "text-sm font-medium text-gray-900 dark:text-white", children: "Owner access" }),
20672
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)(
20673
- RadioGroup2,
20674
- {
20675
- value: activationMode,
20676
- onValueChange: (v) => setActivationMode(v),
20677
- className: "gap-3",
20678
- children: [
20679
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "flex items-start gap-2", children: [
20680
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(RadioGroupItem, { value: "invite", id: "activation-invite", className: "mt-0.5" }),
20681
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
20682
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "activation-invite", className: "font-normal cursor-pointer", children: "Send invite link" }),
20683
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("p", { className: "text-xs text-gray-500 mt-0.5", children: "Owner sets their own password via email or the link below." })
20684
- ] })
21284
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Button, { type: "button", variant: "outline", size: "sm", className: "mt-3", onClick: onSuccess, children: "Back to vendors" })
21285
+ ] }),
21286
+ !message && /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
21287
+ DetailPageLayout,
21288
+ {
21289
+ main: /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)(import_jsx_runtime76.Fragment, { children: [
21290
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("section", { children: [
21291
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("h2", { className: "text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2", children: "Store" }),
21292
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "min-w-0 overflow-hidden border border-gray-200 rounded-lg p-4 bg-gray-50/50 space-y-4", children: [
21293
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
21294
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "vendorName", className: "text-xs font-medium text-gray-600", children: "Store name *" }),
21295
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
21296
+ Input,
21297
+ {
21298
+ id: "vendorName",
21299
+ required: true,
21300
+ value: vendorName,
21301
+ onChange: (e) => setVendorName(e.target.value),
21302
+ className: `mt-1 ${fieldClass}`
21303
+ }
21304
+ )
20685
21305
  ] }),
20686
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "flex items-start gap-2", children: [
20687
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(RadioGroupItem, { value: "password", id: "activation-password", className: "mt-0.5" }),
20688
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
20689
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "activation-password", className: "font-normal cursor-pointer", children: "Set password now" }),
20690
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("p", { className: "text-xs text-gray-500 mt-0.5", children: "Account is active immediately; share the password with the owner securely." })
20691
- ] })
21306
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
21307
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "vendorSlug", className: "text-xs font-medium text-gray-600", children: "Slug (optional)" }),
21308
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
21309
+ Input,
21310
+ {
21311
+ id: "vendorSlug",
21312
+ value: vendorSlug,
21313
+ onChange: (e) => setVendorSlug(e.target.value),
21314
+ placeholder: "auto from name",
21315
+ className: `mt-1 ${fieldClass}`
21316
+ }
21317
+ )
21318
+ ] }),
21319
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
21320
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "vendorEmail", className: "text-xs font-medium text-gray-600", children: "Store email" }),
21321
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
21322
+ Input,
21323
+ {
21324
+ id: "vendorEmail",
21325
+ type: "email",
21326
+ value: vendorEmail,
21327
+ onChange: (e) => setVendorEmail(e.target.value),
21328
+ className: `mt-1 ${fieldClass}`
21329
+ }
21330
+ )
21331
+ ] }),
21332
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
21333
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "vendorPhone", className: "text-xs font-medium text-gray-600", children: "Phone" }),
21334
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
21335
+ Input,
21336
+ {
21337
+ id: "vendorPhone",
21338
+ value: vendorPhone,
21339
+ onChange: (e) => setVendorPhone(e.target.value),
21340
+ className: `mt-1 ${fieldClass}`
21341
+ }
21342
+ )
20692
21343
  ] })
20693
- ]
20694
- }
20695
- ),
20696
- activationMode === "invite" && /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "flex items-center gap-2 pt-1", children: [
20697
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
20698
- Checkbox,
20699
- {
20700
- id: "sendInviteEmail",
20701
- checked: sendInviteEmail,
20702
- onCheckedChange: (checked) => setSendInviteEmail(checked === true)
20703
- }
20704
- ),
20705
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "sendInviteEmail", className: "font-normal cursor-pointer text-sm", children: "Send invite email to owner" })
20706
- ] }),
20707
- activationMode === "password" && /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "space-y-3 pt-1", children: [
20708
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
20709
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "ownerPassword", children: "Password" }),
20710
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
20711
- Input,
20712
- {
20713
- id: "ownerPassword",
20714
- type: "password",
20715
- required: true,
20716
- autoComplete: "new-password",
20717
- value: ownerPassword,
20718
- onChange: (e) => setOwnerPassword(e.target.value),
20719
- className: "mt-1"
20720
- }
20721
- )
21344
+ ] })
20722
21345
  ] }),
20723
- /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
20724
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "ownerPasswordConfirm", children: "Confirm password" }),
20725
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
20726
- Input,
21346
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("section", { children: [
21347
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("h2", { className: "text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2", children: "Owner account" }),
21348
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "min-w-0 overflow-hidden border border-gray-200 rounded-lg p-4 bg-gray-50/50 space-y-4", children: [
21349
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
21350
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "ownerName", className: "text-xs font-medium text-gray-600", children: "Name *" }),
21351
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
21352
+ Input,
21353
+ {
21354
+ id: "ownerName",
21355
+ required: true,
21356
+ value: ownerName,
21357
+ onChange: (e) => setOwnerName(e.target.value),
21358
+ className: `mt-1 ${fieldClass}`
21359
+ }
21360
+ )
21361
+ ] }),
21362
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
21363
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "ownerEmail", className: "text-xs font-medium text-gray-600", children: "Email *" }),
21364
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
21365
+ Input,
21366
+ {
21367
+ id: "ownerEmail",
21368
+ type: "email",
21369
+ required: true,
21370
+ value: ownerEmail,
21371
+ onChange: (e) => setOwnerEmail(e.target.value),
21372
+ className: `mt-1 ${fieldClass}`
21373
+ }
21374
+ )
21375
+ ] })
21376
+ ] })
21377
+ ] })
21378
+ ] }),
21379
+ sidebar: /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("section", { children: [
21380
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("h2", { className: "text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2", children: "Owner access" }),
21381
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "min-w-0 overflow-hidden border border-gray-200 rounded-lg p-4 bg-gray-50/50 space-y-4", children: [
21382
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)(
21383
+ RadioGroup2,
20727
21384
  {
20728
- id: "ownerPasswordConfirm",
20729
- type: "password",
20730
- required: true,
20731
- autoComplete: "new-password",
20732
- value: ownerPasswordConfirm,
20733
- onChange: (e) => setOwnerPasswordConfirm(e.target.value),
20734
- className: "mt-1"
21385
+ value: activationMode,
21386
+ onValueChange: (v) => setActivationMode(v),
21387
+ className: "gap-3",
21388
+ children: [
21389
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "flex items-start gap-2", children: [
21390
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(RadioGroupItem, { value: "invite", id: "activation-invite", className: "mt-0.5" }),
21391
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
21392
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "activation-invite", className: "font-normal cursor-pointer text-sm", children: "Send invite link" }),
21393
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("p", { className: "text-xs text-gray-500 mt-0.5", children: "Owner sets their own password via email or the link below." })
21394
+ ] })
21395
+ ] }),
21396
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "flex items-start gap-2", children: [
21397
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(RadioGroupItem, { value: "password", id: "activation-password", className: "mt-0.5" }),
21398
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
21399
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "activation-password", className: "font-normal cursor-pointer text-sm", children: "Set password now" }),
21400
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("p", { className: "text-xs text-gray-500 mt-0.5", children: "Account is active immediately; share the password with the owner securely." })
21401
+ ] })
21402
+ ] })
21403
+ ]
20735
21404
  }
20736
- )
21405
+ ),
21406
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "flex items-center gap-2 pt-1", children: [
21407
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
21408
+ Checkbox,
21409
+ {
21410
+ id: "sendOwnerEmail",
21411
+ checked: sendOwnerEmail,
21412
+ onCheckedChange: (checked) => setSendOwnerEmail(checked === true)
21413
+ }
21414
+ ),
21415
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "sendOwnerEmail", className: "font-normal cursor-pointer text-sm", children: activationMode === "invite" ? "Send invite email to owner" : "Send welcome email to owner" })
21416
+ ] }),
21417
+ activationMode === "password" && /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { className: "space-y-3 pt-1", children: [
21418
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
21419
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "ownerPassword", className: "text-xs font-medium text-gray-600", children: "Password *" }),
21420
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
21421
+ Input,
21422
+ {
21423
+ id: "ownerPassword",
21424
+ type: "password",
21425
+ required: true,
21426
+ autoComplete: "new-password",
21427
+ value: ownerPassword,
21428
+ onChange: (e) => setOwnerPassword(e.target.value),
21429
+ className: `mt-1 ${fieldClass}`
21430
+ }
21431
+ )
21432
+ ] }),
21433
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", { children: [
21434
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Label3, { htmlFor: "ownerPasswordConfirm", className: "text-xs font-medium text-gray-600", children: "Confirm password *" }),
21435
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
21436
+ Input,
21437
+ {
21438
+ id: "ownerPasswordConfirm",
21439
+ type: "password",
21440
+ required: true,
21441
+ autoComplete: "new-password",
21442
+ value: ownerPasswordConfirm,
21443
+ onChange: (e) => setOwnerPasswordConfirm(e.target.value),
21444
+ className: `mt-1 ${fieldClass}`
21445
+ }
21446
+ )
21447
+ ] })
21448
+ ] }),
21449
+ /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Button, { type: "button", disabled: loading, onClick: handleSubmit, className: "w-full", children: loading ? "Creating\u2026" : submitLabel })
20737
21450
  ] })
20738
21451
  ] })
20739
- ] }),
20740
- error && /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("p", { className: "text-sm text-red-600", children: error }),
20741
- message && /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("p", { className: "text-sm text-green-700", children: message }),
20742
- inviteLink && /* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("p", { className: "text-xs text-gray-600 break-all", children: [
20743
- "Invite link: ",
20744
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)("a", { href: inviteLink, className: "text-blue-600 underline", children: inviteLink })
20745
- ] }),
20746
- /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(Button, { type: "submit", disabled: loading, children: loading ? "Creating\u2026" : submitLabel })
20747
- ] })
21452
+ }
21453
+ )
20748
21454
  ] });
20749
21455
  }
21456
+ function VendorOnboardPage({ showCreateForm }) {
21457
+ const router = (0, import_navigation27.useRouter)();
21458
+ const { data: session } = (0, import_react60.useSession)();
21459
+ const sessionUser = session?.user;
21460
+ const isAdmin = canOnboardVendors(session?.user);
21461
+ if (!isAdmin) {
21462
+ return /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(AccessDenied, { groupName: sessionUser?.groupName });
21463
+ }
21464
+ if (showCreateForm) {
21465
+ return /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(VendorOnboardForm, { onSuccess: () => router.push("/admin/vendor-onboard") });
21466
+ }
21467
+ return /* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
21468
+ AdminCRUD,
21469
+ {
21470
+ title: "Vendors",
21471
+ apiEndpoint: "/api/vendors",
21472
+ columns: [...VENDOR_LIST_COLUMNS],
21473
+ addEditPageUrl: "/admin/vendor-onboard",
21474
+ defaultSortField: "createdAt",
21475
+ defaultSortOrder: "desc"
21476
+ }
21477
+ );
21478
+ }
20750
21479
 
20751
21480
  // src/admin/pages/VendorTeamPage.tsx
20752
21481
  var import_react61 = require("react");
@@ -21259,9 +21988,9 @@ var STORE_CRUD_CONFIGS = {
21259
21988
  // src/admin/pages/DiscountDetailsPage.tsx
21260
21989
  var import_react63 = require("react");
21261
21990
  var import_link14 = __toESM(require("next/link"), 1);
21262
- var import_navigation27 = require("next/navigation");
21991
+ var import_navigation28 = require("next/navigation");
21263
21992
  init_admin_list_return_url();
21264
- var import_lucide_react46 = require("lucide-react");
21993
+ var import_lucide_react47 = require("lucide-react");
21265
21994
  init_button();
21266
21995
  init_utils();
21267
21996
  init_DetailPageLayout();
@@ -21350,7 +22079,7 @@ function RuleTreeNode({ node, depth = 0 }) {
21350
22079
  type: "button",
21351
22080
  onClick: () => setOpen(!open),
21352
22081
  className: "text-gray-400 hover:text-gray-600",
21353
- children: open ? /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react46.ChevronDown, { className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react46.ChevronRight, { className: "h-4 w-4" })
22082
+ children: open ? /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react47.ChevronDown, { className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react47.ChevronRight, { className: "h-4 w-4" })
21354
22083
  }
21355
22084
  ),
21356
22085
  /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "font-semibold text-xs uppercase text-gray-500", children: "Group" }),
@@ -21359,14 +22088,14 @@ function RuleTreeNode({ node, depth = 0 }) {
21359
22088
  node.type && /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "rounded bg-white border border-gray-200 px-2 py-0.5 text-xs text-gray-700", children: RULE_TYPE_LABEL[node.type] ?? node.type }),
21360
22089
  node.subType && /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "text-gray-400 text-xs", children: node.subType }),
21361
22090
  node.comparisonOperator && /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "font-mono text-xs font-semibold text-gray-700", children: node.comparisonOperator }),
21362
- node.value && Object.keys(node.value).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "font-mono text-xs bg-white border border-gray-200 rounded px-2 py-0.5 text-gray-800 break-all", children: Object.keys(node.value).length === 1 && "v" in node.value ? String(node.value.v) : JSON.stringify(node.value) })
22091
+ node.value && Object.keys(node.value).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "font-mono text-xs bg-white border border-gray-200 rounded px-2 py-0.5 text-gray-800 break-all", children: node.type === "quantity" && node.subType === "productId" && "v" in node.value ? `product #${node.value.productId} >= ${node.value.v}` : Object.keys(node.value).length === 1 && "v" in node.value ? String(node.value.v) : JSON.stringify(node.value) })
21363
22092
  ] }) }),
21364
22093
  isGroup && open && (node.children ?? []).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("div", { className: "mt-2 pl-4 space-y-2", children: (node.children ?? []).map((child) => /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(RuleTreeNode, { node: child, depth: depth + 1 }, child.id)) })
21365
22094
  ] });
21366
22095
  }
21367
22096
  function DiscountDetailPage({ discountId }) {
21368
- const router = (0, import_navigation27.useRouter)();
21369
- const searchParams = (0, import_navigation27.useSearchParams)();
22097
+ const router = (0, import_navigation28.useRouter)();
22098
+ const searchParams = (0, import_navigation28.useSearchParams)();
21370
22099
  const listReturnUrl = safeAdminListReturnUrl(searchParams.get("from")) ?? "/admin/discounts";
21371
22100
  const [discount, setDiscount] = (0, import_react63.useState)(null);
21372
22101
  const [loading, setLoading] = (0, import_react63.useState)(true);
@@ -21414,7 +22143,7 @@ function DiscountDetailPage({ discountId }) {
21414
22143
  return /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("div", { className: "rounded-lg bg-white p-6 shadow-md", children: [
21415
22144
  /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("p", { className: "text-red-600", children: error ?? "Not found" }),
21416
22145
  /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_link14.default, { href: listReturnUrl, children: /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)(Button, { variant: "outline", className: "mt-4", children: [
21417
- /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react46.X, { className: "h-4 w-4 mr-2" }),
22146
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react47.X, { className: "h-4 w-4 mr-2" }),
21418
22147
  " Close"
21419
22148
  ] }) })
21420
22149
  ] });
@@ -21436,14 +22165,14 @@ function DiscountDetailPage({ discountId }) {
21436
22165
  title: /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("span", { className: "flex items-center gap-2", children: [
21437
22166
  discount.name,
21438
22167
  discount.isAutomatic && /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-medium text-amber-700", children: [
21439
- /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react46.Zap, { className: "h-3 w-3" }),
22168
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react47.Zap, { className: "h-3 w-3" }),
21440
22169
  " Automatic"
21441
22170
  ] })
21442
22171
  ] }),
21443
22172
  subtitle: discount.isAutomatic ? /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "text-sm text-gray-400 italic", children: "No coupon code \u2014 applied automatically" }) : /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "font-mono text-sm text-gray-400", children: discount.couponCode }),
21444
22173
  closeHref: listReturnUrl,
21445
22174
  menuItems: [
21446
- { label: "Edit discount", icon: import_lucide_react46.Pencil, onClick: () => router.push(editHref) }
22175
+ { label: "Edit discount", icon: import_lucide_react47.Pencil, onClick: () => router.push(editHref) }
21447
22176
  ]
21448
22177
  }
21449
22178
  ),
@@ -21483,7 +22212,7 @@ function DiscountDetailPage({ discountId }) {
21483
22212
  /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("div", { children: [
21484
22213
  /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("dt", { className: "text-gray-500", children: "Application" }),
21485
22214
  /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("dd", { className: "font-medium text-gray-900", children: discount.isAutomatic ? /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("span", { className: "inline-flex items-center gap-1 text-amber-700", children: [
21486
- /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react46.Zap, { className: "h-3.5 w-3.5" }),
22215
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react47.Zap, { className: "h-3.5 w-3.5" }),
21487
22216
  " Automatic"
21488
22217
  ] }) : "Coupon code" })
21489
22218
  ] }),
@@ -21578,7 +22307,7 @@ function DiscountDetailPage({ discountId }) {
21578
22307
  discount.isAutomatic && /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("section", { children: [
21579
22308
  /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("h2", { className: "text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2", children: "Application" }),
21580
22309
  /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("div", { className: "border border-amber-200 rounded-lg p-4 bg-amber-50/50 flex items-start gap-2 text-sm text-amber-800", children: [
21581
- /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react46.Zap, { className: "h-4 w-4 mt-0.5 flex-shrink-0 text-amber-500" }),
22310
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react47.Zap, { className: "h-4 w-4 mt-0.5 flex-shrink-0 text-amber-500" }),
21582
22311
  /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("span", { children: [
21583
22312
  "This discount is applied ",
21584
22313
  /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("strong", { children: "automatically" }),
@@ -21594,15 +22323,15 @@ function DiscountDetailPage({ discountId }) {
21594
22323
 
21595
22324
  // src/admin/pages/DiscountEditPage.tsx
21596
22325
  var import_react65 = require("react");
21597
- var import_navigation28 = require("next/navigation");
22326
+ var import_navigation29 = require("next/navigation");
21598
22327
  init_admin_list_return_url();
21599
- var import_lucide_react48 = require("lucide-react");
22328
+ var import_lucide_react49 = require("lucide-react");
21600
22329
  init_DetailPageLayout();
21601
22330
  init_DetailPageHeader();
21602
22331
 
21603
22332
  // src/admin/pages/DiscountsConditionsBuilder.tsx
21604
22333
  var import_react64 = require("react");
21605
- var import_lucide_react47 = require("lucide-react");
22334
+ var import_lucide_react48 = require("lucide-react");
21606
22335
  init_button();
21607
22336
  var import_jsx_runtime79 = require("react/jsx-runtime");
21608
22337
  function uid() {
@@ -21619,10 +22348,10 @@ function conditionToRule(c) {
21619
22348
  return { ...base, type: "minAmount", comparisonOperator: "gte", value: { v: c.amount ?? "0" } };
21620
22349
  case "minQuantity":
21621
22350
  return { ...base, type: "quantity", comparisonOperator: "gte", value: { v: c.quantity ?? "1" } };
21622
- case "Product":
21623
- return { ...base, type: "product", subType: "productId", comparisonOperator: "eq", value: { v: String(c.productId ?? "") } };
22351
+ case "productMinQuantity":
22352
+ return { ...base, type: "quantity", subType: "productId", comparisonOperator: "gte", value: { v: c.quantity ?? "1", productId: String(c.productId ?? "") } };
21624
22353
  case "nthOrder":
21625
- return { ...base, type: "order", subType: "nthOrder", comparisonOperator: "gte", value: { v: c.nthOrder ?? "1" } };
22354
+ return { ...base, type: "order", subType: "nthOrder", comparisonOperator: "eq", value: { v: c.nthOrder ?? "1" } };
21626
22355
  case "userType":
21627
22356
  return { ...base, type: "user", subType: "userType", comparisonOperator: "eq", value: { v: c.userType ?? "" } };
21628
22357
  case "paymentMethod":
@@ -21688,9 +22417,16 @@ function ruleTreeToFriendly(rules) {
21688
22417
  case "minAmount":
21689
22418
  return { id: rule._clientId ?? uid(), kind: "minAmount", amount: v };
21690
22419
  case "quantity":
22420
+ if (rule.subType === "productId") {
22421
+ return {
22422
+ id: rule._clientId ?? uid(),
22423
+ kind: "productMinQuantity",
22424
+ quantity: v,
22425
+ productId: Number(rule.value?.productId) || null,
22426
+ productName: void 0
22427
+ };
22428
+ }
21691
22429
  return { id: rule._clientId ?? uid(), kind: "minQuantity", quantity: v };
21692
- case "product":
21693
- return { id: rule._clientId ?? uid(), kind: "Product", productId: Number(v) || null, productName: void 0 };
21694
22430
  case "order":
21695
22431
  return { id: rule._clientId ?? uid(), kind: "nthOrder", nthOrder: v };
21696
22432
  case "user":
@@ -21798,14 +22534,14 @@ function ProductPicker({
21798
22534
  onClick: () => setOpen((o) => !o),
21799
22535
  className: "flex items-center gap-2 w-full rounded-md border border-gray-300 px-3 py-1.5 text-sm text-left hover:border-gray-400 focus:outline-none focus:ring-1 focus:ring-gray-400 bg-white",
21800
22536
  children: [
21801
- /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.Package, { className: "h-3.5 w-3.5 text-gray-400 shrink-0" }),
22537
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.Package, { className: "h-3.5 w-3.5 text-gray-400 shrink-0" }),
21802
22538
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: label ? "text-gray-900" : "text-gray-400", children: label ?? "Select product\u2026" })
21803
22539
  ]
21804
22540
  }
21805
22541
  ),
21806
22542
  open && /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "absolute z-50 top-full left-0 right-0 mt-1 rounded-lg border border-gray-200 bg-white shadow-lg", children: [
21807
22543
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("div", { className: "p-2 border-b border-gray-100", children: /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "flex items-center gap-2 px-2 py-1 rounded border border-gray-200", children: [
21808
- /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.Search, { className: "h-3.5 w-3.5 text-gray-400" }),
22544
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.Search, { className: "h-3.5 w-3.5 text-gray-400" }),
21809
22545
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("input", { autoFocus: true, value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search products\u2026", className: "flex-1 text-sm outline-none bg-transparent" })
21810
22546
  ] }) }),
21811
22547
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("div", { className: "max-h-48 overflow-y-auto", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("p", { className: "text-xs text-gray-400 p-3", children: "Loading\u2026" }) : hits.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("p", { className: "text-xs text-gray-400 p-3", children: "No products found" }) : hits.map((p) => /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)(
@@ -21866,14 +22602,14 @@ function CategoryPicker({
21866
22602
  onClick: () => setOpen((o) => !o),
21867
22603
  className: "flex items-center gap-2 w-full rounded-md border border-gray-300 px-3 py-1.5 text-sm text-left hover:border-gray-400 focus:outline-none focus:ring-1 focus:ring-gray-400 bg-white",
21868
22604
  children: [
21869
- /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.Package, { className: "h-3.5 w-3.5 text-gray-400 shrink-0" }),
22605
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.Package, { className: "h-3.5 w-3.5 text-gray-400 shrink-0" }),
21870
22606
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: label ? "text-gray-900" : "text-gray-400", children: label ?? "Select category\u2026" })
21871
22607
  ]
21872
22608
  }
21873
22609
  ),
21874
22610
  open && /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "absolute z-50 top-full left-0 right-0 mt-1 rounded-lg border border-gray-200 bg-white shadow-lg", children: [
21875
22611
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("div", { className: "p-2 border-b border-gray-100", children: /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "flex items-center gap-2 px-2 py-1 rounded border border-gray-200", children: [
21876
- /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.Search, { className: "h-3.5 w-3.5 text-gray-400" }),
22612
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.Search, { className: "h-3.5 w-3.5 text-gray-400" }),
21877
22613
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("input", { autoFocus: true, value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search categories\u2026", className: "flex-1 text-sm outline-none bg-transparent" })
21878
22614
  ] }) }),
21879
22615
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("div", { className: "max-h-48 overflow-y-auto", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("p", { className: "text-xs text-gray-400 p-3", children: "Loading\u2026" }) : hits.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("p", { className: "text-xs text-gray-400 p-3", children: "No categories found" }) : hits.map((c) => /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)(
@@ -21973,14 +22709,14 @@ function CustomerPicker({
21973
22709
  onClick: () => setOpen((o) => !o),
21974
22710
  className: "flex items-center gap-2 w-full rounded-md border border-gray-300 px-3 py-1.5 text-sm text-left hover:border-gray-400 focus:outline-none focus:ring-1 focus:ring-gray-400 bg-white",
21975
22711
  children: [
21976
- /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.User, { className: "h-3.5 w-3.5 text-gray-400 shrink-0" }),
22712
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.User, { className: "h-3.5 w-3.5 text-gray-400 shrink-0" }),
21977
22713
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: displayLabel ? "text-gray-900" : "text-gray-400", children: displayLabel || "Select customer\u2026" })
21978
22714
  ]
21979
22715
  }
21980
22716
  ),
21981
22717
  open && /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "absolute z-50 top-full left-0 right-0 mt-1 rounded-lg border border-gray-200 bg-white shadow-lg", children: [
21982
22718
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("div", { className: "p-2 border-b border-gray-100", children: /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "flex items-center gap-2 px-2 py-1 rounded border border-gray-200", children: [
21983
- /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.Search, { className: "h-3.5 w-3.5 text-gray-400" }),
22719
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.Search, { className: "h-3.5 w-3.5 text-gray-400" }),
21984
22720
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("input", { autoFocus: true, value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search customers\u2026", className: "flex-1 text-sm outline-none bg-transparent" })
21985
22721
  ] }) }),
21986
22722
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("div", { className: "max-h-48 overflow-y-auto", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("p", { className: "text-xs text-gray-400 p-3", children: "Loading\u2026" }) : hits.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("p", { className: "text-xs text-gray-400 p-3", children: "No customers found" }) : hits.map((c) => /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)(
@@ -22012,16 +22748,16 @@ function ConditionCard({
22012
22748
  onRemove
22013
22749
  }) {
22014
22750
  const iconMap = {
22015
- minAmount: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.DollarSign, { className: "h-3.5 w-3.5 text-blue-500" }),
22016
- minQuantity: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.Hash, { className: "h-3.5 w-3.5 text-purple-500" }),
22017
- Product: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.Package, { className: "h-3.5 w-3.5 text-green-500" }),
22018
- nthOrder: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.ShoppingCart, { className: "h-3.5 w-3.5 text-orange-500" }),
22019
- userType: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.User, { className: "h-3.5 w-3.5 text-pink-500" }),
22020
- paymentMethod: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.DollarSign, { className: "h-3.5 w-3.5 text-indigo-500" }),
22021
- category: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.Package, { className: "h-3.5 w-3.5 text-teal-500" }),
22022
- customers: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.User, { className: "h-3.5 w-3.5 text-blue-400" }),
22023
- shipping: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.DollarSign, { className: "h-3.5 w-3.5 text-gray-500" }),
22024
- referral: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.User, { className: "h-3.5 w-3.5 text-rose-500" })
22751
+ minAmount: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.DollarSign, { className: "h-3.5 w-3.5 text-blue-500" }),
22752
+ minQuantity: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.Hash, { className: "h-3.5 w-3.5 text-purple-500" }),
22753
+ productMinQuantity: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.Hash, { className: "h-3.5 w-3.5 text-green-600" }),
22754
+ nthOrder: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.ShoppingCart, { className: "h-3.5 w-3.5 text-orange-500" }),
22755
+ userType: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.User, { className: "h-3.5 w-3.5 text-pink-500" }),
22756
+ paymentMethod: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.DollarSign, { className: "h-3.5 w-3.5 text-indigo-500" }),
22757
+ category: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.Package, { className: "h-3.5 w-3.5 text-teal-500" }),
22758
+ customers: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.User, { className: "h-3.5 w-3.5 text-blue-400" }),
22759
+ shipping: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.DollarSign, { className: "h-3.5 w-3.5 text-gray-500" }),
22760
+ referral: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.User, { className: "h-3.5 w-3.5 text-rose-500" })
22025
22761
  };
22026
22762
  return /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "flex items-start gap-3 rounded-lg border border-gray-200 bg-white p-3 shadow-sm", children: [
22027
22763
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("div", { className: "mt-0.5 shrink-0", children: iconMap[condition.kind] }),
@@ -22036,7 +22772,7 @@ function ConditionCard({
22036
22772
  /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)(SelectContent, { children: [
22037
22773
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(SelectItem, { value: "minAmount", children: "Minimum order amount" }),
22038
22774
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(SelectItem, { value: "minQuantity", children: "Minimum quantity" }),
22039
- /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(SelectItem, { value: "Product", children: "Product" }),
22775
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(SelectItem, { value: "productMinQuantity", children: "Product" }),
22040
22776
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(SelectItem, { value: "nthOrder", children: "Customer's Nth order" }),
22041
22777
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(SelectItem, { value: "userType", children: "User type" }),
22042
22778
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(SelectItem, { value: "paymentMethod", children: "Payment method" }),
@@ -22056,9 +22792,33 @@ function ConditionCard({
22056
22792
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: "text-xs text-gray-500 shrink-0", children: "Qty \u2265" }),
22057
22793
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("input", { type: "number", min: 1, step: "1", value: condition.quantity ?? "", onChange: (e) => onChange({ ...condition, quantity: e.target.value }), className: inputCls3, placeholder: "2" })
22058
22794
  ] }),
22059
- condition.kind === "Product" && /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(ProductPicker, { value: condition.productId, label: condition.productName, onChange: (id, name) => onChange({ ...condition, productId: id, productName: name }) }),
22795
+ condition.kind === "productMinQuantity" && /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "space-y-2", children: [
22796
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
22797
+ ProductPicker,
22798
+ {
22799
+ value: condition.productId,
22800
+ label: condition.productName,
22801
+ onChange: (id, name) => onChange({ ...condition, productId: id, productName: name })
22802
+ }
22803
+ ),
22804
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "flex items-center gap-2", children: [
22805
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: "text-xs text-gray-500 shrink-0", children: "Minimum quantity of this product" }),
22806
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
22807
+ "input",
22808
+ {
22809
+ type: "number",
22810
+ min: 1,
22811
+ step: "1",
22812
+ value: condition.quantity ?? "",
22813
+ onChange: (e) => onChange({ ...condition, quantity: e.target.value }),
22814
+ className: inputCls3,
22815
+ placeholder: "2"
22816
+ }
22817
+ )
22818
+ ] })
22819
+ ] }),
22060
22820
  condition.kind === "nthOrder" && /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "flex items-center gap-2", children: [
22061
- /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: "text-xs text-gray-500 shrink-0", children: "Order number \u2265" }),
22821
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: "text-xs text-gray-500 shrink-0", children: "Order number =" }),
22062
22822
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("input", { type: "number", min: 1, step: "1", value: condition.nthOrder ?? "", onChange: (e) => onChange({ ...condition, nthOrder: e.target.value }), className: inputCls3, placeholder: "2" }),
22063
22823
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: "text-xs text-gray-400 shrink-0", children: "(e.g. 2 = 2nd+ order)" })
22064
22824
  ] }),
@@ -22086,7 +22846,7 @@ function ConditionCard({
22086
22846
  condition.kind === "shipping" && /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("p", { className: "text-xs text-gray-400 italic", children: "Applied when any shipping method is selected." }),
22087
22847
  condition.kind === "referral" && /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("p", { className: "text-xs text-gray-400 italic", children: "Applied when order comes from a referral." })
22088
22848
  ] }),
22089
- /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("button", { type: "button", onClick: onRemove, className: "shrink-0 text-gray-300 hover:text-red-500 transition-colors mt-0.5", children: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.Trash2, { className: "h-4 w-4" }) })
22849
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("button", { type: "button", onClick: onRemove, className: "shrink-0 text-gray-300 hover:text-red-500 transition-colors mt-0.5", children: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.Trash2, { className: "h-4 w-4" }) })
22090
22850
  ] });
22091
22851
  }
22092
22852
  function RewardCard({
@@ -22097,7 +22857,7 @@ function RewardCard({
22097
22857
  discountValue
22098
22858
  }) {
22099
22859
  return /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "flex items-start gap-3 rounded-lg border border-purple-200 bg-purple-50/40 p-3 shadow-sm", children: [
22100
- /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.Gift, { className: "h-3.5 w-3.5 text-purple-500 mt-0.5 shrink-0" }),
22860
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.Gift, { className: "h-3.5 w-3.5 text-purple-500 mt-0.5 shrink-0" }),
22101
22861
  /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "flex-1 min-w-0 space-y-2", children: [
22102
22862
  /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("p", { className: "text-xs font-medium text-purple-700", children: [
22103
22863
  "Reward product \u2014 ",
@@ -22111,7 +22871,7 @@ function RewardCard({
22111
22871
  ] }),
22112
22872
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("p", { className: "text-[11px] text-purple-600", children: "This product will be automatically added to cart when the discount is applied." })
22113
22873
  ] }),
22114
- /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("button", { type: "button", onClick: onRemove, className: "shrink-0 text-gray-300 hover:text-red-500 transition-colors mt-0.5", children: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.Trash2, { className: "h-4 w-4" }) })
22874
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("button", { type: "button", onClick: onRemove, className: "shrink-0 text-gray-300 hover:text-red-500 transition-colors mt-0.5", children: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.Trash2, { className: "h-4 w-4" }) })
22115
22875
  ] });
22116
22876
  }
22117
22877
  function DiscountConditionsBuilder({
@@ -22128,7 +22888,7 @@ function DiscountConditionsBuilder({
22128
22888
  const missingNameIds = [];
22129
22889
  for (const g of groups) {
22130
22890
  for (const c of g.conditions) {
22131
- if (c.kind === "Product" && c.productId && !c.productName)
22891
+ if (c.kind === "productMinQuantity" && c.productId && !c.productName)
22132
22892
  missingNameIds.push({ type: "product", id: c.id, entityId: c.productId, groupId: g.id });
22133
22893
  if (c.kind === "category" && c.categoryId && !c.categoryName)
22134
22894
  missingNameIds.push({ type: "category", id: c.id, entityId: c.categoryId, groupId: g.id });
@@ -22268,7 +23028,7 @@ function DiscountConditionsBuilder({
22268
23028
  type: "button",
22269
23029
  onClick: () => removeGroup(group.id),
22270
23030
  className: "text-gray-300 hover:text-red-500 transition-colors",
22271
- children: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.Trash2, { className: "h-3.5 w-3.5" })
23031
+ children: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.Trash2, { className: "h-3.5 w-3.5" })
22272
23032
  }
22273
23033
  )
22274
23034
  ] }),
@@ -22296,7 +23056,7 @@ function DiscountConditionsBuilder({
22296
23056
  onClick: () => addCondition(group.id),
22297
23057
  className: "flex items-center gap-1.5 text-xs mt-1",
22298
23058
  children: [
22299
- /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.Plus, { className: "h-3.5 w-3.5" }),
23059
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.Plus, { className: "h-3.5 w-3.5" }),
22300
23060
  "Add condition"
22301
23061
  ]
22302
23062
  }
@@ -22312,7 +23072,7 @@ function DiscountConditionsBuilder({
22312
23072
  onClick: addGroup,
22313
23073
  className: "flex items-center gap-1.5 text-xs border-dashed border-gray-300 text-gray-500",
22314
23074
  children: [
22315
- /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.Plus, { className: "h-3.5 w-3.5" }),
23075
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.Plus, { className: "h-3.5 w-3.5" }),
22316
23076
  "Add condition group"
22317
23077
  ]
22318
23078
  }
@@ -22347,7 +23107,7 @@ function DiscountConditionsBuilder({
22347
23107
  onClick: addReward,
22348
23108
  className: "flex items-center gap-1.5 text-xs border-purple-200 text-purple-700 hover:bg-purple-50",
22349
23109
  children: [
22350
- /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.Gift, { className: "h-3.5 w-3.5" }),
23110
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react48.Gift, { className: "h-3.5 w-3.5" }),
22351
23111
  "Add reward product (BXGY / BOGO)"
22352
23112
  ]
22353
23113
  }
@@ -22425,8 +23185,8 @@ function useConvertedValue2(value, fromCurrency, toCurrency, discountType) {
22425
23185
  var inputCls4 = "w-full rounded-md border border-gray-300 px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-gray-400";
22426
23186
  var isCreate4 = (id) => id === "create";
22427
23187
  function DiscountEditPage({ discountId }) {
22428
- const router = (0, import_navigation28.useRouter)();
22429
- const searchParams = (0, import_navigation28.useSearchParams)();
23188
+ const router = (0, import_navigation29.useRouter)();
23189
+ const searchParams = (0, import_navigation29.useSearchParams)();
22430
23190
  const listReturnUrl = safeAdminListReturnUrl(searchParams.get("from")) ?? "/admin/discounts";
22431
23191
  const create = isCreate4(discountId);
22432
23192
  const [loading, setLoading] = (0, import_react65.useState)(!create);
@@ -22587,12 +23347,12 @@ function DiscountEditPage({ discountId }) {
22587
23347
  subtitle: create ? "Create a new discount coupon" : `Editing: ${couponCode || discountId}`,
22588
23348
  closeHref: listReturnUrl,
22589
23349
  menuItems: [
22590
- { label: saving ? "Saving..." : "Save", icon: import_lucide_react48.Save, onClick: handleSave }
23350
+ { label: saving ? "Saving..." : "Save", icon: import_lucide_react49.Save, onClick: handleSave }
22591
23351
  ]
22592
23352
  }
22593
23353
  ),
22594
23354
  errors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime80.jsx)("div", { className: "bg-red-50 border-l-4 border-red-400 p-4 mx-6 mt-4", children: /* @__PURE__ */ (0, import_jsx_runtime80.jsxs)("div", { className: "flex", children: [
22595
- /* @__PURE__ */ (0, import_jsx_runtime80.jsx)(import_lucide_react48.AlertCircle, { className: "h-5 w-5 text-red-400 flex-shrink-0" }),
23355
+ /* @__PURE__ */ (0, import_jsx_runtime80.jsx)(import_lucide_react49.AlertCircle, { className: "h-5 w-5 text-red-400 flex-shrink-0" }),
22596
23356
  /* @__PURE__ */ (0, import_jsx_runtime80.jsxs)("div", { className: "ml-3", children: [
22597
23357
  /* @__PURE__ */ (0, import_jsx_runtime80.jsx)("h3", { className: "text-sm font-medium text-red-800", children: "Please fix the following errors:" }),
22598
23358
  /* @__PURE__ */ (0, import_jsx_runtime80.jsx)("ul", { className: "mt-2 text-sm text-red-700 list-disc pl-5 space-y-1", children: errors.map((e, i) => /* @__PURE__ */ (0, import_jsx_runtime80.jsx)("li", { children: e }, i)) })
@@ -22620,7 +23380,7 @@ function DiscountEditPage({ discountId }) {
22620
23380
  ) }),
22621
23381
  /* @__PURE__ */ (0, import_jsx_runtime80.jsxs)("div", { children: [
22622
23382
  /* @__PURE__ */ (0, import_jsx_runtime80.jsxs)("span", { className: "flex items-center gap-1.5 text-sm font-medium text-gray-700", children: [
22623
- /* @__PURE__ */ (0, import_jsx_runtime80.jsx)(import_lucide_react48.Zap, { className: "h-3.5 w-3.5 text-amber-500" }),
23383
+ /* @__PURE__ */ (0, import_jsx_runtime80.jsx)(import_lucide_react49.Zap, { className: "h-3.5 w-3.5 text-amber-500" }),
22624
23384
  "Automatic discount"
22625
23385
  ] }),
22626
23386
  /* @__PURE__ */ (0, import_jsx_runtime80.jsx)("p", { className: "text-xs text-gray-400 mt-0.5", children: "Applied automatically at checkout \u2014 no coupon code needed. When enabled, the coupon code field is hidden." })
@@ -22871,7 +23631,7 @@ function DiscountEditPage({ discountId }) {
22871
23631
  /* @__PURE__ */ (0, import_jsx_runtime80.jsx)("p", { className: "font-mono font-semibold text-gray-900", children: couponCode || "\u2014" })
22872
23632
  ] }),
22873
23633
  isAutomatic && /* @__PURE__ */ (0, import_jsx_runtime80.jsxs)("p", { className: "flex items-center gap-1 text-amber-600 text-xs font-medium mb-1", children: [
22874
- /* @__PURE__ */ (0, import_jsx_runtime80.jsx)(import_lucide_react48.Zap, { className: "h-3 w-3" }),
23634
+ /* @__PURE__ */ (0, import_jsx_runtime80.jsx)(import_lucide_react49.Zap, { className: "h-3 w-3" }),
22875
23635
  " Automatic \u2014 no code needed"
22876
23636
  ] }),
22877
23637
  /* @__PURE__ */ (0, import_jsx_runtime80.jsx)("p", { className: "text-gray-500 mt-2", children: "Discount" }),
@@ -22907,7 +23667,6 @@ var PAGE_MAP = {
22907
23667
  roles: RolesPage,
22908
23668
  "blog-generator": BlogGeneratorPage,
22909
23669
  schedules: SchedulesPage,
22910
- "vendor-onboard": VendorOnboardPage,
22911
23670
  "vendor-team": VendorTeamPage
22912
23671
  };
22913
23672
  var CRUD_CONFIGS = {
@@ -23063,8 +23822,8 @@ function BlogEditorWrapper({
23063
23822
  );
23064
23823
  }
23065
23824
  function AdminPageResolver({ slug }) {
23066
- const router = (0, import_navigation30.useRouter)();
23067
- const searchParams = (0, import_navigation30.useSearchParams)();
23825
+ const router = (0, import_navigation31.useRouter)();
23826
+ const searchParams = (0, import_navigation31.useSearchParams)();
23068
23827
  const { customCrudConfigs, storeEnabled } = (0, import_react68.useContext)(AdminConfigContext);
23069
23828
  const key = slug?.[0] || "dashboard";
23070
23829
  const crud = (0, import_react68.useMemo)(() => {
@@ -23107,6 +23866,10 @@ function AdminPageResolver({ slug }) {
23107
23866
  /* @__PURE__ */ (0, import_jsx_runtime83.jsx)("span", { className: "ml-2", children: "Opening Plugins\u2026" })
23108
23867
  ] });
23109
23868
  }
23869
+ if (key === "vendor-onboard") {
23870
+ const sub = slug?.[1];
23871
+ return /* @__PURE__ */ (0, import_jsx_runtime83.jsx)(VendorOnboardPage, { showCreateForm: sub === "create" });
23872
+ }
23110
23873
  const Page = PAGE_MAP[key];
23111
23874
  if (Page) {
23112
23875
  return /* @__PURE__ */ (0, import_jsx_runtime83.jsx)(Page, {});
@@ -23197,7 +23960,7 @@ function AdminPageResolver({ slug }) {
23197
23960
  // src/admin/pages/LayoutSettingsPage.tsx
23198
23961
  var import_react69 = require("react");
23199
23962
  init_button();
23200
- var import_lucide_react50 = require("lucide-react");
23963
+ var import_lucide_react51 = require("lucide-react");
23201
23964
  var import_jsx_runtime84 = require("react/jsx-runtime");
23202
23965
  function LayoutSettingsPage() {
23203
23966
  const { theme } = (0, import_react69.useContext)(AdminConfigContext);
@@ -23269,7 +24032,7 @@ function LayoutSettingsPage() {
23269
24032
  disabled: saving,
23270
24033
  className: "bg-white text-gray-800 hover:bg-gray-100 border-0 text-xs",
23271
24034
  children: [
23272
- /* @__PURE__ */ (0, import_jsx_runtime84.jsx)(import_lucide_react50.Save, { className: "h-4 w-4 mr-2" }),
24035
+ /* @__PURE__ */ (0, import_jsx_runtime84.jsx)(import_lucide_react51.Save, { className: "h-4 w-4 mr-2" }),
23273
24036
  saving ? "Saving..." : "Save"
23274
24037
  ]
23275
24038
  }