@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.
- package/README.md +12 -8
- package/dist/admin.cjs +1907 -1144
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.d.cts +7 -2
- package/dist/admin.d.ts +7 -2
- package/dist/admin.js +1955 -1191
- package/dist/admin.js.map +1 -1
- package/dist/api.cjs +2704 -914
- package/dist/api.cjs.map +1 -1
- package/dist/api.d.cts +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/api.js +2664 -875
- package/dist/api.js.map +1 -1
- package/dist/auth.cjs +251 -23
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +53 -22
- package/dist/auth.d.ts +53 -22
- package/dist/auth.js +240 -23
- package/dist/auth.js.map +1 -1
- package/dist/cli.cjs +42 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +42 -2
- package/dist/cli.js.map +1 -1
- package/dist/{index-BcMRGNjS.d.cts → index-Bf5GO8fu.d.cts} +4 -3
- package/dist/{index-Bda8D1Rm.d.ts → index-DOiJuMQA.d.ts} +4 -3
- package/dist/index.cjs +3408 -1284
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +121 -35
- package/dist/index.d.ts +121 -35
- package/dist/index.js +3334 -1226
- package/dist/index.js.map +1 -1
- package/dist/migrations/1778660000003-AddQrTokenToOrders.ts +32 -0
- package/dist/migrations/1778660000004-CreateCustomerAndCustomerContacts.ts +99 -0
- package/dist/migrations/1778660000005-AddCustomerIdToVendorCustomers.ts +39 -0
- package/dist/migrations/1778660000006-UpdateVendorCustomersContactNullable.ts +36 -0
- package/dist/migrations/1778660000007-VendorOwnerCustomerContactsPermission.ts +39 -0
- package/dist/migrations/1779100000000-ChatConversationLeadEmailSent.ts +16 -0
- package/dist/migrations/1779200000000-LlmAgentScope.ts +72 -0
- package/dist/migrations/1779300000000-VendorOwnerContactsPermission.ts +39 -0
- package/dist/migrations/1779400000000-AddCustomerContactIdToOrders.ts +33 -0
- 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
|
|
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
|
|
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
|
-
|
|
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,
|
|
825
|
-
const searchParams = (0,
|
|
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)(
|
|
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)(
|
|
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)(
|
|
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,
|
|
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
|
-
|
|
1279
|
+
import_navigation30 = require("next/navigation");
|
|
1256
1280
|
init_admin_list_return_url();
|
|
1257
|
-
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
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"
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
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
|
-
|
|
3222
|
+
Input,
|
|
3229
3223
|
{
|
|
3230
|
-
type: "
|
|
3231
|
-
|
|
3232
|
-
|
|
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
|
-
|
|
3235
|
+
Input,
|
|
3237
3236
|
{
|
|
3238
|
-
type: "
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
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
|
|
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
|
|
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.
|
|
11209
|
+
const id = Number(row.id);
|
|
11200
11210
|
if (!Number.isFinite(id)) return null;
|
|
11201
|
-
|
|
11202
|
-
|
|
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
|
-
(
|
|
11329
|
-
|
|
11330
|
-
|
|
11331
|
-
|
|
11332
|
-
|
|
11333
|
-
|
|
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
|
|
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
|
|
11563
|
-
|
|
11564
|
-
|
|
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":
|
|
11582
|
-
"contact.email":
|
|
11583
|
-
"contact.phone":
|
|
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 (
|
|
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 (
|
|
11610
|
-
const
|
|
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.
|
|
11691
|
-
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
|
|
11692
|
-
/* @__PURE__ */ (0, import_jsx_runtime55.jsxs)(
|
|
11693
|
-
|
|
11694
|
-
|
|
11695
|
-
|
|
11696
|
-
|
|
11697
|
-
|
|
11698
|
-
|
|
11699
|
-
|
|
11700
|
-
|
|
11701
|
-
|
|
11702
|
-
|
|
11703
|
-
|
|
11704
|
-
|
|
11705
|
-
|
|
11706
|
-
|
|
11707
|
-
|
|
11708
|
-
|
|
11709
|
-
|
|
11710
|
-
|
|
11711
|
-
|
|
11712
|
-
|
|
11713
|
-
/* @__PURE__ */ (0, import_jsx_runtime55.
|
|
11714
|
-
|
|
11715
|
-
|
|
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: "
|
|
11720
|
-
/* @__PURE__ */ (0, import_jsx_runtime55.jsxs)("div", { className: "
|
|
11721
|
-
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
|
|
11722
|
-
/* @__PURE__ */ (0, import_jsx_runtime55.
|
|
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
|
-
|
|
11728
|
-
/* @__PURE__ */ (0, import_jsx_runtime55.
|
|
11729
|
-
|
|
11730
|
-
|
|
11731
|
-
|
|
11732
|
-
/* @__PURE__ */ (0, import_jsx_runtime55.
|
|
11733
|
-
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
/* @__PURE__ */ (0, import_jsx_runtime55.
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11740
|
-
/* @__PURE__ */ (0, import_jsx_runtime55.
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
11744
|
-
/* @__PURE__ */ (0, import_jsx_runtime55.
|
|
11745
|
-
|
|
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
|
-
|
|
11851
|
-
|
|
11852
|
-
|
|
11853
|
-
|
|
11854
|
-
|
|
11855
|
-
|
|
11856
|
-
|
|
11857
|
-
|
|
11858
|
-
|
|
11859
|
-
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
-
|
|
16668
|
-
|
|
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(
|
|
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
|
-
|
|
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() ||
|
|
16730
|
-
const defaultSlug =
|
|
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({
|
|
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 =
|
|
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 = {
|
|
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
|
|
16931
|
-
|
|
16932
|
-
|
|
16933
|
-
|
|
16934
|
-
|
|
16935
|
-
|
|
16936
|
-
|
|
16937
|
-
|
|
16938
|
-
|
|
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
|
-
|
|
17384
|
+
if (!validateChatAgentNumbers()) return;
|
|
17385
|
+
setSaving(true);
|
|
16953
17386
|
try {
|
|
16954
|
-
const
|
|
16955
|
-
|
|
16956
|
-
|
|
16957
|
-
|
|
16958
|
-
|
|
16959
|
-
|
|
16960
|
-
|
|
16961
|
-
|
|
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
|
-
|
|
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
|
|
17399
|
+
import_sonner12.toast.error("Failed to save");
|
|
16976
17400
|
} finally {
|
|
16977
|
-
|
|
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
|
|
16984
|
-
|
|
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
|
|
17911
|
-
/* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
|
|
17912
|
-
|
|
17913
|
-
|
|
17914
|
-
|
|
17915
|
-
|
|
17916
|
-
|
|
17917
|
-
|
|
17918
|
-
|
|
17919
|
-
|
|
17920
|
-
|
|
17921
|
-
|
|
17922
|
-
|
|
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:
|
|
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:
|
|
17929
|
-
value:
|
|
17930
|
-
onChange: (e) =>
|
|
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:
|
|
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:
|
|
17942
|
-
value:
|
|
17943
|
-
|
|
17944
|
-
|
|
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-
|
|
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:
|
|
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
|
-
|
|
18450
|
+
Input,
|
|
17953
18451
|
{
|
|
17954
|
-
id:
|
|
17955
|
-
value:
|
|
17956
|
-
onChange: (e) =>
|
|
17957
|
-
|
|
17958
|
-
|
|
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: "
|
|
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:
|
|
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
|
-
|
|
18525
|
+
Textarea,
|
|
17968
18526
|
{
|
|
17969
|
-
id: "agent-
|
|
17970
|
-
value:
|
|
17971
|
-
onChange: (e) =>
|
|
17972
|
-
|
|
17973
|
-
|
|
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-
|
|
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-
|
|
17983
|
-
value:
|
|
17984
|
-
onChange: (e) =>
|
|
17985
|
-
placeholder: "e.g.
|
|
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
|
-
|
|
17992
|
-
|
|
17993
|
-
|
|
17994
|
-
|
|
17995
|
-
|
|
17996
|
-
|
|
17997
|
-
|
|
17998
|
-
|
|
17999
|
-
|
|
18000
|
-
|
|
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: "
|
|
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: "
|
|
18010
|
-
value:
|
|
18011
|
-
onChange: (e) =>
|
|
18728
|
+
id: "notify-email-system",
|
|
18729
|
+
value: notifyEmailSystem,
|
|
18730
|
+
onChange: (e) => setNotifyEmailSystem(e.target.value),
|
|
18012
18731
|
rows: 4,
|
|
18013
|
-
placeholder:
|
|
18014
|
-
className: "text-
|
|
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.
|
|
18072
|
-
|
|
18073
|
-
|
|
18074
|
-
|
|
18075
|
-
|
|
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
|
-
|
|
18104
|
-
|
|
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.
|
|
18107
|
-
"
|
|
18752
|
+
/* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Plus, { className: "h-3 w-3" }),
|
|
18753
|
+
"Add intent"
|
|
18108
18754
|
]
|
|
18109
18755
|
}
|
|
18110
|
-
)
|
|
18756
|
+
)
|
|
18111
18757
|
] }),
|
|
18112
|
-
/* @__PURE__ */ (0, import_jsx_runtime66.jsx)("
|
|
18113
|
-
|
|
18114
|
-
|
|
18115
|
-
|
|
18116
|
-
|
|
18117
|
-
|
|
18118
|
-
|
|
18119
|
-
|
|
18120
|
-
|
|
18121
|
-
|
|
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
|
-
] })
|
|
18141
|
-
|
|
18142
|
-
|
|
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
|
-
|
|
18175
|
-
|
|
18176
|
-
|
|
18177
|
-
|
|
18178
|
-
className: "
|
|
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
|
-
|
|
20541
|
-
|
|
20542
|
-
|
|
20543
|
-
|
|
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 [
|
|
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 [
|
|
20558
|
-
|
|
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
|
-
|
|
21202
|
+
setErrors([]);
|
|
20575
21203
|
setInviteLink(null);
|
|
20576
21204
|
if (activationMode === "password") {
|
|
20577
21205
|
if (!ownerPassword) {
|
|
20578
|
-
|
|
21206
|
+
setErrors(["Enter a password for the owner account"]);
|
|
20579
21207
|
setLoading(false);
|
|
20580
21208
|
return;
|
|
20581
21209
|
}
|
|
20582
21210
|
if (ownerPassword !== ownerPasswordConfirm) {
|
|
20583
|
-
|
|
21211
|
+
setErrors(["Passwords do not match"]);
|
|
20584
21212
|
setLoading(false);
|
|
20585
21213
|
return;
|
|
20586
21214
|
}
|
|
20587
21215
|
if (ownerPassword.length < 6) {
|
|
20588
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20622
|
-
|
|
20623
|
-
|
|
20624
|
-
setVendorPhone("");
|
|
20625
|
-
setOwnerName("");
|
|
20626
|
-
setOwnerEmail("");
|
|
20627
|
-
setOwnerPassword("");
|
|
20628
|
-
setOwnerPasswordConfirm("");
|
|
21249
|
+
if (!data.inviteLink) {
|
|
21250
|
+
onSuccess();
|
|
21251
|
+
}
|
|
20629
21252
|
} catch {
|
|
20630
|
-
|
|
21253
|
+
setErrors(["Request failed"]);
|
|
20631
21254
|
} finally {
|
|
20632
21255
|
setLoading(false);
|
|
20633
21256
|
}
|
|
20634
21257
|
};
|
|
20635
|
-
const submitLabel = activationMode === "password" ? "Create vendor & set password" :
|
|
20636
|
-
|
|
20637
|
-
|
|
20638
|
-
/* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
|
|
20639
|
-
|
|
20640
|
-
|
|
20641
|
-
|
|
20642
|
-
|
|
20643
|
-
|
|
20644
|
-
|
|
20645
|
-
|
|
20646
|
-
|
|
20647
|
-
|
|
20648
|
-
|
|
20649
|
-
|
|
20650
|
-
/* @__PURE__ */ (0, import_jsx_runtime76.
|
|
20651
|
-
|
|
20652
|
-
|
|
20653
|
-
|
|
20654
|
-
|
|
20655
|
-
|
|
20656
|
-
|
|
20657
|
-
|
|
20658
|
-
|
|
20659
|
-
|
|
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.
|
|
20671
|
-
|
|
20672
|
-
|
|
20673
|
-
|
|
20674
|
-
|
|
20675
|
-
|
|
20676
|
-
|
|
20677
|
-
className: "
|
|
20678
|
-
children: [
|
|
20679
|
-
/* @__PURE__ */ (0, import_jsx_runtime76.jsxs)("div", {
|
|
20680
|
-
/* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
|
|
20681
|
-
/* @__PURE__ */ (0, import_jsx_runtime76.
|
|
20682
|
-
|
|
20683
|
-
|
|
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", {
|
|
20687
|
-
/* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
|
|
20688
|
-
/* @__PURE__ */ (0, import_jsx_runtime76.
|
|
20689
|
-
|
|
20690
|
-
|
|
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)("
|
|
20724
|
-
/* @__PURE__ */ (0, import_jsx_runtime76.jsx)(
|
|
20725
|
-
/* @__PURE__ */ (0, import_jsx_runtime76.
|
|
20726
|
-
|
|
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
|
-
|
|
20729
|
-
|
|
20730
|
-
|
|
20731
|
-
|
|
20732
|
-
|
|
20733
|
-
|
|
20734
|
-
|
|
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
|
-
|
|
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
|
|
21991
|
+
var import_navigation28 = require("next/navigation");
|
|
21263
21992
|
init_admin_list_return_url();
|
|
21264
|
-
var
|
|
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)(
|
|
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,
|
|
21369
|
-
const searchParams = (0,
|
|
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)(
|
|
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)(
|
|
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:
|
|
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)(
|
|
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)(
|
|
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
|
|
22326
|
+
var import_navigation29 = require("next/navigation");
|
|
21598
22327
|
init_admin_list_return_url();
|
|
21599
|
-
var
|
|
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
|
|
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 "
|
|
21623
|
-
return { ...base, type: "
|
|
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: "
|
|
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)(
|
|
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)(
|
|
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)(
|
|
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)(
|
|
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)(
|
|
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)(
|
|
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)(
|
|
22016
|
-
minQuantity: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
|
|
22017
|
-
|
|
22018
|
-
nthOrder: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
|
|
22019
|
-
userType: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
|
|
22020
|
-
paymentMethod: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
|
|
22021
|
-
category: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
|
|
22022
|
-
customers: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
|
|
22023
|
-
shipping: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
|
|
22024
|
-
referral: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
|
|
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: "
|
|
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 === "
|
|
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
|
|
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)(
|
|
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)(
|
|
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)(
|
|
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 === "
|
|
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)(
|
|
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)(
|
|
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)(
|
|
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)(
|
|
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,
|
|
22429
|
-
const searchParams = (0,
|
|
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:
|
|
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)(
|
|
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)(
|
|
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)(
|
|
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,
|
|
23067
|
-
const searchParams = (0,
|
|
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
|
|
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)(
|
|
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
|
}
|