@webority/support-react 0.13.0 → 0.13.3
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/README.md +32 -6
- package/dist/support-react.js +304 -230
- package/dist/types/ReportIssue.d.ts +6 -1
- package/dist/types/SupportCenter.d.ts +1 -0
- package/dist/types/client.d.ts +63 -2
- package/dist/types/components/AttachmentField.d.ts +2 -0
- package/dist/types/components/TicketList.d.ts +25 -3
- package/dist/types/context.d.ts +18 -3
- package/dist/types/index.d.ts +1 -1
- package/dist/types/lib/useDebouncedValue.d.ts +10 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -42,10 +42,18 @@ export function App() {
|
|
|
42
42
|
rejects with a `SupportApiError` carrying `code`, `status` and a `message` written for the
|
|
43
43
|
person, which the components show as-is.
|
|
44
44
|
|
|
45
|
+
`listTickets` returns one page, most recently updated first, in the shape
|
|
46
|
+
`{ items, page, pageSize, totalCount, totalPages }`. Every query part is optional: `status` is one
|
|
47
|
+
of `Open`, `InProgress`, `Resolved` or `Closed`, `search` matches the subject or the ticket
|
|
48
|
+
number, `page` starts at 1, and the API caps `pageSize` at 100. `getTicketCounts` returns
|
|
49
|
+
`{ open, inProgress, resolved, closed, total }`. A ticket's `accountId` and `realm` are `null` on
|
|
50
|
+
the customer side, and its `version` is for support staff only; the components use none of them.
|
|
51
|
+
|
|
45
52
|
| Method | Request |
|
|
46
53
|
|---|---|
|
|
47
|
-
| `getConfig()` | `GET {base}/
|
|
48
|
-
| `listTickets()` | `GET {base}/tickets
|
|
54
|
+
| `getConfig()` | `GET {base}/settings` |
|
|
55
|
+
| `listTickets({ status, search, page, pageSize })` | `GET {base}/tickets?status=&search=&page=&pageSize=`, one page |
|
|
56
|
+
| `getTicketCounts()` | `GET {base}/tickets/counts` |
|
|
49
57
|
| `getTicket(id)` | `GET {base}/tickets/{id}` |
|
|
50
58
|
| `createTicket({ category, subject, body, pageUrl, userAgent, appVersion, files, screenshots })` | `POST {base}/tickets`, multipart |
|
|
51
59
|
| `reply(id, { body, files, screenshots })` | `POST {base}/tickets/{id}/replies`, multipart |
|
|
@@ -58,7 +66,10 @@ person, which the components show as-is.
|
|
|
58
66
|
### `<SupportCenter />`
|
|
59
67
|
|
|
60
68
|
The support page: the customer's tickets with status tabs and a search box, one ticket's
|
|
61
|
-
conversation, and a New ticket dialog.
|
|
69
|
+
conversation, and a New ticket dialog. The server filters, searches and pages the list, so it
|
|
70
|
+
stays quick however many tickets a customer has. The tabs are All, Open, In progress, Resolved
|
|
71
|
+
and Closed, each labelled with its count from `getTicketCounts`. All is chosen first. The search
|
|
72
|
+
asks the server once typing pauses, and a new tab or search starts again at page 1.
|
|
62
73
|
|
|
63
74
|
```jsx
|
|
64
75
|
<SupportCenter appVersion={import.meta.env.VITE_APP_VERSION} defaultCategory="Question" />
|
|
@@ -74,7 +85,8 @@ Support emails link to the support page with `?ticket=<id>`. The centre opens th
|
|
|
74
85
|
keeps the parameter in step as tickets open and close, so a refresh stays on the same ticket.
|
|
75
86
|
In a ticket the customer can reply with files (pasting an image into the message attaches it),
|
|
76
87
|
reopen a settled ticket, and say whether it solved their problem. Files are checked against the
|
|
77
|
-
config's type, size and count limits before anything uploads
|
|
88
|
+
config's type, size and count limits before anything uploads, and a file that breaks a limit gets
|
|
89
|
+
one message, in the package's words.
|
|
78
90
|
|
|
79
91
|
### `<ReportIssue />`
|
|
80
92
|
|
|
@@ -95,6 +107,17 @@ pasting them. The ticket records the page address, the browser and the product v
|
|
|
95
107
|
| `getTicketHref(id)` | Shows a View ticket link to the new ticket. |
|
|
96
108
|
| `onOpenTicket(ticket)` | Called by the View ticket button when there is no href. |
|
|
97
109
|
| `onCreated(ticket)` | Called once the ticket is raised. |
|
|
110
|
+
| `className` | Added to the button's wrapper, floating or inline, to place it. |
|
|
111
|
+
| `style` | Merged over the wrapper's own style. |
|
|
112
|
+
|
|
113
|
+
Place the button with `className` or `style`. Both land on the wrapper that holds the button,
|
|
114
|
+
which is the element that floats. With `floating`, an inset moves it clear of other fixed controls
|
|
115
|
+
such as a chat launcher:
|
|
116
|
+
|
|
117
|
+
```jsx
|
|
118
|
+
<ReportIssue floating style={{ bottom: "5rem" }} />
|
|
119
|
+
<ReportIssue className="ms-auto" />
|
|
120
|
+
```
|
|
98
121
|
|
|
99
122
|
The screenshot is taken before the dialog opens, and the button itself is left out of it.
|
|
100
123
|
Anything else the page marks with `data-support-capture="exclude"` is left out too.
|
|
@@ -110,8 +133,11 @@ support config. A field the product has not set is left out, and nothing renders
|
|
|
110
133
|
|
|
111
134
|
## Hooks
|
|
112
135
|
|
|
113
|
-
`useSupportConfig()`, `useSupportTickets()
|
|
114
|
-
`
|
|
136
|
+
`useSupportConfig()`, `useSupportTickets(query)`, `useSupportTicketCounts()` and
|
|
137
|
+
`useSupportTicket(id)` return the data with `loading`, `error` and `reload`.
|
|
138
|
+
`useSupportTickets` takes the same query as `listTickets` and returns `tickets` (the page's items),
|
|
139
|
+
`totalCount` and `totalPages`; it asks again whenever the query changes. `useSupportTicketCounts`
|
|
140
|
+
returns `counts`. `useSupportTicket` also returns `reply`, `reopen` and `rate`,
|
|
115
141
|
each resolving to the updated ticket. `useSupportClient()` returns the client itself.
|
|
116
142
|
|
|
117
143
|
## Display rules
|
package/dist/support-react.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { createContext as e, useCallback as t, useContext as n, useEffect as r, useId as i, useMemo as a, useRef as o, useState as s } from "react";
|
|
2
|
-
import { AppAlert as c, AppAvatar as l, AppBadge as u, AppButton as d, AppCard as f, AppCopyButton as p, AppDataTable as m, AppFileAttachment as h, AppIcon as g, AppInfoList as _, AppInput as v, AppLink as y, AppModal as b, AppSearch as x, AppSelect as S, AppSkeleton as C, AppSpinner as w, AppStatusBadge as T, AppTableEmptyState as E, AppTabs as D, AppTextarea as O
|
|
3
|
-
import { toBlob as
|
|
4
|
-
import { Fragment as
|
|
2
|
+
import { AppAlert as c, AppAvatar as l, AppBadge as u, AppButton as d, AppCard as f, AppCopyButton as p, AppDataTable as m, AppFileAttachment as h, AppIcon as g, AppInfoList as _, AppInput as v, AppLink as y, AppModal as b, AppSearch as x, AppSelect as S, AppSkeleton as C, AppSpinner as w, AppStatusBadge as T, AppTableEmptyState as E, AppTabs as D, AppTextarea as O } from "@webority/ui-react";
|
|
3
|
+
import { toBlob as k } from "html-to-image";
|
|
4
|
+
import { Fragment as A, jsx as j, jsxs as M } from "react/jsx-runtime";
|
|
5
5
|
//#region src/client.js
|
|
6
6
|
var N = "Something went wrong. Please try again.", P = class extends Error {
|
|
7
7
|
constructor(e, t, n) {
|
|
8
8
|
super(t), this.name = "SupportApiError", this.code = e, this.status = n;
|
|
9
9
|
}
|
|
10
10
|
};
|
|
11
|
-
async function
|
|
11
|
+
async function F(e) {
|
|
12
12
|
if (e && typeof Blob < "u" && e instanceof Blob) try {
|
|
13
13
|
return JSON.parse(await e.text());
|
|
14
14
|
} catch {
|
|
@@ -21,63 +21,69 @@ async function te(e) {
|
|
|
21
21
|
}
|
|
22
22
|
return e ?? null;
|
|
23
23
|
}
|
|
24
|
-
async function
|
|
24
|
+
async function I(e) {
|
|
25
25
|
if (e instanceof P) return e;
|
|
26
26
|
let t = e?.response;
|
|
27
27
|
if (!t) return new P("network", N);
|
|
28
|
-
let n = await
|
|
28
|
+
let n = await F(t.data);
|
|
29
29
|
return new P(typeof n?.code == "string" && n.code ? n.code : "unknown", typeof n?.message == "string" && n.message ? n.message : N, t.status);
|
|
30
30
|
}
|
|
31
|
-
async function
|
|
31
|
+
async function L(e) {
|
|
32
32
|
try {
|
|
33
33
|
return (await e()).data;
|
|
34
34
|
} catch (e) {
|
|
35
|
-
throw await
|
|
35
|
+
throw await I(e);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
function
|
|
38
|
+
function R(e, t, n) {
|
|
39
39
|
n != null && String(n).trim() !== "" && e.append(t, String(n));
|
|
40
40
|
}
|
|
41
|
-
function
|
|
41
|
+
function z(e, t, n, r) {
|
|
42
42
|
(n ?? []).forEach((n, i) => {
|
|
43
43
|
let a = n?.name || `${r}-${i + 1}.png`;
|
|
44
44
|
e.append(t, n, a);
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
|
-
function
|
|
47
|
+
function ee(e, t = "/api/support") {
|
|
48
48
|
let n = t.replace(/\/+$/, ""), r = (e) => `${n}/tickets/${encodeURIComponent(e)}`;
|
|
49
49
|
return {
|
|
50
|
-
getConfig: () =>
|
|
51
|
-
listTickets: (
|
|
52
|
-
|
|
50
|
+
getConfig: () => L(() => e.get(`${n}/settings`)),
|
|
51
|
+
listTickets: ({ status: t, search: r, page: i, pageSize: a } = {}) => {
|
|
52
|
+
let o = new URLSearchParams();
|
|
53
|
+
R(o, "status", t), R(o, "search", r?.trim()), R(o, "page", i), R(o, "pageSize", a);
|
|
54
|
+
let s = o.toString();
|
|
55
|
+
return L(() => e.get(`${n}/tickets${s ? `?${s}` : ""}`));
|
|
56
|
+
},
|
|
57
|
+
getTicketCounts: () => L(() => e.get(`${n}/tickets/counts`)),
|
|
58
|
+
getTicket: (t) => L(() => e.get(r(t))),
|
|
53
59
|
createTicket: ({ category: t, subject: r, body: i, pageUrl: a, userAgent: o, appVersion: s, files: c, screenshots: l }) => {
|
|
54
60
|
let u = new FormData();
|
|
55
|
-
return u.append("category", t ?? ""), u.append("subject", r ?? ""), u.append("body", i ?? ""),
|
|
61
|
+
return u.append("category", t ?? ""), u.append("subject", r ?? ""), u.append("body", i ?? ""), R(u, "pageUrl", a), R(u, "userAgent", o), R(u, "appVersion", s), z(u, "files", c, "file"), z(u, "screenshots", l, "screenshot"), L(() => e.post(`${n}/tickets`, u));
|
|
56
62
|
},
|
|
57
63
|
reply: (t, { body: n, files: i, screenshots: a }) => {
|
|
58
64
|
let o = new FormData();
|
|
59
|
-
return o.append("body", n ?? ""),
|
|
65
|
+
return o.append("body", n ?? ""), z(o, "files", i, "file"), z(o, "screenshots", a, "screenshot"), L(() => e.post(`${r(t)}/replies`, o));
|
|
60
66
|
},
|
|
61
|
-
reopen: (t) =>
|
|
67
|
+
reopen: (t) => L(() => e.post(`${r(t)}/reopen`)),
|
|
62
68
|
rate: (t, { satisfaction: n, comment: i }) => {
|
|
63
69
|
let a = { satisfaction: n };
|
|
64
|
-
return i != null && i.trim() !== "" && (a.comment = i.trim()),
|
|
70
|
+
return i != null && i.trim() !== "" && (a.comment = i.trim()), L(() => e.post(`${r(t)}/rating`, a));
|
|
65
71
|
},
|
|
66
|
-
fetchAttachment: (t) =>
|
|
72
|
+
fetchAttachment: (t) => L(() => e.get(`${n}/attachments/${encodeURIComponent(t)}`, { responseType: "blob" }))
|
|
67
73
|
};
|
|
68
74
|
}
|
|
69
75
|
//#endregion
|
|
70
76
|
//#region src/context.js
|
|
71
|
-
var
|
|
72
|
-
function
|
|
73
|
-
let e = n(
|
|
77
|
+
var B = e(null);
|
|
78
|
+
function V() {
|
|
79
|
+
let e = n(B);
|
|
74
80
|
if (!e) throw Error("Support components and hooks must be used inside <SupportProvider>.");
|
|
75
81
|
return e;
|
|
76
82
|
}
|
|
77
|
-
function
|
|
78
|
-
return
|
|
83
|
+
function H() {
|
|
84
|
+
return V().client;
|
|
79
85
|
}
|
|
80
|
-
function
|
|
86
|
+
function U(e, n, i = !0) {
|
|
81
87
|
let [a, c] = s({
|
|
82
88
|
data: null,
|
|
83
89
|
loading: i,
|
|
@@ -126,8 +132,8 @@ function z(e, n, i = !0) {
|
|
|
126
132
|
setData: m
|
|
127
133
|
};
|
|
128
134
|
}
|
|
129
|
-
function
|
|
130
|
-
let { loadConfig: e } =
|
|
135
|
+
function W() {
|
|
136
|
+
let { loadConfig: e } = V(), { data: t, loading: n, error: r, reload: i } = U(e, "config");
|
|
131
137
|
return {
|
|
132
138
|
config: t,
|
|
133
139
|
loading: n,
|
|
@@ -135,17 +141,39 @@ function B() {
|
|
|
135
141
|
reload: i
|
|
136
142
|
};
|
|
137
143
|
}
|
|
138
|
-
function
|
|
139
|
-
let
|
|
144
|
+
function te(e = {}) {
|
|
145
|
+
let t = H(), { status: n, search: r, page: i, pageSize: a } = e, { data: o, loading: s, error: c, reload: l } = U(() => t.listTickets({
|
|
146
|
+
status: n,
|
|
147
|
+
search: r,
|
|
148
|
+
page: i,
|
|
149
|
+
pageSize: a
|
|
150
|
+
}), JSON.stringify([
|
|
151
|
+
"tickets",
|
|
152
|
+
n ?? null,
|
|
153
|
+
r ?? null,
|
|
154
|
+
i ?? null,
|
|
155
|
+
a ?? null
|
|
156
|
+
]));
|
|
140
157
|
return {
|
|
141
|
-
tickets:
|
|
158
|
+
tickets: o?.items ?? [],
|
|
159
|
+
totalCount: o?.totalCount ?? 0,
|
|
160
|
+
totalPages: o?.totalPages ?? 0,
|
|
161
|
+
loading: s,
|
|
162
|
+
error: c,
|
|
163
|
+
reload: l
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function ne() {
|
|
167
|
+
let e = H(), { data: t, loading: n, error: r, reload: i } = U(() => e.getTicketCounts(), "ticket-counts");
|
|
168
|
+
return {
|
|
169
|
+
counts: t,
|
|
142
170
|
loading: n,
|
|
143
171
|
error: r,
|
|
144
172
|
reload: i
|
|
145
173
|
};
|
|
146
174
|
}
|
|
147
|
-
function
|
|
148
|
-
let n =
|
|
175
|
+
function G(e) {
|
|
176
|
+
let n = H(), { data: r, loading: i, error: a, reload: o, setData: s } = U(() => n.getTicket(e), e ?? "", !!e), c = t(async (e) => {
|
|
149
177
|
let t = await e();
|
|
150
178
|
return s(t), t;
|
|
151
179
|
}, [s]);
|
|
@@ -173,7 +201,7 @@ function H(e) {
|
|
|
173
201
|
}
|
|
174
202
|
//#endregion
|
|
175
203
|
//#region src/lib/format.js
|
|
176
|
-
var
|
|
204
|
+
var re = new Intl.DateTimeFormat(void 0, {
|
|
177
205
|
day: "numeric",
|
|
178
206
|
month: "short",
|
|
179
207
|
year: "numeric",
|
|
@@ -181,12 +209,12 @@ var oe = new Intl.DateTimeFormat(void 0, {
|
|
|
181
209
|
minute: "2-digit",
|
|
182
210
|
timeZoneName: "short"
|
|
183
211
|
});
|
|
184
|
-
function
|
|
212
|
+
function K(e) {
|
|
185
213
|
if (!e) return "-";
|
|
186
214
|
let t = new Date(e);
|
|
187
|
-
return Number.isNaN(t.getTime()) ? "-" :
|
|
215
|
+
return Number.isNaN(t.getTime()) ? "-" : re.format(t);
|
|
188
216
|
}
|
|
189
|
-
function
|
|
217
|
+
function q(e) {
|
|
190
218
|
if (!Number.isFinite(e) || e < 0) return "-";
|
|
191
219
|
if (e < 1024) return `${e} B`;
|
|
192
220
|
if (e < 1024 * 1024) return `${Math.round(e / 1024)} KB`;
|
|
@@ -195,11 +223,11 @@ function W(e) {
|
|
|
195
223
|
}
|
|
196
224
|
//#endregion
|
|
197
225
|
//#region src/lib/files.js
|
|
198
|
-
function
|
|
226
|
+
function ie(e) {
|
|
199
227
|
let t = e?.allowedContentTypes ?? [];
|
|
200
228
|
return t.length > 0 ? t.join(",") : void 0;
|
|
201
229
|
}
|
|
202
|
-
function
|
|
230
|
+
function ae(e, t) {
|
|
203
231
|
if (t.length === 0) return !0;
|
|
204
232
|
let n = (e || "").toLowerCase();
|
|
205
233
|
return t.some((e) => {
|
|
@@ -207,17 +235,17 @@ function ce(e, t) {
|
|
|
207
235
|
return t.endsWith("/*") ? n.startsWith(t.slice(0, -1)) : n === t;
|
|
208
236
|
});
|
|
209
237
|
}
|
|
210
|
-
function
|
|
238
|
+
function J(e, t) {
|
|
211
239
|
let n = e ?? [], r = t?.maxAttachmentsPerMessage ?? 0, i = t?.maxAttachmentBytes ?? 0, a = t?.allowedContentTypes ?? [];
|
|
212
240
|
if (r > 0 && n.length > r) return r === 1 ? "You can attach one file." : `You can attach up to ${r} files.`;
|
|
213
241
|
for (let e of n) {
|
|
214
242
|
let t = e.name || "This file";
|
|
215
|
-
if (!
|
|
216
|
-
if (i > 0 && e.size > i) return `${t} is larger than ${
|
|
243
|
+
if (!ae(e.type, a)) return `${t} is a file type we cannot accept.`;
|
|
244
|
+
if (i > 0 && e.size > i) return `${t} is larger than ${q(i)}.`;
|
|
217
245
|
}
|
|
218
246
|
return null;
|
|
219
247
|
}
|
|
220
|
-
function
|
|
248
|
+
function Y(e) {
|
|
221
249
|
let t = Array.from(e.clipboardData?.items ?? []), n = Date.now();
|
|
222
250
|
return t.filter((e) => e.kind === "file" && e.type.startsWith("image/")).map((e) => e.getAsFile()).filter(Boolean).map((e, t) => {
|
|
223
251
|
let r = (e.type.split("/")[1] || "png").replace("jpeg", "jpg");
|
|
@@ -226,7 +254,7 @@ function K(e) {
|
|
|
226
254
|
}
|
|
227
255
|
//#endregion
|
|
228
256
|
//#region src/components/AttachmentField.jsx
|
|
229
|
-
function
|
|
257
|
+
function X({ id: e, label: t = "Attachments", files: n, onChange: r, config: i, error: a }) {
|
|
230
258
|
let o = i?.maxAttachmentBytes ?? 0, s = o > 0 ? Math.round(o / (1024 * 1024) * 10) / 10 : void 0;
|
|
231
259
|
return /* @__PURE__ */ j(h, {
|
|
232
260
|
id: e,
|
|
@@ -235,30 +263,31 @@ function q({ id: e, label: t = "Attachments", files: n, onChange: r, config: i,
|
|
|
235
263
|
multiple: !0,
|
|
236
264
|
value: n,
|
|
237
265
|
onChange: r,
|
|
238
|
-
accept:
|
|
266
|
+
accept: ie(i),
|
|
239
267
|
maxSizeMb: s,
|
|
268
|
+
validate: !1,
|
|
240
269
|
error: a || void 0
|
|
241
270
|
});
|
|
242
271
|
}
|
|
243
272
|
//#endregion
|
|
244
273
|
//#region src/components/TicketForm.jsx
|
|
245
|
-
function
|
|
274
|
+
function oe(e, t) {
|
|
246
275
|
return t && e.includes(t) ? t : e[0] ?? "";
|
|
247
276
|
}
|
|
248
|
-
function
|
|
249
|
-
let l = t?.categories ?? [], [u, d] = s(() =>
|
|
277
|
+
function se({ id: e, config: t, defaultCategory: n, extraFiles: r = [], onSubmit: i, onBusyChange: a, children: o }) {
|
|
278
|
+
let l = t?.categories ?? [], [u, d] = s(() => oe(l, n)), [f, p] = s(""), [m, h] = s(""), [g, _] = s([]), [y, b] = s({}), [x, C] = s(""), w = (e) => {
|
|
250
279
|
_(e), b((n) => ({
|
|
251
280
|
...n,
|
|
252
|
-
files:
|
|
281
|
+
files: J([...r, ...e], t) ?? void 0
|
|
253
282
|
}));
|
|
254
283
|
}, T = (e) => {
|
|
255
|
-
let t =
|
|
284
|
+
let t = Y(e);
|
|
256
285
|
t.length > 0 && w([...g, ...t]);
|
|
257
286
|
};
|
|
258
287
|
function E() {
|
|
259
288
|
let e = {};
|
|
260
289
|
u || (e.category = "Choose a category."), f.trim() || (e.subject = "Give your ticket a subject."), m.trim() || (e.body = "Tell us what happened.");
|
|
261
|
-
let n =
|
|
290
|
+
let n = J([...r, ...g], t);
|
|
262
291
|
return n && (e.files = n), b(e), Object.keys(e).length === 0;
|
|
263
292
|
}
|
|
264
293
|
async function D(e) {
|
|
@@ -325,7 +354,7 @@ function J({ id: e, config: t, defaultCategory: n, extraFiles: r = [], onSubmit:
|
|
|
325
354
|
error: y.body
|
|
326
355
|
}),
|
|
327
356
|
o,
|
|
328
|
-
/* @__PURE__ */ j(
|
|
357
|
+
/* @__PURE__ */ j(X, {
|
|
329
358
|
id: `${e}-files`,
|
|
330
359
|
files: g,
|
|
331
360
|
onChange: w,
|
|
@@ -337,16 +366,16 @@ function J({ id: e, config: t, defaultCategory: n, extraFiles: r = [], onSubmit:
|
|
|
337
366
|
}
|
|
338
367
|
//#endregion
|
|
339
368
|
//#region src/ReportIssue.jsx
|
|
340
|
-
var
|
|
341
|
-
async function
|
|
342
|
-
let e = await
|
|
343
|
-
filter:
|
|
369
|
+
var ce = "supportCapture", le = { zIndex: "var(--wui-z-fixed)" }, ue = (e) => e?.dataset?.[ce] !== "exclude";
|
|
370
|
+
async function de() {
|
|
371
|
+
let e = await k(document.body, {
|
|
372
|
+
filter: ue,
|
|
344
373
|
pixelRatio: Math.min(window.devicePixelRatio || 1, 2)
|
|
345
374
|
});
|
|
346
375
|
if (!e) throw Error("The page capture came back empty.");
|
|
347
376
|
return new File([e], "screenshot.png", { type: "image/png" });
|
|
348
377
|
}
|
|
349
|
-
function
|
|
378
|
+
function fe({ file: e, onRemove: t }) {
|
|
350
379
|
let [n, i] = s(null);
|
|
351
380
|
return r(() => {
|
|
352
381
|
let t = URL.createObjectURL(e);
|
|
@@ -374,109 +403,112 @@ function me({ file: e, onRemove: t }) {
|
|
|
374
403
|
]
|
|
375
404
|
});
|
|
376
405
|
}
|
|
377
|
-
function
|
|
378
|
-
let
|
|
379
|
-
async function
|
|
380
|
-
|
|
406
|
+
function pe({ label: e = "Report an issue", floating: t = !1, defaultCategory: n = "Bug report", appVersion: r, getTicketHref: a, onOpenTicket: o, onCreated: l, className: u, style: f }) {
|
|
407
|
+
let p = H(), { config: m, error: h } = W(), g = `support-report-${i().replace(/[^a-zA-Z0-9-]/g, "")}`, [_, v] = s(!1), [y, x] = s(!1), [S, C] = s(null), [w, T] = s(!1), [E, D] = s(!1), [O, k] = s(null);
|
|
408
|
+
async function N() {
|
|
409
|
+
x(!0), k(null);
|
|
381
410
|
try {
|
|
382
|
-
|
|
411
|
+
C(await de()), T(!1);
|
|
383
412
|
} catch {
|
|
384
|
-
|
|
413
|
+
C(null), T(!0);
|
|
385
414
|
} finally {
|
|
386
|
-
|
|
415
|
+
x(!1), v(!0);
|
|
387
416
|
}
|
|
388
417
|
}
|
|
389
|
-
let
|
|
390
|
-
|
|
418
|
+
let P = () => {
|
|
419
|
+
E || (v(!1), C(null), k(null));
|
|
391
420
|
};
|
|
392
|
-
async function
|
|
393
|
-
let t = await
|
|
421
|
+
async function F(e) {
|
|
422
|
+
let t = await p.createTicket({
|
|
394
423
|
...e,
|
|
395
424
|
pageUrl: window.location.href,
|
|
396
425
|
userAgent: navigator.userAgent,
|
|
397
426
|
appVersion: r,
|
|
398
|
-
screenshots:
|
|
427
|
+
screenshots: S ? [S] : []
|
|
399
428
|
});
|
|
400
|
-
|
|
429
|
+
k(t), C(null), l?.(t);
|
|
401
430
|
}
|
|
402
|
-
let
|
|
403
|
-
|
|
431
|
+
let I = O && a ? a(O.id) : void 0, L = O ? /* @__PURE__ */ M("div", {
|
|
432
|
+
className: "d-flex justify-content-end gap-2 w-100",
|
|
433
|
+
children: [/* @__PURE__ */ j(d, {
|
|
434
|
+
variant: "secondary",
|
|
435
|
+
onClick: P,
|
|
436
|
+
children: "Close"
|
|
437
|
+
}), O && (I || o) ? /* @__PURE__ */ j(d, {
|
|
438
|
+
href: I,
|
|
439
|
+
onClick: I ? void 0 : () => {
|
|
440
|
+
v(!1), k(null), o(O);
|
|
441
|
+
},
|
|
442
|
+
children: "View ticket"
|
|
443
|
+
}) : null]
|
|
444
|
+
}) : /* @__PURE__ */ M("div", {
|
|
445
|
+
className: "d-flex justify-content-end gap-2 w-100",
|
|
446
|
+
children: [/* @__PURE__ */ j(d, {
|
|
447
|
+
variant: "secondary",
|
|
448
|
+
onClick: P,
|
|
449
|
+
disabled: E,
|
|
450
|
+
children: "Cancel"
|
|
451
|
+
}), /* @__PURE__ */ j(d, {
|
|
452
|
+
type: "submit",
|
|
453
|
+
form: g,
|
|
454
|
+
loading: E,
|
|
455
|
+
disabled: !m,
|
|
456
|
+
children: "Send report"
|
|
457
|
+
})]
|
|
458
|
+
});
|
|
459
|
+
return /* @__PURE__ */ M(A, { children: [/* @__PURE__ */ j("span", {
|
|
404
460
|
"data-support-capture": "exclude",
|
|
405
|
-
className: t ? "position-fixed bottom-0 end-0 m-3" : "d-inline-block",
|
|
406
|
-
style: t ?
|
|
461
|
+
className: [t ? "position-fixed bottom-0 end-0 m-3" : "d-inline-block", u].filter(Boolean).join(" "),
|
|
462
|
+
style: t ? {
|
|
463
|
+
...le,
|
|
464
|
+
...f
|
|
465
|
+
} : f,
|
|
407
466
|
children: /* @__PURE__ */ j(d, {
|
|
408
467
|
leftIcon: "bug",
|
|
409
468
|
variant: t ? "primary" : "outline",
|
|
410
|
-
loading:
|
|
411
|
-
onClick:
|
|
469
|
+
loading: y,
|
|
470
|
+
onClick: N,
|
|
412
471
|
"aria-haspopup": "dialog",
|
|
413
472
|
children: e
|
|
414
473
|
})
|
|
415
474
|
}), /* @__PURE__ */ j(b, {
|
|
416
|
-
open:
|
|
475
|
+
open: _,
|
|
417
476
|
size: "lg",
|
|
418
477
|
title: "Report an issue",
|
|
419
478
|
subtitle: "Tell us what went wrong. We attach a screenshot of this page to help us see it.",
|
|
420
479
|
headerIcon: "bug",
|
|
421
480
|
staticBackdrop: !0,
|
|
422
|
-
onClose:
|
|
423
|
-
footer:
|
|
424
|
-
|
|
425
|
-
children: [/* @__PURE__ */ j(d, {
|
|
426
|
-
variant: "secondary",
|
|
427
|
-
onClick: k,
|
|
428
|
-
children: "Close"
|
|
429
|
-
}), E && (N || o) ? /* @__PURE__ */ j(d, {
|
|
430
|
-
href: N,
|
|
431
|
-
onClick: N ? void 0 : () => {
|
|
432
|
-
g(!1), D(null), o(E);
|
|
433
|
-
},
|
|
434
|
-
children: "View ticket"
|
|
435
|
-
}) : null]
|
|
436
|
-
}) : /* @__PURE__ */ M("div", {
|
|
437
|
-
className: "d-flex justify-content-end gap-2 w-100",
|
|
438
|
-
children: [/* @__PURE__ */ j(d, {
|
|
439
|
-
variant: "secondary",
|
|
440
|
-
onClick: k,
|
|
441
|
-
disabled: w,
|
|
442
|
-
children: "Cancel"
|
|
443
|
-
}), /* @__PURE__ */ j(d, {
|
|
444
|
-
type: "submit",
|
|
445
|
-
form: m,
|
|
446
|
-
loading: w,
|
|
447
|
-
disabled: !f,
|
|
448
|
-
children: "Send report"
|
|
449
|
-
})]
|
|
450
|
-
}),
|
|
451
|
-
children: E ? /* @__PURE__ */ j(c, {
|
|
481
|
+
onClose: P,
|
|
482
|
+
footer: L,
|
|
483
|
+
children: O ? /* @__PURE__ */ j(c, {
|
|
452
484
|
variant: "success",
|
|
453
485
|
role: "status",
|
|
454
|
-
title: `Ticket ${
|
|
486
|
+
title: `Ticket ${O.number} is with the support team`,
|
|
455
487
|
children: "We reply by email. You can follow the ticket on your support page."
|
|
456
|
-
}) :
|
|
457
|
-
id:
|
|
458
|
-
config:
|
|
488
|
+
}) : m ? /* @__PURE__ */ j(se, {
|
|
489
|
+
id: g,
|
|
490
|
+
config: m,
|
|
459
491
|
defaultCategory: n,
|
|
460
|
-
extraFiles:
|
|
461
|
-
onSubmit:
|
|
462
|
-
onBusyChange:
|
|
463
|
-
children:
|
|
464
|
-
file:
|
|
465
|
-
onRemove: () =>
|
|
466
|
-
}) :
|
|
492
|
+
extraFiles: S ? [S] : [],
|
|
493
|
+
onSubmit: F,
|
|
494
|
+
onBusyChange: D,
|
|
495
|
+
children: S ? /* @__PURE__ */ j(fe, {
|
|
496
|
+
file: S,
|
|
497
|
+
onRemove: () => C(null)
|
|
498
|
+
}) : w ? /* @__PURE__ */ j(c, {
|
|
467
499
|
variant: "info",
|
|
468
500
|
children: "We could not take a screenshot of this page. You can attach an image instead."
|
|
469
501
|
}) : null
|
|
470
502
|
}) : /* @__PURE__ */ j(c, {
|
|
471
|
-
variant:
|
|
472
|
-
children:
|
|
503
|
+
variant: h ? "danger" : "info",
|
|
504
|
+
children: h ? h.message || "We could not load the support settings." : "Loading the form."
|
|
473
505
|
})
|
|
474
506
|
})] });
|
|
475
507
|
}
|
|
476
508
|
//#endregion
|
|
477
509
|
//#region src/components/NewTicketModal.jsx
|
|
478
|
-
function
|
|
479
|
-
let c =
|
|
510
|
+
function me({ open: e, onClose: t, onCreated: n, config: r, defaultCategory: a, appVersion: o }) {
|
|
511
|
+
let c = H(), l = `support-new-${i().replace(/[^a-zA-Z0-9-]/g, "")}`, [u, f] = s(!1);
|
|
480
512
|
async function p(e) {
|
|
481
513
|
n(await c.createTicket({
|
|
482
514
|
...e,
|
|
@@ -508,7 +540,7 @@ function ge({ open: e, onClose: t, onCreated: n, config: r, defaultCategory: a,
|
|
|
508
540
|
children: "Send ticket"
|
|
509
541
|
})]
|
|
510
542
|
}),
|
|
511
|
-
children: e && /* @__PURE__ */ j(
|
|
543
|
+
children: e && /* @__PURE__ */ j(se, {
|
|
512
544
|
id: l,
|
|
513
545
|
config: r,
|
|
514
546
|
defaultCategory: a,
|
|
@@ -519,23 +551,23 @@ function ge({ open: e, onClose: t, onCreated: n, config: r, defaultCategory: a,
|
|
|
519
551
|
}
|
|
520
552
|
//#endregion
|
|
521
553
|
//#region src/lib/status.js
|
|
522
|
-
var
|
|
554
|
+
var he = {
|
|
523
555
|
Open: "warning",
|
|
524
556
|
InProgress: "info",
|
|
525
557
|
Resolved: "success",
|
|
526
558
|
Closed: "neutral"
|
|
527
559
|
};
|
|
528
|
-
function
|
|
529
|
-
return
|
|
560
|
+
function ge(e) {
|
|
561
|
+
return he[e] ?? "neutral";
|
|
530
562
|
}
|
|
531
563
|
//#endregion
|
|
532
564
|
//#region src/components/AttachmentThumb.jsx
|
|
533
|
-
var
|
|
565
|
+
var _e = {
|
|
534
566
|
width: 140,
|
|
535
567
|
maxWidth: "100%"
|
|
536
|
-
},
|
|
537
|
-
function
|
|
538
|
-
let t =
|
|
568
|
+
}, ve = { height: 84 };
|
|
569
|
+
function ye({ attachment: e }) {
|
|
570
|
+
let t = H(), n = (e.contentType || "").startsWith("image/"), [i, a] = s(null), [o, c] = s(!1);
|
|
539
571
|
r(() => {
|
|
540
572
|
let n = !0, r = null;
|
|
541
573
|
return a(null), c(!1), t.fetchAttachment(e.id).then((e) => {
|
|
@@ -549,12 +581,12 @@ function be({ attachment: e }) {
|
|
|
549
581
|
children: [
|
|
550
582
|
e.fileName,
|
|
551
583
|
", ",
|
|
552
|
-
|
|
584
|
+
q(e.sizeBytes)
|
|
553
585
|
]
|
|
554
586
|
});
|
|
555
587
|
return o ? /* @__PURE__ */ M("span", {
|
|
556
588
|
className: "d-inline-flex flex-column border rounded overflow-hidden",
|
|
557
|
-
style:
|
|
589
|
+
style: _e,
|
|
558
590
|
children: [/* @__PURE__ */ M("span", {
|
|
559
591
|
className: "d-flex align-items-center justify-content-center gap-1 p-2 wui-text-xs text-body-secondary",
|
|
560
592
|
children: [/* @__PURE__ */ j(g, {
|
|
@@ -564,7 +596,7 @@ function be({ attachment: e }) {
|
|
|
564
596
|
}), u]
|
|
565
597
|
}) : /* @__PURE__ */ M("a", {
|
|
566
598
|
className: "d-inline-flex flex-column border rounded overflow-hidden text-decoration-none",
|
|
567
|
-
style:
|
|
599
|
+
style: _e,
|
|
568
600
|
href: i ?? void 0,
|
|
569
601
|
target: "_blank",
|
|
570
602
|
rel: "noopener noreferrer",
|
|
@@ -572,7 +604,7 @@ function be({ attachment: e }) {
|
|
|
572
604
|
"aria-busy": !i || void 0,
|
|
573
605
|
children: [/* @__PURE__ */ j("span", {
|
|
574
606
|
className: "d-flex align-items-center justify-content-center bg-body-tertiary",
|
|
575
|
-
style:
|
|
607
|
+
style: ve,
|
|
576
608
|
children: i ? n ? /* @__PURE__ */ j("img", {
|
|
577
609
|
src: i,
|
|
578
610
|
alt: e.fileName,
|
|
@@ -589,11 +621,11 @@ function be({ attachment: e }) {
|
|
|
589
621
|
}
|
|
590
622
|
//#endregion
|
|
591
623
|
//#region src/components/MessageThread.jsx
|
|
592
|
-
var
|
|
624
|
+
var be = {
|
|
593
625
|
whiteSpace: "pre-wrap",
|
|
594
626
|
overflowWrap: "anywhere"
|
|
595
627
|
};
|
|
596
|
-
function
|
|
628
|
+
function xe({ messages: e }) {
|
|
597
629
|
return /* @__PURE__ */ j("ol", {
|
|
598
630
|
className: "list-unstyled d-flex flex-column gap-3 mb-0",
|
|
599
631
|
"aria-label": "Conversation",
|
|
@@ -601,7 +633,7 @@ function Se({ messages: e }) {
|
|
|
601
633
|
let t = /* @__PURE__ */ j("time", {
|
|
602
634
|
dateTime: e.createdDateTimeUtc,
|
|
603
635
|
className: "wui-text-xs text-body-secondary text-nowrap",
|
|
604
|
-
children:
|
|
636
|
+
children: K(e.createdDateTimeUtc)
|
|
605
637
|
});
|
|
606
638
|
if (e.authorKind === "System") return /* @__PURE__ */ M("li", {
|
|
607
639
|
className: "text-center wui-text-xs text-body-secondary",
|
|
@@ -637,12 +669,12 @@ function Se({ messages: e }) {
|
|
|
637
669
|
}),
|
|
638
670
|
/* @__PURE__ */ j("div", {
|
|
639
671
|
className: "wui-text-sm",
|
|
640
|
-
style:
|
|
672
|
+
style: be,
|
|
641
673
|
children: e.body
|
|
642
674
|
}),
|
|
643
675
|
e.attachments?.length > 0 && /* @__PURE__ */ j("div", {
|
|
644
676
|
className: "d-flex flex-wrap gap-2 mt-2",
|
|
645
|
-
children: e.attachments.map((e) => /* @__PURE__ */ j(
|
|
677
|
+
children: e.attachments.map((e) => /* @__PURE__ */ j(ye, { attachment: e }, e.id))
|
|
646
678
|
})
|
|
647
679
|
]
|
|
648
680
|
})]
|
|
@@ -652,7 +684,7 @@ function Se({ messages: e }) {
|
|
|
652
684
|
}
|
|
653
685
|
//#endregion
|
|
654
686
|
//#region src/components/RatingPanel.jsx
|
|
655
|
-
function
|
|
687
|
+
function Se({ satisfaction: e, comment: t, canRate: n = !1, commentMaxLength: r, onRate: a }) {
|
|
656
688
|
let o = i(), [l, u] = s(""), [p, m] = s(null), [h, g] = s("");
|
|
657
689
|
if (e) return /* @__PURE__ */ M(c, {
|
|
658
690
|
variant: e === "Satisfied" ? "success" : "info",
|
|
@@ -720,11 +752,11 @@ function Ce({ satisfaction: e, comment: t, canRate: n = !1, commentMaxLength: r,
|
|
|
720
752
|
}
|
|
721
753
|
//#endregion
|
|
722
754
|
//#region src/components/ReplyBox.jsx
|
|
723
|
-
function
|
|
755
|
+
function Ce({ onSend: e, config: t, reopens: n = !1 }) {
|
|
724
756
|
let r = i(), [a, o] = s(""), [l, u] = s([]), [f, p] = s(""), [m, h] = s(""), [g, _] = s(!1), v = (e) => {
|
|
725
|
-
u(e), p(
|
|
757
|
+
u(e), p(J(e, t) ?? "");
|
|
726
758
|
}, y = (e) => {
|
|
727
|
-
let t =
|
|
759
|
+
let t = Y(e);
|
|
728
760
|
t.length > 0 && v([...l, ...t]);
|
|
729
761
|
};
|
|
730
762
|
async function b(n) {
|
|
@@ -734,7 +766,7 @@ function we({ onSend: e, config: t, reopens: n = !1 }) {
|
|
|
734
766
|
h("Write a message before you send it.");
|
|
735
767
|
return;
|
|
736
768
|
}
|
|
737
|
-
let i =
|
|
769
|
+
let i = J(l, t);
|
|
738
770
|
if (i) {
|
|
739
771
|
p(i);
|
|
740
772
|
return;
|
|
@@ -773,7 +805,7 @@ function we({ onSend: e, config: t, reopens: n = !1 }) {
|
|
|
773
805
|
hint: n ? "Sending a reply reopens this ticket. You can paste an image into the message." : "You can paste an image into the message.",
|
|
774
806
|
required: !0
|
|
775
807
|
}),
|
|
776
|
-
/* @__PURE__ */ j(
|
|
808
|
+
/* @__PURE__ */ j(X, {
|
|
777
809
|
id: `${r}-files`,
|
|
778
810
|
files: l,
|
|
779
811
|
onChange: v,
|
|
@@ -794,7 +826,7 @@ function we({ onSend: e, config: t, reopens: n = !1 }) {
|
|
|
794
826
|
}
|
|
795
827
|
//#endregion
|
|
796
828
|
//#region src/components/TicketDetail.jsx
|
|
797
|
-
function
|
|
829
|
+
function Z({ label: e, children: t }) {
|
|
798
830
|
return /* @__PURE__ */ M("div", {
|
|
799
831
|
className: "d-flex flex-column",
|
|
800
832
|
children: [/* @__PURE__ */ j("span", {
|
|
@@ -806,8 +838,8 @@ function X({ label: e, children: t }) {
|
|
|
806
838
|
})]
|
|
807
839
|
});
|
|
808
840
|
}
|
|
809
|
-
function
|
|
810
|
-
let { config: r } =
|
|
841
|
+
function we({ ticketId: e, onBack: t, onChanged: n }) {
|
|
842
|
+
let { config: r } = W(), { ticket: i, loading: a, error: o, reload: l, reply: u, reopen: f, rate: p } = G(e), [m, h] = s(!1), [g, _] = s(""), v = async (e) => {
|
|
811
843
|
let t = await e();
|
|
812
844
|
return n?.(), t;
|
|
813
845
|
};
|
|
@@ -863,32 +895,32 @@ function Te({ ticketId: e, onBack: t, onChanged: n }) {
|
|
|
863
895
|
children: i.subject
|
|
864
896
|
}), i.statusLabel && /* @__PURE__ */ j(T, {
|
|
865
897
|
label: i.statusLabel,
|
|
866
|
-
tone:
|
|
898
|
+
tone: ge(i.status)
|
|
867
899
|
})]
|
|
868
900
|
}),
|
|
869
901
|
/* @__PURE__ */ M("div", {
|
|
870
902
|
className: "row row-cols-2 row-cols-md-4 g-3",
|
|
871
903
|
children: [
|
|
872
|
-
/* @__PURE__ */ j(
|
|
904
|
+
/* @__PURE__ */ j(Z, {
|
|
873
905
|
label: "Ticket",
|
|
874
906
|
children: i.number
|
|
875
907
|
}),
|
|
876
|
-
/* @__PURE__ */ j(
|
|
908
|
+
/* @__PURE__ */ j(Z, {
|
|
877
909
|
label: "Category",
|
|
878
910
|
children: i.category || "-"
|
|
879
911
|
}),
|
|
880
|
-
/* @__PURE__ */ j(
|
|
912
|
+
/* @__PURE__ */ j(Z, {
|
|
881
913
|
label: "Opened",
|
|
882
914
|
children: /* @__PURE__ */ j("time", {
|
|
883
915
|
dateTime: i.createdDateTimeUtc,
|
|
884
|
-
children:
|
|
916
|
+
children: K(i.createdDateTimeUtc)
|
|
885
917
|
})
|
|
886
918
|
}),
|
|
887
|
-
/* @__PURE__ */ j(
|
|
919
|
+
/* @__PURE__ */ j(Z, {
|
|
888
920
|
label: "Last update",
|
|
889
921
|
children: /* @__PURE__ */ j("time", {
|
|
890
922
|
dateTime: i.updatedDateTimeUtc,
|
|
891
|
-
children:
|
|
923
|
+
children: K(i.updatedDateTimeUtc)
|
|
892
924
|
})
|
|
893
925
|
})
|
|
894
926
|
]
|
|
@@ -916,15 +948,15 @@ function Te({ ticketId: e, onBack: t, onChanged: n }) {
|
|
|
916
948
|
className: "align-self-start",
|
|
917
949
|
children: "Reopen ticket"
|
|
918
950
|
}),
|
|
919
|
-
/* @__PURE__ */ j(
|
|
951
|
+
/* @__PURE__ */ j(Se, {
|
|
920
952
|
satisfaction: i.satisfaction,
|
|
921
953
|
comment: i.satisfactionComment,
|
|
922
954
|
canRate: i.canRate,
|
|
923
955
|
commentMaxLength: r?.satisfactionCommentMaxLength,
|
|
924
956
|
onRate: (e) => v(() => p(e))
|
|
925
957
|
}),
|
|
926
|
-
/* @__PURE__ */ j(
|
|
927
|
-
/* @__PURE__ */ j(
|
|
958
|
+
/* @__PURE__ */ j(xe, { messages: i.messages ?? [] }),
|
|
959
|
+
/* @__PURE__ */ j(Ce, {
|
|
928
960
|
config: r,
|
|
929
961
|
reopens: x,
|
|
930
962
|
onSend: (e) => v(() => u(e))
|
|
@@ -934,60 +966,50 @@ function Te({ ticketId: e, onBack: t, onChanged: n }) {
|
|
|
934
966
|
}
|
|
935
967
|
//#endregion
|
|
936
968
|
//#region src/lib/url.js
|
|
937
|
-
var
|
|
938
|
-
function
|
|
939
|
-
return typeof window > "u" ? null : new URLSearchParams(window.location.search).get(
|
|
969
|
+
var Q = "ticket";
|
|
970
|
+
function Te() {
|
|
971
|
+
return typeof window > "u" ? null : new URLSearchParams(window.location.search).get(Q);
|
|
940
972
|
}
|
|
941
|
-
function
|
|
942
|
-
if (typeof window > "u") return e ? `?${
|
|
973
|
+
function Ee(e) {
|
|
974
|
+
if (typeof window > "u") return e ? `?${Q}=${encodeURIComponent(e)}` : "?";
|
|
943
975
|
let t = new URL(window.location.href);
|
|
944
|
-
return e ? t.searchParams.set(
|
|
976
|
+
return e ? t.searchParams.set(Q, e) : t.searchParams.delete(Q), `${t.pathname}${t.search}${t.hash}`;
|
|
945
977
|
}
|
|
946
|
-
function
|
|
978
|
+
function De(e) {
|
|
947
979
|
if (typeof window > "u") return;
|
|
948
|
-
let t =
|
|
980
|
+
let t = Ee(e);
|
|
949
981
|
t !== `${window.location.pathname}${window.location.search}${window.location.hash}` && window.history.replaceState(window.history.state, "", t);
|
|
950
982
|
}
|
|
951
983
|
//#endregion
|
|
952
984
|
//#region src/components/TicketList.jsx
|
|
953
|
-
var
|
|
985
|
+
var Oe = [
|
|
954
986
|
{
|
|
955
|
-
key: "
|
|
956
|
-
label: "
|
|
987
|
+
key: "all",
|
|
988
|
+
label: "All",
|
|
989
|
+
countKey: "total"
|
|
957
990
|
},
|
|
958
991
|
{
|
|
959
992
|
key: "Open",
|
|
960
|
-
label: "Open"
|
|
993
|
+
label: "Open",
|
|
994
|
+
countKey: "open"
|
|
961
995
|
},
|
|
962
996
|
{
|
|
963
997
|
key: "InProgress",
|
|
964
|
-
label: "In progress"
|
|
998
|
+
label: "In progress",
|
|
999
|
+
countKey: "inProgress"
|
|
965
1000
|
},
|
|
966
1001
|
{
|
|
967
1002
|
key: "Resolved",
|
|
968
|
-
label: "Resolved"
|
|
1003
|
+
label: "Resolved",
|
|
1004
|
+
countKey: "resolved"
|
|
969
1005
|
},
|
|
970
1006
|
{
|
|
971
1007
|
key: "Closed",
|
|
972
|
-
label: "Closed"
|
|
973
|
-
|
|
974
|
-
{
|
|
975
|
-
key: "all",
|
|
976
|
-
label: "All"
|
|
1008
|
+
label: "Closed",
|
|
1009
|
+
countKey: "closed"
|
|
977
1010
|
}
|
|
978
1011
|
];
|
|
979
|
-
function ke(e, t) {
|
|
980
|
-
return t === "all" ? !0 : t === "active" ? e.status === "Open" || e.status === "InProgress" : e.status === t;
|
|
981
|
-
}
|
|
982
|
-
function Ae(e, t) {
|
|
983
|
-
return !t || [
|
|
984
|
-
e.number,
|
|
985
|
-
e.subject,
|
|
986
|
-
e.category
|
|
987
|
-
].some((e) => (e || "").toLowerCase().includes(t));
|
|
988
|
-
}
|
|
989
|
-
function je({ tickets: e, loading: t, error: n, onRetry: r, onOpen: i }) {
|
|
990
|
-
let [o, l] = s("active"), [u, f] = s(""), p = u.trim().toLowerCase(), h = a(() => e.filter((e) => Ae(e, p)), [e, p]), g = a(() => Object.fromEntries(Q.map((e) => [e.key, h.filter((t) => ke(t, e.key)).length])), [h]), _ = k(a(() => h.filter((e) => ke(e, o)), [h, o]));
|
|
1012
|
+
function ke({ tickets: e, loading: t, error: n, onRetry: r, onOpen: i, status: a, onStatusChange: o, counts: s, search: l, onSearchChange: u, pagination: f }) {
|
|
991
1013
|
return /* @__PURE__ */ M("div", {
|
|
992
1014
|
className: "d-flex flex-column gap-3",
|
|
993
1015
|
children: [
|
|
@@ -1007,17 +1029,18 @@ function je({ tickets: e, loading: t, error: n, onRetry: r, onOpen: i }) {
|
|
|
1007
1029
|
/* @__PURE__ */ M("div", {
|
|
1008
1030
|
className: "d-flex flex-column flex-md-row gap-2 align-items-md-center justify-content-between",
|
|
1009
1031
|
children: [/* @__PURE__ */ j(D, {
|
|
1010
|
-
tabs:
|
|
1011
|
-
|
|
1012
|
-
|
|
1032
|
+
tabs: Oe.map(({ key: e, label: t, countKey: n }) => ({
|
|
1033
|
+
key: e,
|
|
1034
|
+
label: t,
|
|
1035
|
+
count: s?.[n]
|
|
1013
1036
|
})),
|
|
1014
|
-
value:
|
|
1015
|
-
onChange:
|
|
1037
|
+
value: a,
|
|
1038
|
+
onChange: o,
|
|
1016
1039
|
ariaLabel: "Filter tickets by status",
|
|
1017
1040
|
className: "flex-grow-1"
|
|
1018
1041
|
}), /* @__PURE__ */ j(x, {
|
|
1019
|
-
value:
|
|
1020
|
-
onChange:
|
|
1042
|
+
value: l,
|
|
1043
|
+
onChange: u,
|
|
1021
1044
|
placeholder: "Search tickets",
|
|
1022
1045
|
ariaLabel: "Search tickets"
|
|
1023
1046
|
})]
|
|
@@ -1031,7 +1054,7 @@ function je({ tickets: e, loading: t, error: n, onRetry: r, onOpen: i }) {
|
|
|
1031
1054
|
className: "d-flex flex-column",
|
|
1032
1055
|
style: { minWidth: 0 },
|
|
1033
1056
|
children: [/* @__PURE__ */ j(y, {
|
|
1034
|
-
href:
|
|
1057
|
+
href: Ee(e.id),
|
|
1035
1058
|
animation: "none",
|
|
1036
1059
|
className: "fw-semibold text-break",
|
|
1037
1060
|
onClick: (t) => {
|
|
@@ -1054,7 +1077,7 @@ function je({ tickets: e, loading: t, error: n, onRetry: r, onOpen: i }) {
|
|
|
1054
1077
|
label: "Status",
|
|
1055
1078
|
render: (e) => e.statusLabel ? /* @__PURE__ */ j(T, {
|
|
1056
1079
|
label: e.statusLabel,
|
|
1057
|
-
tone:
|
|
1080
|
+
tone: ge(e.status)
|
|
1058
1081
|
}) : "-"
|
|
1059
1082
|
},
|
|
1060
1083
|
{
|
|
@@ -1069,16 +1092,16 @@ function je({ tickets: e, loading: t, error: n, onRetry: r, onOpen: i }) {
|
|
|
1069
1092
|
cellClassName: "wui-text-sm text-body-secondary text-nowrap",
|
|
1070
1093
|
render: (e) => /* @__PURE__ */ j("time", {
|
|
1071
1094
|
dateTime: e.updatedDateTimeUtc,
|
|
1072
|
-
children:
|
|
1095
|
+
children: K(e.updatedDateTimeUtc)
|
|
1073
1096
|
})
|
|
1074
1097
|
}
|
|
1075
1098
|
],
|
|
1076
|
-
rows:
|
|
1099
|
+
rows: e,
|
|
1077
1100
|
loading: t,
|
|
1078
1101
|
skeletonRows: 4,
|
|
1079
1102
|
ariaLabel: "Support tickets",
|
|
1080
|
-
sourceCount:
|
|
1081
|
-
pagination:
|
|
1103
|
+
sourceCount: s?.total ?? 0,
|
|
1104
|
+
pagination: f,
|
|
1082
1105
|
empty: /* @__PURE__ */ j(E, {
|
|
1083
1106
|
icon: "message-square",
|
|
1084
1107
|
title: "No tickets yet",
|
|
@@ -1094,16 +1117,61 @@ function je({ tickets: e, loading: t, error: n, onRetry: r, onOpen: i }) {
|
|
|
1094
1117
|
});
|
|
1095
1118
|
}
|
|
1096
1119
|
//#endregion
|
|
1120
|
+
//#region src/lib/useDebouncedValue.js
|
|
1121
|
+
function Ae(e, t) {
|
|
1122
|
+
let [n, i] = s(e);
|
|
1123
|
+
return r(() => {
|
|
1124
|
+
let n = setTimeout(() => i(e), t);
|
|
1125
|
+
return () => clearTimeout(n);
|
|
1126
|
+
}, [e, t]), n;
|
|
1127
|
+
}
|
|
1128
|
+
//#endregion
|
|
1097
1129
|
//#region src/SupportCenter.jsx
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1130
|
+
var je = 300, Me = 10;
|
|
1131
|
+
function Ne({ initialTicketId: e, defaultCategory: t, appVersion: n }) {
|
|
1132
|
+
let { config: i, error: a, reload: o } = W(), [l, u] = s("all"), [f, p] = s(""), m = Ae(f.trim(), je), [h, g] = s(Me), _ = `${l}|${m}|${h}`, [v, y] = s({
|
|
1133
|
+
key: _,
|
|
1134
|
+
page: 1
|
|
1135
|
+
}), b = v.key === _ ? v.page : 1, x = (e) => y({
|
|
1136
|
+
key: _,
|
|
1137
|
+
page: e
|
|
1138
|
+
}), S = te({
|
|
1139
|
+
status: l === "all" ? void 0 : l,
|
|
1140
|
+
search: m || void 0,
|
|
1141
|
+
page: b,
|
|
1142
|
+
pageSize: h
|
|
1143
|
+
}), { counts: C, reload: w } = ne(), [T, E] = s(() => e || Te()), [D, O] = s(!1), k = S.totalPages;
|
|
1144
|
+
r(() => {
|
|
1145
|
+
k > 0 && b > k && y({
|
|
1146
|
+
key: _,
|
|
1147
|
+
page: k
|
|
1148
|
+
});
|
|
1149
|
+
}, [
|
|
1150
|
+
k,
|
|
1151
|
+
b,
|
|
1152
|
+
_
|
|
1153
|
+
]);
|
|
1154
|
+
let A = () => {
|
|
1155
|
+
S.reload(), w();
|
|
1156
|
+
}, N = (e) => {
|
|
1157
|
+
E(e), De(e);
|
|
1101
1158
|
};
|
|
1102
|
-
|
|
1103
|
-
ticketId:
|
|
1104
|
-
onBack: () =>
|
|
1105
|
-
onChanged:
|
|
1106
|
-
},
|
|
1159
|
+
if (T) return /* @__PURE__ */ j(we, {
|
|
1160
|
+
ticketId: T,
|
|
1161
|
+
onBack: () => N(null),
|
|
1162
|
+
onChanged: A
|
|
1163
|
+
}, T);
|
|
1164
|
+
let P = S.totalCount === 0 ? 0 : (b - 1) * h + 1, F = {
|
|
1165
|
+
page: b,
|
|
1166
|
+
totalPages: Math.max(1, S.totalPages),
|
|
1167
|
+
pageSize: h,
|
|
1168
|
+
total: S.totalCount,
|
|
1169
|
+
firstRow: P,
|
|
1170
|
+
lastRow: Math.min(S.totalCount, P + S.tickets.length - 1),
|
|
1171
|
+
setPage: x,
|
|
1172
|
+
setPageSize: g
|
|
1173
|
+
};
|
|
1174
|
+
return /* @__PURE__ */ M("section", {
|
|
1107
1175
|
className: "d-flex flex-column gap-3",
|
|
1108
1176
|
"aria-labelledby": "support-center-title",
|
|
1109
1177
|
children: [
|
|
@@ -1115,39 +1183,45 @@ function Me({ initialTicketId: e, defaultCategory: t, appVersion: n }) {
|
|
|
1115
1183
|
children: "Your tickets"
|
|
1116
1184
|
}), /* @__PURE__ */ j(d, {
|
|
1117
1185
|
leftIcon: "plus",
|
|
1118
|
-
onClick: () =>
|
|
1119
|
-
disabled: !
|
|
1186
|
+
onClick: () => O(!0),
|
|
1187
|
+
disabled: !i,
|
|
1120
1188
|
children: "New ticket"
|
|
1121
1189
|
})]
|
|
1122
1190
|
}),
|
|
1123
|
-
|
|
1191
|
+
a && /* @__PURE__ */ M(c, {
|
|
1124
1192
|
variant: "warning",
|
|
1125
1193
|
children: [
|
|
1126
|
-
|
|
1194
|
+
a.message || "We could not load the support settings.",
|
|
1127
1195
|
" ",
|
|
1128
1196
|
/* @__PURE__ */ j(d, {
|
|
1129
1197
|
variant: "ghost",
|
|
1130
1198
|
size: "sm",
|
|
1131
|
-
onClick:
|
|
1199
|
+
onClick: o,
|
|
1132
1200
|
children: "Try again"
|
|
1133
1201
|
})
|
|
1134
1202
|
]
|
|
1135
1203
|
}),
|
|
1136
|
-
/* @__PURE__ */ j(
|
|
1137
|
-
tickets:
|
|
1138
|
-
loading:
|
|
1139
|
-
error:
|
|
1140
|
-
onRetry:
|
|
1141
|
-
onOpen:
|
|
1204
|
+
/* @__PURE__ */ j(ke, {
|
|
1205
|
+
tickets: S.tickets,
|
|
1206
|
+
loading: S.loading,
|
|
1207
|
+
error: S.error,
|
|
1208
|
+
onRetry: A,
|
|
1209
|
+
onOpen: N,
|
|
1210
|
+
status: l,
|
|
1211
|
+
onStatusChange: u,
|
|
1212
|
+
counts: C,
|
|
1213
|
+
search: f,
|
|
1214
|
+
onSearchChange: p,
|
|
1215
|
+
pagination: F
|
|
1142
1216
|
}),
|
|
1143
|
-
|
|
1144
|
-
open:
|
|
1145
|
-
onClose: () =>
|
|
1146
|
-
config:
|
|
1217
|
+
i && /* @__PURE__ */ j(me, {
|
|
1218
|
+
open: D,
|
|
1219
|
+
onClose: () => O(!1),
|
|
1220
|
+
config: i,
|
|
1147
1221
|
defaultCategory: t,
|
|
1148
1222
|
appVersion: n,
|
|
1149
1223
|
onCreated: (e) => {
|
|
1150
|
-
|
|
1224
|
+
O(!1), A(), N(e.id);
|
|
1151
1225
|
}
|
|
1152
1226
|
})
|
|
1153
1227
|
]
|
|
@@ -1156,8 +1230,8 @@ function Me({ initialTicketId: e, defaultCategory: t, appVersion: n }) {
|
|
|
1156
1230
|
//#endregion
|
|
1157
1231
|
//#region src/SupportContact.jsx
|
|
1158
1232
|
var $ = (e) => typeof e == "string" && e.trim() !== "";
|
|
1159
|
-
function
|
|
1160
|
-
let { config: t } =
|
|
1233
|
+
function Pe({ className: e }) {
|
|
1234
|
+
let { config: t } = W();
|
|
1161
1235
|
if (!t) return null;
|
|
1162
1236
|
let n = [];
|
|
1163
1237
|
return $(t.supportEmail) && n.push({
|
|
@@ -1199,7 +1273,7 @@ function Ne({ className: e }) {
|
|
|
1199
1273
|
}
|
|
1200
1274
|
//#endregion
|
|
1201
1275
|
//#region src/SupportProvider.jsx
|
|
1202
|
-
function
|
|
1276
|
+
function Fe({ client: e, children: n }) {
|
|
1203
1277
|
let r = o(null), i = t((t = !1) => {
|
|
1204
1278
|
let n = r.current;
|
|
1205
1279
|
if (t || !n || n.client !== e) {
|
|
@@ -1216,10 +1290,10 @@ function Pe({ client: e, children: n }) {
|
|
|
1216
1290
|
client: e,
|
|
1217
1291
|
loadConfig: i
|
|
1218
1292
|
}), [e, i]);
|
|
1219
|
-
return /* @__PURE__ */ j(
|
|
1293
|
+
return /* @__PURE__ */ j(B.Provider, {
|
|
1220
1294
|
value: s,
|
|
1221
1295
|
children: n
|
|
1222
1296
|
});
|
|
1223
1297
|
}
|
|
1224
1298
|
//#endregion
|
|
1225
|
-
export {
|
|
1299
|
+
export { pe as ReportIssue, P as SupportApiError, Ne as SupportCenter, Pe as SupportContact, Fe as SupportProvider, ee as createSupportClient, H as useSupportClient, W as useSupportConfig, G as useSupportTicket, ne as useSupportTicketCounts, te as useSupportTickets };
|
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
* @param {(id: string) => string} [props.getTicketHref] Where the new ticket can be opened; shows a View ticket link.
|
|
12
12
|
* @param {(ticket: any) => void} [props.onOpenTicket] Called by the View ticket button when there is no href.
|
|
13
13
|
* @param {(ticket: any) => void} [props.onCreated] Called once the ticket is raised.
|
|
14
|
+
* @param {string} [props.className] Added to the button's wrapper, both floating and inline, to place it.
|
|
15
|
+
* @param {import('react').CSSProperties} [props.style] Merged over the wrapper's own style. With
|
|
16
|
+
* `floating`, an inset such as `{ bottom: "5rem" }` moves the button clear of other fixed controls.
|
|
14
17
|
*/
|
|
15
|
-
export default function ReportIssue({ label, floating, defaultCategory, appVersion, getTicketHref, onOpenTicket, onCreated, }: {
|
|
18
|
+
export default function ReportIssue({ label, floating, defaultCategory, appVersion, getTicketHref, onOpenTicket, onCreated, className, style, }: {
|
|
16
19
|
label?: string;
|
|
17
20
|
floating?: boolean;
|
|
18
21
|
defaultCategory?: string;
|
|
@@ -20,4 +23,6 @@ export default function ReportIssue({ label, floating, defaultCategory, appVersi
|
|
|
20
23
|
getTicketHref?: (id: string) => string;
|
|
21
24
|
onOpenTicket?: (ticket: any) => void;
|
|
22
25
|
onCreated?: (ticket: any) => void;
|
|
26
|
+
className?: string;
|
|
27
|
+
style?: import('react').CSSProperties;
|
|
23
28
|
}): import("react").JSX.Element;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* The customer's support page: their tickets, one ticket's conversation, and a New ticket
|
|
3
3
|
* dialog. Opens the ticket named by `?ticket=<id>` in the address (support emails link there),
|
|
4
4
|
* or `initialTicketId` when given, and keeps that parameter in step as tickets open and close.
|
|
5
|
+
* The list is filtered, searched and paged on the server; the status tabs show the server's counts.
|
|
5
6
|
*
|
|
6
7
|
* @param {object} props
|
|
7
8
|
* @param {string} [props.initialTicketId] Open this ticket first. Wins over the address.
|
package/dist/types/client.d.ts
CHANGED
|
@@ -44,9 +44,43 @@ export type RatingInput = {
|
|
|
44
44
|
satisfaction: "Satisfied" | "Unsatisfied";
|
|
45
45
|
comment?: string;
|
|
46
46
|
};
|
|
47
|
+
export type TicketStatus = "Open" | "InProgress" | "Resolved" | "Closed";
|
|
48
|
+
export type TicketListQuery = {
|
|
49
|
+
/**
|
|
50
|
+
* Only tickets in this status; every status when left out.
|
|
51
|
+
*/
|
|
52
|
+
status?: TicketStatus;
|
|
53
|
+
/**
|
|
54
|
+
* Matches the subject or the ticket number.
|
|
55
|
+
*/
|
|
56
|
+
search?: string;
|
|
57
|
+
/**
|
|
58
|
+
* 1-based. The API defaults to 1.
|
|
59
|
+
*/
|
|
60
|
+
page?: number;
|
|
61
|
+
/**
|
|
62
|
+
* The API defaults to 20 and allows up to 100.
|
|
63
|
+
*/
|
|
64
|
+
pageSize?: number;
|
|
65
|
+
};
|
|
66
|
+
export type TicketPage = {
|
|
67
|
+
items: any[];
|
|
68
|
+
page: number;
|
|
69
|
+
pageSize: number;
|
|
70
|
+
totalCount: number;
|
|
71
|
+
totalPages: number;
|
|
72
|
+
};
|
|
73
|
+
export type TicketCounts = {
|
|
74
|
+
open: number;
|
|
75
|
+
inProgress: number;
|
|
76
|
+
resolved: number;
|
|
77
|
+
closed: number;
|
|
78
|
+
total: number;
|
|
79
|
+
};
|
|
47
80
|
export type SupportClient = {
|
|
48
81
|
getConfig: () => Promise<any>;
|
|
49
|
-
listTickets: () => Promise<
|
|
82
|
+
listTickets: (query?: TicketListQuery) => Promise<TicketPage>;
|
|
83
|
+
getTicketCounts: () => Promise<TicketCounts>;
|
|
50
84
|
getTicket: (id: string) => Promise<any>;
|
|
51
85
|
createTicket: (input: NewTicketInput) => Promise<any>;
|
|
52
86
|
reply: (id: string, input: ReplyInput) => Promise<any>;
|
|
@@ -81,10 +115,37 @@ export type SupportClient = {
|
|
|
81
115
|
* @property {"Satisfied"|"Unsatisfied"} satisfaction
|
|
82
116
|
* @property {string} [comment]
|
|
83
117
|
*/
|
|
118
|
+
/**
|
|
119
|
+
* @typedef {"Open"|"InProgress"|"Resolved"|"Closed"} TicketStatus
|
|
120
|
+
*/
|
|
121
|
+
/**
|
|
122
|
+
* @typedef {object} TicketListQuery
|
|
123
|
+
* @property {TicketStatus} [status] Only tickets in this status; every status when left out.
|
|
124
|
+
* @property {string} [search] Matches the subject or the ticket number.
|
|
125
|
+
* @property {number} [page] 1-based. The API defaults to 1.
|
|
126
|
+
* @property {number} [pageSize] The API defaults to 20 and allows up to 100.
|
|
127
|
+
*/
|
|
128
|
+
/**
|
|
129
|
+
* @typedef {object} TicketPage One page of the customer's tickets, most recently updated first.
|
|
130
|
+
* @property {any[]} items
|
|
131
|
+
* @property {number} page
|
|
132
|
+
* @property {number} pageSize
|
|
133
|
+
* @property {number} totalCount
|
|
134
|
+
* @property {number} totalPages
|
|
135
|
+
*/
|
|
136
|
+
/**
|
|
137
|
+
* @typedef {object} TicketCounts How many of the customer's tickets are in each status.
|
|
138
|
+
* @property {number} open
|
|
139
|
+
* @property {number} inProgress
|
|
140
|
+
* @property {number} resolved
|
|
141
|
+
* @property {number} closed
|
|
142
|
+
* @property {number} total
|
|
143
|
+
*/
|
|
84
144
|
/**
|
|
85
145
|
* @typedef {object} SupportClient
|
|
86
146
|
* @property {() => Promise<any>} getConfig
|
|
87
|
-
* @property {() => Promise<
|
|
147
|
+
* @property {(query?: TicketListQuery) => Promise<TicketPage>} listTickets
|
|
148
|
+
* @property {() => Promise<TicketCounts>} getTicketCounts
|
|
88
149
|
* @property {(id: string) => Promise<any>} getTicket
|
|
89
150
|
* @property {(input: NewTicketInput) => Promise<any>} createTicket
|
|
90
151
|
* @property {(id: string, input: ReplyInput) => Promise<any>} reply
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Picked, dropped or pasted files for a message, limited to what the support config allows.
|
|
3
|
+
* The caller checks every file against the config and passes the one message through `error`,
|
|
4
|
+
* so the field's own check stays off and a rejected pick never shows two messages.
|
|
3
5
|
*
|
|
4
6
|
* @param {object} props
|
|
5
7
|
* @param {string} [props.id]
|
|
@@ -1,17 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* One page of the customer's tickets with status tabs and a search box. Filtering, search and
|
|
3
|
+
* paging all happen on the server; this only shows what it is given.
|
|
3
4
|
*
|
|
4
5
|
* @param {object} props
|
|
5
|
-
* @param {any[]} props.tickets
|
|
6
|
+
* @param {any[]} props.tickets The current page.
|
|
6
7
|
* @param {boolean} props.loading
|
|
7
8
|
* @param {any} [props.error]
|
|
8
9
|
* @param {() => void} props.onRetry
|
|
9
10
|
* @param {(id: string) => void} props.onOpen
|
|
11
|
+
* @param {string} props.status A `STATUS_FILTERS` key.
|
|
12
|
+
* @param {(status: string) => void} props.onStatusChange
|
|
13
|
+
* @param {import('../client.js').TicketCounts|null} [props.counts] Labels the tabs once loaded.
|
|
14
|
+
* @param {string} props.search What is in the search box.
|
|
15
|
+
* @param {(search: string) => void} props.onSearchChange
|
|
16
|
+
* @param {{ page: number, totalPages: number, pageSize: number, total: number, firstRow: number, lastRow: number, setPage: (page: number) => void, setPageSize: (size: number) => void }} props.pagination
|
|
10
17
|
*/
|
|
11
|
-
export default function TicketList({ tickets, loading, error, onRetry, onOpen }: {
|
|
18
|
+
export default function TicketList({ tickets, loading, error, onRetry, onOpen, status, onStatusChange, counts, search, onSearchChange, pagination, }: {
|
|
12
19
|
tickets: any[];
|
|
13
20
|
loading: boolean;
|
|
14
21
|
error?: any;
|
|
15
22
|
onRetry: () => void;
|
|
16
23
|
onOpen: (id: string) => void;
|
|
24
|
+
status: string;
|
|
25
|
+
onStatusChange: (status: string) => void;
|
|
26
|
+
counts?: import('../client.js').TicketCounts | null;
|
|
27
|
+
search: string;
|
|
28
|
+
onSearchChange: (search: string) => void;
|
|
29
|
+
pagination: {
|
|
30
|
+
page: number;
|
|
31
|
+
totalPages: number;
|
|
32
|
+
pageSize: number;
|
|
33
|
+
total: number;
|
|
34
|
+
firstRow: number;
|
|
35
|
+
lastRow: number;
|
|
36
|
+
setPage: (page: number) => void;
|
|
37
|
+
setPageSize: (size: number) => void;
|
|
38
|
+
};
|
|
17
39
|
}): import("react").JSX.Element;
|
package/dist/types/context.d.ts
CHANGED
|
@@ -21,12 +21,27 @@ export declare function useSupportConfig(): {
|
|
|
21
21
|
reload: () => void;
|
|
22
22
|
};
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
24
|
+
* One page of the signed-in customer's tickets, filtered and searched on the server. Loads again
|
|
25
|
+
* whenever any part of the query changes; an answer for a query that has since moved on is dropped.
|
|
25
26
|
*
|
|
26
|
-
* @
|
|
27
|
+
* @param {import('./client.js').TicketListQuery} [query]
|
|
28
|
+
* @returns {{ tickets: any[], totalCount: number, totalPages: number, loading: boolean, error: any, reload: () => void }}
|
|
27
29
|
*/
|
|
28
|
-
export declare function useSupportTickets(): {
|
|
30
|
+
export declare function useSupportTickets(query?: import('./client.js').TicketListQuery): {
|
|
29
31
|
tickets: any[];
|
|
32
|
+
totalCount: number;
|
|
33
|
+
totalPages: number;
|
|
34
|
+
loading: boolean;
|
|
35
|
+
error: any;
|
|
36
|
+
reload: () => void;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* How many of the signed-in customer's tickets are in each status, for labelling status tabs.
|
|
40
|
+
*
|
|
41
|
+
* @returns {{ counts: import('./client.js').TicketCounts|null, loading: boolean, error: any, reload: () => void }}
|
|
42
|
+
*/
|
|
43
|
+
export declare function useSupportTicketCounts(): {
|
|
44
|
+
counts: import('./client.js').TicketCounts | null;
|
|
30
45
|
loading: boolean;
|
|
31
46
|
error: any;
|
|
32
47
|
reload: () => void;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createSupportClient, SupportApiError } from "./client.js";
|
|
2
|
-
export { useSupportClient, useSupportConfig, useSupportTicket, useSupportTickets } from "./context.js";
|
|
2
|
+
export { useSupportClient, useSupportConfig, useSupportTicket, useSupportTicketCounts, useSupportTickets } from "./context.js";
|
|
3
3
|
export { default as ReportIssue } from "./ReportIssue.jsx";
|
|
4
4
|
export { default as SupportCenter } from "./SupportCenter.jsx";
|
|
5
5
|
export { default as SupportContact } from "./SupportContact.jsx";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `value`, once it has stopped changing for `delayMs`. Lets a search box ask the server once
|
|
3
|
+
* per pause in typing instead of once per key.
|
|
4
|
+
*
|
|
5
|
+
* @template T
|
|
6
|
+
* @param {T} value
|
|
7
|
+
* @param {number} delayMs
|
|
8
|
+
* @returns {T}
|
|
9
|
+
*/
|
|
10
|
+
export declare function useDebouncedValue<T>(value: T, delayMs: number): T;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/support-react",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.3",
|
|
4
4
|
"description": "Webority customer support for React portals: ticket centre, report-an-issue with a page screenshot, and support contact details, built on @webority/ui-react.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"react": "^19.0.0",
|
|
33
33
|
"react-bootstrap": "^2.10.0",
|
|
34
34
|
"react-dom": "^19.0.0",
|
|
35
|
-
"@webority/ui-react": "0.13.
|
|
35
|
+
"@webority/ui-react": "0.13.3"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@testing-library/dom": "10.4.2",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"typescript": "^7.0.2",
|
|
51
51
|
"vite": "^8.0.12",
|
|
52
52
|
"vitest": "5.0.1",
|
|
53
|
-
"@webority/ui-react": "0.13.
|
|
53
|
+
"@webority/ui-react": "0.13.3"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"html-to-image": "^1.11.13"
|