medusa-contact-us 0.0.20 → 0.0.22

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,11 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useState, useCallback, useEffect, useMemo } from "react";
3
3
  import { defineRouteConfig } from "@medusajs/admin-sdk";
4
- import { Container, Heading, Text, Button, Input, Badge } from "@medusajs/ui";
5
- import { Envelope } from "@medusajs/icons";
6
- const useDebounce = (value, delay) => {
4
+ import { Container, Heading, Text, Button, Input, Badge, Textarea } from "@medusajs/ui";
5
+ import { Envelope, ChatBubbleLeftRight, ArrowLeft } from "@medusajs/icons";
6
+ import { useNavigate, useParams } from "react-router-dom";
7
+ import "@medusajs/admin-shared";
8
+ const useDebounce$1 = (value, delay) => {
7
9
  const [debouncedValue, setDebouncedValue] = useState(value);
8
10
  useEffect(() => {
9
11
  const handler = setTimeout(() => setDebouncedValue(value), delay);
@@ -26,7 +28,7 @@ const ContactEmailSubscriptionsPage = () => {
26
28
  const [items, setItems] = useState([]);
27
29
  const [statusFilter, setStatusFilter] = useState("all");
28
30
  const [query, setQuery] = useState("");
29
- const debouncedQuery = useDebounce(query, 300);
31
+ const debouncedQuery = useDebounce$1(query, 300);
30
32
  const [isLoading, setIsLoading] = useState(true);
31
33
  const [isFetchingMore, setIsFetchingMore] = useState(false);
32
34
  const [error, setError] = useState(null);
@@ -164,10 +166,777 @@ const ContactEmailSubscriptionsPage = () => {
164
166
  ) }) : null
165
167
  ] }) });
166
168
  };
