medusa-contact-us 0.0.17 → 0.0.21

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.
Files changed (26) hide show
  1. package/.medusa/server/src/admin/index.js +493 -4
  2. package/.medusa/server/src/admin/index.mjs +495 -6
  3. package/.medusa/server/src/api/admin/contact-requests/[id]/route.js +17 -0
  4. package/.medusa/server/src/api/admin/contact-requests/[id]/status/route.js +24 -0
  5. package/.medusa/server/src/api/admin/contact-requests/route.js +67 -0
  6. package/.medusa/server/src/api/admin/contact-requests/validators.js +29 -0
  7. package/.medusa/server/src/api/store/contact-requests/route.js +23 -0
  8. package/.medusa/server/src/api/store/contact-requests/validators.js +11 -0
  9. package/.medusa/server/src/helpers/contact-request.js +52 -0
  10. package/.medusa/server/src/helpers/index.js +5 -2
  11. package/.medusa/server/src/index.js +10 -2
  12. package/.medusa/server/src/modules/contact-requests/index.js +14 -0
  13. package/.medusa/server/src/modules/contact-requests/migrations/Migration20241129163317.js +47 -0
  14. package/.medusa/server/src/modules/contact-requests/models/contact-request.js +15 -0
  15. package/.medusa/server/src/modules/contact-requests/service.js +236 -0
  16. package/.medusa/server/src/modules/contact-requests/utils/resolve-options.js +48 -0
  17. package/.medusa/server/src/types/contact-request.js +3 -0
  18. package/.medusa/server/src/workflows/create-contact-request-workflow.js +13 -0
  19. package/.medusa/server/src/workflows/index.js +8 -0
  20. package/.medusa/server/src/workflows/steps/create-contact-request-step.js +13 -0
  21. package/.medusa/server/src/workflows/steps/resolve-status-transition-step.js +15 -0
  22. package/.medusa/server/src/workflows/steps/send-status-notification-step.js +56 -0
  23. package/.medusa/server/src/workflows/steps/update-contact-request-status-step.js +17 -0
  24. package/.medusa/server/src/workflows/update-contact-request-status-workflow.js +33 -0
  25. package/README.md +381 -10
  26. package/package.json +2 -1
@@ -4,7 +4,8 @@ const react = require("react");
4
4
  const adminSdk = require("@medusajs/admin-sdk");
5
5
  const ui = require("@medusajs/ui");
6
6
  const icons = require("@medusajs/icons");
7
- const useDebounce = (value, delay) => {
7
+ const reactRouterDom = require("react-router-dom");
8
+ const useDebounce$1 = (value, delay) => {
8
9
  const [debouncedValue, setDebouncedValue] = react.useState(value);
9
10
  react.useEffect(() => {
10
11
  const handler = setTimeout(() => setDebouncedValue(value), delay);
@@ -27,7 +28,7 @@ const ContactEmailSubscriptionsPage = () => {
27
28
  const [items, setItems] = react.useState([]);
28
29
  const [statusFilter, setStatusFilter] = react.useState("all");
29
30
  const [query, setQuery] = react.useState("");
30
- const debouncedQuery = useDebounce(query, 300);
31
+ const debouncedQuery = useDebounce$1(query, 300);
31
32
  const [isLoading, setIsLoading] = react.useState(true);
32
33
  const [isFetchingMore, setIsFetchingMore] = react.useState(false);
33
34
  const [error, setError] = react.useState(null);
@@ -165,10 +166,478 @@ const ContactEmailSubscriptionsPage = () => {
165
166
  ) }) : null
166
167
  ] }) });
167
168
  };
