@djangocfg/ext-support 1.0.6 → 1.0.8

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 (39) hide show
  1. package/dist/config.cjs +4 -1
  2. package/dist/config.js +4 -1
  3. package/dist/hooks.cjs +79 -89
  4. package/dist/hooks.js +78 -89
  5. package/dist/index.cjs +79 -89
  6. package/dist/index.d.cts +0 -97
  7. package/dist/index.d.ts +0 -97
  8. package/dist/index.js +78 -89
  9. package/package.json +10 -7
  10. package/src/api/generated/ext_support/CLAUDE.md +80 -0
  11. package/src/api/generated/ext_support/_utils/fetchers/ext_support__support.ts +1 -8
  12. package/src/api/generated/ext_support/_utils/fetchers/index.ts +1 -8
  13. package/src/api/generated/ext_support/_utils/hooks/ext_support__support.ts +1 -8
  14. package/src/api/generated/ext_support/_utils/hooks/index.ts +1 -8
  15. package/src/api/generated/ext_support/_utils/schemas/index.ts +1 -8
  16. package/src/api/generated/ext_support/api-instance.ts +1 -8
  17. package/src/api/generated/ext_support/enums.ts +1 -8
  18. package/src/api/generated/ext_support/errors.ts +1 -8
  19. package/src/api/generated/ext_support/ext_support__support/index.ts +1 -8
  20. package/src/api/generated/ext_support/ext_support__support/models.ts +1 -8
  21. package/src/api/generated/ext_support/http.ts +1 -8
  22. package/src/api/generated/ext_support/index.ts +1 -8
  23. package/src/api/generated/ext_support/logger.ts +1 -8
  24. package/src/api/generated/ext_support/retry.ts +1 -8
  25. package/src/api/generated/ext_support/storage.ts +1 -8
  26. package/src/api/generated/ext_support/validation-events.ts +1 -8
  27. package/src/api/index.ts +2 -1
  28. package/src/config.ts +1 -0
  29. package/src/contexts/SupportContext.tsx +9 -13
  30. package/src/contexts/SupportExtensionProvider.tsx +1 -0
  31. package/src/layouts/SupportLayout/SupportLayout.tsx +5 -13
  32. package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +8 -18
  33. package/src/layouts/SupportLayout/components/MessageInput.tsx +5 -10
  34. package/src/layouts/SupportLayout/components/MessageList.tsx +25 -22
  35. package/src/layouts/SupportLayout/components/TicketCard.tsx +10 -10
  36. package/src/layouts/SupportLayout/components/TicketList.tsx +6 -4
  37. package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +9 -2
  38. package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +2 -0
  39. package/src/layouts/SupportLayout/hooks/useInfiniteTickets.ts +2 -0
package/dist/config.cjs CHANGED
@@ -27,7 +27,7 @@ var import_ext_base = require("@djangocfg/ext-base");
27
27
  // package.json
