@djangocfg/ext-support 1.0.4 → 1.0.7

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 (37) hide show
  1. package/dist/config.cjs +3 -1
  2. package/dist/config.js +3 -1
  3. package/dist/hooks.cjs +75 -80
  4. package/dist/hooks.js +74 -80
  5. package/dist/index.cjs +75 -80
  6. package/dist/index.js +74 -80
  7. package/package.json +9 -7
  8. package/src/api/generated/ext_support/CLAUDE.md +80 -0
  9. package/src/api/generated/ext_support/_utils/fetchers/ext_support__support.ts +1 -0
  10. package/src/api/generated/ext_support/_utils/fetchers/index.ts +1 -0
  11. package/src/api/generated/ext_support/_utils/hooks/ext_support__support.ts +1 -0
  12. package/src/api/generated/ext_support/_utils/hooks/index.ts +1 -0
  13. package/src/api/generated/ext_support/_utils/schemas/index.ts +1 -0
  14. package/src/api/generated/ext_support/api-instance.ts +1 -0
  15. package/src/api/generated/ext_support/enums.ts +1 -0
  16. package/src/api/generated/ext_support/errors.ts +1 -0
  17. package/src/api/generated/ext_support/ext_support__support/index.ts +1 -0
  18. package/src/api/generated/ext_support/ext_support__support/models.ts +1 -0
  19. package/src/api/generated/ext_support/http.ts +1 -0
  20. package/src/api/generated/ext_support/index.ts +1 -0
  21. package/src/api/generated/ext_support/logger.ts +1 -0
  22. package/src/api/generated/ext_support/retry.ts +1 -0
  23. package/src/api/generated/ext_support/storage.ts +1 -0
  24. package/src/api/generated/ext_support/validation-events.ts +1 -0
  25. package/src/api/index.ts +2 -1
  26. package/src/config.ts +1 -0
  27. package/src/contexts/SupportContext.tsx +9 -13
  28. package/src/contexts/SupportExtensionProvider.tsx +1 -0
  29. package/src/layouts/SupportLayout/SupportLayout.tsx +5 -13
  30. package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +8 -18
  31. package/src/layouts/SupportLayout/components/MessageInput.tsx +3 -1
  32. package/src/layouts/SupportLayout/components/MessageList.tsx +25 -22
  33. package/src/layouts/SupportLayout/components/TicketCard.tsx +8 -9
  34. package/src/layouts/SupportLayout/components/TicketList.tsx +6 -4
  35. package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +9 -2
  36. package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +2 -0
  37. 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.4",
30
+ version: "1.0.6",
31
31
  description: "Support ticket system extension for DjangoCFG",