168
- const config = adminSdk.defineRouteConfig({
169
+ const config$2 = adminSdk.defineRouteConfig({
169
170
  label: "Contact email list",
170
171
  icon: icons.Envelope
171
172
  });
173
+ const useDebounce = (value, delay) => {
174
+ const [debouncedValue, setDebouncedValue] = react.useState(value);
175
+ react.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 = reactRouterDom.useNavigate();
199
+ const [items, setItems] = react.useState([]);
200
+ const [statusFilter, setStatusFilter] = react.useState("all");
201
+ const [emailQuery, setEmailQuery] = react.useState("");
202
+ const [sourceFilter, setSourceFilter] = react.useState("all");
203
+ const debouncedEmailQuery = useDebounce(emailQuery, 300);
204
+ const [isLoading, setIsLoading] = react.useState(true);
205
+ const [isFetchingMore, setIsFetchingMore] = react.useState(false);
206
+ const [error, setError] = react.useState(null);
207
+ const [offset, setOffset] = react.useState(0);
208
+ const [count, setCount] = react.useState(0);
209
+ const limit = 50;
210
+ const loadRequests = react.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
+ react.useEffect(() => {
259
+ void loadRequests(0, true);
260
+ }, [statusFilter, debouncedEmailQuery, sourceFilter, loadRequests]);
261
+ const hasMore = react.useMemo(() => offset < count, [offset, count]);
262
+ const availableStatuses = react.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 = react.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__ */ jsxRuntime.jsx("div", { className: "w-full p-6", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "mx-auto flex w-full max-w-7xl flex-col gap-6 p-6", children: [
277
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex flex-col gap-3 md:flex-row md:items-center md:justify-between", children: [
278
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
279
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Contact Requests" }),
280
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Manage and track customer contact requests from the storefront" })
281
+ ] }),
282
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "primary", onClick: () => loadRequests(0, true), children: "Refresh" })
283
+ ] }),
284
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 md:flex-row md:items-center md:justify-between", children: [
285
+ /* @__PURE__ */ jsxRuntime.jsx(
286
+ ui.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__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
295
+ /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx("option", { value: "all", children: "All Statuses" }),
303
+ availableStatuses.map((status) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: status, children: status.replace("_", " ").toUpperCase() }, status))
304
+ ]
305
+ }
306
+ ),
307
+ /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx("option", { value: "all", children: "All Sources" }),
315
+ availableSources.map((source) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: source, children: source }, source))
316
+ ]
317
+ }
318
+ )
319
+ ] })
320
+ ] }),
321
+ error ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-strong p-6 text-center", children: [
322
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { weight: "plus", className: "text-ui-fg-error", children: error }),
323
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
324
+ ui.Button,
325
+ {
326
+ variant: "secondary",
327
+ onClick: () => loadRequests(0, true),
328
+ children: "Try again"
329
+ }
330
+ ) })
331
+ ] }) : null,
332
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center py-16", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Loading contact requests..." }) }) : items.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-dashed border-ui-border-strong p-10 text-center", children: [
333
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: "text-xl", children: "No contact requests yet" }),
334
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "mt-2 text-ui-fg-subtle", children: "Contact requests created through the storefront will appear here." })
335
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden rounded-xl border border-ui-border-base", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "min-w-full divide-y divide-ui-border-base", children: [
336
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "bg-ui-bg-subtle", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
337
+ /* @__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
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-ui-fg-muted", children: "Status" }),
339
+ /* @__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
+ /* @__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
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-ui-fg-muted", children: "Actions" })
342
+ ] }) }),
343
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "divide-y divide-ui-border-subtle", children: items.map((request) => /* @__PURE__ */ jsxRuntime.jsxs(
344
+ "tr",
345
+ {
346
+ className: "hover:bg-ui-bg-subtle/60 cursor-pointer",
347
+ onClick: () => navigate(`/contact-requests/${request.id}`),
348
+ children: [
349
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-4 font-medium text-ui-fg-base", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-0.5", children: [
350
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: request.email }),
351
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: request.id })
352
+ ] }) }),
353
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(
354
+ ui.Badge,
355
+ {
356
+ size: "2xsmall",
357
+ className: `uppercase ${getStatusBadgeClass$1(request.status)}`,
358
+ children: request.status.replace("_", " ")
359
+ }
360
+ ) }),
361
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-4 text-ui-fg-subtle", children: request.source ?? "storefront" }),
362
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-4 text-ui-fg-subtle", children: new Date(request.created_at).toLocaleString() }),
363
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(
364
+ ui.Button,
365
+ {
366
+ variant: "transparent",
367
+ size: "small",
368
+ onClick: (e) => {
369
+ e.stopPropagation();
370
+ navigate(`/contact-requests/${request.id}`);
371
+ },
372
+ children: "View"
373
+ }
374
+ ) })
375
+ ]
376
+ },
377
+ request.id
378
+ )) })
379
+ ] }) }),
380
+ hasMore ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
381
+ ui.Button,
382
+ {
383
+ variant: "secondary",
384
+ isLoading: isFetchingMore,
385
+ onClick: () => loadRequests(offset, false),
386
+ children: "Load more"
387
+ }
388
+ ) }) : null
389
+ ] }) });
390
+ };
391
+ const config$1 = adminSdk.defineRouteConfig({
392
+ label: "Contact Requests",
393
+ icon: icons.ChatBubbleLeftRight
394
+ });
395
+ const getStatusBadgeClass = (status) => {
396
+ const statusLower = status.toLowerCase();
397
+ if (statusLower === "pending") {
398
+ return "bg-ui-tag-orange-bg text-ui-tag-orange-text";
399
+ }
400
+ if (statusLower === "in_progress") {
401
+ return "bg-ui-tag-blue-bg text-ui-tag-blue-text";
402
+ }
403
+ if (statusLower === "resolved") {
404
+ return "bg-ui-tag-green-bg text-ui-tag-green-text";
405
+ }
406
+ if (statusLower === "closed") {
407
+ return "bg-ui-tag-grey-bg text-ui-tag-grey-text";
408
+ }
409
+ return "bg-ui-tag-purple-bg text-ui-tag-purple-text";
410
+ };
411
+ const ContactRequestDetailPage = () => {
412
+ const navigate = reactRouterDom.useNavigate();
413
+ const { id } = reactRouterDom.useParams();
414
+ const [request, setRequest] = react.useState(null);
415
+ const [nextAllowedStatuses, setNextAllowedStatuses] = react.useState([]);
416
+ const [selectedStatus, setSelectedStatus] = react.useState("");
417
+ const [isLoading, setIsLoading] = react.useState(true);
418
+ const [isUpdating, setIsUpdating] = react.useState(false);
419
+ const [error, setError] = react.useState(null);
420
+ const [updateError, setUpdateError] = react.useState(null);
421
+ const [updateSuccess, setUpdateSuccess] = react.useState(false);
422
+ react.useEffect(() => {
423
+ if (!id) {
424
+ navigate("/contact-requests");
425
+ return;
426
+ }
427
+ const loadRequest = async () => {
428
+ try {
429
+ setIsLoading(true);
430
+ setError(null);
431
+ const response = await fetch(`/admin/contact-requests/${id}`, {
432
+ credentials: "include"
433
+ });
434
+ if (!response.ok) {
435
+ const message = await response.text();
436
+ throw new Error(message || "Unable to load contact request");
437
+ }
438
+ const payload = await response.json();
439
+ setRequest(payload.request);
440
+ setNextAllowedStatuses(payload.next_allowed_statuses ?? []);
441
+ setSelectedStatus("");
442
+ } catch (loadError) {
443
+ const message = loadError instanceof Error ? loadError.message : "Unable to load contact request";
444
+ setError(message);
445
+ } finally {
446
+ setIsLoading(false);
447
+ }
448
+ };
449
+ void loadRequest();
450
+ }, [id, navigate]);
451
+ const handleStatusUpdate = async () => {
452
+ if (!id || !selectedStatus) {
453
+ return;
454
+ }
455
+ try {
456
+ setIsUpdating(true);
457
+ setUpdateError(null);
458
+ setUpdateSuccess(false);
459
+ const response = await fetch(`/admin/contact-requests/${id}/status`, {
460
+ method: "POST",
461
+ headers: {
462
+ "Content-Type": "application/json"
463
+ },
464
+ credentials: "include",
465
+ body: JSON.stringify({ status: selectedStatus })
466
+ });
467
+ if (!response.ok) {
468
+ const message = await response.text();
469
+ throw new Error(message || "Unable to update status");
470
+ }
471
+ const payload = await response.json();
472
+ setRequest(payload.request);
473
+ setSelectedStatus("");
474
+ setUpdateSuccess(true);
475
+ setTimeout(() => setUpdateSuccess(false), 3e3);
476
+ const detailResponse = await fetch(`/admin/contact-requests/${id}`, {
477
+ credentials: "include"
478
+ });
479
+ if (detailResponse.ok) {
480
+ const detailPayload = await detailResponse.json();
481
+ setNextAllowedStatuses(detailPayload.next_allowed_statuses ?? []);
482
+ }
483
+ } catch (updateErr) {
484
+ const message = updateErr instanceof Error ? updateErr.message : "Unable to update status";
485
+ setUpdateError(message);
486
+ } finally {
487
+ setIsUpdating(false);
488
+ }
489
+ };
490
+ if (isLoading) {
491
+ 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
+ }
493
+ if (error || !request) {
494
+ 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.jsxs("div", { className: "rounded-lg border border-ui-border-strong p-6 text-center", children: [
495
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { weight: "plus", className: "text-ui-fg-error", children: error || "Contact request not found" }),
496
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/contact-requests"), children: "Back to list" }) })
497
+ ] }) }) });
498
+ }
499
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full p-6", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "mx-auto flex w-full max-w-5xl flex-col gap-6 p-6", children: [
500
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex flex-col gap-3", children: [
501
+ /* @__PURE__ */ jsxRuntime.jsxs(
502
+ ui.Button,
503
+ {
504
+ variant: "transparent",
505
+ size: "small",
506
+ onClick: () => navigate("/contact-requests"),
507
+ className: "w-fit",
508
+ children: [
509
+ /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, { className: "mr-2" }),
510
+ "Back to list"
511
+ ]
512
+ }
513
+ ),
514
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1 md:flex-row md:items-center md:justify-between", children: [
515
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
516
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Contact Request Details" }),
517
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: request.id })
518
+ ] }),
519
+ /* @__PURE__ */ jsxRuntime.jsx(
520
+ ui.Badge,
521
+ {
522
+ size: "small",
523
+ className: `uppercase ${getStatusBadgeClass(request.status)}`,
524
+ children: request.status.replace("_", " ")
525
+ }
526
+ )
527
+ ] })
528
+ ] }),
529
+ nextAllowedStatuses.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-base p-6", children: [
530
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-4 text-lg", children: "Update Status" }),
531
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 md:flex-row md:items-end", children: [
532
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
533
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "mb-2 block text-sm font-medium text-ui-fg-base", children: "New Status" }),
534
+ /* @__PURE__ */ jsxRuntime.jsxs(
535
+ "select",
536
+ {
537
+ value: selectedStatus,
538
+ onChange: (event) => setSelectedStatus(event.target.value),
539
+ 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",
540
+ children: [
541
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select new status" }),
542
+ nextAllowedStatuses.map((status) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: status, children: status.replace("_", " ").toUpperCase() }, status))
543
+ ]
544
+ }
545
+ )
546
+ ] }),
547
+ /* @__PURE__ */ jsxRuntime.jsx(
548
+ ui.Button,
549
+ {
550
+ variant: "primary",
551
+ onClick: handleStatusUpdate,
552
+ disabled: !selectedStatus || isUpdating,
553
+ isLoading: isUpdating,
554
+ children: "Update Status"
555
+ }
556
+ )
557
+ ] }),
558
+ updateError && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "mt-2 text-ui-fg-error", children: updateError }),
559
+ updateSuccess && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "mt-2 text-ui-fg-success", children: "Status updated successfully" })
560
+ ] }),
561
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-6 md:grid-cols-2", children: [
562
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-base p-6", children: [
563
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-4 text-lg", children: "Contact Information" }),
564
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
565
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
566
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Email" }),
567
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: request.email })
568
+ ] }),
569
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
570
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Source" }),
571
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: request.source ?? "storefront" })
572
+ ] }),
573
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
574
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Created" }),
575
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: new Date(request.created_at).toLocaleString() })
576
+ ] }),
577
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
578
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Last Updated" }),
579
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: new Date(request.updated_at).toLocaleString() })
580
+ ] })
581
+ ] })
582
+ ] }),
583
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-base p-6", children: [
584
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-4 text-lg", children: "Status History" }),
585
+ request.status_history && request.status_history.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: request.status_history.map((entry, index) => /* @__PURE__ */ jsxRuntime.jsx(
586
+ "div",
587
+ {
588
+ className: "flex items-center justify-between border-b border-ui-border-subtle pb-2 last:border-0",
589
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
590
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
591
+ /* @__PURE__ */ jsxRuntime.jsx(
592
+ ui.Badge,
593
+ {
594
+ size: "2xsmall",
595
+ className: `uppercase ${getStatusBadgeClass(entry.to)}`,
596
+ children: entry.to.replace("_", " ")
597
+ }
598
+ ),
599
+ entry.from && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
600
+ "from ",
601
+ entry.from.replace("_", " ")
602
+ ] })
603
+ ] }),
604
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
605
+ new Date(entry.changed_at).toLocaleString(),
606
+ entry.changed_by && ` by ${entry.changed_by}`
607
+ ] })
608
+ ] })
609
+ },
610
+ index
611
+ )) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "No status history available" })
612
+ ] })
613
+ ] }),
614
+ request.payload && Object.keys(request.payload).length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-base p-6", children: [
615
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-4 text-lg", children: "Request Details" }),
616
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3", children: Object.entries(request.payload).map(([key, value]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
617
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: key.charAt(0).toUpperCase() + key.slice(1).replace("_", " ") }),
618
+ /* @__PURE__ */ jsxRuntime.jsx(
619
+ ui.Textarea,
620
+ {
621
+ value: typeof value === "string" ? value : JSON.stringify(value, null, 2),
622
+ readOnly: true,
623
+ className: "mt-1 min-h-[60px]"
624
+ }
625
+ )
626
+ ] }, key)) })
627
+ ] }),
628
+ request.metadata && Object.keys(request.metadata).length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-base p-6", children: [
629
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-4 text-lg", children: "Metadata" }),
630
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: Object.entries(request.metadata).map(([key, value]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between", children: [
631
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: key }),
632
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "font-medium", children: typeof value === "string" ? value : JSON.stringify(value) })
633
+ ] }, key)) })
634
+ ] })
635
+ ] }) });
636
+ };
637
+ const config = adminSdk.defineRouteConfig({
638
+ label: "Contact Request Details",
639
+ icon: icons.ChatBubbleLeftRight
640
+ });
172
641
  const en = {};
173
642
  const i18nTranslations0 = {
174
643
  en
@@ -179,15 +648,35 @@ const routeModule = {
179
648
  {
180
649
  Component: ContactEmailSubscriptionsPage,
181
650
  path: "/contact-email-subscriptions"
651
+ },
652
+ {
653
+ Component: ContactRequestsPage,
654
+ path: "/contact-requests"
655
+ },
656
+ {
657
+ Component: ContactRequestDetailPage,
658
+ path: "/contact-requests/:id"
182
659
  }
183
660
  ]
184
661
  };
185
662
  const menuItemModule = {
186
663
  menuItems: [
664
+ {
665
+ label: config$2.label,
666
+ icon: config$2.icon,
667
+ path: "/contact-email-subscriptions",
668
+ nested: void 0
669
+ },
670
+ {
671
+ label: config$1.label,
672
+ icon: config$1.icon,
673
+ path: "/contact-requests",
674
+ nested: void 0
675
+ },
187
676
  {
188
677
  label: config.label,
189
678
  icon: config.icon,
190
- path: "/contact-email-subscriptions",
679
+ path: "/contact-requests/:id",
191
680
  nested: void 0
192
681
  }
193
682
  ]