medusa-contact-us 0.0.21 → 0.0.23
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/.medusa/server/src/admin/index.js +446 -0
- package/.medusa/server/src/admin/index.mjs +447 -1
- package/.medusa/server/src/api/admin/contact-requests/[id]/assign/route.js +54 -0
- package/.medusa/server/src/api/admin/contact-requests/[id]/comments/route.js +28 -0
- package/.medusa/server/src/api/admin/contact-requests/[id]/route.js +36 -9
- package/.medusa/server/src/api/admin/contact-requests/validators.js +9 -2
- package/.medusa/server/src/api/admin/users/route.js +70 -0
- package/.medusa/server/src/modules/contact-requests/index.js +4 -2
- package/.medusa/server/src/modules/contact-requests/migrations/Migration20241129163317.js +2 -2
- package/.medusa/server/src/modules/contact-requests/migrations/Migration20251222161335.js +27 -0
- package/.medusa/server/src/modules/contact-requests/migrations/Migration20251222161336.js +37 -0
- package/.medusa/server/src/modules/contact-requests/models/contact-request-comment.js +13 -0
- package/.medusa/server/src/modules/contact-requests/models/contact-request.js +2 -1
- package/.medusa/server/src/modules/contact-requests/service.js +92 -1
- package/.medusa/server/src/modules/contact-subscriptions/migrations/Migration20241126103000.js +2 -2
- package/package.json +8 -5
|
@@ -5,6 +5,7 @@ const adminSdk = require("@medusajs/admin-sdk");
|
|
|
5
5
|
const ui = require("@medusajs/ui");
|
|
6
6
|
const icons = require("@medusajs/icons");
|
|
7
7
|
const reactRouterDom = require("react-router-dom");
|
|
8
|
+
require("@medusajs/admin-shared");
|
|
8
9
|
const useDebounce$1 = (value, delay) => {
|
|
9
10
|
const [debouncedValue, setDebouncedValue] = react.useState(value);
|
|
10
11
|
react.useEffect(() => {
|
|
@@ -336,6 +337,7 @@ const ContactRequestsPage = () => {
|
|
|
336
337
|
/* @__PURE__ */ jsxRuntime.jsx("thead", { className: "bg-ui-bg-subtle", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
337
338
|
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-ui-fg-muted", children: "Email" }),
|
|
338
339
|
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-ui-fg-muted", children: "Status" }),
|
|
340
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-ui-fg-muted", children: "Assigned To" }),
|
|
339
341
|
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-ui-fg-muted", children: "Source" }),
|
|
340
342
|
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-ui-fg-muted", children: "Created" }),
|
|
341
343
|
/* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-ui-fg-muted", children: "Actions" })
|
|
@@ -358,6 +360,7 @@ const ContactRequestsPage = () => {
|
|
|
358
360
|
children: request.status.replace("_", " ")
|
|
359
361
|
}
|
|
360
362
|
) }),
|
|
363
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-4 text-ui-fg-subtle", children: request.assign_to ? /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", children: request.assign_to }) : /* @__PURE__ */ jsxRuntime.jsx("span", { children: "—" }) }),
|
|
361
364
|
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-4 text-ui-fg-subtle", children: request.source ?? "storefront" }),
|
|
362
365
|
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-4 text-ui-fg-subtle", children: new Date(request.created_at).toLocaleString() }),
|
|
363
366
|
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -412,6 +415,7 @@ const ContactRequestDetailPage = () => {
|
|
|
412
415
|
const navigate = reactRouterDom.useNavigate();
|
|
413
416
|
const { id } = reactRouterDom.useParams();
|
|
414
417
|
const [request, setRequest] = react.useState(null);
|
|
418
|
+
const [comments, setComments] = react.useState([]);
|
|
415
419
|
const [nextAllowedStatuses, setNextAllowedStatuses] = react.useState([]);
|
|
416
420
|
const [selectedStatus, setSelectedStatus] = react.useState("");
|
|
417
421
|
const [isLoading, setIsLoading] = react.useState(true);
|
|
@@ -419,6 +423,58 @@ const ContactRequestDetailPage = () => {
|
|
|
419
423
|
const [error, setError] = react.useState(null);
|
|
420
424
|
const [updateError, setUpdateError] = react.useState(null);
|
|
421
425
|
const [updateSuccess, setUpdateSuccess] = react.useState(false);
|
|
426
|
+
const [selectedAssignTo, setSelectedAssignTo] = react.useState("");
|
|
427
|
+
const [isAssigning, setIsAssigning] = react.useState(false);
|
|
428
|
+
const [assignError, setAssignError] = react.useState(null);
|
|
429
|
+
const [assignSuccess, setAssignSuccess] = react.useState(false);
|
|
430
|
+
const [adminUsers, setAdminUsers] = react.useState([]);
|
|
431
|
+
const [adminSearchQuery, setAdminSearchQuery] = react.useState("");
|
|
432
|
+
const [isAdminDropdownOpen, setIsAdminDropdownOpen] = react.useState(false);
|
|
433
|
+
const [isLoadingAdmins, setIsLoadingAdmins] = react.useState(false);
|
|
434
|
+
const [selectedAdminIndex, setSelectedAdminIndex] = react.useState(-1);
|
|
435
|
+
const adminDropdownRef = react.useRef(null);
|
|
436
|
+
const adminInputRef = react.useRef(null);
|
|
437
|
+
const [commentText, setCommentText] = react.useState("");
|
|
438
|
+
const [commentImages, setCommentImages] = react.useState([]);
|
|
439
|
+
const [isCreatingComment, setIsCreatingComment] = react.useState(false);
|
|
440
|
+
const [commentError, setCommentError] = react.useState(null);
|
|
441
|
+
const [commentSuccess, setCommentSuccess] = react.useState(false);
|
|
442
|
+
const useDebounce2 = (value, delay) => {
|
|
443
|
+
const [debouncedValue, setDebouncedValue] = react.useState(value);
|
|
444
|
+
react.useEffect(() => {
|
|
445
|
+
const handler = setTimeout(() => setDebouncedValue(value), delay);
|
|
446
|
+
return () => clearTimeout(handler);
|
|
447
|
+
}, [value, delay]);
|
|
448
|
+
return debouncedValue;
|
|
449
|
+
};
|
|
450
|
+
const debouncedAdminSearch = useDebounce2(adminSearchQuery, 300);
|
|
451
|
+
const fetchAdminUsers = react.useCallback(async (searchQuery) => {
|
|
452
|
+
try {
|
|
453
|
+
setIsLoadingAdmins(true);
|
|
454
|
+
const params = new URLSearchParams();
|
|
455
|
+
if (searchQuery) {
|
|
456
|
+
params.set("q", searchQuery);
|
|
457
|
+
}
|
|
458
|
+
const response = await fetch(`/admin/users?${params.toString()}`, {
|
|
459
|
+
credentials: "include"
|
|
460
|
+
});
|
|
461
|
+
if (!response.ok) {
|
|
462
|
+
throw new Error("Unable to load admin users");
|
|
463
|
+
}
|
|
464
|
+
const payload = await response.json();
|
|
465
|
+
setAdminUsers(payload.users || []);
|
|
466
|
+
} catch (fetchError) {
|
|
467
|
+
console.error("Error fetching admin users:", fetchError);
|
|
468
|
+
setAdminUsers([]);
|
|
469
|
+
} finally {
|
|
470
|
+
setIsLoadingAdmins(false);
|
|
471
|
+
}
|
|
472
|
+
}, []);
|
|
473
|
+
const filteredAdmins = adminUsers.filter((admin) => {
|
|
474
|
+
if (!debouncedAdminSearch) return true;
|
|
475
|
+
const query = debouncedAdminSearch.toLowerCase();
|
|
476
|
+
return admin.name.toLowerCase().includes(query) || admin.email.toLowerCase().includes(query) || admin.id.toLowerCase().includes(query);
|
|
477
|
+
});
|
|
422
478
|
react.useEffect(() => {
|
|
423
479
|
if (!id) {
|
|
424
480
|
navigate("/contact-requests");
|
|
@@ -437,8 +493,11 @@ const ContactRequestDetailPage = () => {
|
|
|
437
493
|
}
|
|
438
494
|
const payload = await response.json();
|
|
439
495
|
setRequest(payload.request);
|
|
496
|
+
setComments(payload.comments ?? []);
|
|
440
497
|
setNextAllowedStatuses(payload.next_allowed_statuses ?? []);
|
|
441
498
|
setSelectedStatus("");
|
|
499
|
+
setSelectedAssignTo("");
|
|
500
|
+
setAdminSearchQuery("");
|
|
442
501
|
} catch (loadError) {
|
|
443
502
|
const message = loadError instanceof Error ? loadError.message : "Unable to load contact request";
|
|
444
503
|
setError(message);
|
|
@@ -448,6 +507,59 @@ const ContactRequestDetailPage = () => {
|
|
|
448
507
|
};
|
|
449
508
|
void loadRequest();
|
|
450
509
|
}, [id, navigate]);
|
|
510
|
+
react.useEffect(() => {
|
|
511
|
+
if (isAdminDropdownOpen) {
|
|
512
|
+
void fetchAdminUsers(debouncedAdminSearch || void 0);
|
|
513
|
+
}
|
|
514
|
+
}, [isAdminDropdownOpen, debouncedAdminSearch, fetchAdminUsers]);
|
|
515
|
+
react.useEffect(() => {
|
|
516
|
+
const handleClickOutside = (event) => {
|
|
517
|
+
if (adminDropdownRef.current && !adminDropdownRef.current.contains(event.target) && adminInputRef.current && !adminInputRef.current.contains(event.target)) {
|
|
518
|
+
setIsAdminDropdownOpen(false);
|
|
519
|
+
setSelectedAdminIndex(-1);
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
if (isAdminDropdownOpen) {
|
|
523
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
524
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
525
|
+
}
|
|
526
|
+
}, [isAdminDropdownOpen]);
|
|
527
|
+
const handleAdminKeyDown = (event) => {
|
|
528
|
+
if (event.key === "ArrowDown") {
|
|
529
|
+
event.preventDefault();
|
|
530
|
+
setSelectedAdminIndex(
|
|
531
|
+
(prev) => prev < filteredAdmins.length - 1 ? prev + 1 : prev
|
|
532
|
+
);
|
|
533
|
+
setIsAdminDropdownOpen(true);
|
|
534
|
+
} else if (event.key === "ArrowUp") {
|
|
535
|
+
event.preventDefault();
|
|
536
|
+
setSelectedAdminIndex((prev) => prev > 0 ? prev - 1 : -1);
|
|
537
|
+
setIsAdminDropdownOpen(true);
|
|
538
|
+
} else if (event.key === "Enter" && selectedAdminIndex >= 0) {
|
|
539
|
+
event.preventDefault();
|
|
540
|
+
const admin = filteredAdmins[selectedAdminIndex];
|
|
541
|
+
if (admin) {
|
|
542
|
+
handleAdminSelect(admin);
|
|
543
|
+
}
|
|
544
|
+
} else if (event.key === "Escape") {
|
|
545
|
+
setIsAdminDropdownOpen(false);
|
|
546
|
+
setSelectedAdminIndex(-1);
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
const handleAdminSelect = (admin) => {
|
|
550
|
+
setSelectedAssignTo(admin.id);
|
|
551
|
+
setAdminSearchQuery(admin.name || admin.email);
|
|
552
|
+
setIsAdminDropdownOpen(false);
|
|
553
|
+
setSelectedAdminIndex(-1);
|
|
554
|
+
};
|
|
555
|
+
react.useEffect(() => {
|
|
556
|
+
if ((request == null ? void 0 : request.assign_to) && adminUsers.length > 0) {
|
|
557
|
+
const assignedAdmin = adminUsers.find((admin) => admin.id === request.assign_to);
|
|
558
|
+
if (assignedAdmin && !adminSearchQuery) {
|
|
559
|
+
setAdminSearchQuery(assignedAdmin.name || assignedAdmin.email);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}, [request == null ? void 0 : request.assign_to, adminUsers, adminSearchQuery]);
|
|
451
563
|
const handleStatusUpdate = async () => {
|
|
452
564
|
if (!id || !selectedStatus) {
|
|
453
565
|
return;
|
|
@@ -478,6 +590,8 @@ const ContactRequestDetailPage = () => {
|
|
|
478
590
|
});
|
|
479
591
|
if (detailResponse.ok) {
|
|
480
592
|
const detailPayload = await detailResponse.json();
|
|
593
|
+
setRequest(detailPayload.request);
|
|
594
|
+
setComments(detailPayload.comments ?? []);
|
|
481
595
|
setNextAllowedStatuses(detailPayload.next_allowed_statuses ?? []);
|
|
482
596
|
}
|
|
483
597
|
} catch (updateErr) {
|
|
@@ -487,6 +601,144 @@ const ContactRequestDetailPage = () => {
|
|
|
487
601
|
setIsUpdating(false);
|
|
488
602
|
}
|
|
489
603
|
};
|
|
604
|
+
const handleAssign = async () => {
|
|
605
|
+
if (!id) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
try {
|
|
609
|
+
setIsAssigning(true);
|
|
610
|
+
setAssignError(null);
|
|
611
|
+
setAssignSuccess(false);
|
|
612
|
+
const response = await fetch(`/admin/contact-requests/${id}/assign`, {
|
|
613
|
+
method: "POST",
|
|
614
|
+
headers: {
|
|
615
|
+
"Content-Type": "application/json"
|
|
616
|
+
},
|
|
617
|
+
credentials: "include",
|
|
618
|
+
body: JSON.stringify({
|
|
619
|
+
assign_to: selectedAssignTo.trim() || null
|
|
620
|
+
})
|
|
621
|
+
});
|
|
622
|
+
if (!response.ok) {
|
|
623
|
+
const message = await response.text();
|
|
624
|
+
throw new Error(message || "Unable to assign contact request");
|
|
625
|
+
}
|
|
626
|
+
const payload = await response.json();
|
|
627
|
+
setRequest(payload.request);
|
|
628
|
+
setSelectedAssignTo("");
|
|
629
|
+
setAssignSuccess(true);
|
|
630
|
+
setTimeout(() => setAssignSuccess(false), 3e3);
|
|
631
|
+
const detailResponse = await fetch(`/admin/contact-requests/${id}`, {
|
|
632
|
+
credentials: "include"
|
|
633
|
+
});
|
|
634
|
+
if (detailResponse.ok) {
|
|
635
|
+
const detailPayload = await detailResponse.json();
|
|
636
|
+
setRequest(detailPayload.request);
|
|
637
|
+
setComments(detailPayload.comments ?? []);
|
|
638
|
+
setNextAllowedStatuses(detailPayload.next_allowed_statuses ?? []);
|
|
639
|
+
}
|
|
640
|
+
} catch (assignErr) {
|
|
641
|
+
const message = assignErr instanceof Error ? assignErr.message : "Unable to assign contact request";
|
|
642
|
+
setAssignError(message);
|
|
643
|
+
} finally {
|
|
644
|
+
setIsAssigning(false);
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
const handleUnassign = async () => {
|
|
648
|
+
if (!id) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
try {
|
|
652
|
+
setIsAssigning(true);
|
|
653
|
+
setAssignError(null);
|
|
654
|
+
setAssignSuccess(false);
|
|
655
|
+
const response = await fetch(`/admin/contact-requests/${id}/assign`, {
|
|
656
|
+
method: "POST",
|
|
657
|
+
headers: {
|
|
658
|
+
"Content-Type": "application/json"
|
|
659
|
+
},
|
|
660
|
+
credentials: "include",
|
|
661
|
+
body: JSON.stringify({
|
|
662
|
+
assign_to: null
|
|
663
|
+
})
|
|
664
|
+
});
|
|
665
|
+
if (!response.ok) {
|
|
666
|
+
const message = await response.text();
|
|
667
|
+
throw new Error(message || "Unable to unassign contact request");
|
|
668
|
+
}
|
|
669
|
+
const payload = await response.json();
|
|
670
|
+
setRequest(payload.request);
|
|
671
|
+
setAssignSuccess(true);
|
|
672
|
+
setTimeout(() => setAssignSuccess(false), 3e3);
|
|
673
|
+
const detailResponse = await fetch(`/admin/contact-requests/${id}`, {
|
|
674
|
+
credentials: "include"
|
|
675
|
+
});
|
|
676
|
+
if (detailResponse.ok) {
|
|
677
|
+
const detailPayload = await detailResponse.json();
|
|
678
|
+
setRequest(detailPayload.request);
|
|
679
|
+
setComments(detailPayload.comments ?? []);
|
|
680
|
+
setNextAllowedStatuses(detailPayload.next_allowed_statuses ?? []);
|
|
681
|
+
}
|
|
682
|
+
} catch (assignErr) {
|
|
683
|
+
const message = assignErr instanceof Error ? assignErr.message : "Unable to unassign contact request";
|
|
684
|
+
setAssignError(message);
|
|
685
|
+
} finally {
|
|
686
|
+
setIsAssigning(false);
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
const handleCreateComment = async () => {
|
|
690
|
+
if (!id || !commentText.trim() && commentImages.length === 0) {
|
|
691
|
+
setCommentError("Please provide a comment or at least one image");
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
try {
|
|
695
|
+
setIsCreatingComment(true);
|
|
696
|
+
setCommentError(null);
|
|
697
|
+
setCommentSuccess(false);
|
|
698
|
+
const response = await fetch(`/admin/contact-requests/${id}/comments`, {
|
|
699
|
+
method: "POST",
|
|
700
|
+
headers: {
|
|
701
|
+
"Content-Type": "application/json"
|
|
702
|
+
},
|
|
703
|
+
credentials: "include",
|
|
704
|
+
body: JSON.stringify({
|
|
705
|
+
comment: commentText.trim() || void 0,
|
|
706
|
+
images: commentImages.filter((img) => img.trim()).length > 0 ? commentImages.filter((img) => img.trim()) : void 0
|
|
707
|
+
})
|
|
708
|
+
});
|
|
709
|
+
if (!response.ok) {
|
|
710
|
+
const message = await response.text();
|
|
711
|
+
throw new Error(message || "Unable to create comment");
|
|
712
|
+
}
|
|
713
|
+
const detailResponse = await fetch(`/admin/contact-requests/${id}`, {
|
|
714
|
+
credentials: "include"
|
|
715
|
+
});
|
|
716
|
+
if (detailResponse.ok) {
|
|
717
|
+
const detailPayload = await detailResponse.json();
|
|
718
|
+
setComments(detailPayload.comments ?? []);
|
|
719
|
+
}
|
|
720
|
+
setCommentText("");
|
|
721
|
+
setCommentImages([]);
|
|
722
|
+
setCommentSuccess(true);
|
|
723
|
+
setTimeout(() => setCommentSuccess(false), 3e3);
|
|
724
|
+
} catch (commentErr) {
|
|
725
|
+
const message = commentErr instanceof Error ? commentErr.message : "Unable to create comment";
|
|
726
|
+
setCommentError(message);
|
|
727
|
+
} finally {
|
|
728
|
+
setIsCreatingComment(false);
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
const addImageInput = () => {
|
|
732
|
+
setCommentImages([...commentImages, ""]);
|
|
733
|
+
};
|
|
734
|
+
const removeImageInput = (index) => {
|
|
735
|
+
setCommentImages(commentImages.filter((_, i) => i !== index));
|
|
736
|
+
};
|
|
737
|
+
const updateImageUrl = (index, value) => {
|
|
738
|
+
const updated = [...commentImages];
|
|
739
|
+
updated[index] = value;
|
|
740
|
+
setCommentImages(updated);
|
|
741
|
+
};
|
|
490
742
|
if (isLoading) {
|
|
491
743
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full p-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "mx-auto flex w-full max-w-5xl flex-col gap-6 p-6", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center py-16", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Loading contact request..." }) }) }) });
|
|
492
744
|
}
|
|
@@ -558,6 +810,103 @@ const ContactRequestDetailPage = () => {
|
|
|
558
810
|
updateError && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "mt-2 text-ui-fg-error", children: updateError }),
|
|
559
811
|
updateSuccess && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "mt-2 text-ui-fg-success", children: "Status updated successfully" })
|
|
560
812
|
] }),
|
|
813
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-base p-6", children: [
|
|
814
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-4 text-lg", children: "Assignment" }),
|
|
815
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
|
|
816
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
817
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Currently Assigned To" }),
|
|
818
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: request.assign_to ? /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "small", className: "mt-1", children: request.assign_to }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-ui-fg-subtle", children: "Unassigned" }) })
|
|
819
|
+
] }),
|
|
820
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 md:flex-row md:items-end", children: [
|
|
821
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 relative", children: [
|
|
822
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "mb-2 block text-sm font-medium text-ui-fg-base", children: "Assign To Admin" }),
|
|
823
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
824
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
825
|
+
ui.Input,
|
|
826
|
+
{
|
|
827
|
+
ref: adminInputRef,
|
|
828
|
+
type: "text",
|
|
829
|
+
value: adminSearchQuery,
|
|
830
|
+
onChange: (event) => {
|
|
831
|
+
setAdminSearchQuery(event.target.value);
|
|
832
|
+
setIsAdminDropdownOpen(true);
|
|
833
|
+
setSelectedAdminIndex(-1);
|
|
834
|
+
},
|
|
835
|
+
onFocus: () => {
|
|
836
|
+
setIsAdminDropdownOpen(true);
|
|
837
|
+
if (adminUsers.length === 0) {
|
|
838
|
+
void fetchAdminUsers();
|
|
839
|
+
}
|
|
840
|
+
},
|
|
841
|
+
onKeyDown: handleAdminKeyDown,
|
|
842
|
+
placeholder: "Search admin by name, email, or ID...",
|
|
843
|
+
className: "w-full"
|
|
844
|
+
}
|
|
845
|
+
),
|
|
846
|
+
isAdminDropdownOpen && /* @__PURE__ */ jsxRuntime.jsx(
|
|
847
|
+
"div",
|
|
848
|
+
{
|
|
849
|
+
ref: adminDropdownRef,
|
|
850
|
+
className: "absolute z-50 mt-1 w-full max-h-60 overflow-auto rounded-md border border-ui-border-base bg-ui-bg-base shadow-lg",
|
|
851
|
+
children: isLoadingAdmins ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 text-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Loading admins..." }) }) : filteredAdmins.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "py-1", children: filteredAdmins.map((admin, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
852
|
+
"li",
|
|
853
|
+
{
|
|
854
|
+
className: `cursor-pointer px-4 py-3 hover:bg-ui-bg-subtle-hover ${index === selectedAdminIndex ? "bg-ui-bg-subtle-hover" : ""}`,
|
|
855
|
+
onClick: () => handleAdminSelect(admin),
|
|
856
|
+
onMouseEnter: () => setSelectedAdminIndex(index),
|
|
857
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
858
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
859
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: admin.name }),
|
|
860
|
+
admin.id === selectedAssignTo && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", children: "Selected" })
|
|
861
|
+
] }),
|
|
862
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: admin.email }),
|
|
863
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
864
|
+
ui.Text,
|
|
865
|
+
{
|
|
866
|
+
size: "xsmall",
|
|
867
|
+
className: "text-ui-fg-muted font-mono",
|
|
868
|
+
children: admin.id
|
|
869
|
+
}
|
|
870
|
+
)
|
|
871
|
+
] })
|
|
872
|
+
},
|
|
873
|
+
admin.id
|
|
874
|
+
)) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 text-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: adminSearchQuery ? "No admins found" : "Start typing to search admins" }) })
|
|
875
|
+
}
|
|
876
|
+
)
|
|
877
|
+
] }),
|
|
878
|
+
selectedAssignTo && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "mt-1 text-ui-fg-subtle", children: [
|
|
879
|
+
"Selected Admin ID: ",
|
|
880
|
+
selectedAssignTo
|
|
881
|
+
] })
|
|
882
|
+
] }),
|
|
883
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
884
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
885
|
+
ui.Button,
|
|
886
|
+
{
|
|
887
|
+
variant: "primary",
|
|
888
|
+
onClick: handleAssign,
|
|
889
|
+
disabled: isAssigning || !selectedAssignTo,
|
|
890
|
+
isLoading: isAssigning,
|
|
891
|
+
children: "Assign"
|
|
892
|
+
}
|
|
893
|
+
),
|
|
894
|
+
request.assign_to && /* @__PURE__ */ jsxRuntime.jsx(
|
|
895
|
+
ui.Button,
|
|
896
|
+
{
|
|
897
|
+
variant: "secondary",
|
|
898
|
+
onClick: handleUnassign,
|
|
899
|
+
disabled: isAssigning,
|
|
900
|
+
isLoading: isAssigning,
|
|
901
|
+
children: "Unassign"
|
|
902
|
+
}
|
|
903
|
+
)
|
|
904
|
+
] })
|
|
905
|
+
] }),
|
|
906
|
+
assignError && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-error", children: assignError }),
|
|
907
|
+
assignSuccess && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-success", children: "Assignment updated successfully" })
|
|
908
|
+
] })
|
|
909
|
+
] }),
|
|
561
910
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-6 md:grid-cols-2", children: [
|
|
562
911
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-base p-6", children: [
|
|
563
912
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-4 text-lg", children: "Contact Information" }),
|
|
@@ -631,6 +980,103 @@ const ContactRequestDetailPage = () => {
|
|
|
631
980
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: key }),
|
|
632
981
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "font-medium", children: typeof value === "string" ? value : JSON.stringify(value) })
|
|
633
982
|
] }, key)) })
|
|
983
|
+
] }),
|
|
984
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-base p-6", children: [
|
|
985
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-4 text-lg", children: "Comments" }),
|
|
986
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 space-y-4 rounded-lg border border-ui-border-subtle bg-ui-bg-subtle p-4", children: [
|
|
987
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
988
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "mb-2 block text-sm font-medium text-ui-fg-base", children: "Add Comment" }),
|
|
989
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
990
|
+
ui.Textarea,
|
|
991
|
+
{
|
|
992
|
+
value: commentText,
|
|
993
|
+
onChange: (event) => setCommentText(event.target.value),
|
|
994
|
+
placeholder: "Enter your comment...",
|
|
995
|
+
className: "min-h-[80px]"
|
|
996
|
+
}
|
|
997
|
+
)
|
|
998
|
+
] }),
|
|
999
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1000
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
|
|
1001
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-sm font-medium text-ui-fg-base", children: "Images (URLs)" }),
|
|
1002
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1003
|
+
ui.Button,
|
|
1004
|
+
{
|
|
1005
|
+
variant: "transparent",
|
|
1006
|
+
size: "small",
|
|
1007
|
+
onClick: addImageInput,
|
|
1008
|
+
type: "button",
|
|
1009
|
+
children: "Add Image URL"
|
|
1010
|
+
}
|
|
1011
|
+
)
|
|
1012
|
+
] }),
|
|
1013
|
+
commentImages.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: commentImages.map((url, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
1014
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1015
|
+
ui.Input,
|
|
1016
|
+
{
|
|
1017
|
+
type: "text",
|
|
1018
|
+
value: url,
|
|
1019
|
+
onChange: (event) => updateImageUrl(index, event.target.value),
|
|
1020
|
+
placeholder: "https://example.com/image.jpg",
|
|
1021
|
+
className: "flex-1"
|
|
1022
|
+
}
|
|
1023
|
+
),
|
|
1024
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1025
|
+
ui.Button,
|
|
1026
|
+
{
|
|
1027
|
+
variant: "transparent",
|
|
1028
|
+
size: "small",
|
|
1029
|
+
onClick: () => removeImageInput(index),
|
|
1030
|
+
type: "button",
|
|
1031
|
+
children: "Remove"
|
|
1032
|
+
}
|
|
1033
|
+
)
|
|
1034
|
+
] }, index)) })
|
|
1035
|
+
] }),
|
|
1036
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1037
|
+
ui.Button,
|
|
1038
|
+
{
|
|
1039
|
+
variant: "primary",
|
|
1040
|
+
onClick: handleCreateComment,
|
|
1041
|
+
disabled: isCreatingComment || !commentText.trim() && commentImages.filter((img) => img.trim()).length === 0,
|
|
1042
|
+
isLoading: isCreatingComment,
|
|
1043
|
+
children: "Add Comment"
|
|
1044
|
+
}
|
|
1045
|
+
),
|
|
1046
|
+
commentError && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-error", children: commentError }),
|
|
1047
|
+
commentSuccess && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-success", children: "Comment added successfully" })
|
|
1048
|
+
] }),
|
|
1049
|
+
comments.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1050
|
+
"div",
|
|
1051
|
+
{
|
|
1052
|
+
className: "rounded-lg border border-ui-border-subtle bg-ui-bg-subtle p-4",
|
|
1053
|
+
children: [
|
|
1054
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2 flex items-center justify-between", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1055
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", children: comment.admin_id }),
|
|
1056
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: new Date(comment.created_at).toLocaleString() })
|
|
1057
|
+
] }) }),
|
|
1058
|
+
comment.comment && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-2 whitespace-pre-wrap", children: comment.comment }),
|
|
1059
|
+
comment.images && comment.images.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 space-y-2", children: [
|
|
1060
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Images:" }),
|
|
1061
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: comment.images.map((imageUrl, index) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1062
|
+
"a",
|
|
1063
|
+
{
|
|
1064
|
+
href: imageUrl,
|
|
1065
|
+
target: "_blank",
|
|
1066
|
+
rel: "noopener noreferrer",
|
|
1067
|
+
className: "text-sm text-ui-fg-interactive hover:underline",
|
|
1068
|
+
children: [
|
|
1069
|
+
"Image ",
|
|
1070
|
+
index + 1
|
|
1071
|
+
]
|
|
1072
|
+
},
|
|
1073
|
+
index
|
|
1074
|
+
)) })
|
|
1075
|
+
] })
|
|
1076
|
+
]
|
|
1077
|
+
},
|
|
1078
|
+
comment.id
|
|
1079
|
+
)) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "No comments yet" })
|
|
634
1080
|
] })
|
|
635
1081
|
] }) });
|
|
636
1082
|
};
|