28
28
  var package_default = {
29
29
  name: "@djangocfg/ext-support",
30
- version: "1.0.6",
30
+ version: "1.0.8",
31
31
  description: "Support ticket system extension for DjangoCFG",
32
32
  keywords: [
33
33
  "django",
@@ -88,9 +88,11 @@ var package_default = {
88
88
  peerDependencies: {
89
89
  "@djangocfg/api": "workspace:*",
90
90
  "@djangocfg/ext-base": "workspace:*",
91
+ "@djangocfg/ui-core": "workspace:*",
91
92
  "@djangocfg/ui-nextjs": "workspace:*",
92
93
  consola: "^3.4.2",
93
94
  "lucide-react": "^0.545.0",
95
+ moment: "^2.30.1",
94
96
  next: "^15.5.7",
95
97
  "p-retry": "^7.0.0",
96
98
  react: "^18 || ^19",
@@ -105,6 +107,7 @@ var package_default = {
105
107
  "@types/node": "^24.7.2",
106
108
  "@types/react": "^19.0.0",
107
109
  consola: "^3.4.2",
110
+ moment: "^2.30.1",
108
111
  "p-retry": "^7.0.0",
109
112
  swr: "^2.3.7",
110
113
  tsup: "^8.5.0",
package/dist/config.js CHANGED
@@ -4,7 +4,7 @@ import { createExtensionConfig } from "@djangocfg/ext-base";
4
4
  // package.json
5
5
  var package_default = {
6
6
  name: "@djangocfg/ext-support",
7
- version: "1.0.6",
7
+ version: "1.0.8",
8
8
  description: "Support ticket system extension for DjangoCFG",
9
9
  keywords: [
10
10
  "django",
@@ -65,9 +65,11 @@ var package_default = {
65
65
  peerDependencies: {
66
66
  "@djangocfg/api": "workspace:*",
67
67
  "@djangocfg/ext-base": "workspace:*",
68
+ "@djangocfg/ui-core": "workspace:*",
68
69
  "@djangocfg/ui-nextjs": "workspace:*",
69
70
  consola: "^3.4.2",
70
71
  "lucide-react": "^0.545.0",
72
+ moment: "^2.30.1",
71
73
  next: "^15.5.7",
72
74
  "p-retry": "^7.0.0",
73
75
  react: "^18 || ^19",
@@ -82,6 +84,7 @@ var package_default = {
82
84
  "@types/node": "^24.7.2",
83
85
  "@types/react": "^19.0.0",
84
86
  consola: "^3.4.2",
87
+ moment: "^2.30.1",
85
88
  "p-retry": "^7.0.0",
86
89
  swr: "^2.3.7",
87
90
  tsup: "^8.5.0",
package/dist/hooks.cjs CHANGED
@@ -4,13 +4,15 @@ var consola = require('consola');
4
4
  var pRetry = require('p-retry');
5
5
  var zod = require('zod');
6
6
  var api = require('@djangocfg/ext-base/api');
7
+ var lucideReact = require('lucide-react');
7
8
  var React7 = require('react');
9
+ var uiNextjs = require('@djangocfg/ui-nextjs');
8
10
  var useSWR = require('swr');
9
11
  var jsxRuntime = require('react/jsx-runtime');
12
+ var moment2 = require('moment');
13
+ var lib = require('@djangocfg/ui-core/lib');
10
14
  var auth = require('@djangocfg/api/auth');
11
15
  var useSWRInfinite = require('swr/infinite');
12
- var uiNextjs = require('@djangocfg/ui-nextjs');
13
- var lucideReact = require('lucide-react');
14
16
  var reactHookForm = require('react-hook-form');
15
17
  var zod$1 = require('@hookform/resolvers/zod');
16
18
  var extBase = require('@djangocfg/ext-base');
@@ -20,6 +22,7 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
20
22
  var pRetry__default = /*#__PURE__*/_interopDefault(pRetry);
21
23
  var React7__default = /*#__PURE__*/_interopDefault(React7);
22
24
  var useSWR__default = /*#__PURE__*/_interopDefault(useSWR);
25
+ var moment2__default = /*#__PURE__*/_interopDefault(moment2);
23
26
  var useSWRInfinite__default = /*#__PURE__*/_interopDefault(useSWRInfinite);
24
27
 
25
28
  var __defProp = Object.defineProperty;
@@ -1784,6 +1787,67 @@ function useSupportContext() {
1784
1787
  }
1785
1788
  return context;
1786
1789
  }
1790
+ var getStatusBadgeVariant = (status) => {
1791
+ switch (status) {
1792
+ case "open":
1793
+ return "default";
1794
+ case "waiting_for_user":
1795
+ return "secondary";
1796
+ case "waiting_for_admin":
1797
+ return "outline";
1798
+ case "resolved":
1799
+ return "outline";
1800
+ case "closed":
1801
+ return "secondary";
1802
+ default:
1803
+ return "default";
1804
+ }
1805
+ };
1806
+ var formatRelativeTime = (date) => {
1807
+ if (!date) return "N/A";
1808
+ const m = moment2__default.default.utc(date).local();
1809
+ const now = moment2__default.default();
1810
+ const diffInSeconds = now.diff(m, "seconds");
1811
+ if (diffInSeconds < 60) return "Just now";
1812
+ if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
1813
+ if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
1814
+ if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`;
1815
+ return m.format("MMM D, YYYY");
1816
+ };
1817
+ var TicketCard = ({ ticket, isSelected, onClick }) => {
1818
+ return /* @__PURE__ */ jsxRuntime.jsx(
1819
+ uiNextjs.Card,
1820
+ {
1821
+ className: lib.cn(
1822
+ "cursor-pointer transition-all duration-200 ease-out",
1823
+ "hover:bg-accent/50 hover:shadow-md hover:scale-[1.02]",
1824
+ "active:scale-[0.98]",
1825
+ isSelected && "bg-accent border-primary shadow-sm"
1826
+ ),
1827
+ onClick,
1828
+ children: /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardContent, { className: "p-4", children: [
1829
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between mb-2", children: [
1830
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold text-sm line-clamp-2 flex-1", children: ticket.subject }),
1831
+ (ticket.unanswered_messages_count || 0) > 0 && /* @__PURE__ */ jsxRuntime.jsx(
1832
+ uiNextjs.Badge,
1833
+ {
1834
+ variant: "destructive",
1835
+ className: "ml-2 shrink-0 animate-pulse",
1836
+ children: ticket.unanswered_messages_count
1837
+ }
1838
+ )
1839
+ ] }),
1840
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
1841
+ /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: getStatusBadgeVariant(ticket.status || "open"), className: "text-xs", children: ticket.status || "open" }),
1842
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
1843
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3 w-3" }),
1844
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatRelativeTime(ticket.created_at) })
1845
+ ] })
1846
+ ] }) })
1847
+ ] })
1848
+ }
1849
+ );
1850
+ };
1787
1851
 
1788
1852
  // src/layouts/SupportLayout/events.ts
1789
1853
  var SUPPORT_LAYOUT_EVENTS = {
@@ -2048,71 +2112,6 @@ function useSupportLayoutContext() {
2048
2112
  }
2049
2113
  return context;
2050
2114
  }
2051
- var getStatusBadgeVariant = (status) => {
2052
- switch (status) {
2053
- case "open":
2054
- return "default";
2055
- case "waiting_for_user":
2056
- return "secondary";
2057
- case "waiting_for_admin":
2058
- return "outline";
2059
- case "resolved":
2060
- return "outline";
2061
- case "closed":
2062
- return "secondary";
2063
- default:
2064
- return "default";
2065
- }
2066
- };
2067
- var formatRelativeTime = (date) => {
2068
- if (!date) return "N/A";
2069
- const now = /* @__PURE__ */ new Date();
2070
- const messageDate = new Date(date);
2071
- const diffInSeconds = Math.floor((now.getTime() - messageDate.getTime()) / 1e3);
2072
- if (diffInSeconds < 60) return "Just now";
2073
- if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
2074
- if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
2075
- if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`;
2076
- return new Date(date).toLocaleDateString("en-US", {
2077
- year: "numeric",
2078
- month: "short",
2079
- day: "numeric"
2080
- });
2081
- };
2082
- var TicketCard = ({ ticket, isSelected, onClick }) => {
2083
- return /* @__PURE__ */ jsxRuntime.jsx(
2084
- uiNextjs.Card,
2085
- {
2086
- className: uiNextjs.cn(
2087
- "cursor-pointer transition-all duration-200 ease-out",
2088
- "hover:bg-accent/50 hover:shadow-md hover:scale-[1.02]",
2089
- "active:scale-[0.98]",
2090
- isSelected && "bg-accent border-primary shadow-sm"
2091
- ),
2092
- onClick,
2093
- children: /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardContent, { className: "p-4", children: [
2094
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between mb-2", children: [
2095
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold text-sm line-clamp-2 flex-1", children: ticket.subject }),
2096
- (ticket.unanswered_messages_count || 0) > 0 && /* @__PURE__ */ jsxRuntime.jsx(
2097
- uiNextjs.Badge,
2098
- {
2099
- variant: "destructive",
2100
- className: "ml-2 shrink-0 animate-pulse",
2101
- children: ticket.unanswered_messages_count
2102
- }
2103
- )
2104
- ] }),
2105
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
2106
- /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: getStatusBadgeVariant(ticket.status || "open"), className: "text-xs", children: ticket.status || "open" }),
2107
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
2108
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3 w-3" }),
2109
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatRelativeTime(ticket.created_at) })
2110
- ] })
2111
- ] }) })
2112
- ] })
2113
- }
2114
- );
2115
- };
2116
2115
  var TicketList = () => {
2117
2116
  const { selectedTicket, selectTicket } = useSupportLayoutContext();
2118
2117
  const {
@@ -2218,28 +2217,23 @@ var TicketList = () => {
2218
2217
  };
2219
2218
  var formatTime = (date) => {
2220
2219
  if (!date) return "";
2221
- return new Date(date).toLocaleTimeString("en-US", {
2222
- hour: "2-digit",
2223
- minute: "2-digit"
2224
- });
2220
+ return moment2__default.default.utc(date).local().format("hh:mm A");
2225
2221
  };
2226
2222
  var formatDate = (date) => {
2227
2223
  if (!date) return "";
2228
- return new Date(date).toLocaleDateString("en-US", {
2229
- year: "numeric",
2230
- month: "short",
2231
- day: "numeric"
2232
- });
2224
+ return moment2__default.default.utc(date).local().format("MMM D, YYYY");
2233
2225
  };
2234
2226
  var MessageBubble = ({ message, isFromUser, currentUser }) => {
2235
2227
  const sender = message.sender;
2228
+ const senderInitial = sender?.display_username?.charAt(0)?.toUpperCase() || sender?.initials || "S";
2229
+ const userInitial = currentUser?.display_username?.charAt(0)?.toUpperCase() || currentUser?.email?.charAt(0)?.toUpperCase() || currentUser?.initials || null;
2236
2230
  return /* @__PURE__ */ jsxRuntime.jsxs(
2237
2231
  "div",
2238
2232
  {
2239
2233
  className: `flex gap-3 ${isFromUser ? "justify-end" : "justify-start"}
2240
2234
  animate-in fade-in slide-in-from-bottom-2 duration-300`,
2241
2235
  children: [
2242
- !isFromUser && /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Avatar, { className: "h-8 w-8 shrink-0", children: sender?.avatar ? /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.AvatarImage, { src: sender.avatar, alt: sender.display_username || "Support" }) : /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.AvatarFallback, { className: "bg-primary text-primary-foreground", children: sender?.is_staff ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Headphones, { className: "h-4 w-4" }) : sender?.display_username?.charAt(0)?.toUpperCase() || sender?.initials || "S" }) }),
2236
+ !isFromUser && /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Avatar, { className: "h-8 w-8 shrink-0", children: sender?.avatar ? /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.AvatarImage, { src: sender.avatar, alt: sender.display_username || "Support" }) : /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.AvatarFallback, { className: "bg-primary text-primary-foreground", children: sender?.is_staff ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Headphones, { className: "h-4 w-4" }) : senderInitial }) }),
2243
2237
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex flex-col gap-1 flex-1 max-w-[80%] ${isFromUser ? "items-end" : "items-start"}`, children: [
2244
2238
  !isFromUser && sender && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground px-1", children: [
2245
2239
  sender.display_username || sender.email || "Support Team",
@@ -2254,7 +2248,7 @@ var MessageBubble = ({ message, isFromUser, currentUser }) => {
2254
2248
  ),
2255
2249
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground px-1", children: formatTime(message.created_at) })
2256
2250
  ] }),
2257
- isFromUser && /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Avatar, { className: "h-8 w-8 shrink-0", children: currentUser?.avatar ? /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.AvatarImage, { src: currentUser.avatar, alt: currentUser.display_username || currentUser.email || "You" }) : /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.AvatarFallback, { className: "bg-primary/10 text-primary font-semibold", children: currentUser?.display_username?.charAt(0)?.toUpperCase() || currentUser?.email?.charAt(0)?.toUpperCase() || currentUser?.initials || /* @__PURE__ */ jsxRuntime.jsx(lucideReact.User, { className: "h-4 w-4" }) }) })
2251
+ isFromUser && /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Avatar, { className: "h-8 w-8 shrink-0", children: currentUser?.avatar ? /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.AvatarImage, { src: currentUser.avatar, alt: currentUser.display_username || currentUser.email || "You" }) : /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.AvatarFallback, { className: "bg-primary/10 text-primary font-semibold", children: userInitial || /* @__PURE__ */ jsxRuntime.jsx(lucideReact.User, { className: "h-4 w-4" }) }) })
2258
2252
  ]
2259
2253
  }
2260
2254
  );
@@ -2372,7 +2366,7 @@ var MessageList = () => {
2372
2366
  messages.map((message, index) => {
2373
2367
  const isFromUser = message.sender?.id && user?.id && String(message.sender.id) === String(user.id) || message.sender?.email && user?.email && message.sender.email === user.email || message.is_from_author && selectedTicket?.user && user?.id && String(selectedTicket.user) === String(user.id);
2374
2368
  const previousMessage = index > 0 ? messages[index - 1] : null;
2375
- const showDateSeparator = previousMessage && new Date(previousMessage.created_at || "").toDateString() !== new Date(message.created_at || "").toDateString();
2369
+ const showDateSeparator = previousMessage && moment2__default.default.utc(previousMessage.created_at || "").format("YYYY-MM-DD") !== moment2__default.default.utc(message.created_at || "").format("YYYY-MM-DD");
2376
2370
  return /* @__PURE__ */ jsxRuntime.jsxs(React7__default.default.Fragment, { children: [
2377
2371
  showDateSeparator && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 my-4", children: [
2378
2372
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 h-px bg-border" }),
@@ -2409,17 +2403,10 @@ var MessageInput = () => {
2409
2403
  try {
2410
2404
  await sendMessage(message.trim());
2411
2405
  setMessage("");
2412
- toast({
2413
- title: "Success",
2414
- description: "Message sent successfully"
2415
- });
2406
+ toast.success("Message sent successfully");
2416
2407
  } catch (error) {
2417
2408
  supportLogger.error("Failed to send message:", error);
2418
- toast({
2419
- title: "Error",
2420
- description: "Failed to send message",
2421
- variant: "destructive"
2422
- });
2409
+ toast.error("Failed to send message");
2423
2410
  } finally {
2424
2411
  setIsSending(false);
2425
2412
  }
@@ -2640,7 +2627,7 @@ var SupportLayout = () => {
2640
2627
  // package.json
2641
2628
  var package_default = {
2642
2629
  name: "@djangocfg/ext-support",
2643
- version: "1.0.6",
2630
+ version: "1.0.8",
2644
2631
  description: "Support ticket system extension for DjangoCFG",
2645
2632
  keywords: [
2646
2633
  "django",
@@ -2701,9 +2688,11 @@ var package_default = {
2701
2688
  peerDependencies: {
2702
2689
  "@djangocfg/api": "workspace:*",
2703
2690
  "@djangocfg/ext-base": "workspace:*",
2691
+ "@djangocfg/ui-core": "workspace:*",
2704
2692
  "@djangocfg/ui-nextjs": "workspace:*",
2705
2693
  consola: "^3.4.2",
2706
2694
  "lucide-react": "^0.545.0",
2695
+ moment: "^2.30.1",
2707
2696
  next: "^15.5.7",
2708
2697
  "p-retry": "^7.0.0",
2709
2698
  react: "^18 || ^19",
@@ -2718,6 +2707,7 @@ var package_default = {
2718
2707
  "@types/node": "^24.7.2",
2719
2708
  "@types/react": "^19.0.0",
2720
2709
  consola: "^3.4.2",
2710
+ moment: "^2.30.1",
2721
2711
  "p-retry": "^7.0.0",
2722
2712
  swr: "^2.3.7",
2723
2713
  tsup: "^8.5.0",
package/dist/hooks.js CHANGED
@@ -2,13 +2,15 @@ import { createConsola, consola } from 'consola';
2
2
  import pRetry, { AbortError } from 'p-retry';
3
3
  import { z } from 'zod';
4
4
  import { createExtensionAPI } from '@djangocfg/ext-base/api';
5
+ import { Clock, MessageSquare, Loader2, Send, Plus, Headphones, User, ArrowLeft, LifeBuoy } from 'lucide-react';
5
6
  import React7, { createContext, useContext, useState, useCallback, useEffect, useRef } from 'react';
7
+ import { Card, CardContent, Badge, Skeleton, ScrollArea, Button, useToast, Textarea, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Form, FormField, FormItem, FormLabel, FormControl, Input, FormMessage, Avatar, AvatarImage, AvatarFallback, ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@djangocfg/ui-nextjs';
6
8
  import useSWR, { useSWRConfig } from 'swr';
7
9
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
+ import moment2 from 'moment';
11
+ import { cn } from '@djangocfg/ui-core/lib';
8
12
  import { useAuth } from '@djangocfg/api/auth';
9
13
  import useSWRInfinite from 'swr/infinite';
10
- import { Card, cn, CardContent, Badge, Skeleton, ScrollArea, Button, useToast, Textarea, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Form, FormField, FormItem, FormLabel, FormControl, Input, FormMessage, Avatar, AvatarImage, AvatarFallback, ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@djangocfg/ui-nextjs';
11
- import { Clock, MessageSquare, Loader2, Send, Plus, Headphones, User, ArrowLeft, LifeBuoy } from 'lucide-react';
12
14
  import { useForm } from 'react-hook-form';
13
15
  import { zodResolver } from '@hookform/resolvers/zod';
14
16
  import { createExtensionConfig } from '@djangocfg/ext-base';
@@ -1775,6 +1777,67 @@ function useSupportContext() {
1775
1777
  }
1776
1778
  return context;
1777
1779
  }
1780
+ var getStatusBadgeVariant = (status) => {
1781
+ switch (status) {
1782
+ case "open":
1783
+ return "default";
1784
+ case "waiting_for_user":
1785
+ return "secondary";
1786
+ case "waiting_for_admin":
1787
+ return "outline";
1788
+ case "resolved":
1789
+ return "outline";
1790
+ case "closed":
1791
+ return "secondary";
1792
+ default:
1793
+ return "default";
1794
+ }
1795
+ };
1796
+ var formatRelativeTime = (date) => {
1797
+ if (!date) return "N/A";
1798
+ const m = moment2.utc(date).local();
1799
+ const now = moment2();
1800
+ const diffInSeconds = now.diff(m, "seconds");
1801
+ if (diffInSeconds < 60) return "Just now";
1802
+ if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
1803
+ if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
1804
+ if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`;
1805
+ return m.format("MMM D, YYYY");
1806
+ };
1807
+ var TicketCard = ({ ticket, isSelected, onClick }) => {
1808
+ return /* @__PURE__ */ jsx(
1809
+ Card,
1810
+ {
1811
+ className: cn(
1812
+ "cursor-pointer transition-all duration-200 ease-out",
1813
+ "hover:bg-accent/50 hover:shadow-md hover:scale-[1.02]",
1814
+ "active:scale-[0.98]",
1815
+ isSelected && "bg-accent border-primary shadow-sm"
1816
+ ),
1817
+ onClick,
1818
+ children: /* @__PURE__ */ jsxs(CardContent, { className: "p-4", children: [
1819
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between mb-2", children: [
1820
+ /* @__PURE__ */ jsx("h3", { className: "font-semibold text-sm line-clamp-2 flex-1", children: ticket.subject }),
1821
+ (ticket.unanswered_messages_count || 0) > 0 && /* @__PURE__ */ jsx(
1822
+ Badge,
1823
+ {
1824
+ variant: "destructive",
1825
+ className: "ml-2 shrink-0 animate-pulse",
1826
+ children: ticket.unanswered_messages_count
1827
+ }
1828
+ )
1829
+ ] }),
1830
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1831
+ /* @__PURE__ */ jsx(Badge, { variant: getStatusBadgeVariant(ticket.status || "open"), className: "text-xs", children: ticket.status || "open" }),
1832
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1833
+ /* @__PURE__ */ jsx(Clock, { className: "h-3 w-3" }),
1834
+ /* @__PURE__ */ jsx("span", { children: formatRelativeTime(ticket.created_at) })
1835
+ ] })
1836
+ ] }) })
1837
+ ] })
1838
+ }
1839
+ );
1840
+ };
1778
1841
 
