@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.
- package/dist/config.cjs +4 -1
- package/dist/config.js +4 -1
- package/dist/hooks.cjs +79 -89
- package/dist/hooks.js +78 -89
- package/dist/index.cjs +79 -89
- package/dist/index.d.cts +0 -97
- package/dist/index.d.ts +0 -97
- package/dist/index.js +78 -89
- package/package.json +10 -7
- package/src/api/generated/ext_support/CLAUDE.md +80 -0
- package/src/api/generated/ext_support/_utils/fetchers/ext_support__support.ts +1 -8
- package/src/api/generated/ext_support/_utils/fetchers/index.ts +1 -8
- package/src/api/generated/ext_support/_utils/hooks/ext_support__support.ts +1 -8
- package/src/api/generated/ext_support/_utils/hooks/index.ts +1 -8
- package/src/api/generated/ext_support/_utils/schemas/index.ts +1 -8
- package/src/api/generated/ext_support/api-instance.ts +1 -8
- package/src/api/generated/ext_support/enums.ts +1 -8
- package/src/api/generated/ext_support/errors.ts +1 -8
- package/src/api/generated/ext_support/ext_support__support/index.ts +1 -8
- package/src/api/generated/ext_support/ext_support__support/models.ts +1 -8
- package/src/api/generated/ext_support/http.ts +1 -8
- package/src/api/generated/ext_support/index.ts +1 -8
- package/src/api/generated/ext_support/logger.ts +1 -8
- package/src/api/generated/ext_support/retry.ts +1 -8
- package/src/api/generated/ext_support/storage.ts +1 -8
- package/src/api/generated/ext_support/validation-events.ts +1 -8
- package/src/api/index.ts +2 -1
- package/src/config.ts +1 -0
- package/src/contexts/SupportContext.tsx +9 -13
- package/src/contexts/SupportExtensionProvider.tsx +1 -0
- package/src/layouts/SupportLayout/SupportLayout.tsx +5 -13
- package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +8 -18
- package/src/layouts/SupportLayout/components/MessageInput.tsx +5 -10
- package/src/layouts/SupportLayout/components/MessageList.tsx +25 -22
- package/src/layouts/SupportLayout/components/TicketCard.tsx +10 -10
- package/src/layouts/SupportLayout/components/TicketList.tsx +6 -4
- package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +9 -2
- package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +2 -0
- 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.
|
|
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.
|
|
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
|
|
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
|
|
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" }) :
|
|
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:
|
|
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 &&
|
|
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.
|
|
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
|
|
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
|
|
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" }) :
|
|
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:
|
|
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 &&
|
|
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.
|
|
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",
|