167
- const config = defineRouteConfig({
169
+ const config$2 = defineRouteConfig({
168
170
  label: "Contact email list",
169
171
  icon: Envelope
170
172
  });
173
+ const useDebounce = (value, delay) => {
174
+ const [debouncedValue, setDebouncedValue] = useState(value);
175
+ useEffect(() => {
176
+ const handler = setTimeout(() => setDebouncedValue(value), delay);
177
+ return () => clearTimeout(handler);
178
+ }, [value, delay]);
179
+ return debouncedValue;
180
+ };
181
+ const getStatusBadgeClass$1 = (status) => {
182
+ const statusLower = status.toLowerCase();
183
+ if (statusLower === "pending") {
184
+ return "bg-ui-tag-orange-bg text-ui-tag-orange-text";
185
+ }
186
+ if (statusLower === "in_progress") {
187
+ return "bg-ui-tag-blue-bg text-ui-tag-blue-text";
188
+ }
189
+ if (statusLower === "resolved") {
190
+ return "bg-ui-tag-green-bg text-ui-tag-green-text";
191
+ }
192
+ if (statusLower === "closed") {
193
+ return "bg-ui-tag-grey-bg text-ui-tag-grey-text";
194
+ }
195
+ return "bg-ui-tag-purple-bg text-ui-tag-purple-text";
196
+ };
197
+ const ContactRequestsPage = () => {
198
+ const navigate = useNavigate();
199
+ const [items, setItems] = useState([]);
200
+ const [statusFilter, setStatusFilter] = useState("all");
201
+ const [emailQuery, setEmailQuery] = useState("");
202
+ const [sourceFilter, setSourceFilter] = useState("all");
203
+ const debouncedEmailQuery = useDebounce(emailQuery, 300);
204
+ const [isLoading, setIsLoading] = useState(true);
205
+ const [isFetchingMore, setIsFetchingMore] = useState(false);
206
+ const [error, setError] = useState(null);
207
+ const [offset, setOffset] = useState(0);
208
+ const [count, setCount] = useState(0);
209
+ const limit = 50;
210
+ const loadRequests = useCallback(
211
+ async (nextOffset, replace = false) => {
212
+ var _a;
213
+ try {
214
+ if (replace) {
215
+ setIsLoading(true);
216
+ } else {
217
+ setIsFetchingMore(true);
218
+ }
219
+ setError(null);
220
+ const params = new URLSearchParams();
221
+ params.set("limit", String(limit));
222
+ params.set("offset", String(nextOffset));
223
+ if (statusFilter !== "all") {
224
+ params.set("status", statusFilter);
225
+ }
226
+ if (debouncedEmailQuery.trim()) {
227
+ params.set("email", debouncedEmailQuery.trim());
228
+ }
229
+ if (sourceFilter !== "all") {
230
+ params.set("source", sourceFilter);
231
+ }
232
+ params.set("order", "created_at");
233
+ params.set("order_direction", "DESC");
234
+ const response = await fetch(
235
+ `/admin/contact-requests?${params.toString()}`,
236
+ { credentials: "include" }
237
+ );
238
+ if (!response.ok) {
239
+ const message = await response.text();
240
+ throw new Error(message || "Unable to load contact requests");
241
+ }
242
+ const payload = await response.json();
243
+ setCount(payload.count ?? 0);
244
+ setOffset(nextOffset + (((_a = payload.requests) == null ? void 0 : _a.length) ?? 0));
245
+ setItems(
246
+ (prev) => replace ? payload.requests ?? [] : [...prev, ...payload.requests ?? []]
247
+ );
248
+ } catch (loadError) {
249
+ const message = loadError instanceof Error ? loadError.message : "Unable to load contact requests";
250
+ setError(message);
251
+ } finally {
252
+ setIsLoading(false);
253
+ setIsFetchingMore(false);
254
+ }
255
+ },
256
+ [statusFilter, debouncedEmailQuery, sourceFilter]
257
+ );
258
+ useEffect(() => {
259
+ void loadRequests(0, true);
260
+ }, [statusFilter, debouncedEmailQuery, sourceFilter, loadRequests]);
261
+ const hasMore = useMemo(() => offset < count, [offset, count]);
262
+ const availableStatuses = useMemo(() => {
263
+ const statuses = /* @__PURE__ */ new Set();
264
+ items.forEach((item) => statuses.add(item.status));
265
+ return Array.from(statuses).sort();
266
+ }, [items]);
267
+ const availableSources = useMemo(() => {
268
+ const sources = /* @__PURE__ */ new Set();
269
+ items.forEach((item) => {
270
+ if (item.source) {
271
+ sources.add(item.source);
272
+ }
273
+ });
274
+ return Array.from(sources).sort();
275
+ }, [items]);
276
+ return /* @__PURE__ */ jsx("div", { className: "w-full p-6", children: /* @__PURE__ */ jsxs(Container, { className: "mx-auto flex w-full max-w-7xl flex-col gap-6 p-6", children: [
277
+ /* @__PURE__ */ jsxs("header", { className: "flex flex-col gap-3 md:flex-row md:items-center md:justify-between", children: [
278
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
279
+ /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Contact Requests" }),
280
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Manage and track customer contact requests from the storefront" })
281
+ ] }),
282
+ /* @__PURE__ */ jsx(Button, { variant: "primary", onClick: () => loadRequests(0, true), children: "Refresh" })
283
+ ] }),
284
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 md:flex-row md:items-center md:justify-between", children: [
285
+ /* @__PURE__ */ jsx(
286
+ Input,
287
+ {
288
+ placeholder: "Search by email",
289
+ value: emailQuery,
290
+ onChange: (event) => setEmailQuery(event.target.value),
291
+ className: "md:max-w-sm"
292
+ }
293
+ ),
294
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
295
+ /* @__PURE__ */ jsxs(
296
+ "select",
297
+ {
298
+ value: statusFilter,
299
+ onChange: (event) => setStatusFilter(event.target.value),
300
+ className: "h-9 rounded-md border border-ui-border-base bg-transparent px-3 text-sm text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive md:max-w-xs",
301
+ children: [
302
+ /* @__PURE__ */ jsx("option", { value: "all", children: "All Statuses" }),
303
+ availableStatuses.map((status) => /* @__PURE__ */ jsx("option", { value: status, children: status.replace("_", " ").toUpperCase() }, status))
304
+ ]
305
+ }
306
+ ),
307
+ /* @__PURE__ */ jsxs(
308
+ "select",
309
+ {
310
+ value: sourceFilter,
311
+ onChange: (event) => setSourceFilter(event.target.value),
312
+ className: "h-9 rounded-md border border-ui-border-base bg-transparent px-3 text-sm text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive md:max-w-xs",
313
+ children: [
314
+ /* @__PURE__ */ jsx("option", { value: "all", children: "All Sources" }),
315
+ availableSources.map((source) => /* @__PURE__ */ jsx("option", { value: source, children: source }, source))
316
+ ]
317
+ }
318
+ )
319
+ ] })
320
+ ] }),
321
+ error ? /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-ui-border-strong p-6 text-center", children: [
322
+ /* @__PURE__ */ jsx(Text, { weight: "plus", className: "text-ui-fg-error", children: error }),
323
+ /* @__PURE__ */ jsx("div", { className: "mt-4 flex justify-center", children: /* @__PURE__ */ jsx(
324
+ Button,
325
+ {
326
+ variant: "secondary",
327
+ onClick: () => loadRequests(0, true),
328
+ children: "Try again"
329
+ }
330
+ ) })
331
+ ] }) : null,
332
+ isLoading ? /* @__PURE__ */ jsx("div", { className: "flex justify-center py-16", children: /* @__PURE__ */ jsx(Text, { children: "Loading contact requests..." }) }) : items.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-dashed border-ui-border-strong p-10 text-center", children: [
333
+ /* @__PURE__ */ jsx(Heading, { level: "h3", className: "text-xl", children: "No contact requests yet" }),
334
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "mt-2 text-ui-fg-subtle", children: "Contact requests created through the storefront will appear here." })
335
+ ] }) : /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-xl border border-ui-border-base", children: /* @__PURE__ */ jsxs("table", { className: "min-w-full divide-y divide-ui-border-base", children: [
336
+ /* @__PURE__ */ jsx("thead", { className: "bg-ui-bg-subtle", children: /* @__PURE__ */ jsxs("tr", { children: [
337
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-ui-fg-muted", children: "Email" }),
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" }),
340
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-ui-fg-muted", children: "Source" }),
341
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-ui-fg-muted", children: "Created" }),
342
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-ui-fg-muted", children: "Actions" })
343
+ ] }) }),
344
+ /* @__PURE__ */ jsx("tbody", { className: "divide-y divide-ui-border-subtle", children: items.map((request) => /* @__PURE__ */ jsxs(
345
+ "tr",
346
+ {
347
+ className: "hover:bg-ui-bg-subtle/60 cursor-pointer",
348
+ onClick: () => navigate(`/contact-requests/${request.id}`),
349
+ children: [
350
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-4 font-medium text-ui-fg-base", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
351
+ /* @__PURE__ */ jsx("span", { children: request.email }),
352
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: request.id })
353
+ ] }) }),
354
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-4", children: /* @__PURE__ */ jsx(
355
+ Badge,
356
+ {
357
+ size: "2xsmall",
358
+ className: `uppercase ${getStatusBadgeClass$1(request.status)}`,
359
+ children: request.status.replace("_", " ")
360
+ }
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: "—" }) }),
363
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-4 text-ui-fg-subtle", children: request.source ?? "storefront" }),
364
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-4 text-ui-fg-subtle", children: new Date(request.created_at).toLocaleString() }),
365
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-4", children: /* @__PURE__ */ jsx(
366
+ Button,
367
+ {
368
+ variant: "transparent",
369
+ size: "small",
370
+ onClick: (e) => {
371
+ e.stopPropagation();
372
+ navigate(`/contact-requests/${request.id}`);
373
+ },
374
+ children: "View"
375
+ }
376
+ ) })
377
+ ]
378
+ },
379
+ request.id
380
+ )) })
381
+ ] }) }),
382
+ hasMore ? /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(
383
+ Button,
384
+ {
385
+ variant: "secondary",
386
+ isLoading: isFetchingMore,
387
+ onClick: () => loadRequests(offset, false),
388
+ children: "Load more"
389
+ }
390
+ ) }) : null
391
+ ] }) });
392
+ };
393
+ const config$1 = defineRouteConfig({
394
+ label: "Contact Requests",
395
+ icon: ChatBubbleLeftRight
396
+ });
397
+ const getStatusBadgeClass = (status) => {
398
+ const statusLower = status.toLowerCase();
399
+ if (statusLower === "pending") {
400
+ return "bg-ui-tag-orange-bg text-ui-tag-orange-text";
401
+ }
402
+ if (statusLower === "in_progress") {
403
+ return "bg-ui-tag-blue-bg text-ui-tag-blue-text";
404
+ }
405
+ if (statusLower === "resolved") {
406
+ return "bg-ui-tag-green-bg text-ui-tag-green-text";
407
+ }
408
+ if (statusLower === "closed") {
409
+ return "bg-ui-tag-grey-bg text-ui-tag-grey-text";
410
+ }
411
+ return "bg-ui-tag-purple-bg text-ui-tag-purple-text";
412
+ };
413
+ const ContactRequestDetailPage = () => {
414
+ const navigate = useNavigate();
415
+ const { id } = useParams();
416
+ const [request, setRequest] = useState(null);
417
+ const [comments, setComments] = useState([]);
418
+ const [nextAllowedStatuses, setNextAllowedStatuses] = useState([]);
419
+ const [selectedStatus, setSelectedStatus] = useState("");
420
+ const [isLoading, setIsLoading] = useState(true);
421
+ const [isUpdating, setIsUpdating] = useState(false);
422
+ const [error, setError] = useState(null);
423
+ const [updateError, setUpdateError] = useState(null);
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 [commentText, setCommentText] = useState("");
430
+ const [commentImages, setCommentImages] = useState([]);
431
+ const [isCreatingComment, setIsCreatingComment] = useState(false);
432
+ const [commentError, setCommentError] = useState(null);
433
+ const [commentSuccess, setCommentSuccess] = useState(false);
434
+ useEffect(() => {
435
+ if (!id) {
436
+ navigate("/contact-requests");
437
+ return;
438
+ }
439
+ const loadRequest = async () => {
440
+ try {
441
+ setIsLoading(true);
442
+ setError(null);
443
+ const response = await fetch(`/admin/contact-requests/${id}`, {
444
+ credentials: "include"
445
+ });
446
+ if (!response.ok) {
447
+ const message = await response.text();
448
+ throw new Error(message || "Unable to load contact request");
449
+ }
450
+ const payload = await response.json();
451
+ setRequest(payload.request);
452
+ setComments(payload.comments ?? []);
453
+ setNextAllowedStatuses(payload.next_allowed_statuses ?? []);
454
+ setSelectedStatus("");
455
+ setSelectedAssignTo("");
456
+ } catch (loadError) {
457
+ const message = loadError instanceof Error ? loadError.message : "Unable to load contact request";
458
+ setError(message);
459
+ } finally {
460
+ setIsLoading(false);
461
+ }
462
+ };
463
+ void loadRequest();
464
+ }, [id, navigate]);
465
+ const handleStatusUpdate = async () => {
466
+ if (!id || !selectedStatus) {
467
+ return;
468
+ }
469
+ try {
470
+ setIsUpdating(true);
471
+ setUpdateError(null);
472
+ setUpdateSuccess(false);
473
+ const response = await fetch(`/admin/contact-requests/${id}/status`, {
474
+ method: "POST",
475
+ headers: {
476
+ "Content-Type": "application/json"
477
+ },
478
+ credentials: "include",
479
+ body: JSON.stringify({ status: selectedStatus })
480
+ });
481
+ if (!response.ok) {
482
+ const message = await response.text();
483
+ throw new Error(message || "Unable to update status");
484
+ }
485
+ const payload = await response.json();
486
+ setRequest(payload.request);
487
+ setSelectedStatus("");
488
+ setUpdateSuccess(true);
489
+ setTimeout(() => setUpdateSuccess(false), 3e3);
490
+ const detailResponse = await fetch(`/admin/contact-requests/${id}`, {
491
+ credentials: "include"
492
+ });
493
+ if (detailResponse.ok) {
494
+ const detailPayload = await detailResponse.json();
495
+ setRequest(detailPayload.request);
496
+ setComments(detailPayload.comments ?? []);
497
+ setNextAllowedStatuses(detailPayload.next_allowed_statuses ?? []);
498
+ }
499
+ } catch (updateErr) {
500
+ const message = updateErr instanceof Error ? updateErr.message : "Unable to update status";
501
+ setUpdateError(message);
502
+ } finally {
503
+ setIsUpdating(false);
504
+ }
505
+ };
506
+ const handleAssign = async () => {
507
+ if (!id) {
508
+ return;
509
+ }
510
+ try {
511
+ setIsAssigning(true);
512
+ setAssignError(null);
513
+ setAssignSuccess(false);
514
+ const response = await fetch(`/admin/contact-requests/${id}/assign`, {
515
+ method: "POST",
516
+ headers: {
517
+ "Content-Type": "application/json"
518
+ },
519
+ credentials: "include",
520
+ body: JSON.stringify({
521
+ assign_to: selectedAssignTo.trim() || null
522
+ })
523
+ });
524
+ if (!response.ok) {
525
+ const message = await response.text();
526
+ throw new Error(message || "Unable to assign contact request");
527
+ }
528
+ const payload = await response.json();
529
+ setRequest(payload.request);
530
+ setSelectedAssignTo("");
531
+ setAssignSuccess(true);
532
+ setTimeout(() => setAssignSuccess(false), 3e3);
533
+ const detailResponse = await fetch(`/admin/contact-requests/${id}`, {
534
+ credentials: "include"
535
+ });
536
+ if (detailResponse.ok) {
537
+ const detailPayload = await detailResponse.json();
538
+ setRequest(detailPayload.request);
539
+ setComments(detailPayload.comments ?? []);
540
+ setNextAllowedStatuses(detailPayload.next_allowed_statuses ?? []);
541
+ }
542
+ } catch (assignErr) {
543
+ const message = assignErr instanceof Error ? assignErr.message : "Unable to assign contact request";
544
+ setAssignError(message);
545
+ } finally {
546
+ setIsAssigning(false);
547
+ }
548
+ };
549
+ const handleUnassign = async () => {
550
+ if (!id) {
551
+ return;
552
+ }
553
+ try {
554
+ setIsAssigning(true);
555
+ setAssignError(null);
556
+ setAssignSuccess(false);
557
+ const response = await fetch(`/admin/contact-requests/${id}/assign`, {
558
+ method: "POST",
559
+ headers: {
560
+ "Content-Type": "application/json"
561
+ },
562
+ credentials: "include",
563
+ body: JSON.stringify({
564
+ assign_to: null
565
+ })
566
+ });
567
+ if (!response.ok) {
568
+ const message = await response.text();
569
+ throw new Error(message || "Unable to unassign contact request");
570
+ }
571
+ const payload = await response.json();
572
+ setRequest(payload.request);
573
+ setAssignSuccess(true);
574
+ setTimeout(() => setAssignSuccess(false), 3e3);
575
+ const detailResponse = await fetch(`/admin/contact-requests/${id}`, {
576
+ credentials: "include"
577
+ });
578
+ if (detailResponse.ok) {
579
+ const detailPayload = await detailResponse.json();
580
+ setRequest(detailPayload.request);
581
+ setComments(detailPayload.comments ?? []);
582
+ setNextAllowedStatuses(detailPayload.next_allowed_statuses ?? []);
583
+ }
584
+ } catch (assignErr) {
585
+ const message = assignErr instanceof Error ? assignErr.message : "Unable to unassign contact request";
586
+ setAssignError(message);
587
+ } finally {
588
+ setIsAssigning(false);
589
+ }
590
+ };
591
+ const handleCreateComment = async () => {
592
+ if (!id || !commentText.trim() && commentImages.length === 0) {
593
+ setCommentError("Please provide a comment or at least one image");
594
+ return;
595
+ }
596
+ try {
597
+ setIsCreatingComment(true);
598
+ setCommentError(null);
599
+ setCommentSuccess(false);
600
+ const response = await fetch(`/admin/contact-requests/${id}/comments`, {
601
+ method: "POST",
602
+ headers: {
603
+ "Content-Type": "application/json"
604
+ },
605
+ credentials: "include",
606
+ body: JSON.stringify({
607
+ comment: commentText.trim() || void 0,
608
+ images: commentImages.filter((img) => img.trim()).length > 0 ? commentImages.filter((img) => img.trim()) : void 0
609
+ })
610
+ });
611
+ if (!response.ok) {
612
+ const message = await response.text();
613
+ throw new Error(message || "Unable to create comment");
614
+ }
615
+ const detailResponse = await fetch(`/admin/contact-requests/${id}`, {
616
+ credentials: "include"
617
+ });
618
+ if (detailResponse.ok) {
619
+ const detailPayload = await detailResponse.json();
620
+ setComments(detailPayload.comments ?? []);
621
+ }
622
+ setCommentText("");
623
+ setCommentImages([]);
624
+ setCommentSuccess(true);
625
+ setTimeout(() => setCommentSuccess(false), 3e3);
626
+ } catch (commentErr) {
627
+ const message = commentErr instanceof Error ? commentErr.message : "Unable to create comment";
628
+ setCommentError(message);
629
+ } finally {
630
+ setIsCreatingComment(false);
631
+ }
632
+ };
633
+ const addImageInput = () => {
634
+ setCommentImages([...commentImages, ""]);
635
+ };
636
+ const removeImageInput = (index) => {
637
+ setCommentImages(commentImages.filter((_, i) => i !== index));
638
+ };
639
+ const updateImageUrl = (index, value) => {
640
+ const updated = [...commentImages];
641
+ updated[index] = value;
642
+ setCommentImages(updated);
643
+ };
644
+ if (isLoading) {
645
+ 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..." }) }) }) });
646
+ }
647
+ if (error || !request) {
648
+ 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__ */ jsxs("div", { className: "rounded-lg border border-ui-border-strong p-6 text-center", children: [
649
+ /* @__PURE__ */ jsx(Text, { weight: "plus", className: "text-ui-fg-error", children: error || "Contact request not found" }),
650
+ /* @__PURE__ */ jsx("div", { className: "mt-4 flex justify-center", children: /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/contact-requests"), children: "Back to list" }) })
651
+ ] }) }) });
652
+ }
653
+ return /* @__PURE__ */ jsx("div", { className: "w-full p-6", children: /* @__PURE__ */ jsxs(Container, { className: "mx-auto flex w-full max-w-5xl flex-col gap-6 p-6", children: [
654
+ /* @__PURE__ */ jsxs("header", { className: "flex flex-col gap-3", children: [
655
+ /* @__PURE__ */ jsxs(
656
+ Button,
657
+ {
658
+ variant: "transparent",
659
+ size: "small",
660
+ onClick: () => navigate("/contact-requests"),
661
+ className: "w-fit",
662
+ children: [
663
+ /* @__PURE__ */ jsx(ArrowLeft, { className: "mr-2" }),
664
+ "Back to list"
665
+ ]
666
+ }
667
+ ),
668
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 md:flex-row md:items-center md:justify-between", children: [
669
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
670
+ /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Contact Request Details" }),
671
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: request.id })
672
+ ] }),
673
+ /* @__PURE__ */ jsx(
674
+ Badge,
675
+ {
676
+ size: "small",
677
+ className: `uppercase ${getStatusBadgeClass(request.status)}`,
678
+ children: request.status.replace("_", " ")
679
+ }
680
+ )
681
+ ] })
682
+ ] }),
683
+ nextAllowedStatuses.length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-base p-6", children: [
684
+ /* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-4 text-lg", children: "Update Status" }),
685
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 md:flex-row md:items-end", children: [
686
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
687
+ /* @__PURE__ */ jsx("label", { className: "mb-2 block text-sm font-medium text-ui-fg-base", children: "New Status" }),
688
+ /* @__PURE__ */ jsxs(
689
+ "select",
690
+ {
691
+ value: selectedStatus,
692
+ onChange: (event) => setSelectedStatus(event.target.value),
693
+ className: "h-9 w-full rounded-md border border-ui-border-base bg-transparent px-3 text-sm text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive",
694
+ children: [
695
+ /* @__PURE__ */ jsx("option", { value: "", children: "Select new status" }),
696
+ nextAllowedStatuses.map((status) => /* @__PURE__ */ jsx("option", { value: status, children: status.replace("_", " ").toUpperCase() }, status))
697
+ ]
698
+ }
699
+ )
700
+ ] }),
701
+ /* @__PURE__ */ jsx(
702
+ Button,
703
+ {
704
+ variant: "primary",
705
+ onClick: handleStatusUpdate,
706
+ disabled: !selectedStatus || isUpdating,
707
+ isLoading: isUpdating,
708
+ children: "Update Status"
709
+ }
710
+ )
711
+ ] }),
712
+ updateError && /* @__PURE__ */ jsx(Text, { size: "small", className: "mt-2 text-ui-fg-error", children: updateError }),
713
+ updateSuccess && /* @__PURE__ */ jsx(Text, { size: "small", className: "mt-2 text-ui-fg-success", children: "Status updated successfully" })
714
+ ] }),
715
+ /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-base p-6", children: [
716
+ /* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-4 text-lg", children: "Assignment" }),
717
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
718
+ /* @__PURE__ */ jsxs("div", { children: [
719
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Currently Assigned To" }),
720
+ /* @__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" }) })
721
+ ] }),
722
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 md:flex-row md:items-end", children: [
723
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
724
+ /* @__PURE__ */ jsx("label", { className: "mb-2 block text-sm font-medium text-ui-fg-base", children: "Admin User ID" }),
725
+ /* @__PURE__ */ jsx(
726
+ Input,
727
+ {
728
+ type: "text",
729
+ value: selectedAssignTo,
730
+ onChange: (event) => setSelectedAssignTo(event.target.value),
731
+ placeholder: "Enter admin user ID",
732
+ className: "w-full"
733
+ }
734
+ )
735
+ ] }),
736
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
737
+ /* @__PURE__ */ jsx(
738
+ Button,
739
+ {
740
+ variant: "primary",
741
+ onClick: handleAssign,
742
+ disabled: isAssigning,
743
+ isLoading: isAssigning,
744
+ children: "Assign"
745
+ }
746
+ ),
747
+ request.assign_to && /* @__PURE__ */ jsx(
748
+ Button,
749
+ {
750
+ variant: "secondary",
751
+ onClick: handleUnassign,
752
+ disabled: isAssigning,
753
+ isLoading: isAssigning,
754
+ children: "Unassign"
755
+ }
756
+ )
757
+ ] })
758
+ ] }),
759
+ assignError && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-error", children: assignError }),
760
+ assignSuccess && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-success", children: "Assignment updated successfully" })
761
+ ] })
762
+ ] }),
763
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-6 md:grid-cols-2", children: [
764
+ /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-base p-6", children: [
765
+ /* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-4 text-lg", children: "Contact Information" }),
766
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
767
+ /* @__PURE__ */ jsxs("div", { children: [
768
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Email" }),
769
+ /* @__PURE__ */ jsx(Text, { className: "font-medium", children: request.email })
770
+ ] }),
771
+ /* @__PURE__ */ jsxs("div", { children: [
772
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Source" }),
773
+ /* @__PURE__ */ jsx(Text, { className: "font-medium", children: request.source ?? "storefront" })
774
+ ] }),
775
+ /* @__PURE__ */ jsxs("div", { children: [
776
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Created" }),
777
+ /* @__PURE__ */ jsx(Text, { className: "font-medium", children: new Date(request.created_at).toLocaleString() })
778
+ ] }),
779
+ /* @__PURE__ */ jsxs("div", { children: [
780
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Last Updated" }),
781
+ /* @__PURE__ */ jsx(Text, { className: "font-medium", children: new Date(request.updated_at).toLocaleString() })
782
+ ] })
783
+ ] })
784
+ ] }),
785
+ /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-base p-6", children: [
786
+ /* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-4 text-lg", children: "Status History" }),
787
+ request.status_history && request.status_history.length > 0 ? /* @__PURE__ */ jsx("div", { className: "space-y-2", children: request.status_history.map((entry, index) => /* @__PURE__ */ jsx(
788
+ "div",
789
+ {
790
+ className: "flex items-center justify-between border-b border-ui-border-subtle pb-2 last:border-0",
791
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
792
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
793
+ /* @__PURE__ */ jsx(
794
+ Badge,
795
+ {
796
+ size: "2xsmall",
797
+ className: `uppercase ${getStatusBadgeClass(entry.to)}`,
798
+ children: entry.to.replace("_", " ")
799
+ }
800
+ ),
801
+ entry.from && /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
802
+ "from ",
803
+ entry.from.replace("_", " ")
804
+ ] })
805
+ ] }),
806
+ /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
807
+ new Date(entry.changed_at).toLocaleString(),
808
+ entry.changed_by && ` by ${entry.changed_by}`
809
+ ] })
810
+ ] })
811
+ },
812
+ index
813
+ )) }) : /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "No status history available" })
814
+ ] })
815
+ ] }),
816
+ request.payload && Object.keys(request.payload).length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-base p-6", children: [
817
+ /* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-4 text-lg", children: "Request Details" }),
818
+ /* @__PURE__ */ jsx("div", { className: "space-y-3", children: Object.entries(request.payload).map(([key, value]) => /* @__PURE__ */ jsxs("div", { children: [
819
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: key.charAt(0).toUpperCase() + key.slice(1).replace("_", " ") }),
820
+ /* @__PURE__ */ jsx(
821
+ Textarea,
822
+ {
823
+ value: typeof value === "string" ? value : JSON.stringify(value, null, 2),
824
+ readOnly: true,
825
+ className: "mt-1 min-h-[60px]"
826
+ }
827
+ )
828
+ ] }, key)) })
829
+ ] }),
830
+ request.metadata && Object.keys(request.metadata).length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-base p-6", children: [
831
+ /* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-4 text-lg", children: "Metadata" }),
832
+ /* @__PURE__ */ jsx("div", { className: "space-y-2", children: Object.entries(request.metadata).map(([key, value]) => /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
833
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: key }),
834
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "font-medium", children: typeof value === "string" ? value : JSON.stringify(value) })
835
+ ] }, key)) })
836
+ ] }),
837
+ /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-base p-6", children: [
838
+ /* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-4 text-lg", children: "Comments" }),
839
+ /* @__PURE__ */ jsxs("div", { className: "mb-6 space-y-4 rounded-lg border border-ui-border-subtle bg-ui-bg-subtle p-4", children: [
840
+ /* @__PURE__ */ jsxs("div", { children: [
841
+ /* @__PURE__ */ jsx("label", { className: "mb-2 block text-sm font-medium text-ui-fg-base", children: "Add Comment" }),
842
+ /* @__PURE__ */ jsx(
843
+ Textarea,
844
+ {
845
+ value: commentText,
846
+ onChange: (event) => setCommentText(event.target.value),
847
+ placeholder: "Enter your comment...",
848
+ className: "min-h-[80px]"
849
+ }
850
+ )
851
+ ] }),
852
+ /* @__PURE__ */ jsxs("div", { children: [
853
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
854
+ /* @__PURE__ */ jsx("label", { className: "text-sm font-medium text-ui-fg-base", children: "Images (URLs)" }),
855
+ /* @__PURE__ */ jsx(
856
+ Button,
857
+ {
858
+ variant: "transparent",
859
+ size: "small",
860
+ onClick: addImageInput,
861
+ type: "button",
862
+ children: "Add Image URL"
863
+ }
864
+ )
865
+ ] }),
866
+ commentImages.length > 0 && /* @__PURE__ */ jsx("div", { className: "space-y-2", children: commentImages.map((url, index) => /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
867
+ /* @__PURE__ */ jsx(
868
+ Input,
869
+ {
870
+ type: "text",
871
+ value: url,
872
+ onChange: (event) => updateImageUrl(index, event.target.value),
873
+ placeholder: "https://example.com/image.jpg",
874
+ className: "flex-1"
875
+ }
876
+ ),
877
+ /* @__PURE__ */ jsx(
878
+ Button,
879
+ {
880
+ variant: "transparent",
881
+ size: "small",
882
+ onClick: () => removeImageInput(index),
883
+ type: "button",
884
+ children: "Remove"
885
+ }
886
+ )
887
+ ] }, index)) })
888
+ ] }),
889
+ /* @__PURE__ */ jsx(
890
+ Button,
891
+ {
892
+ variant: "primary",
893
+ onClick: handleCreateComment,
894
+ disabled: isCreatingComment || !commentText.trim() && commentImages.filter((img) => img.trim()).length === 0,
895
+ isLoading: isCreatingComment,
896
+ children: "Add Comment"
897
+ }
898
+ ),
899
+ commentError && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-error", children: commentError }),
900
+ commentSuccess && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-success", children: "Comment added successfully" })
901
+ ] }),
902
+ comments.length > 0 ? /* @__PURE__ */ jsx("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ jsxs(
903
+ "div",
904
+ {
905
+ className: "rounded-lg border border-ui-border-subtle bg-ui-bg-subtle p-4",
906
+ children: [
907
+ /* @__PURE__ */ jsx("div", { className: "mb-2 flex items-center justify-between", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
908
+ /* @__PURE__ */ jsx(Badge, { size: "2xsmall", children: comment.admin_id }),
909
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: new Date(comment.created_at).toLocaleString() })
910
+ ] }) }),
911
+ comment.comment && /* @__PURE__ */ jsx(Text, { className: "mb-2 whitespace-pre-wrap", children: comment.comment }),
912
+ comment.images && comment.images.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3 space-y-2", children: [
913
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Images:" }),
914
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: comment.images.map((imageUrl, index) => /* @__PURE__ */ jsxs(
915
+ "a",
916
+ {
917
+ href: imageUrl,
918
+ target: "_blank",
919
+ rel: "noopener noreferrer",
920
+ className: "text-sm text-ui-fg-interactive hover:underline",
921
+ children: [
922
+ "Image ",
923
+ index + 1
924
+ ]
925
+ },
926
+ index
927
+ )) })
928
+ ] })
929
+ ]
930
+ },
931
+ comment.id
932
+ )) }) : /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "No comments yet" })
933
+ ] })
934
+ ] }) });
935
+ };
936
+ const config = defineRouteConfig({
937
+ label: "Contact Request Details",
938
+ icon: ChatBubbleLeftRight
939
+ });
171
940
  const en = {};
172
941
  const i18nTranslations0 = {
173
942
  en
@@ -178,15 +947,35 @@ const routeModule = {
178
947
  {
179
948
  Component: ContactEmailSubscriptionsPage,
180
949
  path: "/contact-email-subscriptions"
950
+ },
951
+ {
952
+ Component: ContactRequestsPage,
953
+ path: "/contact-requests"
954
+ },
955
+ {
956
+ Component: ContactRequestDetailPage,
957
+ path: "/contact-requests/:id"
181
958
  }
182
959
  ]
183
960
  };
184
961
  const menuItemModule = {
185
962
  menuItems: [
963
+ {
964
+ label: config$2.label,
965
+ icon: config$2.icon,
966
+ path: "/contact-email-subscriptions",
967
+ nested: void 0
968
+ },
969
+ {
970
+ label: config$1.label,
971
+ icon: config$1.icon,
972
+ path: "/contact-requests",
973
+ nested: void 0
974
+ },
186
975
  {
187
976
  label: config.label,
188
977
  icon: config.icon,
189
- path: "/contact-email-subscriptions",
978
+ path: "/contact-requests/:id",
190
979
  nested: void 0
191
980
  }
192
981
  ]