@infuro/cms-core 1.0.45 → 1.0.46
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 +19 -2
- package/dist/admin.cjs +873 -214
- package/dist/admin.d.cts +7 -1
- package/dist/admin.d.ts +7 -1
- package/dist/admin.js +874 -215
- package/dist/api.cjs +40 -37
- package/dist/api.d.cts +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/api.js +8 -5
- package/dist/auth.cjs +23 -23
- package/dist/auth.d.cts +1 -0
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +2 -2
- package/dist/chunk-2KUCQVAQ.js +936 -0
- package/dist/chunk-3K5AIEIZ.js +256 -0
- package/dist/{chunk-I6NH72MO.js → chunk-57O3HZPG.js} +9 -1
- package/dist/chunk-7PMHZRF3.cjs +872 -0
- package/dist/chunk-BXYZDMTZ.cjs +953 -0
- package/dist/chunk-CQHXW4TR.js +107 -0
- package/dist/{chunk-NNQBWDCI.js → chunk-CRYKVJTO.js} +55 -959
- package/dist/chunk-DBPSJYLZ.js +47 -0
- package/dist/{chunk-TAHX46EI.cjs → chunk-FBKDMRQL.cjs} +9 -1
- package/dist/chunk-GUSHM5HN.js +862 -0
- package/dist/chunk-HY3AXLOI.cjs +265 -0
- package/dist/chunk-L3525VXI.js +243 -0
- package/dist/{chunk-ZMRQ72F6.cjs → chunk-NBY4NVTY.cjs} +3 -3
- package/dist/chunk-OY2YTBMP.cjs +50 -0
- package/dist/{chunk-BLR6H5GL.js → chunk-SF2XKMCI.js} +3 -3
- package/dist/{chunk-22FFHLTO.cjs → chunk-TCSGFAWB.cjs} +56 -970
- package/dist/{chunk-F5XFHYTG.cjs → chunk-UDVLFVEC.cjs} +661 -382
- package/dist/{chunk-MVXLRANV.js → chunk-UKC3HYXI.js} +591 -323
- package/dist/chunk-V25RDUOU.cjs +248 -0
- package/dist/chunk-XMRMZ6NQ.cjs +109 -0
- package/dist/cli.cjs +9 -4
- package/dist/cli.js +9 -4
- package/dist/{email-queue-TPZWLTIV.cjs → email-queue-6NJY6HNM.cjs} +7 -5
- package/dist/email-queue-OI2HSCGE.js +4 -0
- package/dist/erp-order-invoice-3GXDE443.js +3 -0
- package/dist/erp-order-invoice-EYYNR526.cjs +24 -0
- package/dist/index.cjs +391 -364
- package/dist/index.d.cts +267 -14
- package/dist/index.d.ts +267 -14
- package/dist/index.js +12 -110
- package/dist/migrations/1781810000000-OrderItemsVariantId.ts +44 -0
- package/dist/migrations/1781900000000-UserInviteStatusToken.ts +46 -0
- package/dist/migrations/1782000000000-ComboSlug.ts +58 -0
- package/dist/migrations/1782100000000-DiscountDeviceType.ts +20 -0
- package/dist/order-inventory-2MYRKEPC.cjs +39 -0
- package/dist/order-inventory-76V4XIGW.js +2 -0
- package/dist/order-notification-dispatcher-SBQAMM5V.cjs +21 -0
- package/dist/{order-notification-dispatcher-XWIJYDGA.js → order-notification-dispatcher-ZJZB2HUN.js} +5 -2
- package/dist/retire-soft-deleted-unique-5VXBA5XX.cjs +15 -0
- package/dist/retire-soft-deleted-unique-NXQIH4BC.js +2 -0
- package/dist/send-order-placed-emails-B5ZVJT7T.cjs +15 -0
- package/dist/send-order-placed-emails-WPI37KRZ.js +6 -0
- package/package.json +5 -4
- package/src/admin/admin.css +27 -24
- package/dist/chunk-5ELSW2UP.js +0 -150
- package/dist/chunk-PNOKNSG2.cjs +0 -222
- package/dist/chunk-WEMDMVHQ.js +0 -216
- package/dist/chunk-XW5ATPRW.cjs +0 -155
- package/dist/email-queue-CGZBVVAS.js +0 -2
- package/dist/erp-order-invoice-3YISSOWW.js +0 -3
- package/dist/erp-order-invoice-ZR5F2KTA.cjs +0 -12
- package/dist/order-notification-dispatcher-RU7BTGGX.cjs +0 -18
package/dist/admin.js
CHANGED
|
@@ -10,7 +10,7 @@ import { Slot } from '@radix-ui/react-slot';
|
|
|
10
10
|
import { cva } from 'class-variance-authority';
|
|
11
11
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
12
12
|
import * as LucideIcons from 'lucide-react';
|
|
13
|
-
import { ChevronDown, User, Settings, LogOut, LayoutDashboard, Store, FolderTree, Layers, Building2, CalendarDays, ShoppingBag, Package, Receipt, BadgePercent, ShoppingCart, CreditCard, Users, Inbox, File, LinkIcon, ClipboardList, MessageSquare, Image as Image$1, FileText, Shield, Puzzle, X, Plus, MoreVertical, Upload, Download, Edit, Copy, RefreshCw, Trash2,
|
|
13
|
+
import { ChevronDown, User, Settings, LogOut, LayoutDashboard, Store, FolderTree, Layers, Building2, CalendarDays, ShoppingBag, Package, Receipt, BadgePercent, ShoppingCart, CreditCard, Users, Inbox, File, LinkIcon, ClipboardList, MessageSquare, Image as Image$1, FileText, Shield, Puzzle, X, Plus, MoreVertical, Upload, Download, Edit, Copy, RefreshCw, Trash2, Mail, Check, Save, Search, AlertCircle, MapPin, BarChart3, TrendingUp, UserCheck, Activity, Globe, Eye, MousePointer, ArrowLeftRight, RotateCcw, KeyRound, Loader2, CalendarClock, Rss, ArrowLeft, Pencil, FolderPlus, LayoutGrid, List, Move, Archive, Edit2, ExternalLink, Power, Star, ChevronUp, Zap, Filter, CheckCircle2, ImageIcon, GripVertical, ChevronRight, ArrowRight, ArrowUpRight, IndianRupee, XCircle, Clock, Play, MessageCircle, Bot, FileUp, Tag, Box, SlidersHorizontal, Calculator, Link2 as Link2$1, Unlink, Gift, MoreHorizontal, Bell, QrCode, Car, Circle, Settings2, DollarSign, Hash, ChevronLeft, Share2, Smartphone, HardDrive, Folder, Film } from 'lucide-react';
|
|
14
14
|
import Image from 'next/image';
|
|
15
15
|
import Link2 from 'next/link';
|
|
16
16
|
import { usePathname, useSearchParams, useRouter } from 'next/navigation';
|
|
@@ -459,7 +459,7 @@ var init_admin_config_context = __esm({
|
|
|
459
459
|
var CMS_VERSION;
|
|
460
460
|
var init_cms_version = __esm({
|
|
461
461
|
"src/lib/cms-version.ts"() {
|
|
462
|
-
CMS_VERSION = "1.0.
|
|
462
|
+
CMS_VERSION = "1.0.46" ;
|
|
463
463
|
}
|
|
464
464
|
});
|
|
465
465
|
function useCatalogCategories(enabled = true) {
|
|
@@ -1061,8 +1061,10 @@ var init_admin_theme = __esm({
|
|
|
1061
1061
|
"src/admin/admin-theme.ts"() {
|
|
1062
1062
|
ADMIN_THEME_CSS = `
|
|
1063
1063
|
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap");
|
|
1064
|
+
html{color-scheme:light}
|
|
1064
1065
|
:root{--background:0 0% 100%;--foreground:0 0% 9%;--card:0 0% 100%;--card-foreground:0 0% 9%;--popover:0 0% 100%;--popover-foreground:0 0% 9%;--primary:0 0% 9%;--primary-foreground:0 0% 98%;--secondary:0 0% 96.1%;--secondary-foreground:0 0% 9%;--muted:0 0% 96.1%;--muted-foreground:0 0% 45.1%;--accent:0 0% 96.1%;--accent-foreground:0 0% 9%;--destructive:0 84.2% 60.2%;--destructive-foreground:0 0% 98%;--border:0 0% 89.8%;--input:0 0% 89.8%;--ring:0 0% 3.9%;--chart-1:0 0% 40%;--chart-2:0 0% 55%;--chart-3:0 0% 70%;--chart-4:0 0% 45%;--chart-5:0 0% 60%;--radius:0.375rem;--sidebar-background:0 0% 100%;--sidebar-foreground:0 0% 9%;--sidebar-primary:0 0% 9%;--sidebar-primary-foreground:0 0% 98%;--sidebar-accent:0 0% 96.1%;--sidebar-accent-foreground:0 0% 9%;--sidebar-border:0 0% 89.8%;--sidebar-ring:0 0% 9%}
|
|
1065
|
-
|
|
1066
|
+
/* Dark mode disabled: keep light tokens even if a host app adds .dark on <html> */
|
|
1067
|
+
.dark{--background:0 0% 100%;--foreground:0 0% 9%;--card:0 0% 100%;--card-foreground:0 0% 9%;--popover:0 0% 100%;--popover-foreground:0 0% 9%;--primary:0 0% 9%;--primary-foreground:0 0% 98%;--secondary:0 0% 96.1%;--secondary-foreground:0 0% 9%;--muted:0 0% 96.1%;--muted-foreground:0 0% 45.1%;--accent:0 0% 96.1%;--accent-foreground:0 0% 9%;--destructive:0 84.2% 60.2%;--destructive-foreground:0 0% 98%;--border:0 0% 89.8%;--input:0 0% 89.8%;--ring:0 0% 3.9%;--sidebar-background:0 0% 100%;--sidebar-foreground:0 0% 9%;--sidebar-primary:0 0% 9%;--sidebar-primary-foreground:0 0% 98%;--sidebar-accent:0 0% 96.1%;--sidebar-accent-foreground:0 0% 9%;--sidebar-border:0 0% 89.8%;--sidebar-ring:0 0% 9%}
|
|
1066
1068
|
*,*::before,*::after{border-color:hsl(var(--border))}
|
|
1067
1069
|
body{font-family:"Inter",ui-sans-serif,system-ui,sans-serif;font-size:14px}
|
|
1068
1070
|
.admin-app{font-size:13.5px}
|
|
@@ -1177,6 +1179,39 @@ var init_auth_debug = __esm({
|
|
|
1177
1179
|
__name(summarizeSessionUserForLog, "summarizeSessionUserForLog");
|
|
1178
1180
|
}
|
|
1179
1181
|
});
|
|
1182
|
+
function useForceAdminLightMode() {
|
|
1183
|
+
useEffect(() => {
|
|
1184
|
+
const root = document.documentElement;
|
|
1185
|
+
const hadDark = root.classList.contains("dark");
|
|
1186
|
+
const prevColorScheme = root.style.colorScheme;
|
|
1187
|
+
const applyLight = /* @__PURE__ */ __name(() => {
|
|
1188
|
+
root.classList.remove("dark");
|
|
1189
|
+
root.style.colorScheme = "light";
|
|
1190
|
+
}, "applyLight");
|
|
1191
|
+
applyLight();
|
|
1192
|
+
const observer = new MutationObserver(() => {
|
|
1193
|
+
if (root.classList.contains("dark")) applyLight();
|
|
1194
|
+
});
|
|
1195
|
+
observer.observe(root, {
|
|
1196
|
+
attributes: true,
|
|
1197
|
+
attributeFilter: [
|
|
1198
|
+
"class"
|
|
1199
|
+
]
|
|
1200
|
+
});
|
|
1201
|
+
return () => {
|
|
1202
|
+
observer.disconnect();
|
|
1203
|
+
root.style.colorScheme = prevColorScheme;
|
|
1204
|
+
if (hadDark) root.classList.add("dark");
|
|
1205
|
+
else root.classList.remove("dark");
|
|
1206
|
+
};
|
|
1207
|
+
}, []);
|
|
1208
|
+
}
|
|
1209
|
+
var init_force_light_mode = __esm({
|
|
1210
|
+
"src/admin/force-light-mode.ts"() {
|
|
1211
|
+
"use client";
|
|
1212
|
+
__name(useForceAdminLightMode, "useForceAdminLightMode");
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1180
1215
|
function useAdminViewSettings() {
|
|
1181
1216
|
useEffect(() => {
|
|
1182
1217
|
fetch("/api/settings/admin_view").then((r) => r.ok ? r.json() : null).then((data) => {
|
|
@@ -1198,6 +1233,7 @@ function AdminLayoutInner({ children }) {
|
|
|
1198
1233
|
const isMobile = useIsMobile();
|
|
1199
1234
|
const vendorBlockRedirectRef = useRef(false);
|
|
1200
1235
|
useAdminViewSettings();
|
|
1236
|
+
useForceAdminLightMode();
|
|
1201
1237
|
const isPublicPath = PUBLIC_ADMIN_PATHS.includes(pathname);
|
|
1202
1238
|
const isVendorOnlySession = session?.user?.isVendorPortal === true && session?.user?.isRBACAdmin !== true;
|
|
1203
1239
|
useEffect(() => {
|
|
@@ -1486,6 +1522,7 @@ var init_AdminLayout = __esm({
|
|
|
1486
1522
|
init_admin_config_context();
|
|
1487
1523
|
init_builtin_plugin_descriptors();
|
|
1488
1524
|
init_auth_debug();
|
|
1525
|
+
init_force_light_mode();
|
|
1489
1526
|
PUBLIC_ADMIN_PATHS = [
|
|
1490
1527
|
"/admin/signin",
|
|
1491
1528
|
"/admin/forgot-password",
|
|
@@ -1736,6 +1773,225 @@ var init_file_upload = __esm({
|
|
|
1736
1773
|
__name(FileUpload, "FileUpload");
|
|
1737
1774
|
}
|
|
1738
1775
|
});
|
|
1776
|
+
|
|
1777
|
+
// src/lib/media-url.ts
|
|
1778
|
+
function isVideoUploadMime(mime) {
|
|
1779
|
+
const t = String(mime ?? "").trim().toLowerCase();
|
|
1780
|
+
return VIDEO_UPLOAD_MIME_TYPES.includes(t);
|
|
1781
|
+
}
|
|
1782
|
+
function isVideoMediaUrl(url) {
|
|
1783
|
+
if (!url || typeof url !== "string") return false;
|
|
1784
|
+
const trimmed = url.trim();
|
|
1785
|
+
if (!trimmed) return false;
|
|
1786
|
+
try {
|
|
1787
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
1788
|
+
return VIDEO_EXT_RE.test(new URL(trimmed).pathname);
|
|
1789
|
+
}
|
|
1790
|
+
} catch {
|
|
1791
|
+
}
|
|
1792
|
+
const path = trimmed.split("?")[0]?.split("#")[0] ?? trimmed;
|
|
1793
|
+
return VIDEO_EXT_RE.test(path);
|
|
1794
|
+
}
|
|
1795
|
+
var VIDEO_EXT_RE, IMAGE_UPLOAD_MIME_TYPES, VIDEO_UPLOAD_MIME_TYPES, MEDIA_UPLOAD_MIME_TYPES, VIDEO_UPLOAD_MAX_BYTES;
|
|
1796
|
+
var init_media_url = __esm({
|
|
1797
|
+
"src/lib/media-url.ts"() {
|
|
1798
|
+
VIDEO_EXT_RE = /\.(mp4|webm|mov)(?:$|[?#])/i;
|
|
1799
|
+
IMAGE_UPLOAD_MIME_TYPES = [
|
|
1800
|
+
"image/jpeg",
|
|
1801
|
+
"image/png",
|
|
1802
|
+
"image/gif",
|
|
1803
|
+
"image/webp"
|
|
1804
|
+
];
|
|
1805
|
+
VIDEO_UPLOAD_MIME_TYPES = [
|
|
1806
|
+
"video/mp4",
|
|
1807
|
+
"video/webm",
|
|
1808
|
+
"video/quicktime"
|
|
1809
|
+
];
|
|
1810
|
+
MEDIA_UPLOAD_MIME_TYPES = [
|
|
1811
|
+
...IMAGE_UPLOAD_MIME_TYPES,
|
|
1812
|
+
...VIDEO_UPLOAD_MIME_TYPES
|
|
1813
|
+
];
|
|
1814
|
+
VIDEO_UPLOAD_MAX_BYTES = 100 * 1024 * 1024;
|
|
1815
|
+
__name(isVideoUploadMime, "isVideoUploadMime");
|
|
1816
|
+
__name(isVideoMediaUrl, "isVideoMediaUrl");
|
|
1817
|
+
}
|
|
1818
|
+
});
|
|
1819
|
+
function ImageOrUrlField({ label, value, onChange, placeholder = "https://\u2026", inputClassName = "w-full rounded-md border border-gray-300 px-2 py-1.5 text-sm", labelClassName = "block text-xs font-medium text-gray-600 mb-1", previewVariant = "banner", maxSizeMb = 10 }) {
|
|
1820
|
+
const fileInputRef = useRef(null);
|
|
1821
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
1822
|
+
const [error, setError] = useState(null);
|
|
1823
|
+
const [lightboxOpen, setLightboxOpen] = useState(false);
|
|
1824
|
+
const previewCls = previewVariant === "logo" ? "h-20 w-20 rounded-md border border-gray-200 bg-white object-contain cursor-pointer" : "max-h-32 w-full rounded-md border border-gray-200 bg-gray-50 object-cover cursor-pointer";
|
|
1825
|
+
const closeLightbox = useCallback(() => setLightboxOpen(false), []);
|
|
1826
|
+
useEffect(() => {
|
|
1827
|
+
if (!lightboxOpen) return;
|
|
1828
|
+
const onKeyDown = /* @__PURE__ */ __name((e) => {
|
|
1829
|
+
if (e.key === "Escape") closeLightbox();
|
|
1830
|
+
}, "onKeyDown");
|
|
1831
|
+
window.addEventListener("keydown", onKeyDown);
|
|
1832
|
+
const prevOverflow = document.body.style.overflow;
|
|
1833
|
+
document.body.style.overflow = "hidden";
|
|
1834
|
+
return () => {
|
|
1835
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
1836
|
+
document.body.style.overflow = prevOverflow;
|
|
1837
|
+
};
|
|
1838
|
+
}, [
|
|
1839
|
+
lightboxOpen,
|
|
1840
|
+
closeLightbox
|
|
1841
|
+
]);
|
|
1842
|
+
const handleUpload = useCallback(async (file) => {
|
|
1843
|
+
setError(null);
|
|
1844
|
+
if (!MEDIA_UPLOAD_MIME_TYPES.includes(file.type)) {
|
|
1845
|
+
setError("Unsupported type. Use JPG, PNG, GIF, WEBP, or video MP4 / WebM / MOV");
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
const isVideo2 = isVideoUploadMime(file.type);
|
|
1849
|
+
const limit = isVideo2 ? VIDEO_UPLOAD_MAX_BYTES : maxSizeMb * 1024 * 1024;
|
|
1850
|
+
if (file.size > limit) {
|
|
1851
|
+
setError(isVideo2 ? `Video must be under ${VIDEO_UPLOAD_MAX_BYTES / (1024 * 1024)}MB` : `File must be under ${Math.round(limit / (1024 * 1024))}MB`);
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
setIsUploading(true);
|
|
1855
|
+
try {
|
|
1856
|
+
const formData = new FormData();
|
|
1857
|
+
formData.append("file", file);
|
|
1858
|
+
const response = await fetch("/api/upload", {
|
|
1859
|
+
method: "POST",
|
|
1860
|
+
body: formData
|
|
1861
|
+
});
|
|
1862
|
+
const data = await response.json();
|
|
1863
|
+
if (!response.ok) {
|
|
1864
|
+
throw new Error(data.error || data.details || "Upload failed");
|
|
1865
|
+
}
|
|
1866
|
+
onChange(data.filePath ?? "");
|
|
1867
|
+
} catch (err) {
|
|
1868
|
+
setError(err instanceof Error ? err.message : "Upload failed");
|
|
1869
|
+
} finally {
|
|
1870
|
+
setIsUploading(false);
|
|
1871
|
+
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
1872
|
+
}
|
|
1873
|
+
}, [
|
|
1874
|
+
maxSizeMb,
|
|
1875
|
+
onChange
|
|
1876
|
+
]);
|
|
1877
|
+
const onFileChange = useCallback((e) => {
|
|
1878
|
+
const file = e.target.files?.[0];
|
|
1879
|
+
if (file) void handleUpload(file);
|
|
1880
|
+
}, [
|
|
1881
|
+
handleUpload
|
|
1882
|
+
]);
|
|
1883
|
+
const trimmed = value.trim();
|
|
1884
|
+
const isVideo = isVideoMediaUrl(trimmed);
|
|
1885
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
1886
|
+
className: "space-y-2"
|
|
1887
|
+
}, /* @__PURE__ */ React.createElement("label", {
|
|
1888
|
+
className: labelClassName
|
|
1889
|
+
}, label), trimmed ? /* @__PURE__ */ React.createElement("div", {
|
|
1890
|
+
className: "flex items-start gap-3"
|
|
1891
|
+
}, isVideo ? (
|
|
1892
|
+
// eslint-disable-next-line jsx-a11y/media-has-caption
|
|
1893
|
+
/* @__PURE__ */ React.createElement("video", {
|
|
1894
|
+
src: trimmed,
|
|
1895
|
+
className: previewCls,
|
|
1896
|
+
muted: true,
|
|
1897
|
+
playsInline: true,
|
|
1898
|
+
preload: "metadata",
|
|
1899
|
+
onClick: /* @__PURE__ */ __name(() => setLightboxOpen(true), "onClick"),
|
|
1900
|
+
title: "Click to enlarge"
|
|
1901
|
+
})
|
|
1902
|
+
) : (
|
|
1903
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
1904
|
+
/* @__PURE__ */ React.createElement("img", {
|
|
1905
|
+
src: trimmed,
|
|
1906
|
+
alt: label,
|
|
1907
|
+
className: previewCls,
|
|
1908
|
+
role: "button",
|
|
1909
|
+
tabIndex: 0,
|
|
1910
|
+
title: "Click to enlarge",
|
|
1911
|
+
onClick: /* @__PURE__ */ __name(() => setLightboxOpen(true), "onClick"),
|
|
1912
|
+
onKeyDown: /* @__PURE__ */ __name((e) => {
|
|
1913
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1914
|
+
e.preventDefault();
|
|
1915
|
+
setLightboxOpen(true);
|
|
1916
|
+
}
|
|
1917
|
+
}, "onKeyDown"),
|
|
1918
|
+
onError: /* @__PURE__ */ __name((e) => {
|
|
1919
|
+
e.currentTarget.style.display = "none";
|
|
1920
|
+
}, "onError")
|
|
1921
|
+
})
|
|
1922
|
+
), /* @__PURE__ */ React.createElement("button", {
|
|
1923
|
+
type: "button",
|
|
1924
|
+
onClick: /* @__PURE__ */ __name(() => onChange(""), "onClick"),
|
|
1925
|
+
className: "inline-flex items-center gap-1 rounded border border-gray-300 bg-white px-2 py-1 text-xs text-gray-600 hover:bg-gray-50"
|
|
1926
|
+
}, /* @__PURE__ */ React.createElement(X, {
|
|
1927
|
+
className: "h-3 w-3"
|
|
1928
|
+
}), "Clear")) : null, /* @__PURE__ */ React.createElement("div", {
|
|
1929
|
+
className: "flex flex-wrap gap-2"
|
|
1930
|
+
}, /* @__PURE__ */ React.createElement("input", {
|
|
1931
|
+
type: "text",
|
|
1932
|
+
inputMode: "url",
|
|
1933
|
+
autoComplete: "off",
|
|
1934
|
+
value,
|
|
1935
|
+
onChange: /* @__PURE__ */ __name((e) => {
|
|
1936
|
+
setError(null);
|
|
1937
|
+
onChange(e.target.value);
|
|
1938
|
+
}, "onChange"),
|
|
1939
|
+
placeholder,
|
|
1940
|
+
className: `${inputClassName} min-w-0 flex-1`
|
|
1941
|
+
}), /* @__PURE__ */ React.createElement("input", {
|
|
1942
|
+
ref: fileInputRef,
|
|
1943
|
+
type: "file",
|
|
1944
|
+
accept: ACCEPT,
|
|
1945
|
+
onChange: onFileChange,
|
|
1946
|
+
className: "hidden",
|
|
1947
|
+
disabled: isUploading
|
|
1948
|
+
}), /* @__PURE__ */ React.createElement("button", {
|
|
1949
|
+
type: "button",
|
|
1950
|
+
onClick: /* @__PURE__ */ __name(() => fileInputRef.current?.click(), "onClick"),
|
|
1951
|
+
disabled: isUploading,
|
|
1952
|
+
className: "inline-flex shrink-0 items-center gap-1.5 rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50"
|
|
1953
|
+
}, /* @__PURE__ */ React.createElement(Upload, {
|
|
1954
|
+
className: "h-3.5 w-3.5"
|
|
1955
|
+
}), isUploading ? "Uploading\u2026" : "Upload")), error ? /* @__PURE__ */ React.createElement("p", {
|
|
1956
|
+
className: "flex items-center gap-1.5 text-xs text-red-600"
|
|
1957
|
+
}, /* @__PURE__ */ React.createElement(AlertCircle, {
|
|
1958
|
+
className: "h-3.5 w-3.5 shrink-0"
|
|
1959
|
+
}), error) : /* @__PURE__ */ React.createElement("p", {
|
|
1960
|
+
className: "text-xs text-gray-500"
|
|
1961
|
+
}, "Paste a URL or upload an image (PNG, JPG, GIF, WEBP up to ", maxSizeMb, "MB) or video (MP4, WebM, MOV up to", " ", VIDEO_UPLOAD_MAX_BYTES / (1024 * 1024), "MB)"), lightboxOpen && trimmed ? /* @__PURE__ */ React.createElement("div", {
|
|
1962
|
+
className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/70 p-4",
|
|
1963
|
+
role: "dialog",
|
|
1964
|
+
"aria-modal": "true",
|
|
1965
|
+
"aria-label": `${label} preview`,
|
|
1966
|
+
onClick: closeLightbox
|
|
1967
|
+
}, isVideo ? (
|
|
1968
|
+
// eslint-disable-next-line jsx-a11y/media-has-caption
|
|
1969
|
+
/* @__PURE__ */ React.createElement("video", {
|
|
1970
|
+
src: trimmed,
|
|
1971
|
+
controls: true,
|
|
1972
|
+
autoPlay: true,
|
|
1973
|
+
className: "max-h-[90vh] max-w-[90vw] rounded-md shadow-lg",
|
|
1974
|
+
onClick: /* @__PURE__ */ __name((e) => e.stopPropagation(), "onClick")
|
|
1975
|
+
})
|
|
1976
|
+
) : (
|
|
1977
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
1978
|
+
/* @__PURE__ */ React.createElement("img", {
|
|
1979
|
+
src: trimmed,
|
|
1980
|
+
alt: label,
|
|
1981
|
+
className: "max-h-[90vh] max-w-[90vw] rounded-md object-contain shadow-lg",
|
|
1982
|
+
onClick: /* @__PURE__ */ __name((e) => e.stopPropagation(), "onClick")
|
|
1983
|
+
})
|
|
1984
|
+
)) : null);
|
|
1985
|
+
}
|
|
1986
|
+
var ACCEPT;
|
|
1987
|
+
var init_ImageOrUrlField = __esm({
|
|
1988
|
+
"src/components/Admin/ImageOrUrlField.tsx"() {
|
|
1989
|
+
"use client";
|
|
1990
|
+
init_media_url();
|
|
1991
|
+
ACCEPT = MEDIA_UPLOAD_MIME_TYPES.join(",");
|
|
1992
|
+
__name(ImageOrUrlField, "ImageOrUrlField");
|
|
1993
|
+
}
|
|
1994
|
+
});
|
|
1739
1995
|
function RelationAutocomplete({ apiEndpoint, value, onChange, labelField = "name", valueField = "id", placeholder = "Select\u2026", disabled = false, queryParams }) {
|
|
1740
1996
|
const [open, setOpen] = useState(false);
|
|
1741
1997
|
const [search, setSearch] = useState("");
|
|
@@ -2324,12 +2580,7 @@ function CreateEditForm({ isOpen, onClose, onSaveSuccess, apiEndpoint, columns,
|
|
|
2324
2580
|
return;
|
|
2325
2581
|
}
|
|
2326
2582
|
const result = await res.json();
|
|
2327
|
-
|
|
2328
|
-
toast.success(`User invite created! Invite link: ${result.inviteLink}
|
|
2329
|
-
|
|
2330
|
-
Note: ${result.note}`);
|
|
2331
|
-
}
|
|
2332
|
-
onSaveSuccess?.();
|
|
2583
|
+
onSaveSuccess?.(result);
|
|
2333
2584
|
onClose();
|
|
2334
2585
|
} catch (error) {
|
|
2335
2586
|
console.error("Error saving data:", error);
|
|
@@ -2339,6 +2590,7 @@ Note: ${result.note}`);
|
|
|
2339
2590
|
const visibleColumns = columns.filter((col) => {
|
|
2340
2591
|
if (col.hideInForm) return false;
|
|
2341
2592
|
if (!existingData && col.hideInCreate) return false;
|
|
2593
|
+
if (existingData && col.hideInEdit) return false;
|
|
2342
2594
|
return true;
|
|
2343
2595
|
});
|
|
2344
2596
|
const isFieldDisabled = /* @__PURE__ */ __name((path) => {
|
|
@@ -2375,7 +2627,7 @@ Note: ${result.note}`);
|
|
|
2375
2627
|
const selectValue = optionSet && rawStr !== "" && optionSet.has(rawStr) ? rawStr : void 0;
|
|
2376
2628
|
return /* @__PURE__ */ React26__default.createElement(React26__default.Fragment, {
|
|
2377
2629
|
key: path
|
|
2378
|
-
}, /* @__PURE__ */ React26__default.createElement("div", null, /* @__PURE__ */ React26__default.createElement(Label3, {
|
|
2630
|
+
}, /* @__PURE__ */ React26__default.createElement("div", null, col.type !== "image" && /* @__PURE__ */ React26__default.createElement(Label3, {
|
|
2379
2631
|
className: "block text-sm font-medium mb-1"
|
|
2380
2632
|
}, col.displayName, [
|
|
2381
2633
|
"subtotal",
|
|
@@ -2502,6 +2754,16 @@ Note: ${result.note}`);
|
|
|
2502
2754
|
disabled
|
|
2503
2755
|
}) : col.type === "file" ? /* @__PURE__ */ React26__default.createElement(FileUpload, {
|
|
2504
2756
|
onUploadSuccess: /* @__PURE__ */ __name((url) => handleFileUpload(path, url), "onUploadSuccess")
|
|
2757
|
+
}) : col.type === "image" ? /* @__PURE__ */ React26__default.createElement(ImageOrUrlField, {
|
|
2758
|
+
label: col.displayName,
|
|
2759
|
+
value: typeof formData[path] === "string" ? formData[path] : "",
|
|
2760
|
+
onChange: /* @__PURE__ */ __name((url) => setFormData((prev) => ({
|
|
2761
|
+
...prev,
|
|
2762
|
+
[path]: url
|
|
2763
|
+
})), "onChange"),
|
|
2764
|
+
placeholder: col.placeholder ?? "Image URL or upload",
|
|
2765
|
+
previewVariant: "logo",
|
|
2766
|
+
labelClassName: "block text-sm font-medium mb-1"
|
|
2505
2767
|
}) : /* @__PURE__ */ React26__default.createElement(Input, {
|
|
2506
2768
|
type: col.type || "text",
|
|
2507
2769
|
placeholder: [
|
|
@@ -2547,6 +2809,7 @@ var init_CreateEditForm = __esm({
|
|
|
2547
2809
|
init_switch();
|
|
2548
2810
|
init_label();
|
|
2549
2811
|
init_file_upload();
|
|
2812
|
+
init_ImageOrUrlField();
|
|
2550
2813
|
init_RelationAutocomplete();
|
|
2551
2814
|
init_RelationMultiSelect();
|
|
2552
2815
|
init_use_mobile();
|
|
@@ -3898,11 +4161,7 @@ function formatArrayListCell(value, col) {
|
|
|
3898
4161
|
return items.join(", ");
|
|
3899
4162
|
}
|
|
3900
4163
|
function formatCellValue(value, col) {
|
|
3901
|
-
if (col.type === "boolean") return value ?
|
|
3902
|
-
className: "h-4 w-4 text-green-600"
|
|
3903
|
-
}) : /* @__PURE__ */ React.createElement(X, {
|
|
3904
|
-
className: "h-4 w-4 text-red-600"
|
|
3905
|
-
});
|
|
4164
|
+
if (col.type === "boolean") return value ? "Yes" : "No";
|
|
3906
4165
|
if (col.type === "date") return formatDate(value);
|
|
3907
4166
|
if (col.type === "datetime") return formatDateTime(value);
|
|
3908
4167
|
if (col.type === "multiSelect" || Array.isArray(value) || typeof value === "string" && value.trim().startsWith("[")) {
|
|
@@ -4083,6 +4342,9 @@ function AdminCRUD({ title, apiEndpoint, columns, addEditPageUrl, customViewPage
|
|
|
4083
4342
|
const [bulkDialogOpen, setBulkDialogOpen] = useState(false);
|
|
4084
4343
|
const [isRefetching, setIsRefetching] = useState(false);
|
|
4085
4344
|
const [roleOptions, setRoleOptions] = useState([]);
|
|
4345
|
+
const [inviteDialog, setInviteDialog] = useState(null);
|
|
4346
|
+
const [sendingInviteEmail, setSendingInviteEmail] = useState(false);
|
|
4347
|
+
const [linkCopied, setLinkCopied] = useState(false);
|
|
4086
4348
|
const hasLoadedRef = useRef(false);
|
|
4087
4349
|
const isMobile = useIsMobile();
|
|
4088
4350
|
const showGroupColumn = !!manageUserGroups && roleOptions.length > 0;
|
|
@@ -4541,22 +4803,77 @@ function AdminCRUD({ title, apiEndpoint, columns, addEditPageUrl, customViewPage
|
|
|
4541
4803
|
method: "POST",
|
|
4542
4804
|
headers: {
|
|
4543
4805
|
"Content-Type": "application/json"
|
|
4544
|
-
}
|
|
4806
|
+
},
|
|
4807
|
+
body: JSON.stringify({
|
|
4808
|
+
sendEmail: true,
|
|
4809
|
+
rotate: true
|
|
4810
|
+
})
|
|
4545
4811
|
});
|
|
4546
4812
|
if (!res.ok) {
|
|
4547
4813
|
const error2 = await res.json();
|
|
4548
|
-
throw new Error(error2.error || "Failed to
|
|
4814
|
+
throw new Error(error2.error || "Failed to resend invite");
|
|
4549
4815
|
}
|
|
4550
4816
|
const result = await res.json();
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
toast.success(`New invite link generated.${note}`);
|
|
4554
|
-
}
|
|
4817
|
+
toast.success(result.message || "Invite resent. Previous invite link is no longer valid.");
|
|
4818
|
+
setRefreshKey((k) => k + 1);
|
|
4555
4819
|
} catch (error2) {
|
|
4556
4820
|
console.error("Error regenerating invite:", error2);
|
|
4557
|
-
toast.error(error2 instanceof Error ? error2.message : "Failed to
|
|
4821
|
+
toast.error(error2 instanceof Error ? error2.message : "Failed to resend invite");
|
|
4558
4822
|
}
|
|
4559
4823
|
}, "handleRegenerateInvite");
|
|
4824
|
+
const copyInviteLink = /* @__PURE__ */ __name(async () => {
|
|
4825
|
+
const inviteLink = inviteDialog?.inviteLink;
|
|
4826
|
+
if (!inviteLink) {
|
|
4827
|
+
toast.error("Invite link is not available");
|
|
4828
|
+
return;
|
|
4829
|
+
}
|
|
4830
|
+
try {
|
|
4831
|
+
await navigator.clipboard.writeText(inviteLink);
|
|
4832
|
+
setLinkCopied(true);
|
|
4833
|
+
toast.success("Invite link copied");
|
|
4834
|
+
window.setTimeout(() => setLinkCopied(false), 2e3);
|
|
4835
|
+
} catch {
|
|
4836
|
+
toast.error("Could not copy invite link");
|
|
4837
|
+
}
|
|
4838
|
+
}, "copyInviteLink");
|
|
4839
|
+
const sendInviteEmailFromDialog = /* @__PURE__ */ __name(async () => {
|
|
4840
|
+
if (!inviteDialog) return;
|
|
4841
|
+
setSendingInviteEmail(true);
|
|
4842
|
+
try {
|
|
4843
|
+
const res = await fetch(`/api/users/${inviteDialog.userId}/regenerate-invite`, {
|
|
4844
|
+
method: "POST",
|
|
4845
|
+
headers: {
|
|
4846
|
+
"Content-Type": "application/json"
|
|
4847
|
+
},
|
|
4848
|
+
body: JSON.stringify({
|
|
4849
|
+
sendEmail: true,
|
|
4850
|
+
rotate: false
|
|
4851
|
+
})
|
|
4852
|
+
});
|
|
4853
|
+
const data2 = await res.json();
|
|
4854
|
+
if (!res.ok) {
|
|
4855
|
+
toast.error(data2.error || data2.message || "Failed to send invite email");
|
|
4856
|
+
return;
|
|
4857
|
+
}
|
|
4858
|
+
const nextLink = typeof data2.inviteLink === "string" ? data2.inviteLink.trim() : "";
|
|
4859
|
+
if (nextLink) {
|
|
4860
|
+
setInviteDialog((prev) => prev ? {
|
|
4861
|
+
...prev,
|
|
4862
|
+
inviteLink: nextLink
|
|
4863
|
+
} : prev);
|
|
4864
|
+
}
|
|
4865
|
+
toast.success(data2.emailSent ? "Invite email sent" : data2.message || "Invite email queued");
|
|
4866
|
+
} catch {
|
|
4867
|
+
toast.error("Failed to send invite email");
|
|
4868
|
+
} finally {
|
|
4869
|
+
setSendingInviteEmail(false);
|
|
4870
|
+
}
|
|
4871
|
+
}, "sendInviteEmailFromDialog");
|
|
4872
|
+
const closeInviteDialog = /* @__PURE__ */ __name(() => {
|
|
4873
|
+
setInviteDialog(null);
|
|
4874
|
+
setLinkCopied(false);
|
|
4875
|
+
setRefreshKey((k) => k + 1);
|
|
4876
|
+
}, "closeInviteDialog");
|
|
4560
4877
|
const addEditPage = addEditPageUrl && addEditPageUrl.length > 0;
|
|
4561
4878
|
const createQueryString = useMemo(() => {
|
|
4562
4879
|
if (!createQueryParams || Object.keys(createQueryParams).length === 0) return "";
|
|
@@ -4834,11 +5151,11 @@ function AdminCRUD({ title, apiEndpoint, columns, addEditPageUrl, customViewPage
|
|
|
4834
5151
|
onClick: /* @__PURE__ */ __name(() => handleDuplicateClick(item), "onClick")
|
|
4835
5152
|
}, /* @__PURE__ */ React.createElement(Copy, {
|
|
4836
5153
|
className: "h-4 w-4 mr-2"
|
|
4837
|
-
}), "Duplicate")), resourceName === "users" && /* @__PURE__ */ React.createElement(DropdownMenuItem, {
|
|
5154
|
+
}), "Duplicate")), resourceName === "users" && String(item.inviteStatus ?? "") === "pending" && /* @__PURE__ */ React.createElement(DropdownMenuItem, {
|
|
4838
5155
|
onClick: /* @__PURE__ */ __name(() => handleRegenerateInvite(Number(item.id)), "onClick")
|
|
4839
5156
|
}, /* @__PURE__ */ React.createElement(RefreshCw, {
|
|
4840
5157
|
className: "h-4 w-4 mr-2"
|
|
4841
|
-
}), "
|
|
5158
|
+
}), "Resend invite"), /* @__PURE__ */ React.createElement(DropdownMenuItem, {
|
|
4842
5159
|
onClick: /* @__PURE__ */ __name(() => handleDeleteClick(item), "onClick"),
|
|
4843
5160
|
className: "text-red-600 focus:text-red-600"
|
|
4844
5161
|
}, /* @__PURE__ */ React.createElement(Trash2, {
|
|
@@ -4942,17 +5259,17 @@ function AdminCRUD({ title, apiEndpoint, columns, addEditPageUrl, customViewPage
|
|
|
4942
5259
|
className: "h-3.5 w-3.5"
|
|
4943
5260
|
}), /* @__PURE__ */ React.createElement("span", {
|
|
4944
5261
|
className: "sr-only"
|
|
4945
|
-
}, "Duplicate")), resourceName === "users" && /* @__PURE__ */ React.createElement(Button, {
|
|
5262
|
+
}, "Duplicate")), resourceName === "users" && String(item.inviteStatus ?? "") === "pending" && /* @__PURE__ */ React.createElement(Button, {
|
|
4946
5263
|
variant: "outline",
|
|
4947
5264
|
size: "icon",
|
|
4948
5265
|
className: "h-7 w-7",
|
|
4949
5266
|
onClick: /* @__PURE__ */ __name(() => handleRegenerateInvite(Number(item.id)), "onClick"),
|
|
4950
|
-
title: "
|
|
5267
|
+
title: "Resend invite"
|
|
4951
5268
|
}, /* @__PURE__ */ React.createElement(RefreshCw, {
|
|
4952
5269
|
className: "h-3.5 w-3.5"
|
|
4953
5270
|
}), /* @__PURE__ */ React.createElement("span", {
|
|
4954
5271
|
className: "sr-only"
|
|
4955
|
-
}, "
|
|
5272
|
+
}, "Resend invite")), /* @__PURE__ */ React.createElement(Button, {
|
|
4956
5273
|
variant: "outline",
|
|
4957
5274
|
size: "icon",
|
|
4958
5275
|
className: "h-7 w-7 border-red-300 text-red-600 hover:text-red-700",
|
|
@@ -5001,12 +5318,55 @@ function AdminCRUD({ title, apiEndpoint, columns, addEditPageUrl, customViewPage
|
|
|
5001
5318
|
setIsFormOpen(false);
|
|
5002
5319
|
setRefreshKey((k) => k + 1);
|
|
5003
5320
|
}, "onClose"),
|
|
5321
|
+
onSaveSuccess: /* @__PURE__ */ __name((result) => {
|
|
5322
|
+
if (resourceName === "users" && result && typeof result.inviteLink === "string" && result.inviteLink.trim() && !editingItem) {
|
|
5323
|
+
const userId = Number(result.user?.id ?? result.id);
|
|
5324
|
+
const inviteLink = result.inviteLink.trim();
|
|
5325
|
+
if (Number.isFinite(userId) && userId > 0) {
|
|
5326
|
+
setLinkCopied(false);
|
|
5327
|
+
setInviteDialog({
|
|
5328
|
+
userId,
|
|
5329
|
+
inviteLink
|
|
5330
|
+
});
|
|
5331
|
+
toast.success(typeof result.message === "string" ? result.message : "User created successfully");
|
|
5332
|
+
return;
|
|
5333
|
+
}
|
|
5334
|
+
}
|
|
5335
|
+
setRefreshKey((k) => k + 1);
|
|
5336
|
+
}, "onSaveSuccess"),
|
|
5004
5337
|
apiEndpoint,
|
|
5005
5338
|
columns,
|
|
5006
5339
|
existingData: editingItem,
|
|
5007
5340
|
duplicateSeed,
|
|
5008
5341
|
resource: resourceName
|
|
5009
5342
|
}), /* @__PURE__ */ React.createElement(Dialog, {
|
|
5343
|
+
open: inviteDialog != null,
|
|
5344
|
+
onOpenChange: /* @__PURE__ */ __name((open) => {
|
|
5345
|
+
if (!open) closeInviteDialog();
|
|
5346
|
+
}, "onOpenChange")
|
|
5347
|
+
}, /* @__PURE__ */ React.createElement(DialogContent, {
|
|
5348
|
+
className: "max-w-md"
|
|
5349
|
+
}, /* @__PURE__ */ React.createElement(DialogHeader, null, /* @__PURE__ */ React.createElement(DialogTitle, null, "User created"), /* @__PURE__ */ React.createElement(DialogDescription, null, "Send an invite email, or copy the invite link to share another way. The link is not shown here.")), /* @__PURE__ */ React.createElement("div", {
|
|
5350
|
+
className: "flex flex-col gap-2 py-2"
|
|
5351
|
+
}, /* @__PURE__ */ React.createElement(Button, {
|
|
5352
|
+
type: "button",
|
|
5353
|
+
disabled: sendingInviteEmail,
|
|
5354
|
+
onClick: /* @__PURE__ */ __name(() => void sendInviteEmailFromDialog(), "onClick")
|
|
5355
|
+
}, /* @__PURE__ */ React.createElement(Mail, {
|
|
5356
|
+
className: "h-4 w-4 mr-2"
|
|
5357
|
+
}), sendingInviteEmail ? "Sending\u2026" : "Send email"), /* @__PURE__ */ React.createElement(Button, {
|
|
5358
|
+
type: "button",
|
|
5359
|
+
variant: "outline",
|
|
5360
|
+
onClick: /* @__PURE__ */ __name(() => void copyInviteLink(), "onClick")
|
|
5361
|
+
}, linkCopied ? /* @__PURE__ */ React.createElement(Check, {
|
|
5362
|
+
className: "h-4 w-4 mr-2"
|
|
5363
|
+
}) : /* @__PURE__ */ React.createElement(Copy, {
|
|
5364
|
+
className: "h-4 w-4 mr-2"
|
|
5365
|
+
}), linkCopied ? "Copied" : "Copy invite link")), /* @__PURE__ */ React.createElement(DialogFooter, null, /* @__PURE__ */ React.createElement(Button, {
|
|
5366
|
+
type: "button",
|
|
5367
|
+
variant: "secondary",
|
|
5368
|
+
onClick: closeInviteDialog
|
|
5369
|
+
}, "Done")))), /* @__PURE__ */ React.createElement(Dialog, {
|
|
5010
5370
|
open: deleteDialogOpen,
|
|
5011
5371
|
onOpenChange: /* @__PURE__ */ __name((open) => {
|
|
5012
5372
|
console.debug("[CRUD][delete] dialog onOpenChange", {
|
|
@@ -7732,155 +8092,6 @@ var init_ComponentSettings = __esm({
|
|
|
7732
8092
|
__name(ComponentSettings, "ComponentSettings");
|
|
7733
8093
|
}
|
|
7734
8094
|
});
|
|
7735
|
-
function ImageOrUrlField({ label, value, onChange, placeholder = "https://\u2026", inputClassName = "w-full rounded-md border border-gray-300 px-2 py-1.5 text-sm", labelClassName = "block text-xs font-medium text-gray-600 mb-1", previewVariant = "banner", maxSizeMb = 10 }) {
|
|
7736
|
-
const fileInputRef = useRef(null);
|
|
7737
|
-
const [isUploading, setIsUploading] = useState(false);
|
|
7738
|
-
const [error, setError] = useState(null);
|
|
7739
|
-
const [lightboxOpen, setLightboxOpen] = useState(false);
|
|
7740
|
-
const previewCls = previewVariant === "logo" ? "h-20 w-20 rounded-md border border-gray-200 bg-white object-contain cursor-pointer" : "max-h-32 w-full rounded-md border border-gray-200 bg-gray-50 object-cover cursor-pointer";
|
|
7741
|
-
const closeLightbox = useCallback(() => setLightboxOpen(false), []);
|
|
7742
|
-
useEffect(() => {
|
|
7743
|
-
if (!lightboxOpen) return;
|
|
7744
|
-
const onKeyDown = /* @__PURE__ */ __name((e) => {
|
|
7745
|
-
if (e.key === "Escape") closeLightbox();
|
|
7746
|
-
}, "onKeyDown");
|
|
7747
|
-
window.addEventListener("keydown", onKeyDown);
|
|
7748
|
-
const prevOverflow = document.body.style.overflow;
|
|
7749
|
-
document.body.style.overflow = "hidden";
|
|
7750
|
-
return () => {
|
|
7751
|
-
window.removeEventListener("keydown", onKeyDown);
|
|
7752
|
-
document.body.style.overflow = prevOverflow;
|
|
7753
|
-
};
|
|
7754
|
-
}, [
|
|
7755
|
-
lightboxOpen,
|
|
7756
|
-
closeLightbox
|
|
7757
|
-
]);
|
|
7758
|
-
const handleUpload = useCallback(async (file) => {
|
|
7759
|
-
setError(null);
|
|
7760
|
-
if (!ACCEPTED_TYPES.includes(file.type)) {
|
|
7761
|
-
setError(`Unsupported file type. Use: ${ACCEPTED_TYPES.map((t) => t.replace("image/", "")).join(", ")}`);
|
|
7762
|
-
return;
|
|
7763
|
-
}
|
|
7764
|
-
if (file.size > maxSizeMb * 1024 * 1024) {
|
|
7765
|
-
setError(`File must be under ${maxSizeMb}MB`);
|
|
7766
|
-
return;
|
|
7767
|
-
}
|
|
7768
|
-
setIsUploading(true);
|
|
7769
|
-
try {
|
|
7770
|
-
const formData = new FormData();
|
|
7771
|
-
formData.append("file", file);
|
|
7772
|
-
const response = await fetch("/api/upload", {
|
|
7773
|
-
method: "POST",
|
|
7774
|
-
body: formData
|
|
7775
|
-
});
|
|
7776
|
-
const data = await response.json();
|
|
7777
|
-
if (!response.ok) {
|
|
7778
|
-
throw new Error(data.error || data.details || "Upload failed");
|
|
7779
|
-
}
|
|
7780
|
-
onChange(data.filePath ?? "");
|
|
7781
|
-
} catch (err) {
|
|
7782
|
-
setError(err instanceof Error ? err.message : "Upload failed");
|
|
7783
|
-
} finally {
|
|
7784
|
-
setIsUploading(false);
|
|
7785
|
-
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
7786
|
-
}
|
|
7787
|
-
}, [
|
|
7788
|
-
maxSizeMb,
|
|
7789
|
-
onChange
|
|
7790
|
-
]);
|
|
7791
|
-
const onFileChange = useCallback((e) => {
|
|
7792
|
-
const file = e.target.files?.[0];
|
|
7793
|
-
if (file) void handleUpload(file);
|
|
7794
|
-
}, [
|
|
7795
|
-
handleUpload
|
|
7796
|
-
]);
|
|
7797
|
-
const trimmed = value.trim();
|
|
7798
|
-
return /* @__PURE__ */ React.createElement("div", {
|
|
7799
|
-
className: "space-y-2"
|
|
7800
|
-
}, /* @__PURE__ */ React.createElement("label", {
|
|
7801
|
-
className: labelClassName
|
|
7802
|
-
}, label), trimmed ? /* @__PURE__ */ React.createElement("div", {
|
|
7803
|
-
className: "flex items-start gap-3"
|
|
7804
|
-
}, /* @__PURE__ */ React.createElement("img", {
|
|
7805
|
-
src: trimmed,
|
|
7806
|
-
alt: label,
|
|
7807
|
-
className: previewCls,
|
|
7808
|
-
role: "button",
|
|
7809
|
-
tabIndex: 0,
|
|
7810
|
-
title: "Click to enlarge",
|
|
7811
|
-
onClick: /* @__PURE__ */ __name(() => setLightboxOpen(true), "onClick"),
|
|
7812
|
-
onKeyDown: /* @__PURE__ */ __name((e) => {
|
|
7813
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
7814
|
-
e.preventDefault();
|
|
7815
|
-
setLightboxOpen(true);
|
|
7816
|
-
}
|
|
7817
|
-
}, "onKeyDown"),
|
|
7818
|
-
onError: /* @__PURE__ */ __name((e) => {
|
|
7819
|
-
e.currentTarget.style.display = "none";
|
|
7820
|
-
}, "onError")
|
|
7821
|
-
}), /* @__PURE__ */ React.createElement("button", {
|
|
7822
|
-
type: "button",
|
|
7823
|
-
onClick: /* @__PURE__ */ __name(() => onChange(""), "onClick"),
|
|
7824
|
-
className: "inline-flex items-center gap-1 rounded border border-gray-300 bg-white px-2 py-1 text-xs text-gray-600 hover:bg-gray-50"
|
|
7825
|
-
}, /* @__PURE__ */ React.createElement(X, {
|
|
7826
|
-
className: "h-3 w-3"
|
|
7827
|
-
}), "Clear")) : null, /* @__PURE__ */ React.createElement("div", {
|
|
7828
|
-
className: "flex flex-wrap gap-2"
|
|
7829
|
-
}, /* @__PURE__ */ React.createElement("input", {
|
|
7830
|
-
type: "url",
|
|
7831
|
-
value,
|
|
7832
|
-
onChange: /* @__PURE__ */ __name((e) => {
|
|
7833
|
-
setError(null);
|
|
7834
|
-
onChange(e.target.value);
|
|
7835
|
-
}, "onChange"),
|
|
7836
|
-
placeholder,
|
|
7837
|
-
className: `${inputClassName} min-w-0 flex-1`
|
|
7838
|
-
}), /* @__PURE__ */ React.createElement("input", {
|
|
7839
|
-
ref: fileInputRef,
|
|
7840
|
-
type: "file",
|
|
7841
|
-
accept: ACCEPTED_TYPES.join(","),
|
|
7842
|
-
onChange: onFileChange,
|
|
7843
|
-
className: "hidden",
|
|
7844
|
-
disabled: isUploading
|
|
7845
|
-
}), /* @__PURE__ */ React.createElement("button", {
|
|
7846
|
-
type: "button",
|
|
7847
|
-
onClick: /* @__PURE__ */ __name(() => fileInputRef.current?.click(), "onClick"),
|
|
7848
|
-
disabled: isUploading,
|
|
7849
|
-
className: "inline-flex shrink-0 items-center gap-1.5 rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50"
|
|
7850
|
-
}, /* @__PURE__ */ React.createElement(Upload, {
|
|
7851
|
-
className: "h-3.5 w-3.5"
|
|
7852
|
-
}), isUploading ? "Uploading\u2026" : "Upload")), error ? /* @__PURE__ */ React.createElement("p", {
|
|
7853
|
-
className: "flex items-center gap-1.5 text-xs text-red-600"
|
|
7854
|
-
}, /* @__PURE__ */ React.createElement(AlertCircle, {
|
|
7855
|
-
className: "h-3.5 w-3.5 shrink-0"
|
|
7856
|
-
}), error) : /* @__PURE__ */ React.createElement("p", {
|
|
7857
|
-
className: "text-xs text-gray-500"
|
|
7858
|
-
}, "Paste a URL or upload an image (PNG, JPG, GIF, WEBP up to ", maxSizeMb, "MB)"), lightboxOpen && trimmed ? /* @__PURE__ */ React.createElement("div", {
|
|
7859
|
-
className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/70 p-4",
|
|
7860
|
-
role: "dialog",
|
|
7861
|
-
"aria-modal": "true",
|
|
7862
|
-
"aria-label": `${label} preview`,
|
|
7863
|
-
onClick: closeLightbox
|
|
7864
|
-
}, /* @__PURE__ */ React.createElement("img", {
|
|
7865
|
-
src: trimmed,
|
|
7866
|
-
alt: label,
|
|
7867
|
-
className: "max-h-[90vh] max-w-[90vw] rounded-md object-contain shadow-lg",
|
|
7868
|
-
onClick: /* @__PURE__ */ __name((e) => e.stopPropagation(), "onClick")
|
|
7869
|
-
})) : null);
|
|
7870
|
-
}
|
|
7871
|
-
var ACCEPTED_TYPES;
|
|
7872
|
-
var init_ImageOrUrlField = __esm({
|
|
7873
|
-
"src/components/Admin/ImageOrUrlField.tsx"() {
|
|
7874
|
-
"use client";
|
|
7875
|
-
ACCEPTED_TYPES = [
|
|
7876
|
-
"image/jpeg",
|
|
7877
|
-
"image/png",
|
|
7878
|
-
"image/gif",
|
|
7879
|
-
"image/webp"
|
|
7880
|
-
];
|
|
7881
|
-
__name(ImageOrUrlField, "ImageOrUrlField");
|
|
7882
|
-
}
|
|
7883
|
-
});
|
|
7884
8095
|
function generateId() {
|
|
7885
8096
|
return `nav_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
|
7886
8097
|
}
|
|
@@ -8880,7 +9091,7 @@ var init_AuthFooter = __esm({
|
|
|
8880
9091
|
});
|
|
8881
9092
|
function AuthPageLayout({ children }) {
|
|
8882
9093
|
return /* @__PURE__ */ React.createElement("div", {
|
|
8883
|
-
className: "min-h-screen flex flex-col bg-gray-50
|
|
9094
|
+
className: "min-h-screen flex flex-col bg-gray-50"
|
|
8884
9095
|
}, /* @__PURE__ */ React.createElement("div", {
|
|
8885
9096
|
className: "flex-1 flex items-center justify-center px-4 py-6"
|
|
8886
9097
|
}, /* @__PURE__ */ React.createElement("div", {
|
|
@@ -8921,6 +9132,7 @@ var init_SignInPage = __esm({
|
|
|
8921
9132
|
const [email, setEmail] = useState("");
|
|
8922
9133
|
const [password, setPassword] = useState("");
|
|
8923
9134
|
const [error, setError] = useState("");
|
|
9135
|
+
const [success, setSuccess] = useState("");
|
|
8924
9136
|
const [loading, setLoading] = useState(false);
|
|
8925
9137
|
const { status } = useSession();
|
|
8926
9138
|
const searchParams = useSearchParams();
|
|
@@ -8928,6 +9140,9 @@ var init_SignInPage = __esm({
|
|
|
8928
9140
|
if (searchParams.get("error")) {
|
|
8929
9141
|
setError("Invalid email or password");
|
|
8930
9142
|
}
|
|
9143
|
+
if (searchParams.get("activated") === "1") {
|
|
9144
|
+
setSuccess("Account activated. Sign in with your new password.");
|
|
9145
|
+
}
|
|
8931
9146
|
}, [
|
|
8932
9147
|
searchParams
|
|
8933
9148
|
]);
|
|
@@ -8988,41 +9203,45 @@ var init_SignInPage = __esm({
|
|
|
8988
9203
|
}
|
|
8989
9204
|
}, "handleSubmit");
|
|
8990
9205
|
return /* @__PURE__ */ React.createElement(AuthPageLayout, null, /* @__PURE__ */ React.createElement("div", {
|
|
8991
|
-
className: "w-full shadow-md rounded-lg bg-white
|
|
9206
|
+
className: "w-full shadow-md rounded-lg bg-white px-4 py-6 sm:px-6 sm:py-8"
|
|
8992
9207
|
}, /* @__PURE__ */ React.createElement("h3", {
|
|
8993
|
-
className: "mb-1 text-center text-xl font-bold text-black
|
|
9208
|
+
className: "mb-1 text-center text-xl font-bold text-black sm:text-2xl"
|
|
8994
9209
|
}, "Sign in"), /* @__PURE__ */ React.createElement("p", {
|
|
8995
9210
|
className: "mb-6 text-center text-sm text-body-color"
|
|
8996
|
-
}, "Enter your credentials"),
|
|
8997
|
-
className: "mb-4 rounded-md bg-
|
|
9211
|
+
}, "Enter your credentials"), success && /* @__PURE__ */ React.createElement("div", {
|
|
9212
|
+
className: "mb-4 rounded-md bg-green-50 p-3 text-sm text-green-700"
|
|
9213
|
+
}, success), error && /* @__PURE__ */ React.createElement("div", {
|
|
9214
|
+
className: "mb-4 rounded-md bg-red-50 p-3 text-sm text-red-600"
|
|
8998
9215
|
}, error), /* @__PURE__ */ React.createElement("form", {
|
|
8999
9216
|
onSubmit: handleSubmit
|
|
9000
9217
|
}, /* @__PURE__ */ React.createElement("div", {
|
|
9001
9218
|
className: "mb-4"
|
|
9002
9219
|
}, /* @__PURE__ */ React.createElement("label", {
|
|
9003
9220
|
htmlFor: "email",
|
|
9004
|
-
className: "mb-1.5 block text-sm text-
|
|
9221
|
+
className: "mb-1.5 block text-sm text-gray-900"
|
|
9005
9222
|
}, "Email"), /* @__PURE__ */ React.createElement("input", {
|
|
9006
9223
|
type: "email",
|
|
9007
9224
|
name: "email",
|
|
9225
|
+
id: "email",
|
|
9008
9226
|
value: email,
|
|
9009
9227
|
onChange: /* @__PURE__ */ __name((e) => setEmail(e.target.value), "onChange"),
|
|
9010
9228
|
placeholder: "Enter email",
|
|
9011
9229
|
required: true,
|
|
9012
|
-
className: "border border-gray-300 w-full rounded bg-gray-50 px-3 py-2 text-sm text-gray-900 outline-none focus:border-gray-500 focus:ring-1 focus:ring-gray-500
|
|
9230
|
+
className: "border border-gray-300 w-full rounded bg-gray-50 px-3 py-2 text-sm text-gray-900 outline-none focus:border-gray-500 focus:ring-1 focus:ring-gray-500"
|
|
9013
9231
|
})), /* @__PURE__ */ React.createElement("div", {
|
|
9014
9232
|
className: "mb-4"
|
|
9015
9233
|
}, /* @__PURE__ */ React.createElement("label", {
|
|
9016
9234
|
htmlFor: "password",
|
|
9017
|
-
className: "mb-1.5 block text-sm text-
|
|
9235
|
+
className: "mb-1.5 block text-sm text-gray-900"
|
|
9018
9236
|
}, "Password"), /* @__PURE__ */ React.createElement("input", {
|
|
9019
9237
|
type: "password",
|
|
9020
9238
|
name: "password",
|
|
9239
|
+
id: "password",
|
|
9021
9240
|
value: password,
|
|
9022
9241
|
onChange: /* @__PURE__ */ __name((e) => setPassword(e.target.value), "onChange"),
|
|
9023
9242
|
placeholder: "Enter password",
|
|
9024
9243
|
required: true,
|
|
9025
|
-
className: "border border-gray-300 w-full rounded bg-gray-50 px-3 py-2 text-sm text-gray-900 outline-none focus:border-gray-500 focus:ring-1 focus:ring-gray-500
|
|
9244
|
+
className: "border border-gray-300 w-full rounded bg-gray-50 px-3 py-2 text-sm text-gray-900 outline-none focus:border-gray-500 focus:ring-1 focus:ring-gray-500"
|
|
9026
9245
|
})), /* @__PURE__ */ React.createElement("div", {
|
|
9027
9246
|
className: "mb-4 flex items-center justify-between"
|
|
9028
9247
|
}, /* @__PURE__ */ React.createElement("label", {
|
|
@@ -9036,7 +9255,7 @@ var init_SignInPage = __esm({
|
|
|
9036
9255
|
}, "Forgot password?")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("button", {
|
|
9037
9256
|
type: "submit",
|
|
9038
9257
|
disabled: loading,
|
|
9039
|
-
className: "w-full rounded bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-800 disabled:opacity-50 disabled:cursor-not-allowed
|
|
9258
|
+
className: "w-full rounded bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-800 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
9040
9259
|
}, loading ? "Signing in..." : "Sign in")))));
|
|
9041
9260
|
}, "SigninPage");
|
|
9042
9261
|
SignInPage_default = SigninPage;
|
|
@@ -9080,11 +9299,11 @@ function ForgotPasswordPage() {
|
|
|
9080
9299
|
}
|
|
9081
9300
|
}, "handleSubmit");
|
|
9082
9301
|
return /* @__PURE__ */ React.createElement(AuthPageLayout, null, /* @__PURE__ */ React.createElement("div", {
|
|
9083
|
-
className: "w-full space-y-4 p-5 sm:p-6 bg-white
|
|
9302
|
+
className: "w-full space-y-4 p-5 sm:p-6 bg-white rounded-lg shadow-md"
|
|
9084
9303
|
}, /* @__PURE__ */ React.createElement("div", {
|
|
9085
9304
|
className: "text-center"
|
|
9086
9305
|
}, /* @__PURE__ */ React.createElement("h2", {
|
|
9087
|
-
className: "text-xl font-bold text-gray-900
|
|
9306
|
+
className: "text-xl font-bold text-gray-900"
|
|
9088
9307
|
}, "Forgot password"), /* @__PURE__ */ React.createElement("p", {
|
|
9089
9308
|
className: "mt-1 text-sm text-body-color"
|
|
9090
9309
|
}, "Enter your email for a reset link.")), /* @__PURE__ */ React.createElement("form", {
|
|
@@ -9102,7 +9321,7 @@ function ForgotPasswordPage() {
|
|
|
9102
9321
|
placeholder: "you@example.com",
|
|
9103
9322
|
className: "mt-1 h-9 text-sm"
|
|
9104
9323
|
})), message && /* @__PURE__ */ React.createElement("div", {
|
|
9105
|
-
className: `p-2.5 rounded-md text-sm ${message.includes("receive") ? "bg-green-50
|
|
9324
|
+
className: `p-2.5 rounded-md text-sm ${message.includes("receive") ? "bg-green-50 text-green-700" : "bg-red-50 text-red-700"}`
|
|
9106
9325
|
}, message), /* @__PURE__ */ React.createElement(Button, {
|
|
9107
9326
|
type: "submit",
|
|
9108
9327
|
disabled: loading,
|
|
@@ -9185,9 +9404,9 @@ function ResetPasswordPageContent() {
|
|
|
9185
9404
|
}, "handleSubmit");
|
|
9186
9405
|
if (!token) {
|
|
9187
9406
|
return /* @__PURE__ */ React.createElement(AuthPageLayout, null, /* @__PURE__ */ React.createElement("div", {
|
|
9188
|
-
className: "w-full space-y-4 p-5 sm:p-6 bg-white
|
|
9407
|
+
className: "w-full space-y-4 p-5 sm:p-6 bg-white rounded-lg shadow-md text-center"
|
|
9189
9408
|
}, /* @__PURE__ */ React.createElement("h2", {
|
|
9190
|
-
className: "text-xl font-bold text-gray-900
|
|
9409
|
+
className: "text-xl font-bold text-gray-900"
|
|
9191
9410
|
}, "Invalid reset link"), /* @__PURE__ */ React.createElement("p", {
|
|
9192
9411
|
className: "text-sm text-body-color"
|
|
9193
9412
|
}, message), /* @__PURE__ */ React.createElement(Link2, {
|
|
@@ -9196,11 +9415,11 @@ function ResetPasswordPageContent() {
|
|
|
9196
9415
|
}, "Request a new link")));
|
|
9197
9416
|
}
|
|
9198
9417
|
return /* @__PURE__ */ React.createElement(AuthPageLayout, null, /* @__PURE__ */ React.createElement("div", {
|
|
9199
|
-
className: "w-full space-y-4 p-5 sm:p-6 bg-white
|
|
9418
|
+
className: "w-full space-y-4 p-5 sm:p-6 bg-white rounded-lg shadow-md"
|
|
9200
9419
|
}, /* @__PURE__ */ React.createElement("div", {
|
|
9201
9420
|
className: "text-center"
|
|
9202
9421
|
}, /* @__PURE__ */ React.createElement("h2", {
|
|
9203
|
-
className: "text-xl font-bold text-gray-900
|
|
9422
|
+
className: "text-xl font-bold text-gray-900"
|
|
9204
9423
|
}, "Set new password"), /* @__PURE__ */ React.createElement("p", {
|
|
9205
9424
|
className: "mt-1 text-sm text-body-color"
|
|
9206
9425
|
}, "Enter your new password.")), /* @__PURE__ */ React.createElement("form", {
|
|
@@ -9235,7 +9454,7 @@ function ResetPasswordPageContent() {
|
|
|
9235
9454
|
}), "onChange"),
|
|
9236
9455
|
className: "mt-1 h-9 text-sm"
|
|
9237
9456
|
})), message && /* @__PURE__ */ React.createElement("div", {
|
|
9238
|
-
className: `p-2.5 rounded-md text-sm ${message.includes("successfully") || message.includes("now sign in") ? "bg-green-50
|
|
9457
|
+
className: `p-2.5 rounded-md text-sm ${message.includes("successfully") || message.includes("now sign in") ? "bg-green-50 text-green-700" : "bg-red-50 text-red-700"}`
|
|
9239
9458
|
}, message), /* @__PURE__ */ React.createElement(Button, {
|
|
9240
9459
|
type: "submit",
|
|
9241
9460
|
disabled: loading,
|
|
@@ -9250,7 +9469,7 @@ function ResetPasswordPageContent() {
|
|
|
9250
9469
|
function ResetPasswordPage() {
|
|
9251
9470
|
return /* @__PURE__ */ React.createElement(Suspense, {
|
|
9252
9471
|
fallback: /* @__PURE__ */ React.createElement("div", {
|
|
9253
|
-
className: "min-h-screen flex items-center justify-center bg-gray-50
|
|
9472
|
+
className: "min-h-screen flex items-center justify-center bg-gray-50"
|
|
9254
9473
|
}, /* @__PURE__ */ React.createElement("div", {
|
|
9255
9474
|
className: "max-w-[400px] w-full p-6 text-center"
|
|
9256
9475
|
}, /* @__PURE__ */ React.createElement("div", {
|
|
@@ -9278,6 +9497,7 @@ __export(InvitePage_exports, {
|
|
|
9278
9497
|
default: () => InvitePage
|
|
9279
9498
|
});
|
|
9280
9499
|
function InvitePageContent() {
|
|
9500
|
+
const router = useRouter();
|
|
9281
9501
|
const searchParams = useSearchParams();
|
|
9282
9502
|
const token = searchParams.get("token");
|
|
9283
9503
|
const [formData, setFormData] = useState({
|
|
@@ -9304,8 +9524,13 @@ function InvitePageContent() {
|
|
|
9304
9524
|
setInviteValid(true);
|
|
9305
9525
|
setMessage("");
|
|
9306
9526
|
} else {
|
|
9527
|
+
const err = typeof result.error === "string" ? result.error : "";
|
|
9528
|
+
if (/already (active|accepted)/i.test(err)) {
|
|
9529
|
+
router.replace("/admin/signin?activated=1");
|
|
9530
|
+
return;
|
|
9531
|
+
}
|
|
9307
9532
|
setInviteValid(false);
|
|
9308
|
-
setMessage(
|
|
9533
|
+
setMessage(err || "This invitation is no longer valid.");
|
|
9309
9534
|
}
|
|
9310
9535
|
} catch {
|
|
9311
9536
|
if (!cancelled) {
|
|
@@ -9347,11 +9572,8 @@ function InvitePageContent() {
|
|
|
9347
9572
|
});
|
|
9348
9573
|
const result = await res.json();
|
|
9349
9574
|
if (res.ok) {
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
password: "",
|
|
9353
|
-
confirmPassword: ""
|
|
9354
|
-
});
|
|
9575
|
+
router.replace("/admin/signin?activated=1");
|
|
9576
|
+
return;
|
|
9355
9577
|
} else {
|
|
9356
9578
|
setMessage(result.error || "Failed to activate account");
|
|
9357
9579
|
}
|
|
@@ -9417,7 +9639,7 @@ function InvitePageContent() {
|
|
|
9417
9639
|
}), "onChange"),
|
|
9418
9640
|
className: "mt-1 h-9 text-sm"
|
|
9419
9641
|
})), message && /* @__PURE__ */ React26__default.createElement("div", {
|
|
9420
|
-
className:
|
|
9642
|
+
className: "p-2.5 rounded-md text-sm bg-red-50 text-red-700"
|
|
9421
9643
|
}, message), /* @__PURE__ */ React26__default.createElement(Button, {
|
|
9422
9644
|
type: "submit",
|
|
9423
9645
|
disabled: loading,
|
|
@@ -11183,6 +11405,15 @@ function renderListPrice(value, item) {
|
|
|
11183
11405
|
function renderListThumbnail(url) {
|
|
11184
11406
|
const trimmed = url.trim();
|
|
11185
11407
|
if (!trimmed) return "\u2014";
|
|
11408
|
+
if (isVideoMediaUrl(trimmed)) {
|
|
11409
|
+
return createElement("video", {
|
|
11410
|
+
src: trimmed,
|
|
11411
|
+
muted: true,
|
|
11412
|
+
playsInline: true,
|
|
11413
|
+
preload: "metadata",
|
|
11414
|
+
className: "h-10 w-10 rounded border border-gray-200 bg-black object-cover"
|
|
11415
|
+
});
|
|
11416
|
+
}
|
|
11186
11417
|
return createElement("img", {
|
|
11187
11418
|
src: trimmed,
|
|
11188
11419
|
alt: "",
|
|
@@ -11203,6 +11434,7 @@ function productListImageUrl(item) {
|
|
|
11203
11434
|
var STORE_CRUD_CONFIGS;
|
|
11204
11435
|
var init_store_crud_configs = __esm({
|
|
11205
11436
|
"src/admin/store-crud-configs.ts"() {
|
|
11437
|
+
init_media_url();
|
|
11206
11438
|
__name(renderListPrice, "renderListPrice");
|
|
11207
11439
|
__name(renderListThumbnail, "renderListThumbnail");
|
|
11208
11440
|
__name(productListImageUrl, "productListImageUrl");
|
|
@@ -11415,6 +11647,10 @@ var init_store_crud_configs = __esm({
|
|
|
11415
11647
|
field: "name",
|
|
11416
11648
|
displayName: "Name"
|
|
11417
11649
|
},
|
|
11650
|
+
{
|
|
11651
|
+
field: "slug",
|
|
11652
|
+
displayName: "Slug"
|
|
11653
|
+
},
|
|
11418
11654
|
{
|
|
11419
11655
|
field: "eventId",
|
|
11420
11656
|
displayName: "Event",
|
|
@@ -11908,6 +12144,14 @@ var init_store_crud_configs = __esm({
|
|
|
11908
12144
|
title: "Categories",
|
|
11909
12145
|
apiEndpoint: "/api/product_categories",
|
|
11910
12146
|
columns: [
|
|
12147
|
+
{
|
|
12148
|
+
field: "image",
|
|
12149
|
+
displayName: "Image",
|
|
12150
|
+
type: "image",
|
|
12151
|
+
listFilter: false,
|
|
12152
|
+
placeholder: "Image URL or upload",
|
|
12153
|
+
render: /* @__PURE__ */ __name((value) => renderListThumbnail(typeof value === "string" ? value : ""), "render")
|
|
12154
|
+
},
|
|
11911
12155
|
{
|
|
11912
12156
|
field: "name",
|
|
11913
12157
|
displayName: "Name"
|
|
@@ -12514,6 +12758,134 @@ var SettingsPage_exports = {};
|
|
|
12514
12758
|
__export(SettingsPage_exports, {
|
|
12515
12759
|
default: () => SettingsPage
|
|
12516
12760
|
});
|
|
12761
|
+
function InvoiceTemplatePreview({ id }) {
|
|
12762
|
+
if (id === "modern") {
|
|
12763
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
12764
|
+
className: "h-28 rounded-md border border-slate-200 bg-white overflow-hidden shadow-sm"
|
|
12765
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12766
|
+
className: "h-7 bg-teal-700 px-2 flex flex-col justify-center"
|
|
12767
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12768
|
+
className: "h-1.5 w-14 rounded bg-white/90"
|
|
12769
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
12770
|
+
className: "h-1 w-10 rounded bg-white/50 mt-1"
|
|
12771
|
+
})), /* @__PURE__ */ React.createElement("div", {
|
|
12772
|
+
className: "p-2 space-y-1.5"
|
|
12773
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12774
|
+
className: "flex gap-2"
|
|
12775
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12776
|
+
className: "flex-1 space-y-1"
|
|
12777
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12778
|
+
className: "h-1 w-8 bg-teal-600/70 rounded"
|
|
12779
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
12780
|
+
className: "h-1 w-full bg-slate-200 rounded"
|
|
12781
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
12782
|
+
className: "h-1 w-4/5 bg-slate-200 rounded"
|
|
12783
|
+
})), /* @__PURE__ */ React.createElement("div", {
|
|
12784
|
+
className: "flex-1 space-y-1"
|
|
12785
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12786
|
+
className: "h-1 w-8 bg-teal-600/70 rounded"
|
|
12787
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
12788
|
+
className: "h-1 w-full bg-slate-200 rounded"
|
|
12789
|
+
}))), /* @__PURE__ */ React.createElement("div", {
|
|
12790
|
+
className: "h-1 w-full bg-slate-100 rounded"
|
|
12791
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
12792
|
+
className: "h-1 w-full bg-slate-100 rounded"
|
|
12793
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
12794
|
+
className: "ml-auto h-1.5 w-12 bg-teal-700/80 rounded"
|
|
12795
|
+
})));
|
|
12796
|
+
}
|
|
12797
|
+
if (id === "compact") {
|
|
12798
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
12799
|
+
className: "h-28 rounded-md border border-slate-300 bg-white overflow-hidden shadow-sm p-1.5 space-y-1"
|
|
12800
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12801
|
+
className: "h-4 bg-slate-100 rounded flex items-center px-1"
|
|
12802
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12803
|
+
className: "h-1 w-10 bg-slate-700 rounded"
|
|
12804
|
+
})), /* @__PURE__ */ React.createElement("div", {
|
|
12805
|
+
className: "grid grid-cols-4 gap-0.5"
|
|
12806
|
+
}, Array.from({
|
|
12807
|
+
length: 8
|
|
12808
|
+
}).map((_, i) => /* @__PURE__ */ React.createElement("div", {
|
|
12809
|
+
key: i,
|
|
12810
|
+
className: "h-1 bg-slate-200 rounded"
|
|
12811
|
+
}))), /* @__PURE__ */ React.createElement("div", {
|
|
12812
|
+
className: "space-y-0.5"
|
|
12813
|
+
}, Array.from({
|
|
12814
|
+
length: 5
|
|
12815
|
+
}).map((_, i) => /* @__PURE__ */ React.createElement("div", {
|
|
12816
|
+
key: i,
|
|
12817
|
+
className: "flex gap-0.5"
|
|
12818
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12819
|
+
className: "h-1 flex-[3] bg-slate-200 rounded"
|
|
12820
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
12821
|
+
className: "h-1 flex-1 bg-slate-300 rounded"
|
|
12822
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
12823
|
+
className: "h-1 flex-1 bg-slate-300 rounded"
|
|
12824
|
+
})))), /* @__PURE__ */ React.createElement("div", {
|
|
12825
|
+
className: "ml-auto h-1 w-10 bg-slate-800 rounded"
|
|
12826
|
+
}));
|
|
12827
|
+
}
|
|
12828
|
+
if (id === "bold") {
|
|
12829
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
12830
|
+
className: "h-28 rounded-md border border-red-200 bg-white overflow-hidden shadow-sm"
|
|
12831
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12832
|
+
className: "h-9 bg-red-800 px-2 flex flex-col justify-center gap-1"
|
|
12833
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12834
|
+
className: "h-2 w-16 rounded bg-white"
|
|
12835
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
12836
|
+
className: "h-1 w-12 rounded bg-white/60"
|
|
12837
|
+
})), /* @__PURE__ */ React.createElement("div", {
|
|
12838
|
+
className: "p-2 space-y-1.5"
|
|
12839
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12840
|
+
className: "h-1 w-20 bg-slate-300 rounded"
|
|
12841
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
12842
|
+
className: "border border-red-100 rounded p-1 space-y-0.5 bg-red-50/40"
|
|
12843
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12844
|
+
className: "h-1 w-full bg-slate-200 rounded"
|
|
12845
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
12846
|
+
className: "h-1 w-full bg-slate-200 rounded"
|
|
12847
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
12848
|
+
className: "h-1.5 w-14 ml-auto bg-red-800 rounded"
|
|
12849
|
+
}))));
|
|
12850
|
+
}
|
|
12851
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
12852
|
+
className: "h-28 rounded-md border border-slate-200 bg-white overflow-hidden shadow-sm p-2 space-y-1.5"
|
|
12853
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12854
|
+
className: "h-5 rounded bg-slate-100 px-1.5 flex items-center justify-between"
|
|
12855
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12856
|
+
className: "h-1.5 w-12 bg-slate-700 rounded"
|
|
12857
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
12858
|
+
className: "h-1 w-8 bg-slate-400 rounded"
|
|
12859
|
+
})), /* @__PURE__ */ React.createElement("div", {
|
|
12860
|
+
className: "flex gap-2"
|
|
12861
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12862
|
+
className: "flex-1 space-y-0.5"
|
|
12863
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12864
|
+
className: "h-1 w-6 bg-slate-600 rounded"
|
|
12865
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
12866
|
+
className: "h-1 w-full bg-slate-200 rounded"
|
|
12867
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
12868
|
+
className: "h-1 w-3/4 bg-slate-200 rounded"
|
|
12869
|
+
})), /* @__PURE__ */ React.createElement("div", {
|
|
12870
|
+
className: "flex-1 space-y-0.5"
|
|
12871
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12872
|
+
className: "h-1 w-6 bg-slate-600 rounded"
|
|
12873
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
12874
|
+
className: "h-1 w-full bg-slate-200 rounded"
|
|
12875
|
+
}))), /* @__PURE__ */ React.createElement("div", {
|
|
12876
|
+
className: "border-t border-slate-200 pt-1 space-y-0.5"
|
|
12877
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12878
|
+
className: "h-1 w-full bg-slate-100 rounded"
|
|
12879
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
12880
|
+
className: "h-1 w-full bg-slate-100 rounded"
|
|
12881
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
12882
|
+
className: "h-1 w-full bg-slate-100 rounded"
|
|
12883
|
+
})), /* @__PURE__ */ React.createElement("div", {
|
|
12884
|
+
className: "flex justify-end"
|
|
12885
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
12886
|
+
className: "h-1.5 w-10 bg-slate-700 rounded"
|
|
12887
|
+
})));
|
|
12888
|
+
}
|
|
12517
12889
|
function hexToHsl(hex) {
|
|
12518
12890
|
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
12519
12891
|
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
@@ -12564,6 +12936,25 @@ function SettingsPage() {
|
|
|
12564
12936
|
const [requireEventApproval, setRequireEventApproval] = useState(false);
|
|
12565
12937
|
const [storeSettingsLoading, setStoreSettingsLoading] = useState(true);
|
|
12566
12938
|
const [currency, setCurrency] = useState(DEFAULT_CURRENCY);
|
|
12939
|
+
const [invoiceTemplate, setInvoiceTemplate] = useState("classic");
|
|
12940
|
+
const [companyLogo, setCompanyLogo] = useState("");
|
|
12941
|
+
const [companyName, setCompanyName] = useState("");
|
|
12942
|
+
const [companyEmail, setCompanyEmail] = useState("");
|
|
12943
|
+
const [companyPhone, setCompanyPhone] = useState("");
|
|
12944
|
+
const [companyAddress, setCompanyAddress] = useState("");
|
|
12945
|
+
const handleStoreLogoChange = /* @__PURE__ */ __name((newLogo) => {
|
|
12946
|
+
setCompanyLogo(newLogo);
|
|
12947
|
+
setNavbarConfig((prev) => ({
|
|
12948
|
+
...prev,
|
|
12949
|
+
logo: newLogo
|
|
12950
|
+
}));
|
|
12951
|
+
}, "handleStoreLogoChange");
|
|
12952
|
+
const handleNavbarConfigChange = /* @__PURE__ */ __name((newConfig) => {
|
|
12953
|
+
setNavbarConfig(newConfig);
|
|
12954
|
+
if (newConfig.logo !== companyLogo) {
|
|
12955
|
+
setCompanyLogo(newConfig.logo || "");
|
|
12956
|
+
}
|
|
12957
|
+
}, "handleNavbarConfigChange");
|
|
12567
12958
|
const [currencies, setCurrencies] = useState([]);
|
|
12568
12959
|
const [currenciesLoading, setCurrenciesLoading] = useState(true);
|
|
12569
12960
|
const [showAddCurrency, setShowAddCurrency] = useState(false);
|
|
@@ -12608,7 +12999,11 @@ function SettingsPage() {
|
|
|
12608
12999
|
setActiveThemeId((data.activeThemeId || "").trim());
|
|
12609
13000
|
if (data.navbar) {
|
|
12610
13001
|
try {
|
|
12611
|
-
|
|
13002
|
+
const parsed = JSON.parse(data.navbar);
|
|
13003
|
+
setNavbarConfig(parsed);
|
|
13004
|
+
if (parsed.logo) {
|
|
13005
|
+
setCompanyLogo((prev) => prev || parsed.logo);
|
|
13006
|
+
}
|
|
12612
13007
|
} catch {
|
|
12613
13008
|
}
|
|
12614
13009
|
}
|
|
@@ -12618,6 +13013,19 @@ function SettingsPage() {
|
|
|
12618
13013
|
fetch("/api/settings/store").then((r) => r.ok ? r.json() : {}).then((data) => {
|
|
12619
13014
|
setStoreEnabled(data.enabled === "true");
|
|
12620
13015
|
setCurrency(data.currency || DEFAULT_CURRENCY);
|
|
13016
|
+
setInvoiceTemplate(data.invoiceTemplate || "classic");
|
|
13017
|
+
const logo = data.invoiceLogoUrl || data.companyLogo || data.logo || "";
|
|
13018
|
+
setCompanyLogo((prev) => logo || prev);
|
|
13019
|
+
setCompanyName(data.companyName || data.invoiceCompanyName || data.name || "");
|
|
13020
|
+
setCompanyEmail(data.companyEmail || data.invoiceEmail || data.email || "");
|
|
13021
|
+
setCompanyPhone(data.companyPhone || data.invoicePhone || data.phone || "");
|
|
13022
|
+
setCompanyAddress(data.companyAddress || data.invoiceAddress || data.address || "");
|
|
13023
|
+
if (logo) {
|
|
13024
|
+
setNavbarConfig((prev) => ({
|
|
13025
|
+
...prev,
|
|
13026
|
+
logo: prev.logo || logo
|
|
13027
|
+
}));
|
|
13028
|
+
}
|
|
12621
13029
|
}).finally(() => setStoreSettingsLoading(false));
|
|
12622
13030
|
fetch("/api/settings/multi_vendor").then((r) => r.ok ? r.json() : {}).then((data) => {
|
|
12623
13031
|
setMultiVendorEnabled(data.enabled !== "false");
|
|
@@ -12722,6 +13130,27 @@ function SettingsPage() {
|
|
|
12722
13130
|
}
|
|
12723
13131
|
})
|
|
12724
13132
|
});
|
|
13133
|
+
await fetch("/api/settings/store", {
|
|
13134
|
+
method: "PUT",
|
|
13135
|
+
headers: {
|
|
13136
|
+
"Content-Type": "application/json"
|
|
13137
|
+
},
|
|
13138
|
+
body: JSON.stringify({
|
|
13139
|
+
invoiceLogoUrl: {
|
|
13140
|
+
value: navbarConfig.logo || "",
|
|
13141
|
+
type: "public"
|
|
13142
|
+
},
|
|
13143
|
+
companyLogo: {
|
|
13144
|
+
value: navbarConfig.logo || "",
|
|
13145
|
+
type: "public"
|
|
13146
|
+
},
|
|
13147
|
+
logo: {
|
|
13148
|
+
value: navbarConfig.logo || "",
|
|
13149
|
+
type: "public"
|
|
13150
|
+
}
|
|
13151
|
+
})
|
|
13152
|
+
}).catch(() => {
|
|
13153
|
+
});
|
|
12725
13154
|
toast.success("Navbar saved");
|
|
12726
13155
|
} catch {
|
|
12727
13156
|
toast.error("Failed to save");
|
|
@@ -12745,8 +13174,57 @@ function SettingsPage() {
|
|
|
12745
13174
|
currency: {
|
|
12746
13175
|
value: currency,
|
|
12747
13176
|
type: "public"
|
|
13177
|
+
},
|
|
13178
|
+
invoiceTemplate: {
|
|
13179
|
+
value: invoiceTemplate || "classic",
|
|
13180
|
+
type: "public"
|
|
13181
|
+
},
|
|
13182
|
+
invoiceLogoUrl: {
|
|
13183
|
+
value: companyLogo,
|
|
13184
|
+
type: "public"
|
|
13185
|
+
},
|
|
13186
|
+
companyLogo: {
|
|
13187
|
+
value: companyLogo,
|
|
13188
|
+
type: "public"
|
|
13189
|
+
},
|
|
13190
|
+
logo: {
|
|
13191
|
+
value: companyLogo,
|
|
13192
|
+
type: "public"
|
|
13193
|
+
},
|
|
13194
|
+
companyName: {
|
|
13195
|
+
value: companyName,
|
|
13196
|
+
type: "public"
|
|
13197
|
+
},
|
|
13198
|
+
companyEmail: {
|
|
13199
|
+
value: companyEmail,
|
|
13200
|
+
type: "public"
|
|
13201
|
+
},
|
|
13202
|
+
companyPhone: {
|
|
13203
|
+
value: companyPhone,
|
|
13204
|
+
type: "public"
|
|
13205
|
+
},
|
|
13206
|
+
companyAddress: {
|
|
13207
|
+
value: companyAddress,
|
|
13208
|
+
type: "public"
|
|
13209
|
+
}
|
|
13210
|
+
})
|
|
13211
|
+
});
|
|
13212
|
+
const updatedNav = {
|
|
13213
|
+
...navbarConfig,
|
|
13214
|
+
logo: companyLogo
|
|
13215
|
+
};
|
|
13216
|
+
await fetch("/api/settings/theme", {
|
|
13217
|
+
method: "PUT",
|
|
13218
|
+
headers: {
|
|
13219
|
+
"Content-Type": "application/json"
|
|
13220
|
+
},
|
|
13221
|
+
body: JSON.stringify({
|
|
13222
|
+
navbar: {
|
|
13223
|
+
value: JSON.stringify(updatedNav),
|
|
13224
|
+
type: "public"
|
|
12748
13225
|
}
|
|
12749
13226
|
})
|
|
13227
|
+
}).catch(() => {
|
|
12750
13228
|
});
|
|
12751
13229
|
await fetch("/api/settings/multi_vendor", {
|
|
12752
13230
|
method: "PUT",
|
|
@@ -13030,7 +13508,7 @@ function SettingsPage() {
|
|
|
13030
13508
|
className: "text-sm text-gray-500 mb-4"
|
|
13031
13509
|
}, "Set logo and menu items."), /* @__PURE__ */ React.createElement(NavbarEditor, {
|
|
13032
13510
|
config: navbarConfig,
|
|
13033
|
-
onChange:
|
|
13511
|
+
onChange: handleNavbarConfigChange
|
|
13034
13512
|
})), /* @__PURE__ */ React.createElement(Button, {
|
|
13035
13513
|
onClick: handleSaveNavbar,
|
|
13036
13514
|
disabled: saving
|
|
@@ -13116,7 +13594,80 @@ function SettingsPage() {
|
|
|
13116
13594
|
className: "text-sm font-medium text-gray-700"
|
|
13117
13595
|
}, "Require event approval"), /* @__PURE__ */ React.createElement("p", {
|
|
13118
13596
|
className: "text-xs text-gray-500 mt-0.5"
|
|
13119
|
-
}, "When on, new vendor events start as pending. Admins approve or reject; approval activates the event (live)."))) : null), /* @__PURE__ */ React.createElement("div",
|
|
13597
|
+
}, "When on, new vendor events start as pending. Admins approve or reject; approval activates the event (live)."))) : null), /* @__PURE__ */ React.createElement("div", {
|
|
13598
|
+
className: "space-y-4 p-4 bg-gray-50 rounded-lg border border-gray-200"
|
|
13599
|
+
}, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", {
|
|
13600
|
+
className: "text-base font-semibold text-gray-900"
|
|
13601
|
+
}, "Invoice Header & Store Details"), /* @__PURE__ */ React.createElement("p", {
|
|
13602
|
+
className: "text-xs text-gray-500 mt-0.5"
|
|
13603
|
+
}, "Configure company logo, name, email, phone, and address displayed on generated invoice headers.")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
|
|
13604
|
+
label: "Company Logo",
|
|
13605
|
+
value: companyLogo,
|
|
13606
|
+
onChange: handleStoreLogoChange,
|
|
13607
|
+
previewVariant: "logo",
|
|
13608
|
+
placeholder: "Logo image URL or upload image"
|
|
13609
|
+
})), /* @__PURE__ */ React.createElement("div", {
|
|
13610
|
+
className: "grid grid-cols-1 sm:grid-cols-2 gap-4"
|
|
13611
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
13612
|
+
className: "space-y-1.5"
|
|
13613
|
+
}, /* @__PURE__ */ React.createElement(Label3, {
|
|
13614
|
+
className: "text-xs font-medium text-gray-700"
|
|
13615
|
+
}, "Company Name"), /* @__PURE__ */ React.createElement(Input, {
|
|
13616
|
+
value: companyName,
|
|
13617
|
+
onChange: /* @__PURE__ */ __name((e) => setCompanyName(e.target.value), "onChange"),
|
|
13618
|
+
placeholder: "e.g. Acme Corp",
|
|
13619
|
+
className: "h-9 text-sm bg-white"
|
|
13620
|
+
})), /* @__PURE__ */ React.createElement("div", {
|
|
13621
|
+
className: "space-y-1.5"
|
|
13622
|
+
}, /* @__PURE__ */ React.createElement(Label3, {
|
|
13623
|
+
className: "text-xs font-medium text-gray-700"
|
|
13624
|
+
}, "Company Email"), /* @__PURE__ */ React.createElement(Input, {
|
|
13625
|
+
type: "email",
|
|
13626
|
+
value: companyEmail,
|
|
13627
|
+
onChange: /* @__PURE__ */ __name((e) => setCompanyEmail(e.target.value), "onChange"),
|
|
13628
|
+
placeholder: "e.g. support@acme.com",
|
|
13629
|
+
className: "h-9 text-sm bg-white"
|
|
13630
|
+
})), /* @__PURE__ */ React.createElement("div", {
|
|
13631
|
+
className: "space-y-1.5"
|
|
13632
|
+
}, /* @__PURE__ */ React.createElement(Label3, {
|
|
13633
|
+
className: "text-xs font-medium text-gray-700"
|
|
13634
|
+
}, "Company Phone"), /* @__PURE__ */ React.createElement(Input, {
|
|
13635
|
+
type: "tel",
|
|
13636
|
+
value: companyPhone,
|
|
13637
|
+
onChange: /* @__PURE__ */ __name((e) => setCompanyPhone(e.target.value), "onChange"),
|
|
13638
|
+
placeholder: "e.g. +1 555-0199",
|
|
13639
|
+
className: "h-9 text-sm bg-white"
|
|
13640
|
+
})), /* @__PURE__ */ React.createElement("div", {
|
|
13641
|
+
className: "space-y-1.5 sm:col-span-2"
|
|
13642
|
+
}, /* @__PURE__ */ React.createElement(Label3, {
|
|
13643
|
+
className: "text-xs font-medium text-gray-700"
|
|
13644
|
+
}, "Company Address"), /* @__PURE__ */ React.createElement(Textarea, {
|
|
13645
|
+
value: companyAddress,
|
|
13646
|
+
onChange: /* @__PURE__ */ __name((e) => setCompanyAddress(e.target.value), "onChange"),
|
|
13647
|
+
placeholder: "e.g. 123 Business Rd, Suite 100\nNew York, NY 10001",
|
|
13648
|
+
rows: 3,
|
|
13649
|
+
className: "text-sm bg-white"
|
|
13650
|
+
})))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
|
|
13651
|
+
className: "block text-sm font-semibold text-gray-700 mb-1"
|
|
13652
|
+
}, "Invoice PDF template"), /* @__PURE__ */ React.createElement("p", {
|
|
13653
|
+
className: "text-xs text-gray-500 mb-3"
|
|
13654
|
+
}, "Used when generating invoices in the app (and as fallback when ERP invoice is unavailable). Company logo, name, email, phone, and address set above are shown in the invoice header."), /* @__PURE__ */ React.createElement("div", {
|
|
13655
|
+
className: "grid gap-3 sm:grid-cols-2"
|
|
13656
|
+
}, INVOICE_TEMPLATE_CARDS.map((opt) => {
|
|
13657
|
+
const selected = invoiceTemplate === opt.id;
|
|
13658
|
+
return /* @__PURE__ */ React.createElement("button", {
|
|
13659
|
+
key: opt.id,
|
|
13660
|
+
type: "button",
|
|
13661
|
+
onClick: /* @__PURE__ */ __name(() => setInvoiceTemplate(opt.id), "onClick"),
|
|
13662
|
+
className: `text-left rounded-lg border p-3 transition ${selected ? "border-blue-500 bg-blue-50 ring-1 ring-blue-500" : "border-gray-200 bg-white hover:border-gray-300"}`
|
|
13663
|
+
}, /* @__PURE__ */ React.createElement(InvoiceTemplatePreview, {
|
|
13664
|
+
id: opt.id
|
|
13665
|
+
}), /* @__PURE__ */ React.createElement("div", {
|
|
13666
|
+
className: "text-sm font-semibold text-gray-800 mt-2"
|
|
13667
|
+
}, opt.label), /* @__PURE__ */ React.createElement("div", {
|
|
13668
|
+
className: "text-xs text-gray-500 mt-0.5"
|
|
13669
|
+
}, opt.description));
|
|
13670
|
+
}))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", {
|
|
13120
13671
|
className: "flex items-center justify-between mb-1"
|
|
13121
13672
|
}, /* @__PURE__ */ React.createElement("label", {
|
|
13122
13673
|
className: "block text-sm font-semibold text-gray-700"
|
|
@@ -13304,7 +13855,7 @@ function SettingsPage() {
|
|
|
13304
13855
|
className: "h-4 w-4 mr-2"
|
|
13305
13856
|
}), saving ? "Saving..." : "Save")));
|
|
13306
13857
|
}
|
|
13307
|
-
var DEFAULT_CURRENCY, DEFAULTS;
|
|
13858
|
+
var DEFAULT_CURRENCY, DEFAULTS, INVOICE_TEMPLATE_CARDS;
|
|
13308
13859
|
var init_SettingsPage = __esm({
|
|
13309
13860
|
"src/admin/pages/SettingsPage.tsx"() {
|
|
13310
13861
|
"use client";
|
|
@@ -13313,6 +13864,8 @@ var init_SettingsPage = __esm({
|
|
|
13313
13864
|
init_switch();
|
|
13314
13865
|
init_label();
|
|
13315
13866
|
init_admin_config_context();
|
|
13867
|
+
init_textarea();
|
|
13868
|
+
init_ImageOrUrlField();
|
|
13316
13869
|
init_NavbarEditor();
|
|
13317
13870
|
DEFAULT_CURRENCY = "INR";
|
|
13318
13871
|
DEFAULTS = {
|
|
@@ -13320,6 +13873,29 @@ var init_SettingsPage = __esm({
|
|
|
13320
13873
|
secondaryColor: "#6366f1",
|
|
13321
13874
|
compactView: "false"
|
|
13322
13875
|
};
|
|
13876
|
+
INVOICE_TEMPLATE_CARDS = [
|
|
13877
|
+
{
|
|
13878
|
+
id: "classic",
|
|
13879
|
+
label: "Classic",
|
|
13880
|
+
description: "Traditional invoice with lined items and totals footer."
|
|
13881
|
+
},
|
|
13882
|
+
{
|
|
13883
|
+
id: "modern",
|
|
13884
|
+
label: "Modern",
|
|
13885
|
+
description: "Clean layout with accent header and open spacing."
|
|
13886
|
+
},
|
|
13887
|
+
{
|
|
13888
|
+
id: "compact",
|
|
13889
|
+
label: "Compact",
|
|
13890
|
+
description: "Dense layout suited to longer item lists."
|
|
13891
|
+
},
|
|
13892
|
+
{
|
|
13893
|
+
id: "bold",
|
|
13894
|
+
label: "Bold",
|
|
13895
|
+
description: "Strong header band and clear payment summary."
|
|
13896
|
+
}
|
|
13897
|
+
];
|
|
13898
|
+
__name(InvoiceTemplatePreview, "InvoiceTemplatePreview");
|
|
13323
13899
|
__name(hexToHsl, "hexToHsl");
|
|
13324
13900
|
__name(genId, "genId");
|
|
13325
13901
|
__name(SettingsPage, "SettingsPage");
|
|
@@ -31645,6 +32221,7 @@ function ComboEditPage({ comboId }) {
|
|
|
31645
32221
|
const [saving, setSaving] = useState(false);
|
|
31646
32222
|
const [errors, setErrors] = useState([]);
|
|
31647
32223
|
const [name, setName] = useState("");
|
|
32224
|
+
const [slug, setSlug] = useState("");
|
|
31648
32225
|
const [desc, setDesc] = useState("");
|
|
31649
32226
|
const [eventId, setEventId] = useState("");
|
|
31650
32227
|
const [priceStr, setPriceStr] = useState("");
|
|
@@ -31784,6 +32361,7 @@ function ComboEditPage({ comboId }) {
|
|
|
31784
32361
|
const data = await res.json();
|
|
31785
32362
|
if (cancelled) return;
|
|
31786
32363
|
setName(data.name ?? "");
|
|
32364
|
+
setSlug(typeof data.slug === "string" ? data.slug : "");
|
|
31787
32365
|
setDesc(data.desc ?? "");
|
|
31788
32366
|
setEventId(data.eventId != null ? String(data.eventId) : "");
|
|
31789
32367
|
if (data.eventId != null) {
|
|
@@ -31824,6 +32402,7 @@ function ComboEditPage({ comboId }) {
|
|
|
31824
32402
|
if (create && duplicateSourceId) {
|
|
31825
32403
|
const rawName = (data.name ?? "").trim() || "Combo";
|
|
31826
32404
|
setName(rawName.toLowerCase().startsWith("copy of ") ? `${rawName} (copy)` : `Copy of ${rawName}`);
|
|
32405
|
+
setSlug("");
|
|
31827
32406
|
}
|
|
31828
32407
|
} catch {
|
|
31829
32408
|
if (!cancelled) setErrors([
|
|
@@ -31909,15 +32488,15 @@ function ComboEditPage({ comboId }) {
|
|
|
31909
32488
|
]);
|
|
31910
32489
|
return;
|
|
31911
32490
|
}
|
|
31912
|
-
if (addonItems.length
|
|
32491
|
+
if (addonItems.length < minSelectableItems) {
|
|
31913
32492
|
setErrors([
|
|
31914
|
-
`Please add at least ${minSelectableItems} add-on item(s). Currently ${addonItems.length} selected.`
|
|
32493
|
+
`Please add at least ${minSelectableItems} add-on item(s) so customers can meet the minimum. Currently ${addonItems.length} selected.`
|
|
31915
32494
|
]);
|
|
31916
32495
|
return;
|
|
31917
32496
|
}
|
|
31918
|
-
if (addonItems.length
|
|
32497
|
+
if (maxSelectableItems > addonItems.length) {
|
|
31919
32498
|
setErrors([
|
|
31920
|
-
`
|
|
32499
|
+
`Max add-on items (${maxSelectableItems}) cannot be greater than the number of add-on items selected (${addonItems.length}).`
|
|
31921
32500
|
]);
|
|
31922
32501
|
return;
|
|
31923
32502
|
}
|
|
@@ -31937,6 +32516,7 @@ function ComboEditPage({ comboId }) {
|
|
|
31937
32516
|
const resolvedEventId = trimmedEventId && /^\d+$/.test(trimmedEventId) ? Number(trimmedEventId) : null;
|
|
31938
32517
|
const payload = {
|
|
31939
32518
|
name: trimmedName,
|
|
32519
|
+
slug: slug.trim() || void 0,
|
|
31940
32520
|
desc: desc || null,
|
|
31941
32521
|
eventId: resolvedEventId,
|
|
31942
32522
|
price,
|
|
@@ -32028,6 +32608,14 @@ function ComboEditPage({ comboId }) {
|
|
|
32028
32608
|
className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm"
|
|
32029
32609
|
})), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
|
|
32030
32610
|
className: "block text-xs font-medium text-gray-600 mb-1"
|
|
32611
|
+
}, "Slug"), /* @__PURE__ */ React.createElement("input", {
|
|
32612
|
+
type: "text",
|
|
32613
|
+
value: slug,
|
|
32614
|
+
onChange: /* @__PURE__ */ __name((e) => setSlug(e.target.value), "onChange"),
|
|
32615
|
+
placeholder: "Auto generate from name",
|
|
32616
|
+
className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm"
|
|
32617
|
+
})), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
|
|
32618
|
+
className: "block text-xs font-medium text-gray-600 mb-1"
|
|
32031
32619
|
}, "Description"), /* @__PURE__ */ React.createElement("textarea", {
|
|
32032
32620
|
value: desc,
|
|
32033
32621
|
onChange: /* @__PURE__ */ __name((e) => setDesc(e.target.value), "onChange"),
|
|
@@ -32141,7 +32729,7 @@ function ComboEditPage({ comboId }) {
|
|
|
32141
32729
|
className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm"
|
|
32142
32730
|
}))), /* @__PURE__ */ React.createElement("p", {
|
|
32143
32731
|
className: "text-xs text-gray-400 mt-1"
|
|
32144
|
-
}, "Customers
|
|
32732
|
+
}, "List all available add-ons above. Customers choose between ", minSelectableItems, " and", " ", maxSelectableItems, " of those items (max cannot exceed the add-on list size)."))), /* @__PURE__ */ React.createElement("section", null, /* @__PURE__ */ React.createElement("h2", {
|
|
32145
32733
|
className: "text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2"
|
|
32146
32734
|
}, "Availability"), /* @__PURE__ */ React.createElement("div", {
|
|
32147
32735
|
className: "min-w-0 overflow-hidden border border-gray-200 rounded-lg p-4 bg-gray-50/50 space-y-4"
|
|
@@ -33640,6 +34228,7 @@ var init_DiscountDetailsPage = __esm({
|
|
|
33640
34228
|
category: "Category",
|
|
33641
34229
|
cart: "Cart",
|
|
33642
34230
|
paymentMethod: "Payment method",
|
|
34231
|
+
deviceType: "Device type",
|
|
33643
34232
|
order: "Order",
|
|
33644
34233
|
quantity: "Quantity",
|
|
33645
34234
|
time: "Time",
|
|
@@ -33731,6 +34320,15 @@ function conditionToRule(c) {
|
|
|
33731
34320
|
v: c.paymentMethod ?? ""
|
|
33732
34321
|
}
|
|
33733
34322
|
};
|
|
34323
|
+
case "deviceType":
|
|
34324
|
+
return {
|
|
34325
|
+
...base,
|
|
34326
|
+
type: "deviceType",
|
|
34327
|
+
comparisonOperator: "eq",
|
|
34328
|
+
value: {
|
|
34329
|
+
v: c.deviceType ?? ""
|
|
34330
|
+
}
|
|
34331
|
+
};
|
|
33734
34332
|
case "category":
|
|
33735
34333
|
return {
|
|
33736
34334
|
...base,
|
|
@@ -33903,6 +34501,12 @@ function ruleTreeToFriendly(rules) {
|
|
|
33903
34501
|
kind: "paymentMethod",
|
|
33904
34502
|
paymentMethod: v
|
|
33905
34503
|
};
|
|
34504
|
+
case "deviceType":
|
|
34505
|
+
return {
|
|
34506
|
+
id: rule._clientId ?? uid(),
|
|
34507
|
+
kind: "deviceType",
|
|
34508
|
+
deviceType: v
|
|
34509
|
+
};
|
|
33906
34510
|
case "category":
|
|
33907
34511
|
return {
|
|
33908
34512
|
id: rule._clientId ?? uid(),
|
|
@@ -34465,6 +35069,9 @@ function ConditionCard({ condition, onChange, onRemove, eventsOn }) {
|
|
|
34465
35069
|
paymentMethod: /* @__PURE__ */ React.createElement(DollarSign, {
|
|
34466
35070
|
className: "h-3.5 w-3.5 text-indigo-500"
|
|
34467
35071
|
}),
|
|
35072
|
+
deviceType: /* @__PURE__ */ React.createElement(DollarSign, {
|
|
35073
|
+
className: "h-3.5 w-3.5 text-cyan-500"
|
|
35074
|
+
}),
|
|
34468
35075
|
category: /* @__PURE__ */ React.createElement(Package, {
|
|
34469
35076
|
className: "h-3.5 w-3.5 text-teal-500"
|
|
34470
35077
|
}),
|
|
@@ -34507,6 +35114,8 @@ function ConditionCard({ condition, onChange, onRemove, eventsOn }) {
|
|
|
34507
35114
|
}, "User type"), /* @__PURE__ */ React.createElement(SelectItem, {
|
|
34508
35115
|
value: "paymentMethod"
|
|
34509
35116
|
}, "Payment method"), /* @__PURE__ */ React.createElement(SelectItem, {
|
|
35117
|
+
value: "deviceType"
|
|
35118
|
+
}, "Device type"), /* @__PURE__ */ React.createElement(SelectItem, {
|
|
34510
35119
|
value: "category"
|
|
34511
35120
|
}, "Category"), /* @__PURE__ */ React.createElement(SelectItem, {
|
|
34512
35121
|
value: "customers"
|
|
@@ -34660,7 +35269,21 @@ function ConditionCard({ condition, onChange, onRemove, eventsOn }) {
|
|
|
34660
35269
|
value: "cod"
|
|
34661
35270
|
}, "Cash on Delivery"), /* @__PURE__ */ React.createElement(SelectItem, {
|
|
34662
35271
|
value: "wallet"
|
|
34663
|
-
}, "Wallet"))), condition.kind === "
|
|
35272
|
+
}, "Wallet"))), condition.kind === "deviceType" && /* @__PURE__ */ React.createElement(Select, {
|
|
35273
|
+
value: condition.deviceType ?? "",
|
|
35274
|
+
onValueChange: /* @__PURE__ */ __name((v) => onChange({
|
|
35275
|
+
...condition,
|
|
35276
|
+
deviceType: v
|
|
35277
|
+
}), "onValueChange")
|
|
35278
|
+
}, /* @__PURE__ */ React.createElement(SelectTrigger, {
|
|
35279
|
+
className: "h-8 text-xs"
|
|
35280
|
+
}, /* @__PURE__ */ React.createElement(SelectValue, {
|
|
35281
|
+
placeholder: "Select device type\u2026"
|
|
35282
|
+
})), /* @__PURE__ */ React.createElement(SelectContent, null, /* @__PURE__ */ React.createElement(SelectItem, {
|
|
35283
|
+
value: "web"
|
|
35284
|
+
}, "Web"), /* @__PURE__ */ React.createElement(SelectItem, {
|
|
35285
|
+
value: "mobile"
|
|
35286
|
+
}, "Mobile"))), condition.kind === "category" && /* @__PURE__ */ React.createElement(CategoryPicker, {
|
|
34664
35287
|
value: condition.categoryId,
|
|
34665
35288
|
label: condition.categoryName,
|
|
34666
35289
|
onChange: /* @__PURE__ */ __name((id, name) => onChange({
|
|
@@ -37885,6 +38508,11 @@ var init_AdminPageResolver = __esm({
|
|
|
37885
38508
|
field: "email",
|
|
37886
38509
|
displayName: "Email"
|
|
37887
38510
|
},
|
|
38511
|
+
{
|
|
38512
|
+
field: "inviteStatus",
|
|
38513
|
+
displayName: "Invite status",
|
|
38514
|
+
hideInForm: true
|
|
38515
|
+
},
|
|
37888
38516
|
{
|
|
37889
38517
|
field: "blocked",
|
|
37890
38518
|
displayName: "Blocked",
|
|
@@ -38090,6 +38718,29 @@ function LayoutSettingsPage() {
|
|
|
38090
38718
|
footer: JSON.stringify(footerValues)
|
|
38091
38719
|
})
|
|
38092
38720
|
});
|
|
38721
|
+
if (navbarConfig.logo) {
|
|
38722
|
+
await fetch("/api/settings/store", {
|
|
38723
|
+
method: "PUT",
|
|
38724
|
+
headers: {
|
|
38725
|
+
"Content-Type": "application/json"
|
|
38726
|
+
},
|
|
38727
|
+
body: JSON.stringify({
|
|
38728
|
+
invoiceLogoUrl: {
|
|
38729
|
+
value: navbarConfig.logo,
|
|
38730
|
+
type: "public"
|
|
38731
|
+
},
|
|
38732
|
+
companyLogo: {
|
|
38733
|
+
value: navbarConfig.logo,
|
|
38734
|
+
type: "public"
|
|
38735
|
+
},
|
|
38736
|
+
logo: {
|
|
38737
|
+
value: navbarConfig.logo,
|
|
38738
|
+
type: "public"
|
|
38739
|
+
}
|
|
38740
|
+
})
|
|
38741
|
+
}).catch(() => {
|
|
38742
|
+
});
|
|
38743
|
+
}
|
|
38093
38744
|
} finally {
|
|
38094
38745
|
setSaving(false);
|
|
38095
38746
|
}
|
|
@@ -38516,18 +39167,26 @@ var init_config = __esm({
|
|
|
38516
39167
|
];
|
|
38517
39168
|
}
|
|
38518
39169
|
});
|
|
39170
|
+
function ForceAdminLightMode() {
|
|
39171
|
+
useForceAdminLightMode();
|
|
39172
|
+
return null;
|
|
39173
|
+
}
|
|
38519
39174
|
function CmsProviders({ children }) {
|
|
38520
39175
|
return /* @__PURE__ */ React.createElement(ThemeProvider, {
|
|
38521
39176
|
attribute: "class",
|
|
38522
|
-
defaultTheme: "
|
|
38523
|
-
|
|
38524
|
-
|
|
39177
|
+
defaultTheme: "light",
|
|
39178
|
+
forcedTheme: "light",
|
|
39179
|
+
enableSystem: false,
|
|
39180
|
+
enableColorScheme: true
|
|
39181
|
+
}, /* @__PURE__ */ React.createElement(ForceAdminLightMode, null), children, /* @__PURE__ */ React.createElement(Toaster, {
|
|
38525
39182
|
position: "top-right"
|
|
38526
39183
|
}));
|
|
38527
39184
|
}
|
|
38528
39185
|
var init_CmsProviders = __esm({
|
|
38529
39186
|
"src/admin/CmsProviders.tsx"() {
|
|
38530
39187
|
"use client";
|
|
39188
|
+
init_force_light_mode();
|
|
39189
|
+
__name(ForceAdminLightMode, "ForceAdminLightMode");
|
|
38531
39190
|
__name(CmsProviders, "CmsProviders");
|
|
38532
39191
|
}
|
|
38533
39192
|
});
|