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