@webority/mobile-support 0.0.13 → 0.1.0
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/lib/commonjs/SupportProvider.js +1 -335
- package/lib/commonjs/client.js +1 -149
- package/lib/commonjs/clipboard.js +1 -10
- package/lib/commonjs/components/Attachments.js +1 -246
- package/lib/commonjs/components/NewSupportTicket.js +1 -210
- package/lib/commonjs/components/PhotoTray.js +1 -141
- package/lib/commonjs/components/SupportContact.js +1 -134
- package/lib/commonjs/components/SupportTicketDetail.js +1 -348
- package/lib/commonjs/components/SupportTicketList.js +1 -279
- package/lib/commonjs/device.js +1 -29
- package/lib/commonjs/files.js +1 -37
- package/lib/commonjs/format.js +1 -29
- package/lib/commonjs/index.js +1 -97
- package/lib/commonjs/photos.js +1 -67
- package/lib/commonjs/report/ReportIssueButton.js +1 -57
- package/lib/commonjs/report/captureAndReport.js +1 -34
- package/lib/commonjs/report/index.js +1 -20
- package/lib/commonjs/share/index.js +1 -44
- package/lib/commonjs/types.js +1 -2
- package/lib/commonjs/validation.js +1 -45
- package/lib/module/SupportProvider.js +1 -323
- package/lib/module/client.js +1 -143
- package/lib/module/clipboard.js +1 -5
- package/lib/module/components/Attachments.js +1 -240
- package/lib/module/components/NewSupportTicket.js +1 -205
- package/lib/module/components/PhotoTray.js +1 -136
- package/lib/module/components/SupportContact.js +1 -129
- package/lib/module/components/SupportTicketDetail.js +1 -343
- package/lib/module/components/SupportTicketList.js +1 -273
- package/lib/module/device.js +1 -24
- package/lib/module/files.js +1 -31
- package/lib/module/format.js +1 -23
- package/lib/module/index.js +1 -10
- package/lib/module/photos.js +1 -61
- package/lib/module/report/ReportIssueButton.js +1 -52
- package/lib/module/report/captureAndReport.js +1 -29
- package/lib/module/report/index.js +1 -12
- package/lib/module/share/index.js +1 -37
- package/lib/module/types.js +0 -2
- package/lib/module/validation.js +1 -38
- package/lib/typescript/commonjs/SupportProvider.d.ts +4 -0
- package/lib/typescript/commonjs/format.d.ts +6 -4
- package/lib/typescript/module/SupportProvider.d.ts +4 -0
- package/lib/typescript/module/format.d.ts +6 -4
- package/package.json +8 -6
|
@@ -1,335 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.useSupportTickets = exports.useSupportTicket = exports.useSupportConfig = exports.useSupportClient = exports.useCreateSupportTicket = exports.useAttachmentOpener = exports.useAttachmentLoader = exports.SupportProvider = void 0;
|
|
7
|
-
var _react = require("react");
|
|
8
|
-
var _client = require("./client.js");
|
|
9
|
-
var _files = require("./files.js");
|
|
10
|
-
var _jsxRuntime = require("react/jsx-runtime");
|
|
11
|
-
/** A downloaded attachment, ready for the OS to open. */
|
|
12
|
-
|
|
13
|
-
/** Hands a downloaded file to the OS. `shareSupportFile` from the `/share` entry is one. */
|
|
14
|
-
|
|
15
|
-
const SupportContext = /*#__PURE__*/(0, _react.createContext)(null);
|
|
16
|
-
const SupportProvider = ({
|
|
17
|
-
client,
|
|
18
|
-
openFile,
|
|
19
|
-
children
|
|
20
|
-
}) => {
|
|
21
|
-
const [config, setConfig] = (0, _react.useState)(null);
|
|
22
|
-
const [configError, setConfigError] = (0, _react.useState)(null);
|
|
23
|
-
const [configLoading, setConfigLoading] = (0, _react.useState)(true);
|
|
24
|
-
const [revision, setRevision] = (0, _react.useState)(0);
|
|
25
|
-
// Attachments never change once stored, so each is fetched once per session.
|
|
26
|
-
const attachments = (0, _react.useRef)(new Map());
|
|
27
|
-
const reloadConfig = (0, _react.useCallback)(async () => {
|
|
28
|
-
setConfigLoading(true);
|
|
29
|
-
try {
|
|
30
|
-
setConfig(await client.getConfig());
|
|
31
|
-
setConfigError(null);
|
|
32
|
-
} catch (error) {
|
|
33
|
-
setConfigError((0, _client.toSupportApiError)(error));
|
|
34
|
-
} finally {
|
|
35
|
-
setConfigLoading(false);
|
|
36
|
-
}
|
|
37
|
-
}, [client]);
|
|
38
|
-
(0, _react.useEffect)(() => {
|
|
39
|
-
attachments.current.clear();
|
|
40
|
-
void reloadConfig();
|
|
41
|
-
}, [reloadConfig]);
|
|
42
|
-
const loadAttachment = (0, _react.useCallback)(attachment => {
|
|
43
|
-
const cached = attachments.current.get(attachment.id);
|
|
44
|
-
if (cached) {
|
|
45
|
-
return cached;
|
|
46
|
-
}
|
|
47
|
-
const pending = client.getAttachmentDataUri(attachment);
|
|
48
|
-
attachments.current.set(attachment.id, pending);
|
|
49
|
-
pending.catch(() => attachments.current.delete(attachment.id));
|
|
50
|
-
return pending;
|
|
51
|
-
}, [client]);
|
|
52
|
-
const openAttachment = (0, _react.useMemo)(() => openFile ? async attachment => {
|
|
53
|
-
const uri = await (0, _files.saveAttachmentToCache)(attachment, () => client.getAttachmentBytes(attachment.id));
|
|
54
|
-
await openFile({
|
|
55
|
-
uri,
|
|
56
|
-
name: attachment.fileName,
|
|
57
|
-
contentType: attachment.contentType
|
|
58
|
-
});
|
|
59
|
-
} : null, [client, openFile]);
|
|
60
|
-
const bumpRevision = (0, _react.useCallback)(() => setRevision(r => r + 1), []);
|
|
61
|
-
const value = (0, _react.useMemo)(() => ({
|
|
62
|
-
client,
|
|
63
|
-
config,
|
|
64
|
-
configError,
|
|
65
|
-
configLoading,
|
|
66
|
-
reloadConfig,
|
|
67
|
-
loadAttachment,
|
|
68
|
-
openAttachment,
|
|
69
|
-
revision,
|
|
70
|
-
bumpRevision
|
|
71
|
-
}), [client, config, configError, configLoading, reloadConfig, loadAttachment, openAttachment, revision, bumpRevision]);
|
|
72
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(SupportContext.Provider, {
|
|
73
|
-
value: value,
|
|
74
|
-
children: children
|
|
75
|
-
});
|
|
76
|
-
};
|
|
77
|
-
exports.SupportProvider = SupportProvider;
|
|
78
|
-
const useSupportContext = () => {
|
|
79
|
-
const ctx = (0, _react.useContext)(SupportContext);
|
|
80
|
-
if (!ctx) {
|
|
81
|
-
throw new Error('[@webority/mobile-support] Support screens and hooks need a <SupportProvider client={...}> above them.');
|
|
82
|
-
}
|
|
83
|
-
return ctx;
|
|
84
|
-
};
|
|
85
|
-
const useSupportClient = () => useSupportContext().client;
|
|
86
|
-
|
|
87
|
-
/** @internal Used by the attachment thumbnails. */
|
|
88
|
-
exports.useSupportClient = useSupportClient;
|
|
89
|
-
const useAttachmentLoader = () => useSupportContext().loadAttachment;
|
|
90
|
-
|
|
91
|
-
/** @internal Used by the file rows; null when the app gave no `openFile`. */
|
|
92
|
-
exports.useAttachmentLoader = useAttachmentLoader;
|
|
93
|
-
const useAttachmentOpener = () => useSupportContext().openAttachment;
|
|
94
|
-
exports.useAttachmentOpener = useAttachmentOpener;
|
|
95
|
-
const useSupportConfig = () => {
|
|
96
|
-
const {
|
|
97
|
-
config,
|
|
98
|
-
configLoading,
|
|
99
|
-
configError,
|
|
100
|
-
reloadConfig
|
|
101
|
-
} = useSupportContext();
|
|
102
|
-
return {
|
|
103
|
-
config,
|
|
104
|
-
loading: configLoading,
|
|
105
|
-
error: configError,
|
|
106
|
-
reload: reloadConfig
|
|
107
|
-
};
|
|
108
|
-
};
|
|
109
|
-
exports.useSupportConfig = useSupportConfig;
|
|
110
|
-
const DEFAULT_PAGE_SIZE = 20;
|
|
111
|
-
const useSupportTickets = (options = {}) => {
|
|
112
|
-
const pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
113
|
-
const {
|
|
114
|
-
client,
|
|
115
|
-
revision
|
|
116
|
-
} = useSupportContext();
|
|
117
|
-
const [status, setStatus] = (0, _react.useState)(null);
|
|
118
|
-
const [search, setSearch] = (0, _react.useState)('');
|
|
119
|
-
const [tickets, setTickets] = (0, _react.useState)([]);
|
|
120
|
-
const [counts, setCounts] = (0, _react.useState)(null);
|
|
121
|
-
const [page, setPage] = (0, _react.useState)(0);
|
|
122
|
-
const [totalPages, setTotalPages] = (0, _react.useState)(0);
|
|
123
|
-
const [loading, setLoading] = (0, _react.useState)(true);
|
|
124
|
-
const [refreshing, setRefreshing] = (0, _react.useState)(false);
|
|
125
|
-
const [loadingMore, setLoadingMore] = (0, _react.useState)(false);
|
|
126
|
-
const [error, setError] = (0, _react.useState)(null);
|
|
127
|
-
const [loadMoreError, setLoadMoreError] = (0, _react.useState)(null);
|
|
128
|
-
const mounted = (0, _react.useRef)(true);
|
|
129
|
-
// Each first-page load takes a new number; a response carrying an older one
|
|
130
|
-
// belongs to a filter or search the person has already left, and is dropped.
|
|
131
|
-
const sequence = (0, _react.useRef)(0);
|
|
132
|
-
const moreInFlight = (0, _react.useRef)(false);
|
|
133
|
-
// The client whose counts are on screen; null until a load with counts lands.
|
|
134
|
-
const countsFrom = (0, _react.useRef)(null);
|
|
135
|
-
(0, _react.useEffect)(() => {
|
|
136
|
-
mounted.current = true;
|
|
137
|
-
return () => {
|
|
138
|
-
mounted.current = false;
|
|
139
|
-
};
|
|
140
|
-
}, []);
|
|
141
|
-
const query = (0, _react.useCallback)(nextPage => ({
|
|
142
|
-
status: status ?? undefined,
|
|
143
|
-
search: search.trim() || undefined,
|
|
144
|
-
page: nextPage,
|
|
145
|
-
pageSize
|
|
146
|
-
}), [status, search, pageSize]);
|
|
147
|
-
const loadFirstPage = (0, _react.useCallback)(async mode => {
|
|
148
|
-
sequence.current += 1;
|
|
149
|
-
const seq = sequence.current;
|
|
150
|
-
const current = () => mounted.current && seq === sequence.current;
|
|
151
|
-
moreInFlight.current = false;
|
|
152
|
-
setLoadingMore(false);
|
|
153
|
-
if (mode === 'refresh') {
|
|
154
|
-
setRefreshing(true);
|
|
155
|
-
} else if (mode === 'replace') {
|
|
156
|
-
setLoading(true);
|
|
157
|
-
setTickets([]);
|
|
158
|
-
}
|
|
159
|
-
// Counts ignore the status and search, so a filter change keeps the ones it has.
|
|
160
|
-
const withCounts = mode !== 'replace' || countsFrom.current !== client;
|
|
161
|
-
try {
|
|
162
|
-
const [first, nextCounts] = await Promise.all([client.listTickets(query(1)), withCounts ? client.getTicketCounts() : null]);
|
|
163
|
-
if (current()) {
|
|
164
|
-
setTickets(first.items);
|
|
165
|
-
setPage(first.page);
|
|
166
|
-
setTotalPages(first.totalPages);
|
|
167
|
-
if (nextCounts) {
|
|
168
|
-
setCounts(nextCounts);
|
|
169
|
-
countsFrom.current = client;
|
|
170
|
-
}
|
|
171
|
-
setError(null);
|
|
172
|
-
setLoadMoreError(null);
|
|
173
|
-
}
|
|
174
|
-
} catch (e) {
|
|
175
|
-
if (current()) {
|
|
176
|
-
setError((0, _client.toSupportApiError)(e));
|
|
177
|
-
}
|
|
178
|
-
} finally {
|
|
179
|
-
if (current()) {
|
|
180
|
-
setLoading(false);
|
|
181
|
-
setRefreshing(false);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}, [client, query]);
|
|
185
|
-
const seenRevision = (0, _react.useRef)(revision);
|
|
186
|
-
(0, _react.useEffect)(() => {
|
|
187
|
-
// A write elsewhere refetches without blanking the rows on screen.
|
|
188
|
-
const mode = seenRevision.current === revision ? 'replace' : 'quiet';
|
|
189
|
-
seenRevision.current = revision;
|
|
190
|
-
void loadFirstPage(mode);
|
|
191
|
-
}, [loadFirstPage, revision]);
|
|
192
|
-
const hasMore = page < totalPages;
|
|
193
|
-
const loadMore = (0, _react.useCallback)(async () => {
|
|
194
|
-
if (moreInFlight.current || loading || refreshing || !hasMore) {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
const seq = sequence.current;
|
|
198
|
-
const current = () => mounted.current && seq === sequence.current;
|
|
199
|
-
moreInFlight.current = true;
|
|
200
|
-
setLoadingMore(true);
|
|
201
|
-
setLoadMoreError(null);
|
|
202
|
-
try {
|
|
203
|
-
const next = await client.listTickets(query(page + 1));
|
|
204
|
-
if (current()) {
|
|
205
|
-
// Order is by last update, so a ticket that changed between two pages
|
|
206
|
-
// can arrive twice.
|
|
207
|
-
setTickets(prev => {
|
|
208
|
-
const ids = new Set(prev.map(t => t.id));
|
|
209
|
-
return [...prev, ...next.items.filter(t => !ids.has(t.id))];
|
|
210
|
-
});
|
|
211
|
-
setPage(next.page);
|
|
212
|
-
setTotalPages(next.totalPages);
|
|
213
|
-
}
|
|
214
|
-
} catch (e) {
|
|
215
|
-
if (current()) {
|
|
216
|
-
setLoadMoreError((0, _client.toSupportApiError)(e));
|
|
217
|
-
}
|
|
218
|
-
} finally {
|
|
219
|
-
if (current()) {
|
|
220
|
-
moreInFlight.current = false;
|
|
221
|
-
setLoadingMore(false);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}, [client, query, page, hasMore, loading, refreshing]);
|
|
225
|
-
const refresh = (0, _react.useCallback)(() => loadFirstPage('refresh'), [loadFirstPage]);
|
|
226
|
-
const createTicket = useCreateSupportTicket();
|
|
227
|
-
return {
|
|
228
|
-
tickets,
|
|
229
|
-
counts,
|
|
230
|
-
status,
|
|
231
|
-
search,
|
|
232
|
-
setStatus,
|
|
233
|
-
setSearch,
|
|
234
|
-
loading,
|
|
235
|
-
refreshing,
|
|
236
|
-
loadingMore,
|
|
237
|
-
hasMore,
|
|
238
|
-
error,
|
|
239
|
-
loadMoreError,
|
|
240
|
-
refresh,
|
|
241
|
-
loadMore,
|
|
242
|
-
createTicket
|
|
243
|
-
};
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
/** Raises a ticket and tells every mounted ticket list to refetch. */
|
|
247
|
-
exports.useSupportTickets = useSupportTickets;
|
|
248
|
-
const useCreateSupportTicket = () => {
|
|
249
|
-
const {
|
|
250
|
-
client,
|
|
251
|
-
bumpRevision
|
|
252
|
-
} = useSupportContext();
|
|
253
|
-
return (0, _react.useCallback)(async input => {
|
|
254
|
-
const created = await client.createTicket(input);
|
|
255
|
-
bumpRevision();
|
|
256
|
-
return created;
|
|
257
|
-
}, [client, bumpRevision]);
|
|
258
|
-
};
|
|
259
|
-
exports.useCreateSupportTicket = useCreateSupportTicket;
|
|
260
|
-
const useSupportTicket = id => {
|
|
261
|
-
const {
|
|
262
|
-
client,
|
|
263
|
-
bumpRevision
|
|
264
|
-
} = useSupportContext();
|
|
265
|
-
const [ticket, setTicket] = (0, _react.useState)(null);
|
|
266
|
-
const [loading, setLoading] = (0, _react.useState)(true);
|
|
267
|
-
const [error, setError] = (0, _react.useState)(null);
|
|
268
|
-
const [actionError, setActionError] = (0, _react.useState)(null);
|
|
269
|
-
const [busy, setBusy] = (0, _react.useState)(false);
|
|
270
|
-
const mounted = (0, _react.useRef)(true);
|
|
271
|
-
(0, _react.useEffect)(() => {
|
|
272
|
-
mounted.current = true;
|
|
273
|
-
return () => {
|
|
274
|
-
mounted.current = false;
|
|
275
|
-
};
|
|
276
|
-
}, []);
|
|
277
|
-
const reload = (0, _react.useCallback)(async () => {
|
|
278
|
-
setLoading(true);
|
|
279
|
-
try {
|
|
280
|
-
const next = await client.getTicket(id);
|
|
281
|
-
if (mounted.current) {
|
|
282
|
-
setTicket(next);
|
|
283
|
-
setError(null);
|
|
284
|
-
}
|
|
285
|
-
} catch (e) {
|
|
286
|
-
if (mounted.current) {
|
|
287
|
-
setError((0, _client.toSupportApiError)(e));
|
|
288
|
-
}
|
|
289
|
-
} finally {
|
|
290
|
-
if (mounted.current) {
|
|
291
|
-
setLoading(false);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}, [client, id]);
|
|
295
|
-
(0, _react.useEffect)(() => {
|
|
296
|
-
void reload();
|
|
297
|
-
}, [reload]);
|
|
298
|
-
const act = (0, _react.useCallback)(async run => {
|
|
299
|
-
setBusy(true);
|
|
300
|
-
setActionError(null);
|
|
301
|
-
try {
|
|
302
|
-
const next = await run();
|
|
303
|
-
if (mounted.current) {
|
|
304
|
-
setTicket(next);
|
|
305
|
-
}
|
|
306
|
-
bumpRevision();
|
|
307
|
-
return true;
|
|
308
|
-
} catch (e) {
|
|
309
|
-
if (mounted.current) {
|
|
310
|
-
setActionError((0, _client.toSupportApiError)(e));
|
|
311
|
-
}
|
|
312
|
-
return false;
|
|
313
|
-
} finally {
|
|
314
|
-
if (mounted.current) {
|
|
315
|
-
setBusy(false);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}, [bumpRevision]);
|
|
319
|
-
const reply = (0, _react.useCallback)(input => act(() => client.reply(id, input)), [act, client, id]);
|
|
320
|
-
const reopen = (0, _react.useCallback)(() => act(() => client.reopen(id)), [act, client, id]);
|
|
321
|
-
const rate = (0, _react.useCallback)(input => act(() => client.rate(id, input)), [act, client, id]);
|
|
322
|
-
return {
|
|
323
|
-
ticket,
|
|
324
|
-
loading,
|
|
325
|
-
error,
|
|
326
|
-
actionError,
|
|
327
|
-
busy,
|
|
328
|
-
reload,
|
|
329
|
-
reply,
|
|
330
|
-
reopen,
|
|
331
|
-
rate
|
|
332
|
-
};
|
|
333
|
-
};
|
|
334
|
-
exports.useSupportTicket = useSupportTicket;
|
|
335
|
-
//# sourceMappingURL=SupportProvider.js.map
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.useSupportTimeZone=exports.useSupportTickets=exports.useSupportTicket=exports.useSupportConfig=exports.useSupportClient=exports.useCreateSupportTicket=exports.useAttachmentOpener=exports.useAttachmentLoader=exports.SupportProvider=void 0;var _react=require("react"),_client=require("./client.js"),_files=require("./files.js"),_format=require("./format.js"),_jsxRuntime=require("react/jsx-runtime");const SupportContext=(0,_react.createContext)(null),SupportProvider=({client:e,openFile:t,timeZone:r=_format.DEFAULT_TIME_ZONE,children:c})=>{const[u,o]=(0,_react.useState)(null),[a,n]=(0,_react.useState)(null),[s,p]=(0,_react.useState)(!0),[i,l]=(0,_react.useState)(0),S=(0,_react.useRef)(new Map),_=(0,_react.useCallback)(async()=>{p(!0);try{o(await e.getConfig()),n(null)}catch(e){n((0,_client.toSupportApiError)(e))}finally{p(!1)}},[e]);(0,_react.useEffect)(()=>{S.current.clear(),_()},[_]);const f=(0,_react.useCallback)(t=>{const r=S.current.get(t.id);if(r)return r;const c=e.getAttachmentDataUri(t);return S.current.set(t.id,c),c.catch(()=>S.current.delete(t.id)),c},[e]),C=(0,_react.useMemo)(()=>t?async r=>{const c=await(0,_files.saveAttachmentToCache)(r,()=>e.getAttachmentBytes(r.id));await t({uri:c,name:r.fileName,contentType:r.contentType})}:null,[e,t]),m=(0,_react.useCallback)(()=>l(e=>e+1),[]),d=(0,_react.useMemo)(()=>({client:e,config:u,configError:a,configLoading:s,reloadConfig:_,loadAttachment:f,openAttachment:C,timeZone:r,revision:i,bumpRevision:m}),[e,u,a,s,_,f,C,r,i,m]);return(0,_jsxRuntime.jsx)(SupportContext.Provider,{value:d,children:c})};exports.SupportProvider=SupportProvider;const useSupportContext=()=>{const e=(0,_react.useContext)(SupportContext);if(!e)throw new Error("[@webority/mobile-support] Support screens and hooks need a <SupportProvider client={...}> above them.");return e},useSupportClient=()=>useSupportContext().client;exports.useSupportClient=useSupportClient;const useAttachmentLoader=()=>useSupportContext().loadAttachment;exports.useAttachmentLoader=useAttachmentLoader;const useAttachmentOpener=()=>useSupportContext().openAttachment;exports.useAttachmentOpener=useAttachmentOpener;const useSupportTimeZone=()=>useSupportContext().timeZone;exports.useSupportTimeZone=useSupportTimeZone;const useSupportConfig=()=>{const{config:e,configLoading:t,configError:r,reloadConfig:c}=useSupportContext();return{config:e,loading:t,error:r,reload:c}};exports.useSupportConfig=useSupportConfig;const DEFAULT_PAGE_SIZE=20,useSupportTickets=(e={})=>{const t=e.pageSize??20,{client:r,revision:c}=useSupportContext(),[u,o]=(0,_react.useState)(null),[a,n]=(0,_react.useState)(""),[s,p]=(0,_react.useState)([]),[i,l]=(0,_react.useState)(null),[S,_]=(0,_react.useState)(0),[f,C]=(0,_react.useState)(0),[m,d]=(0,_react.useState)(!0),[x,h]=(0,_react.useState)(!1),[k,g]=(0,_react.useState)(!1),[T,y]=(0,_react.useState)(null),[A,b]=(0,_react.useState)(null),E=(0,_react.useRef)(!0),v=(0,_react.useRef)(0),w=(0,_react.useRef)(!1),R=(0,_react.useRef)(null);(0,_react.useEffect)(()=>(E.current=!0,()=>{E.current=!1}),[]);const P=(0,_react.useCallback)(e=>({status:u??void 0,search:a.trim()||void 0,page:e,pageSize:t}),[u,a,t]),M=(0,_react.useCallback)(async e=>{v.current+=1;const t=v.current,current=()=>E.current&&t===v.current;w.current=!1,g(!1),"refresh"===e?h(!0):"replace"===e&&(d(!0),p([]));const c="replace"!==e||R.current!==r;try{const[e,t]=await Promise.all([r.listTickets(P(1)),c?r.getTicketCounts():null]);current()&&(p(e.items),_(e.page),C(e.totalPages),t&&(l(t),R.current=r),y(null),b(null))}catch(e){current()&&y((0,_client.toSupportApiError)(e))}finally{current()&&(d(!1),h(!1))}},[r,P]),Z=(0,_react.useRef)(c);(0,_react.useEffect)(()=>{const e=Z.current===c?"replace":"quiet";Z.current=c,M(e)},[M,c]);const j=S<f,L=(0,_react.useCallback)(async()=>{if(w.current||m||x||!j)return;const e=v.current,current=()=>E.current&&e===v.current;w.current=!0,g(!0),b(null);try{const e=await r.listTickets(P(S+1));current()&&(p(t=>{const r=new Set(t.map(e=>e.id));return[...t,...e.items.filter(e=>!r.has(e.id))]}),_(e.page),C(e.totalPages))}catch(e){current()&&b((0,_client.toSupportApiError)(e))}finally{current()&&(w.current=!1,g(!1))}},[r,P,S,j,m,x]),q=(0,_react.useCallback)(()=>M("refresh"),[M]),O=useCreateSupportTicket();return{tickets:s,counts:i,status:u,search:a,setStatus:o,setSearch:n,loading:m,refreshing:x,loadingMore:k,hasMore:j,error:T,loadMoreError:A,refresh:q,loadMore:L,createTicket:O}};exports.useSupportTickets=useSupportTickets;const useCreateSupportTicket=()=>{const{client:e,bumpRevision:t}=useSupportContext();return(0,_react.useCallback)(async r=>{const c=await e.createTicket(r);return t(),c},[e,t])};exports.useCreateSupportTicket=useCreateSupportTicket;const useSupportTicket=e=>{const{client:t,bumpRevision:r}=useSupportContext(),[c,u]=(0,_react.useState)(null),[o,a]=(0,_react.useState)(!0),[n,s]=(0,_react.useState)(null),[p,i]=(0,_react.useState)(null),[l,S]=(0,_react.useState)(!1),_=(0,_react.useRef)(!0);(0,_react.useEffect)(()=>(_.current=!0,()=>{_.current=!1}),[]);const f=(0,_react.useCallback)(async()=>{a(!0);try{const r=await t.getTicket(e);_.current&&(u(r),s(null))}catch(e){_.current&&s((0,_client.toSupportApiError)(e))}finally{_.current&&a(!1)}},[t,e]);(0,_react.useEffect)(()=>{f()},[f]);const C=(0,_react.useCallback)(async e=>{S(!0),i(null);try{const t=await e();return _.current&&u(t),r(),!0}catch(e){return _.current&&i((0,_client.toSupportApiError)(e)),!1}finally{_.current&&S(!1)}},[r]),m=(0,_react.useCallback)(r=>C(()=>t.reply(e,r)),[C,t,e]),d=(0,_react.useCallback)(()=>C(()=>t.reopen(e)),[C,t,e]),x=(0,_react.useCallback)(r=>C(()=>t.rate(e,r)),[C,t,e]);return{ticket:c,loading:o,error:n,actionError:p,busy:l,reload:f,reply:m,reopen:d,rate:x}};exports.useSupportTicket=useSupportTicket;
|
package/lib/commonjs/client.js
CHANGED
|
@@ -1,149 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.toSupportApiError = exports.createSupportClient = exports.SupportApiError = exports.OFFLINE_MESSAGE = exports.GENERIC_MESSAGE = void 0;
|
|
7
|
-
var _mobileCore = require("@webority/mobile-core");
|
|
8
|
-
/**
|
|
9
|
-
* The slice of an HTTP client the support client needs. The app's signed-in
|
|
10
|
-
* axios instance (mobile-core's `userClient`, or its own) satisfies it as is, so
|
|
11
|
-
* whatever it adds to a request (bearer token, tenant header, refresh on 401)
|
|
12
|
-
* applies to support calls too. Declared structurally so no axios type leaks into
|
|
13
|
-
* this package's declarations.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const OFFLINE_MESSAGE = exports.OFFLINE_MESSAGE = 'You are offline. Check your connection and try again.';
|
|
17
|
-
const GENERIC_MESSAGE = exports.GENERIC_MESSAGE = 'Something went wrong. Try again in a moment.';
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* A failed support call. `message` is written for the person and is shown as is;
|
|
21
|
-
* `code` is the backend's stable code, for branching only.
|
|
22
|
-
*/
|
|
23
|
-
class SupportApiError extends Error {
|
|
24
|
-
constructor(code, message, status) {
|
|
25
|
-
super(message);
|
|
26
|
-
this.name = 'SupportApiError';
|
|
27
|
-
this.code = code;
|
|
28
|
-
this.status = status;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
exports.SupportApiError = SupportApiError;
|
|
32
|
-
/** Turns whatever the HTTP client threw into a SupportApiError. */
|
|
33
|
-
const toSupportApiError = error => {
|
|
34
|
-
if (error instanceof SupportApiError) {
|
|
35
|
-
return error;
|
|
36
|
-
}
|
|
37
|
-
const e = error ?? {};
|
|
38
|
-
// By name, not instanceof: a second copy of mobile-core would fail instanceof.
|
|
39
|
-
if (e.name === 'RequestOfflineError') {
|
|
40
|
-
return new SupportApiError('offline', OFFLINE_MESSAGE, null);
|
|
41
|
-
}
|
|
42
|
-
const status = typeof e.response?.status === 'number' ? e.response.status : null;
|
|
43
|
-
const data = e.response?.data;
|
|
44
|
-
if (data && typeof data.message === 'string' && data.message.trim()) {
|
|
45
|
-
const code = typeof data.code === 'string' ? data.code : 'unknown';
|
|
46
|
-
return new SupportApiError(code, data.message, status);
|
|
47
|
-
}
|
|
48
|
-
if (!e.response) {
|
|
49
|
-
return new SupportApiError('network', OFFLINE_MESSAGE, null);
|
|
50
|
-
}
|
|
51
|
-
return new SupportApiError('unknown', GENERIC_MESSAGE, status);
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// Photo uploads get the fleet's multipart timeout, not the 30 s JSON one.
|
|
55
|
-
exports.toSupportApiError = toSupportApiError;
|
|
56
|
-
const UPLOAD_TIMEOUT_MS = 300000;
|
|
57
|
-
const MULTIPART = {
|
|
58
|
-
'Content-Type': 'multipart/form-data'
|
|
59
|
-
};
|
|
60
|
-
const appendParts = (form, field, parts) => {
|
|
61
|
-
for (const part of parts ?? []) {
|
|
62
|
-
// React Native's FormData takes a { uri, name, type } object as a file part.
|
|
63
|
-
form.append(field, {
|
|
64
|
-
uri: part.uri,
|
|
65
|
-
name: part.name,
|
|
66
|
-
type: part.type
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
const appendText = (form, field, value) => {
|
|
71
|
-
if (value !== undefined && value !== '') {
|
|
72
|
-
form.append(field, value);
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
const queryString = query => {
|
|
76
|
-
const pairs = [];
|
|
77
|
-
const add = (key, value) => {
|
|
78
|
-
if (value !== undefined && value !== '') {
|
|
79
|
-
pairs.push(`${key}=${encodeURIComponent(String(value))}`);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
add('status', query.status);
|
|
83
|
-
add('search', query.search?.trim());
|
|
84
|
-
add('page', query.page);
|
|
85
|
-
add('pageSize', query.pageSize);
|
|
86
|
-
return pairs.length > 0 ? `?${pairs.join('&')}` : '';
|
|
87
|
-
};
|
|
88
|
-
const createSupportClient = (http, basePath = '/api/support') => {
|
|
89
|
-
const base = basePath.replace(/\/+$/, '');
|
|
90
|
-
const ticketUrl = id => `${base}/tickets/${encodeURIComponent(id)}`;
|
|
91
|
-
const call = async request => {
|
|
92
|
-
try {
|
|
93
|
-
const response = await request();
|
|
94
|
-
return response.data;
|
|
95
|
-
} catch (error) {
|
|
96
|
-
throw toSupportApiError(error);
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
const getBytes = id => call(() => http.get(`${base}/attachments/${encodeURIComponent(id)}`, {
|
|
100
|
-
responseType: 'arraybuffer'
|
|
101
|
-
}));
|
|
102
|
-
return {
|
|
103
|
-
basePath: base,
|
|
104
|
-
getConfig: () => call(() => http.get(`${base}/settings`)),
|
|
105
|
-
listTickets: (query = {}) => call(() => http.get(`${base}/tickets${queryString(query)}`)),
|
|
106
|
-
getTicketCounts: () => call(() => http.get(`${base}/tickets/counts`)),
|
|
107
|
-
getTicket: id => call(() => http.get(ticketUrl(id))),
|
|
108
|
-
createTicket: input => {
|
|
109
|
-
const form = new FormData();
|
|
110
|
-
form.append('category', input.category);
|
|
111
|
-
form.append('subject', input.subject);
|
|
112
|
-
form.append('body', input.body);
|
|
113
|
-
appendText(form, 'pageUrl', input.pageUrl);
|
|
114
|
-
appendText(form, 'userAgent', input.userAgent);
|
|
115
|
-
appendText(form, 'appVersion', input.appVersion);
|
|
116
|
-
appendParts(form, 'files', input.files);
|
|
117
|
-
appendParts(form, 'screenshots', input.screenshots);
|
|
118
|
-
return call(() => http.post(`${base}/tickets`, form, {
|
|
119
|
-
headers: MULTIPART,
|
|
120
|
-
timeout: UPLOAD_TIMEOUT_MS
|
|
121
|
-
}));
|
|
122
|
-
},
|
|
123
|
-
reply: (id, input) => {
|
|
124
|
-
const form = new FormData();
|
|
125
|
-
form.append('body', input.body);
|
|
126
|
-
appendParts(form, 'files', input.files);
|
|
127
|
-
appendParts(form, 'screenshots', input.screenshots);
|
|
128
|
-
return call(() => http.post(`${ticketUrl(id)}/replies`, form, {
|
|
129
|
-
headers: MULTIPART,
|
|
130
|
-
timeout: UPLOAD_TIMEOUT_MS
|
|
131
|
-
}));
|
|
132
|
-
},
|
|
133
|
-
reopen: id => call(() => http.post(`${ticketUrl(id)}/reopen`)),
|
|
134
|
-
rate: (id, input) => call(() => http.post(`${ticketUrl(id)}/rating`, {
|
|
135
|
-
satisfaction: input.satisfaction,
|
|
136
|
-
comment: input.comment?.trim() ? input.comment.trim() : null
|
|
137
|
-
})),
|
|
138
|
-
getAttachmentBytes: getBytes,
|
|
139
|
-
getAttachmentDataUri: async attachment => {
|
|
140
|
-
const bytes = await getBytes(attachment.id);
|
|
141
|
-
return `data:${attachment.contentType};base64,${(0, _mobileCore.encodeBase64)(bytes)}`;
|
|
142
|
-
},
|
|
143
|
-
confirmAction: token => call(() => http.post(`${base}/actions/confirm`, {
|
|
144
|
-
token
|
|
145
|
-
}))
|
|
146
|
-
};
|
|
147
|
-
};
|
|
148
|
-
exports.createSupportClient = createSupportClient;
|
|
149
|
-
//# sourceMappingURL=client.js.map
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.toSupportApiError=exports.createSupportClient=exports.SupportApiError=exports.OFFLINE_MESSAGE=exports.GENERIC_MESSAGE=void 0;var _mobileCore=require("@webority/mobile-core");const OFFLINE_MESSAGE=exports.OFFLINE_MESSAGE="You are offline. Check your connection and try again.",GENERIC_MESSAGE=exports.GENERIC_MESSAGE="Something went wrong. Try again in a moment.";class SupportApiError extends Error{constructor(e,t,r){super(t),this.name="SupportApiError",this.code=e,this.status=r}}exports.SupportApiError=SupportApiError;const toSupportApiError=e=>{if(e instanceof SupportApiError)return e;const t=e??{};if("RequestOfflineError"===t.name)return new SupportApiError("offline",OFFLINE_MESSAGE,null);const r="number"==typeof t.response?.status?t.response.status:null,o=t.response?.data;if(o&&"string"==typeof o.message&&o.message.trim()){const e="string"==typeof o.code?o.code:"unknown";return new SupportApiError(e,o.message,r)}return t.response?new SupportApiError("unknown",GENERIC_MESSAGE,r):new SupportApiError("network",OFFLINE_MESSAGE,null)};exports.toSupportApiError=toSupportApiError;const UPLOAD_TIMEOUT_MS=3e5,MULTIPART={"Content-Type":"multipart/form-data"},appendParts=(e,t,r)=>{for(const o of r??[])e.append(t,{uri:o.uri,name:o.name,type:o.type})},appendText=(e,t,r)=>{void 0!==r&&""!==r&&e.append(t,r)},queryString=e=>{const t=[],add=(e,r)=>{void 0!==r&&""!==r&&t.push(`${e}=${encodeURIComponent(String(r))}`)};return add("status",e.status),add("search",e.search?.trim()),add("page",e.page),add("pageSize",e.pageSize),t.length>0?`?${t.join("&")}`:""},createSupportClient=(e,t="/api/support")=>{const r=t.replace(/\/+$/,""),ticketUrl=e=>`${r}/tickets/${encodeURIComponent(e)}`,call=async e=>{try{return(await e()).data}catch(e){throw toSupportApiError(e)}},getBytes=t=>call(()=>e.get(`${r}/attachments/${encodeURIComponent(t)}`,{responseType:"arraybuffer"}));return{basePath:r,getConfig:()=>call(()=>e.get(`${r}/settings`)),listTickets:(t={})=>call(()=>e.get(`${r}/tickets${queryString(t)}`)),getTicketCounts:()=>call(()=>e.get(`${r}/tickets/counts`)),getTicket:t=>call(()=>e.get(ticketUrl(t))),createTicket:t=>{const o=new FormData;return o.append("category",t.category),o.append("subject",t.subject),o.append("body",t.body),appendText(o,"pageUrl",t.pageUrl),appendText(o,"userAgent",t.userAgent),appendText(o,"appVersion",t.appVersion),appendParts(o,"files",t.files),appendParts(o,"screenshots",t.screenshots),call(()=>e.post(`${r}/tickets`,o,{headers:MULTIPART,timeout:3e5}))},reply:(t,r)=>{const o=new FormData;return o.append("body",r.body),appendParts(o,"files",r.files),appendParts(o,"screenshots",r.screenshots),call(()=>e.post(`${ticketUrl(t)}/replies`,o,{headers:MULTIPART,timeout:3e5}))},reopen:t=>call(()=>e.post(`${ticketUrl(t)}/reopen`)),rate:(t,r)=>call(()=>e.post(`${ticketUrl(t)}/rating`,{satisfaction:r.satisfaction,comment:r.comment?.trim()?r.comment.trim():null})),getAttachmentBytes:getBytes,getAttachmentDataUri:async e=>{const t=await getBytes(e.id);return`data:${e.contentType};base64,${(0,_mobileCore.encodeBase64)(t)}`},confirmAction:t=>call(()=>e.post(`${r}/actions/confirm`,{token:t}))}};exports.createSupportClient=createSupportClient;
|
|
@@ -1,10 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.copyText = void 0;
|
|
7
|
-
/** The one place `expo-clipboard` is named. */
|
|
8
|
-
const copyText = text => require('expo-clipboard').setStringAsync(text);
|
|
9
|
-
exports.copyText = copyText;
|
|
10
|
-
//# sourceMappingURL=clipboard.js.map
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.copyText=void 0;const copyText=e=>require("expo-clipboard").setStringAsync(e);exports.copyText=copyText;
|