32
32
  keywords: [
33
33
  "django",
@@ -91,6 +91,7 @@ var package_default = {
91
91
  "@djangocfg/ui-nextjs": "workspace:*",
92
92
  consola: "^3.4.2",
93
93
  "lucide-react": "^0.545.0",
94
+ moment: "^2.30.1",
94
95
  next: "^15.5.7",
95
96
  "p-retry": "^7.0.0",
96
97
  react: "^18 || ^19",
@@ -105,6 +106,7 @@ var package_default = {
105
106
  "@types/node": "^24.7.2",
106
107
  "@types/react": "^19.0.0",
107
108
  consola: "^3.4.2",
109
+ moment: "^2.30.1",
108
110
  "p-retry": "^7.0.0",
109
111
  swr: "^2.3.7",
110
112
  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.4",
7
+ version: "1.0.6",
8
8
  description: "Support ticket system extension for DjangoCFG",
9
9
  keywords: [
10
10
  "django",
@@ -68,6 +68,7 @@ var package_default = {
68
68
  "@djangocfg/ui-nextjs": "workspace:*",
69
69
  consola: "^3.4.2",
70
70
  "lucide-react": "^0.545.0",
71
+ moment: "^2.30.1",
71
72
  next: "^15.5.7",
72
73
  "p-retry": "^7.0.0",
73
74
  react: "^18 || ^19",
@@ -82,6 +83,7 @@ var package_default = {
82
83
  "@types/node": "^24.7.2",
83
84
  "@types/react": "^19.0.0",
84
85
  consola: "^3.4.2",
86
+ moment: "^2.30.1",
85
87
  "p-retry": "^7.0.0",
86
88
  swr: "^2.3.7",
87
89
  tsup: "^8.5.0",
package/dist/hooks.cjs CHANGED
@@ -4,13 +4,14 @@ 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');
10
13
  var auth = require('@djangocfg/api/auth');
11
14
  var useSWRInfinite = require('swr/infinite');
12
- var uiNextjs = require('@djangocfg/ui-nextjs');
13
- var lucideReact = require('lucide-react');
14
15
  var reactHookForm = require('react-hook-form');
15
16
  var zod$1 = require('@hookform/resolvers/zod');
16
17
  var extBase = require('@djangocfg/ext-base');
@@ -20,6 +21,7 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
20
21
  var pRetry__default = /*#__PURE__*/_interopDefault(pRetry);
21
22
  var React7__default = /*#__PURE__*/_interopDefault(React7);
22
23
  var useSWR__default = /*#__PURE__*/_interopDefault(useSWR);
24
+ var moment2__default = /*#__PURE__*/_interopDefault(moment2);
23
25
  var useSWRInfinite__default = /*#__PURE__*/_interopDefault(useSWRInfinite);
24
26
 
25
27
  var __defProp = Object.defineProperty;
@@ -1784,6 +1786,67 @@ function useSupportContext() {
1784
1786
  }
1785
1787
  return context;
1786
1788
  }
1789
+ var getStatusBadgeVariant = (status) => {
1790
+ switch (status) {
1791
+ case "open":
1792
+ return "default";
1793
+ case "waiting_for_user":
1794
+ return "secondary";
1795
+ case "waiting_for_admin":
1796
+ return "outline";
1797
+ case "resolved":
1798
+ return "outline";
1799
+ case "closed":
1800
+ return "secondary";
1801
+ default:
1802
+ return "default";
1803
+ }
1804
+ };
1805
+ var formatRelativeTime = (date) => {
1806
+ if (!date) return "N/A";
1807
+ const m = moment2__default.default.utc(date).local();
1808
+ const now = moment2__default.default();
1809
+ const diffInSeconds = now.diff(m, "seconds");
1810
+ if (diffInSeconds < 60) return "Just now";
1811
+ if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
1812
+ if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
1813
+ if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`;
1814
+ return m.format("MMM D, YYYY");
1815
+ };
1816
+ var TicketCard = ({ ticket, isSelected, onClick }) => {
1817
+ return /* @__PURE__ */ jsxRuntime.jsx(
1818
+ uiNextjs.Card,
1819
+ {
1820
+ className: uiNextjs.cn(
1821
+ "cursor-pointer transition-all duration-200 ease-out",
1822
+ "hover:bg-accent/50 hover:shadow-md hover:scale-[1.02]",
1823
+ "active:scale-[0.98]",
1824
+ isSelected && "bg-accent border-primary shadow-sm"
1825
+ ),
1826
+ onClick,
1827
+ children: /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardContent, { className: "p-4", children: [
1828
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between mb-2", children: [
1829
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold text-sm line-clamp-2 flex-1", children: ticket.subject }),
1830
+ (ticket.unanswered_messages_count || 0) > 0 && /* @__PURE__ */ jsxRuntime.jsx(
1831
+ uiNextjs.Badge,
1832
+ {
1833
+ variant: "destructive",
1834
+ className: "ml-2 shrink-0 animate-pulse",
1835
+ children: ticket.unanswered_messages_count
1836
+ }
1837
+ )
1838
+ ] }),
1839
+ /* @__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: [
1840
+ /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: getStatusBadgeVariant(ticket.status || "open"), className: "text-xs", children: ticket.status || "open" }),
1841
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
1842
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3 w-3" }),
1843
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatRelativeTime(ticket.created_at) })
1844
+ ] })
1845
+ ] }) })
1846
+ ] })
1847
+ }
1848
+ );
1849
+ };
1787
1850
 
1788
1851
  // src/layouts/SupportLayout/events.ts
1789
1852
  var SUPPORT_LAYOUT_EVENTS = {
@@ -2048,71 +2111,6 @@ function useSupportLayoutContext() {
2048
2111
  }
2049
2112
  return context;
2050
2113
  }
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
2114
  var TicketList = () => {
2117
2115
  const { selectedTicket, selectTicket } = useSupportLayoutContext();
2118
2116
  const {
@@ -2218,28 +2216,23 @@ var TicketList = () => {
2218
2216
  };
2219
2217
  var formatTime = (date) => {
2220
2218
  if (!date) return "";
2221
- return new Date(date).toLocaleTimeString("en-US", {
2222
- hour: "2-digit",
2223
- minute: "2-digit"
2224
- });
2219
+ return moment2__default.default.utc(date).local().format("hh:mm A");
2225
2220
  };
2226
2221
  var formatDate = (date) => {
2227
2222
  if (!date) return "";
2228
- return new Date(date).toLocaleDateString("en-US", {
2229
- year: "numeric",
2230
- month: "short",
2231
- day: "numeric"
2232
- });
2223
+ return moment2__default.default.utc(date).local().format("MMM D, YYYY");
2233
2224
  };
2234
2225
  var MessageBubble = ({ message, isFromUser, currentUser }) => {
2235
2226
  const sender = message.sender;
2227
+ const senderInitial = sender?.display_username?.charAt(0)?.toUpperCase() || sender?.initials || "S";
2228
+ const userInitial = currentUser?.display_username?.charAt(0)?.toUpperCase() || currentUser?.email?.charAt(0)?.toUpperCase() || currentUser?.initials || null;
2236
2229
  return /* @__PURE__ */ jsxRuntime.jsxs(
2237
2230
  "div",
2238
2231
  {
2239
2232
  className: `flex gap-3 ${isFromUser ? "justify-end" : "justify-start"}
2240
2233
  animate-in fade-in slide-in-from-bottom-2 duration-300`,
2241
2234
  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" }) }),
2235
+ !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
2236
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex flex-col gap-1 flex-1 max-w-[80%] ${isFromUser ? "items-end" : "items-start"}`, children: [
2244
2237
  !isFromUser && sender && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground px-1", children: [
2245
2238
  sender.display_username || sender.email || "Support Team",
@@ -2254,7 +2247,7 @@ var MessageBubble = ({ message, isFromUser, currentUser }) => {
2254
2247
  ),
2255
2248
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground px-1", children: formatTime(message.created_at) })
2256
2249
  ] }),
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" }) }) })
2250
+ 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
2251
  ]
2259
2252
  }
2260
2253
  );
@@ -2372,7 +2365,7 @@ var MessageList = () => {
2372
2365
  messages.map((message, index) => {
2373
2366
  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
2367
  const previousMessage = index > 0 ? messages[index - 1] : null;
2375
- const showDateSeparator = previousMessage && new Date(previousMessage.created_at || "").toDateString() !== new Date(message.created_at || "").toDateString();
2368
+ 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
2369
  return /* @__PURE__ */ jsxRuntime.jsxs(React7__default.default.Fragment, { children: [
2377
2370
  showDateSeparator && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 my-4", children: [
2378
2371
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 h-px bg-border" }),
@@ -2640,7 +2633,7 @@ var SupportLayout = () => {
2640
2633
  // package.json
2641
2634
  var package_default = {
2642
2635
  name: "@djangocfg/ext-support",
2643
- version: "1.0.4",
2636
+ version: "1.0.6",
2644
2637
  description: "Support ticket system extension for DjangoCFG",
2645
2638
  keywords: [
2646
2639
  "django",
@@ -2704,6 +2697,7 @@ var package_default = {
2704
2697
  "@djangocfg/ui-nextjs": "workspace:*",
2705
2698
  consola: "^3.4.2",
2706
2699
  "lucide-react": "^0.545.0",
2700
+ moment: "^2.30.1",
2707
2701
  next: "^15.5.7",
2708
2702
  "p-retry": "^7.0.0",
2709
2703
  react: "^18 || ^19",
@@ -2718,6 +2712,7 @@ var package_default = {
2718
2712
  "@types/node": "^24.7.2",
2719
2713
  "@types/react": "^19.0.0",
2720
2714
  consola: "^3.4.2",
2715
+ moment: "^2.30.1",
2721
2716
  "p-retry": "^7.0.0",
2722
2717
  swr: "^2.3.7",
2723
2718
  tsup: "^8.5.0",
package/dist/hooks.js CHANGED
@@ -2,13 +2,14 @@ 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, 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';
6
8
  import useSWR, { useSWRConfig } from 'swr';
7
9
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
+ import moment2 from 'moment';
8
11
  import { useAuth } from '@djangocfg/api/auth';
9
12
  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
13
  import { useForm } from 'react-hook-form';
13
14
  import { zodResolver } from '@hookform/resolvers/zod';
14
15
  import { createExtensionConfig } from '@djangocfg/ext-base';
@@ -1775,6 +1776,67 @@ function useSupportContext() {
1775
1776
  }
1776
1777
  return context;
1777
1778
  }
1779
+ var getStatusBadgeVariant = (status) => {
1780
+ switch (status) {
1781
+ case "open":
1782
+ return "default";
1783
+ case "waiting_for_user":
1784
+ return "secondary";
1785
+ case "waiting_for_admin":
1786
+ return "outline";
1787
+ case "resolved":
1788
+ return "outline";
1789
+ case "closed":
1790
+ return "secondary";
1791
+ default:
1792
+ return "default";
1793
+ }
1794
+ };
1795
+ var formatRelativeTime = (date) => {
1796
+ if (!date) return "N/A";
1797
+ const m = moment2.utc(date).local();
1798
+ const now = moment2();
1799
+ const diffInSeconds = now.diff(m, "seconds");
1800
+ if (diffInSeconds < 60) return "Just now";
1801
+ if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
1802
+ if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
1803
+ if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)}d ago`;
1804
+ return m.format("MMM D, YYYY");
1805
+ };
1806
+ var TicketCard = ({ ticket, isSelected, onClick }) => {
1807
+ return /* @__PURE__ */ jsx(
1808
+ Card,
1809
+ {
1810
+ className: cn(
1811
+ "cursor-pointer transition-all duration-200 ease-out",
1812
+ "hover:bg-accent/50 hover:shadow-md hover:scale-[1.02]",
1813
+ "active:scale-[0.98]",
1814
+ isSelected && "bg-accent border-primary shadow-sm"
1815
+ ),
1816
+ onClick,
1817
+ children: /* @__PURE__ */ jsxs(CardContent, { className: "p-4", children: [
1818
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between mb-2", children: [
1819
+ /* @__PURE__ */ jsx("h3", { className: "font-semibold text-sm line-clamp-2 flex-1", children: ticket.subject }),
1820
+ (ticket.unanswered_messages_count || 0) > 0 && /* @__PURE__ */ jsx(
1821
+ Badge,
1822
+ {
1823
+ variant: "destructive",
1824
+ className: "ml-2 shrink-0 animate-pulse",
1825
+ children: ticket.unanswered_messages_count
1826
+ }
1827
+ )
1828
+ ] }),
1829
+ /* @__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: [
1830
+ /* @__PURE__ */ jsx(Badge, { variant: getStatusBadgeVariant(ticket.status || "open"), className: "text-xs", children: ticket.status || "open" }),
1831
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1832
+ /* @__PURE__ */ jsx(Clock, { className: "h-3 w-3" }),
1833
+ /* @__PURE__ */ jsx("span", { children: formatRelativeTime(ticket.created_at) })
1834
+ ] })
1835
+ ] }) })
1836
+ ] })
1837
+ }
1838
+ );
1839
+ };
1778
1840
 
1779
1841
  // src/layouts/SupportLayout/events.ts
1780
1842
  var SUPPORT_LAYOUT_EVENTS = {
@@ -2039,71 +2101,6 @@ function useSupportLayoutContext() {
2039
2101
  }
2040
2102
  return context;
2041
2103
  }
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
2104
  var TicketList = () => {
2108
2105
  const { selectedTicket, selectTicket } = useSupportLayoutContext();
2109
2106
  const {
@@ -2209,28 +2206,23 @@ var TicketList = () => {
2209
2206
  };
2210
2207
  var formatTime = (date) => {
2211
2208
  if (!date) return "";
2212
- return new Date(date).toLocaleTimeString("en-US", {
2213
- hour: "2-digit",
2214
- minute: "2-digit"
2215
- });
2209
+ return moment2.utc(date).local().format("hh:mm A");
2216
2210
  };
2217
2211
  var formatDate = (date) => {
2218
2212
  if (!date) return "";
2219
- return new Date(date).toLocaleDateString("en-US", {
2220
- year: "numeric",
2221
- month: "short",
2222
- day: "numeric"
2223
- });
2213
+ return moment2.utc(date).local().format("MMM D, YYYY");
2224
2214
  };
2225
2215
  var MessageBubble = ({ message, isFromUser, currentUser }) => {
2226
2216
  const sender = message.sender;
2217
+ const senderInitial = sender?.display_username?.charAt(0)?.toUpperCase() || sender?.initials || "S";
2218
+ const userInitial = currentUser?.display_username?.charAt(0)?.toUpperCase() || currentUser?.email?.charAt(0)?.toUpperCase() || currentUser?.initials || null;
2227
2219
  return /* @__PURE__ */ jsxs(
2228
2220
  "div",
2229
2221
  {
2230
2222
  className: `flex gap-3 ${isFromUser ? "justify-end" : "justify-start"}
2231
2223
  animate-in fade-in slide-in-from-bottom-2 duration-300`,
2232
2224
  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" }) }),
2225
+ !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
2226
  /* @__PURE__ */ jsxs("div", { className: `flex flex-col gap-1 flex-1 max-w-[80%] ${isFromUser ? "items-end" : "items-start"}`, children: [
2235
2227
  !isFromUser && sender && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground px-1", children: [
2236
2228
  sender.display_username || sender.email || "Support Team",
@@ -2245,7 +2237,7 @@ var MessageBubble = ({ message, isFromUser, currentUser }) => {
2245
2237
  ),
2246
2238
  /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground px-1", children: formatTime(message.created_at) })
2247
2239
  ] }),
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" }) }) })
2240
+ 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
2241
  ]
2250
2242
  }
2251
2243
  );
@@ -2363,7 +2355,7 @@ var MessageList = () => {
2363
2355
  messages.map((message, index) => {
2364
2356
  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
2357
  const previousMessage = index > 0 ? messages[index - 1] : null;
2366
- const showDateSeparator = previousMessage && new Date(previousMessage.created_at || "").toDateString() !== new Date(message.created_at || "").toDateString();
2358
+ const showDateSeparator = previousMessage && moment2.utc(previousMessage.created_at || "").format("YYYY-MM-DD") !== moment2.utc(message.created_at || "").format("YYYY-MM-DD");
2367
2359
  return /* @__PURE__ */ jsxs(React7.Fragment, { children: [
2368
2360
  showDateSeparator && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 my-4", children: [
2369
2361
  /* @__PURE__ */ jsx("div", { className: "flex-1 h-px bg-border" }),
@@ -2631,7 +2623,7 @@ var SupportLayout = () => {
2631
2623
  // package.json
2632
2624
  var package_default = {
2633
2625
  name: "@djangocfg/ext-support",
2634
- version: "1.0.4",
2626
+ version: "1.0.6",
2635
2627
  description: "Support ticket system extension for DjangoCFG",
2636
2628
  keywords: [
2637
2629
  "django",
@@ -2695,6 +2687,7 @@ var package_default = {
2695
2687
  "@djangocfg/ui-nextjs": "workspace:*",
2696
2688
  consola: "^3.4.2",
2697
2689
  "lucide-react": "^0.545.0",
2690
+ moment: "^2.30.1",
2698
2691
  next: "^15.5.7",
2699
2692
  "p-retry": "^7.0.0",
2700
2693
  react: "^18 || ^19",
@@ -2709,6 +2702,7 @@ var package_default = {
2709
2702
  "@types/node": "^24.7.2",
2710
2703
  "@types/react": "^19.0.0",
2711
2704
  consola: "^3.4.2",
2705
+ moment: "^2.30.1",
2712
2706
  "p-retry": "^7.0.0",
2713
2707
  swr: "^2.3.7",
2714
2708
  tsup: "^8.5.0",