1779
1842
  // src/layouts/SupportLayout/events.ts
1780
1843
  var SUPPORT_LAYOUT_EVENTS = {
@@ -2039,71 +2102,6 @@ function useSupportLayoutContext() {
2039
2102
  }
2040
2103
  return context;
2041
2104
  }
2042
- var getStatusBadgeVariant = (status) => {
2043
- switch (status) {
2044
- case "open":
2045
- return "default";
2046
- case "waiting_for_user":
2047
- return "secondary";
2048
- case "waiting_for_admin":
2049
- return "outline";
2050
- case "resolved":
2051
- return "outline";
2052
- case "closed":
2053
- return "secondary";
2054
- default:
2055
- return "default";
2056
- }
2057
- };
2058
- var formatRelativeTime = (date) => {
2059
- if (!date) return "N/A";
2060
- const now = /* @__PURE__ */ new Date();
2061
- const messageDate = new Date(date);
2062
- const diffInSeconds = Math.floor((now.getTime() - messageDate.getTime()) / 1e3);
2063
- if (diffInSeconds < 60) return "Just now";
2064
- if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
2065
- if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
2066
- if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`;
2067
- return new Date(date).toLocaleDateString("en-US", {
2068
- year: "numeric",
2069
- month: "short",
2070
- day: "numeric"
2071
- });
2072
- };
2073
- var TicketCard = ({ ticket, isSelected, onClick }) => {
2074
- return /* @__PURE__ */ jsx(
2075
- Card,
2076
- {
2077
- className: cn(
2078
- "cursor-pointer transition-all duration-200 ease-out",
2079
- "hover:bg-accent/50 hover:shadow-md hover:scale-[1.02]",
2080
- "active:scale-[0.98]",
2081
- isSelected && "bg-accent border-primary shadow-sm"
2082
- ),
2083
- onClick,
2084
- children: /* @__PURE__ */ jsxs(CardContent, { className: "p-4", children: [
2085
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between mb-2", children: [
2086
- /* @__PURE__ */ jsx("h3", { className: "font-semibold text-sm line-clamp-2 flex-1", children: ticket.subject }),
2087
- (ticket.unanswered_messages_count || 0) > 0 && /* @__PURE__ */ jsx(
2088
- Badge,
2089
- {
2090
- variant: "destructive",
2091
- className: "ml-2 shrink-0 animate-pulse",
2092
- children: ticket.unanswered_messages_count
2093
- }
2094
- )
2095
- ] }),
2096
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2097
- /* @__PURE__ */ jsx(Badge, { variant: getStatusBadgeVariant(ticket.status || "open"), className: "text-xs", children: ticket.status || "open" }),
2098
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
2099
- /* @__PURE__ */ jsx(Clock, { className: "h-3 w-3" }),
2100
- /* @__PURE__ */ jsx("span", { children: formatRelativeTime(ticket.created_at) })
2101
- ] })
2102
- ] }) })
2103
- ] })
2104
- }
2105
- );
2106
- };
2107
2105
  var TicketList = () => {
2108
2106
  const { selectedTicket, selectTicket } = useSupportLayoutContext();
2109
2107
  const {
@@ -2209,28 +2207,23 @@ var TicketList = () => {
2209
2207
  };
2210
2208
  var formatTime = (date) => {
2211
2209
  if (!date) return "";
2212
- return new Date(date).toLocaleTimeString("en-US", {
2213
- hour: "2-digit",
2214
- minute: "2-digit"
2215
- });
2210
+ return moment2.utc(date).local().format("hh:mm A");
2216
2211
  };
2217
2212
  var formatDate = (date) => {
2218
2213
  if (!date) return "";
2219
- return new Date(date).toLocaleDateString("en-US", {
2220
- year: "numeric",
2221
- month: "short",
2222
- day: "numeric"
2223
- });
2214
+ return moment2.utc(date).local().format("MMM D, YYYY");
2224
2215
  };
2225
2216
  var MessageBubble = ({ message, isFromUser, currentUser }) => {
2226
2217
  const sender = message.sender;
2218
+ const senderInitial = sender?.display_username?.charAt(0)?.toUpperCase() || sender?.initials || "S";
2219
+ const userInitial = currentUser?.display_username?.charAt(0)?.toUpperCase() || currentUser?.email?.charAt(0)?.toUpperCase() || currentUser?.initials || null;
2227
2220
  return /* @__PURE__ */ jsxs(
2228
2221
  "div",
2229
2222
  {
2230
2223
  className: `flex gap-3 ${isFromUser ? "justify-end" : "justify-start"}
2231
2224
  animate-in fade-in slide-in-from-bottom-2 duration-300`,
2232
2225
  children: [
2233
- !isFromUser && /* @__PURE__ */ jsx(Avatar, { className: "h-8 w-8 shrink-0", children: sender?.avatar ? /* @__PURE__ */ jsx(AvatarImage, { src: sender.avatar, alt: sender.display_username || "Support" }) : /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-primary text-primary-foreground", children: sender?.is_staff ? /* @__PURE__ */ jsx(Headphones, { className: "h-4 w-4" }) : sender?.display_username?.charAt(0)?.toUpperCase() || sender?.initials || "S" }) }),
2226
+ !isFromUser && /* @__PURE__ */ jsx(Avatar, { className: "h-8 w-8 shrink-0", children: sender?.avatar ? /* @__PURE__ */ jsx(AvatarImage, { src: sender.avatar, alt: sender.display_username || "Support" }) : /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-primary text-primary-foreground", children: sender?.is_staff ? /* @__PURE__ */ jsx(Headphones, { className: "h-4 w-4" }) : senderInitial }) }),
2234
2227
  /* @__PURE__ */ jsxs("div", { className: `flex flex-col gap-1 flex-1 max-w-[80%] ${isFromUser ? "items-end" : "items-start"}`, children: [
2235
2228
  !isFromUser && sender && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground px-1", children: [
2236
2229
  sender.display_username || sender.email || "Support Team",
@@ -2245,7 +2238,7 @@ var MessageBubble = ({ message, isFromUser, currentUser }) => {
2245
2238
  ),
2246
2239
  /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground px-1", children: formatTime(message.created_at) })
2247
2240
  ] }),
2248
- isFromUser && /* @__PURE__ */ jsx(Avatar, { className: "h-8 w-8 shrink-0", children: currentUser?.avatar ? /* @__PURE__ */ jsx(AvatarImage, { src: currentUser.avatar, alt: currentUser.display_username || currentUser.email || "You" }) : /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-primary/10 text-primary font-semibold", children: currentUser?.display_username?.charAt(0)?.toUpperCase() || currentUser?.email?.charAt(0)?.toUpperCase() || currentUser?.initials || /* @__PURE__ */ jsx(User, { className: "h-4 w-4" }) }) })
2241
+ isFromUser && /* @__PURE__ */ jsx(Avatar, { className: "h-8 w-8 shrink-0", children: currentUser?.avatar ? /* @__PURE__ */ jsx(AvatarImage, { src: currentUser.avatar, alt: currentUser.display_username || currentUser.email || "You" }) : /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-primary/10 text-primary font-semibold", children: userInitial || /* @__PURE__ */ jsx(User, { className: "h-4 w-4" }) }) })
2249
2242
  ]
2250
2243
  }
2251
2244
  );
@@ -2363,7 +2356,7 @@ var MessageList = () => {
2363
2356
  messages.map((message, index) => {
2364
2357
  const isFromUser = message.sender?.id && user?.id && String(message.sender.id) === String(user.id) || message.sender?.email && user?.email && message.sender.email === user.email || message.is_from_author && selectedTicket?.user && user?.id && String(selectedTicket.user) === String(user.id);
2365
2358
  const previousMessage = index > 0 ? messages[index - 1] : null;
2366
- const showDateSeparator = previousMessage && new Date(previousMessage.created_at || "").toDateString() !== new Date(message.created_at || "").toDateString();
2359
+ const showDateSeparator = previousMessage && moment2.utc(previousMessage.created_at || "").format("YYYY-MM-DD") !== moment2.utc(message.created_at || "").format("YYYY-MM-DD");
2367
2360
  return /* @__PURE__ */ jsxs(React7.Fragment, { children: [
2368
2361
  showDateSeparator && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 my-4", children: [
2369
2362
  /* @__PURE__ */ jsx("div", { className: "flex-1 h-px bg-border" }),
@@ -2400,17 +2393,10 @@ var MessageInput = () => {
2400
2393
  try {
2401
2394
  await sendMessage(message.trim());
2402
2395
  setMessage("");
2403
- toast({
2404
- title: "Success",
2405
- description: "Message sent successfully"
2406
- });
2396
+ toast.success("Message sent successfully");
2407
2397
  } catch (error) {
2408
2398
  supportLogger.error("Failed to send message:", error);
2409
- toast({
2410
- title: "Error",
2411
- description: "Failed to send message",
2412
- variant: "destructive"
2413
- });
2399
+ toast.error("Failed to send message");
2414
2400
  } finally {
2415
2401
  setIsSending(false);
2416
2402
  }
@@ -2631,7 +2617,7 @@ var SupportLayout = () => {
2631
2617
  // package.json
2632
2618
  var package_default = {
2633
2619
  name: "@djangocfg/ext-support",
2634
- version: "1.0.6",
2620
+ version: "1.0.8",
2635
2621
  description: "Support ticket system extension for DjangoCFG",
2636
2622
  keywords: [
2637
2623
  "django",
@@ -2692,9 +2678,11 @@ var package_default = {
2692
2678
  peerDependencies: {
2693
2679
  "@djangocfg/api": "workspace:*",
2694
2680
  "@djangocfg/ext-base": "workspace:*",
2681
+ "@djangocfg/ui-core": "workspace:*",
2695
2682
  "@djangocfg/ui-nextjs": "workspace:*",
2696
2683
  consola: "^3.4.2",
2697
2684
  "lucide-react": "^0.545.0",
2685
+ moment: "^2.30.1",
2698
2686
  next: "^15.5.7",
2699
2687
  "p-retry": "^7.0.0",
2700
2688
  react: "^18 || ^19",
@@ -2709,6 +2697,7 @@ var package_default = {
2709
2697
  "@types/node": "^24.7.2",
2710
2698
  "@types/react": "^19.0.0",
2711
2699
  consola: "^3.4.2",
2700
+ moment: "^2.30.1",
2712
2701
  "p-retry": "^7.0.0",
2713
2702
  swr: "^2.3.7",
2714
2703
  tsup: "^8.5.0",