@webority/mobile-support 0.0.12 → 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.
Files changed (47) hide show
  1. package/lib/commonjs/SupportProvider.js +1 -335
  2. package/lib/commonjs/client.js +1 -164
  3. package/lib/commonjs/clipboard.js +1 -10
  4. package/lib/commonjs/components/Attachments.js +1 -246
  5. package/lib/commonjs/components/NewSupportTicket.js +1 -210
  6. package/lib/commonjs/components/PhotoTray.js +1 -141
  7. package/lib/commonjs/components/SupportContact.js +1 -134
  8. package/lib/commonjs/components/SupportTicketDetail.js +1 -348
  9. package/lib/commonjs/components/SupportTicketList.js +1 -279
  10. package/lib/commonjs/device.js +1 -29
  11. package/lib/commonjs/files.js +1 -37
  12. package/lib/commonjs/format.js +1 -29
  13. package/lib/commonjs/index.js +1 -97
  14. package/lib/commonjs/photos.js +1 -67
  15. package/lib/commonjs/report/ReportIssueButton.js +1 -57
  16. package/lib/commonjs/report/captureAndReport.js +1 -34
  17. package/lib/commonjs/report/index.js +1 -20
  18. package/lib/commonjs/share/index.js +1 -44
  19. package/lib/commonjs/types.js +1 -2
  20. package/lib/commonjs/validation.js +1 -45
  21. package/lib/module/SupportProvider.js +1 -323
  22. package/lib/module/client.js +1 -156
  23. package/lib/module/clipboard.js +1 -5
  24. package/lib/module/components/Attachments.js +1 -240
  25. package/lib/module/components/NewSupportTicket.js +1 -205
  26. package/lib/module/components/PhotoTray.js +1 -136
  27. package/lib/module/components/SupportContact.js +1 -129
  28. package/lib/module/components/SupportTicketDetail.js +1 -343
  29. package/lib/module/components/SupportTicketList.js +1 -273
  30. package/lib/module/device.js +1 -24
  31. package/lib/module/files.js +1 -31
  32. package/lib/module/format.js +1 -23
  33. package/lib/module/index.js +1 -10
  34. package/lib/module/photos.js +1 -61
  35. package/lib/module/report/ReportIssueButton.js +1 -52
  36. package/lib/module/report/captureAndReport.js +1 -29
  37. package/lib/module/report/index.js +1 -12
  38. package/lib/module/share/index.js +1 -37
  39. package/lib/module/types.js +0 -2
  40. package/lib/module/validation.js +1 -38
  41. package/lib/typescript/commonjs/SupportProvider.d.ts +4 -0
  42. package/lib/typescript/commonjs/client.d.ts +0 -1
  43. package/lib/typescript/commonjs/format.d.ts +6 -4
  44. package/lib/typescript/module/SupportProvider.d.ts +4 -0
  45. package/lib/typescript/module/client.d.ts +0 -1
  46. package/lib/typescript/module/format.d.ts +6 -4
  47. package/package.json +8 -6
@@ -1,279 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.statusFilterOptions = exports.SupportTicketList = exports.STATUS_TONES = void 0;
7
- var _mobileUi = require("@webority/mobile-ui");
8
- var _react = require("react");
9
- var _reactNative = require("react-native");
10
- var _format = require("../format.js");
11
- var _SupportProvider = require("../SupportProvider.js");
12
- var _jsxRuntime = require("react/jsx-runtime");
13
- const STATUS_TONES = exports.STATUS_TONES = {
14
- Open: 'warning',
15
- InProgress: 'info',
16
- Resolved: 'success',
17
- Closed: 'neutral'
18
- };
19
-
20
- // The counts endpoint carries numbers only, so the chip wording lives here. It
21
- // matches the server's own labels (Webority.Support SupportDisplay).
22
- const STATUS_FILTERS = [{
23
- value: 'Open',
24
- label: 'Open',
25
- count: 'open'
26
- }, {
27
- value: 'InProgress',
28
- label: 'In progress',
29
- count: 'inProgress'
30
- }, {
31
- value: 'Resolved',
32
- label: 'Resolved',
33
- count: 'resolved'
34
- }, {
35
- value: 'Closed',
36
- label: 'Closed',
37
- count: 'closed'
38
- }];
39
- const ALL = 'all';
40
- /**
41
- * All, then each status that has tickets. The selected status stays even at
42
- * zero, so the chip the person is on never vanishes under them.
43
- */
44
- const statusFilterOptions = (counts, selected) => [{
45
- value: ALL,
46
- label: 'All',
47
- count: counts.total
48
- }, ...STATUS_FILTERS.filter(s => counts[s.count] > 0 || s.value === selected).map(s => ({
49
- value: s.value,
50
- label: s.label,
51
- count: counts[s.count]
52
- }))];
53
- exports.statusFilterOptions = statusFilterOptions;
54
- const SEARCH_DEBOUNCE_MS = 400;
55
- const SupportTicketList = ({
56
- onOpenTicket,
57
- onNewTicket,
58
- header,
59
- pageSize,
60
- testID = 'support-ticket-list'
61
- }) => {
62
- const theme = (0, _mobileUi.useTheme)();
63
- const styles = (0, _react.useMemo)(() => buildStyles(theme), [theme]);
64
- const {
65
- tickets,
66
- counts,
67
- status,
68
- search,
69
- setStatus,
70
- setSearch,
71
- loading,
72
- refreshing,
73
- loadingMore,
74
- error,
75
- loadMoreError,
76
- refresh,
77
- loadMore
78
- } = (0, _SupportProvider.useSupportTickets)({
79
- pageSize
80
- });
81
- const hasAnyTicket = (counts?.total ?? 0) > 0;
82
- const filtered = status !== null || search.trim() !== '';
83
- const listHeader = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
84
- style: styles.header,
85
- children: [header, onNewTicket && /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Button, {
86
- title: "New ticket",
87
- leftIcon: "add",
88
- onPress: onNewTicket,
89
- fullWidth: true
90
- }), error && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
91
- style: styles.error,
92
- accessibilityLiveRegion: "polite",
93
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Text, {
94
- variant: "bodySmall",
95
- color: "error",
96
- children: error.message
97
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Button, {
98
- title: "Try again",
99
- size: "sm",
100
- variant: "ghost",
101
- onPress: () => void refresh()
102
- })]
103
- }), counts && hasAnyTicket && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
104
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.SearchBar, {
105
- value: search,
106
- onChangeText: setSearch,
107
- debounceMs: SEARCH_DEBOUNCE_MS,
108
- placeholder: "Search by subject or ticket number",
109
- accessibilityLabel: "Search tickets",
110
- testID: "support-ticket-search"
111
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
112
- horizontal: true,
113
- showsHorizontalScrollIndicator: false,
114
- contentContainerStyle: styles.filters,
115
- accessibilityRole: "tablist",
116
- accessibilityLabel: "Filter by status",
117
- children: statusFilterOptions(counts, status).map(o => /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Chip, {
118
- label: `${o.label} (${o.count})`,
119
- selected: (status ?? ALL) === o.value,
120
- onPress: () => setStatus(o.value === ALL ? null : o.value),
121
- accessibilityLabel: `${o.label}, ${o.count} tickets`,
122
- testID: `support-filter-${o.value}`
123
- }, o.value))
124
- })]
125
- })]
126
- });
127
- if (loading && counts === null && !error) {
128
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
129
- style: styles.center,
130
- testID: testID,
131
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Spinner, {
132
- accessibilityLabel: "Loading your tickets"
133
- })
134
- });
135
- }
136
- const empty = () => {
137
- if (loading) {
138
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
139
- style: styles.footer,
140
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Spinner, {
141
- accessibilityLabel: "Loading your tickets"
142
- })
143
- });
144
- }
145
- if (error) {
146
- return null;
147
- }
148
- if (!hasAnyTicket) {
149
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.EmptyState, {
150
- title: "No tickets yet",
151
- description: "Raise a ticket and it will show here with its status.",
152
- action: onNewTicket ? {
153
- label: 'New ticket',
154
- onPress: onNewTicket
155
- } : undefined
156
- });
157
- }
158
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.EmptyState, {
159
- title: "No tickets match",
160
- description: filtered ? 'Try another status or search.' : undefined
161
- });
162
- };
163
- const footer = loadingMore ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
164
- style: styles.footer,
165
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Spinner, {
166
- size: "sm",
167
- accessibilityLabel: "Loading more tickets"
168
- })
169
- }) : loadMoreError ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
170
- style: [styles.footer, styles.error],
171
- accessibilityLiveRegion: "polite",
172
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Text, {
173
- variant: "bodySmall",
174
- color: "error",
175
- children: loadMoreError.message
176
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Button, {
177
- title: "Load more",
178
- size: "sm",
179
- variant: "ghost",
180
- onPress: () => void loadMore()
181
- })]
182
- }) : null;
183
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
184
- testID: testID,
185
- data: tickets,
186
- keyExtractor: t => t.id,
187
- contentContainerStyle: styles.content,
188
- ListHeaderComponent: listHeader,
189
- ListFooterComponent: footer,
190
- onEndReached: () => void loadMore(),
191
- onEndReachedThreshold: 0.5,
192
- refreshControl: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.RefreshControl, {
193
- refreshing: refreshing,
194
- onRefresh: () => void refresh()
195
- }),
196
- ListEmptyComponent: empty(),
197
- renderItem: ({
198
- item
199
- }) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_mobileUi.Card, {
200
- onPress: () => onOpenTicket(item),
201
- accessibilityLabel: `${item.subject}, ${item.statusLabel}, ticket ${item.number}`,
202
- bodyStyle: styles.card,
203
- testID: `support-ticket-${item.id}`,
204
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
205
- style: styles.cardTop,
206
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Text, {
207
- variant: "caption",
208
- color: "tertiary",
209
- children: item.number
210
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.StatusBadge, {
211
- label: item.statusLabel,
212
- tone: STATUS_TONES[item.status] ?? 'neutral',
213
- size: "sm"
214
- })]
215
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Text, {
216
- variant: "title",
217
- numberOfLines: 2,
218
- children: item.subject
219
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Text, {
220
- variant: "bodySmall",
221
- color: "secondary",
222
- children: item.category
223
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
224
- style: styles.cardBottom,
225
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Text, {
226
- variant: "caption",
227
- color: "tertiary",
228
- children: `Updated ${(0, _format.formatWhen)(item.updatedDateTimeUtc)}`
229
- }), item.awaitingCustomer && /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Text, {
230
- variant: "caption",
231
- color: "warning",
232
- children: "Waiting for your reply"
233
- })]
234
- })]
235
- })
236
- });
237
- };
238
- exports.SupportTicketList = SupportTicketList;
239
- const buildStyles = theme => _reactNative.StyleSheet.create({
240
- content: {
241
- padding: theme.spacing.md,
242
- gap: theme.spacing.sm
243
- },
244
- header: {
245
- gap: theme.spacing.sm,
246
- marginBottom: theme.spacing.xs
247
- },
248
- filters: {
249
- gap: theme.spacing.xs
250
- },
251
- error: {
252
- gap: theme.spacing.xs,
253
- alignItems: 'flex-start'
254
- },
255
- center: {
256
- flex: 1,
257
- alignItems: 'center',
258
- justifyContent: 'center',
259
- padding: theme.spacing.xl
260
- },
261
- footer: {
262
- paddingVertical: theme.spacing.md,
263
- alignItems: 'center'
264
- },
265
- card: {
266
- gap: theme.spacing.xxs
267
- },
268
- cardTop: {
269
- flexDirection: 'row',
270
- justifyContent: 'space-between',
271
- alignItems: 'center'
272
- },
273
- cardBottom: {
274
- flexDirection: 'row',
275
- justifyContent: 'space-between',
276
- marginTop: theme.spacing.xxs
277
- }
278
- });
279
- //# sourceMappingURL=SupportTicketList.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.statusFilterOptions=exports.SupportTicketList=exports.STATUS_TONES=void 0;var _mobileUi=require("@webority/mobile-ui"),_react=require("react"),_reactNative=require("react-native"),_format=require("../format.js"),_SupportProvider=require("../SupportProvider.js"),_jsxRuntime=require("react/jsx-runtime");const STATUS_TONES=exports.STATUS_TONES={Open:"warning",InProgress:"info",Resolved:"success",Closed:"neutral"},STATUS_FILTERS=[{value:"Open",label:"Open",count:"open"},{value:"InProgress",label:"In progress",count:"inProgress"},{value:"Resolved",label:"Resolved",count:"resolved"},{value:"Closed",label:"Closed",count:"closed"}],ALL="all",statusFilterOptions=(e,t)=>[{value:ALL,label:"All",count:e.total},...STATUS_FILTERS.filter(i=>e[i.count]>0||i.value===t).map(t=>({value:t.value,label:t.label,count:e[t.count]}))];exports.statusFilterOptions=statusFilterOptions;const SEARCH_DEBOUNCE_MS=400,SupportTicketList=({onOpenTicket:e,onNewTicket:t,header:i,pageSize:s,testID:r="support-ticket-list"})=>{const o=(0,_mobileUi.useTheme)(),a=(0,_react.useMemo)(()=>buildStyles(o),[o]),{tickets:n,counts:l,status:c,search:u,setStatus:m,setSearch:p,loading:d,refreshing:x,loadingMore:_,error:b,loadMoreError:j,refresh:S,loadMore:h}=(0,_SupportProvider.useSupportTickets)({pageSize:s}),g=(0,_SupportProvider.useSupportTimeZone)(),v=(l?.total??0)>0,y=null!==c||""!==u.trim(),R=(0,_jsxRuntime.jsxs)(_reactNative.View,{style:a.header,children:[i,t&&(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"New ticket",leftIcon:"add",onPress:t,fullWidth:!0}),b&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:a.error,accessibilityLiveRegion:"polite",children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"error",children:b.message}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Try again",size:"sm",variant:"ghost",onPress:()=>{S()}})]}),l&&v&&(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_mobileUi.SearchBar,{value:u,onChangeText:p,debounceMs:400,placeholder:"Search by subject or ticket number",accessibilityLabel:"Search tickets",testID:"support-ticket-search"}),(0,_jsxRuntime.jsx)(_reactNative.ScrollView,{horizontal:!0,showsHorizontalScrollIndicator:!1,contentContainerStyle:a.filters,accessibilityRole:"tablist",accessibilityLabel:"Filter by status",children:statusFilterOptions(l,c).map(e=>(0,_jsxRuntime.jsx)(_mobileUi.Chip,{label:`${e.label} (${e.count})`,selected:(c??ALL)===e.value,onPress:()=>m(e.value===ALL?null:e.value),accessibilityLabel:`${e.label}, ${e.count} tickets`,testID:`support-filter-${e.value}`},e.value))})]})]});if(d&&null===l&&!b)return(0,_jsxRuntime.jsx)(_reactNative.View,{style:a.center,testID:r,children:(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{accessibilityLabel:"Loading your tickets"})});const T=_?(0,_jsxRuntime.jsx)(_reactNative.View,{style:a.footer,children:(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{size:"sm",accessibilityLabel:"Loading more tickets"})}):j?(0,_jsxRuntime.jsxs)(_reactNative.View,{style:[a.footer,a.error],accessibilityLiveRegion:"polite",children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"error",children:j.message}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Load more",size:"sm",variant:"ghost",onPress:()=>{h()}})]}):null;return(0,_jsxRuntime.jsx)(_reactNative.FlatList,{testID:r,data:n,keyExtractor:e=>e.id,contentContainerStyle:a.content,ListHeaderComponent:R,ListFooterComponent:T,onEndReached:()=>{h()},onEndReachedThreshold:.5,refreshControl:(0,_jsxRuntime.jsx)(_reactNative.RefreshControl,{refreshing:x,onRefresh:()=>{S()}}),ListEmptyComponent:d?(0,_jsxRuntime.jsx)(_reactNative.View,{style:a.footer,children:(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{accessibilityLabel:"Loading your tickets"})}):b?null:v?(0,_jsxRuntime.jsx)(_mobileUi.EmptyState,{title:"No tickets match",description:y?"Try another status or search.":void 0}):(0,_jsxRuntime.jsx)(_mobileUi.EmptyState,{title:"No tickets yet",description:"Raise a ticket and it will show here with its status.",action:t?{label:"New ticket",onPress:t}:void 0}),renderItem:({item:t})=>(0,_jsxRuntime.jsxs)(_mobileUi.Card,{onPress:()=>e(t),accessibilityLabel:`${t.subject}, ${t.statusLabel}, ticket ${t.number}`,bodyStyle:a.card,testID:`support-ticket-${t.id}`,children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:a.cardTop,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",children:t.number}),(0,_jsxRuntime.jsx)(_mobileUi.StatusBadge,{label:t.statusLabel,tone:STATUS_TONES[t.status]??"neutral",size:"sm"})]}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"title",numberOfLines:2,children:t.subject}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"secondary",children:t.category}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:a.cardBottom,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",children:`Updated ${(0,_format.formatWhen)(t.updatedDateTimeUtc,g)}`}),t.awaitingCustomer&&(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"warning",children:"Waiting for your reply"})]})]})})};exports.SupportTicketList=SupportTicketList;const buildStyles=e=>_reactNative.StyleSheet.create({content:{padding:e.spacing.md,gap:e.spacing.sm},header:{gap:e.spacing.sm,marginBottom:e.spacing.xs},filters:{gap:e.spacing.xs},error:{gap:e.spacing.xs,alignItems:"flex-start"},center:{flex:1,alignItems:"center",justifyContent:"center",padding:e.spacing.xl},footer:{paddingVertical:e.spacing.md,alignItems:"center"},card:{gap:e.spacing.xxs},cardTop:{flexDirection:"row",justifyContent:"space-between",alignItems:"center"},cardBottom:{flexDirection:"row",justifyContent:"space-between",marginTop:e.spacing.xxs}});
@@ -1,29 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.describeDevice = void 0;
7
- var _mobileCore = require("@webority/mobile-core");
8
- var _reactNative = require("react-native");
9
- const PLATFORM_NAMES = {
10
- ios: 'iOS',
11
- android: 'Android'
12
- };
13
-
14
- /**
15
- * The device line a ticket carries as its user agent: platform, OS version and,
16
- * when the app registered one with mobile-core's `setDeviceContext`, the model.
17
- */
18
- const describeDevice = () => {
19
- const context = (0, _mobileCore.getDeviceContext)();
20
- const platform = PLATFORM_NAMES[_reactNative.Platform.OS] ?? _reactNative.Platform.OS;
21
- const osVersion = context?.osVersion ?? String(_reactNative.Platform.Version ?? '');
22
- const parts = [`${platform} ${osVersion}`.trim()];
23
- if (context?.deviceModel) {
24
- parts.push(context.deviceModel);
25
- }
26
- return parts.join('; ');
27
- };
28
- exports.describeDevice = describeDevice;
29
- //# sourceMappingURL=device.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.describeDevice=void 0;var _mobileCore=require("@webority/mobile-core"),_reactNative=require("react-native");const PLATFORM_NAMES={ios:"iOS",android:"Android"},describeDevice=()=>{const e=(0,_mobileCore.getDeviceContext)(),r=[`${PLATFORM_NAMES[_reactNative.Platform.OS]??_reactNative.Platform.OS} ${e?.osVersion??String(_reactNative.Platform.Version??"")}`.trim()];return e?.deviceModel&&r.push(e.deviceModel),r.join("; ")};exports.describeDevice=describeDevice;
@@ -1,37 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.saveAttachmentToCache = exports.safeFileName = void 0;
7
- var _expoFileSystem = require("expo-file-system");
8
- const FOLDER = 'support-attachments';
9
-
10
- /** A server-supplied name reduced to one path segment, so it cannot leave the cache folder. */
11
- const safeFileName = name => {
12
- const last = name.split(/[/\\]/).pop() ?? '';
13
- const cleaned = Array.from(last).filter(char => (char.codePointAt(0) ?? 0) > 0x1f).join('').trim();
14
- return cleaned === '' || cleaned === '.' || cleaned === '..' ? 'attachment' : cleaned;
15
- };
16
-
17
- /**
18
- * Writes an attachment to the app's cache and returns its `file://` URI. The id
19
- * prefix keeps two tickets' `report.pdf` apart. Attachments never change once
20
- * stored, so a file already on disk is reused rather than fetched again.
21
- */
22
- exports.safeFileName = safeFileName;
23
- const saveAttachmentToCache = async (attachment, loadBytes) => {
24
- const file = new _expoFileSystem.File(_expoFileSystem.Paths.cache, FOLDER, `${attachment.id}-${safeFileName(attachment.fileName)}`);
25
- if (file.exists && file.size > 0) {
26
- return file.uri;
27
- }
28
- const bytes = await loadBytes();
29
- file.create({
30
- intermediates: true,
31
- overwrite: true
32
- });
33
- file.write(new Uint8Array(bytes));
34
- return file.uri;
35
- };
36
- exports.saveAttachmentToCache = saveAttachmentToCache;
37
- //# sourceMappingURL=files.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.saveAttachmentToCache=exports.safeFileName=void 0;var _expoFileSystem=require("expo-file-system");const FOLDER="support-attachments",safeFileName=e=>{const t=e.split(/[/\\]/).pop()??"",a=Array.from(t).filter(e=>(e.codePointAt(0)??0)>31).join("").trim();return""===a||"."===a||".."===a?"attachment":a};exports.safeFileName=safeFileName;const saveAttachmentToCache=async(e,t)=>{const a=new _expoFileSystem.File(_expoFileSystem.Paths.cache,FOLDER,`${e.id}-${safeFileName(e.fileName)}`);if(a.exists&&a.size>0)return a.uri;const s=await t();return a.create({intermediates:!0,overwrite:!0}),a.write(new Uint8Array(s)),a.uri};exports.saveAttachmentToCache=saveAttachmentToCache;
@@ -1,29 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.isImage = exports.formatWhen = exports.formatBytes = void 0;
7
- var _mobileCore = require("@webority/mobile-core");
8
- /**
9
- * A timestamp in the device's own time zone. The backend sends UTC; mobile-core's
10
- * formatter goes through Intl without pinning a zone, so the reader sees their
11
- * own clock.
12
- */
13
- const formatWhen = utc => utc ? (0, _mobileCore.formatDateTime)(utc) || '-' : '-';
14
-
15
- /** A byte count as a person reads it. */
16
- exports.formatWhen = formatWhen;
17
- const formatBytes = bytes => {
18
- if (bytes >= 1024 * 1024) {
19
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
20
- }
21
- if (bytes >= 1024) {
22
- return `${Math.round(bytes / 1024)} KB`;
23
- }
24
- return `${bytes} bytes`;
25
- };
26
- exports.formatBytes = formatBytes;
27
- const isImage = contentType => contentType.startsWith('image/');
28
- exports.isImage = isImage;
29
- //# sourceMappingURL=format.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.isImage=exports.formatWhen=exports.formatBytes=exports.DEFAULT_TIME_ZONE=void 0;const DEFAULT_TIME_ZONE=exports.DEFAULT_TIME_ZONE="Asia/Kolkata",formatWhen=(t,e=DEFAULT_TIME_ZONE)=>{if(!t)return"-";const o=new Date(t);if(Number.isNaN(o.getTime()))return"-";const r={};for(const t of new Intl.DateTimeFormat("en-US",{timeZone:e,day:"2-digit",month:"short",year:"numeric",hour:"numeric",minute:"2-digit",hour12:!0}).formatToParts(o))r[t.type]=t.value;return`${r.day}-${r.month}-${r.year}, ${r.hour}:${r.minute} ${(r.dayPeriod??"").toUpperCase()}`};exports.formatWhen=formatWhen;const formatBytes=t=>t>=1048576?`${(t/1048576).toFixed(1)} MB`:t>=1024?`${Math.round(t/1024)} KB`:`${t} bytes`;exports.formatBytes=formatBytes;const isImage=t=>t.startsWith("image/");exports.isImage=isImage;
@@ -1,97 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- Object.defineProperty(exports, "NewSupportTicket", {
7
- enumerable: true,
8
- get: function () {
9
- return _NewSupportTicket.NewSupportTicket;
10
- }
11
- });
12
- Object.defineProperty(exports, "SupportApiError", {
13
- enumerable: true,
14
- get: function () {
15
- return _client.SupportApiError;
16
- }
17
- });
18
- Object.defineProperty(exports, "SupportContact", {
19
- enumerable: true,
20
- get: function () {
21
- return _SupportContact.SupportContact;
22
- }
23
- });
24
- Object.defineProperty(exports, "SupportProvider", {
25
- enumerable: true,
26
- get: function () {
27
- return _SupportProvider.SupportProvider;
28
- }
29
- });
30
- Object.defineProperty(exports, "SupportTicketDetail", {
31
- enumerable: true,
32
- get: function () {
33
- return _SupportTicketDetail.SupportTicketDetail;
34
- }
35
- });
36
- Object.defineProperty(exports, "SupportTicketList", {
37
- enumerable: true,
38
- get: function () {
39
- return _SupportTicketList.SupportTicketList;
40
- }
41
- });
42
- Object.defineProperty(exports, "createSupportClient", {
43
- enumerable: true,
44
- get: function () {
45
- return _client.createSupportClient;
46
- }
47
- });
48
- Object.defineProperty(exports, "describeDevice", {
49
- enumerable: true,
50
- get: function () {
51
- return _device.describeDevice;
52
- }
53
- });
54
- Object.defineProperty(exports, "toSupportApiError", {
55
- enumerable: true,
56
- get: function () {
57
- return _client.toSupportApiError;
58
- }
59
- });
60
- Object.defineProperty(exports, "useCreateSupportTicket", {
61
- enumerable: true,
62
- get: function () {
63
- return _SupportProvider.useCreateSupportTicket;
64
- }
65
- });
66
- Object.defineProperty(exports, "useSupportClient", {
67
- enumerable: true,
68
- get: function () {
69
- return _SupportProvider.useSupportClient;
70
- }
71
- });
72
- Object.defineProperty(exports, "useSupportConfig", {
73
- enumerable: true,
74
- get: function () {
75
- return _SupportProvider.useSupportConfig;
76
- }
77
- });
78
- Object.defineProperty(exports, "useSupportTicket", {
79
- enumerable: true,
80
- get: function () {
81
- return _SupportProvider.useSupportTicket;
82
- }
83
- });
84
- Object.defineProperty(exports, "useSupportTickets", {
85
- enumerable: true,
86
- get: function () {
87
- return _SupportProvider.useSupportTickets;
88
- }
89
- });
90
- var _client = require("./client.js");
91
- var _NewSupportTicket = require("./components/NewSupportTicket.js");
92
- var _SupportContact = require("./components/SupportContact.js");
93
- var _SupportTicketDetail = require("./components/SupportTicketDetail.js");
94
- var _SupportTicketList = require("./components/SupportTicketList.js");
95
- var _device = require("./device.js");
96
- var _SupportProvider = require("./SupportProvider.js");
97
- //# sourceMappingURL=index.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),Object.defineProperty(exports,"NewSupportTicket",{enumerable:!0,get:function(){return _NewSupportTicket.NewSupportTicket}}),Object.defineProperty(exports,"SupportApiError",{enumerable:!0,get:function(){return _client.SupportApiError}}),Object.defineProperty(exports,"SupportContact",{enumerable:!0,get:function(){return _SupportContact.SupportContact}}),Object.defineProperty(exports,"SupportProvider",{enumerable:!0,get:function(){return _SupportProvider.SupportProvider}}),Object.defineProperty(exports,"SupportTicketDetail",{enumerable:!0,get:function(){return _SupportTicketDetail.SupportTicketDetail}}),Object.defineProperty(exports,"SupportTicketList",{enumerable:!0,get:function(){return _SupportTicketList.SupportTicketList}}),Object.defineProperty(exports,"createSupportClient",{enumerable:!0,get:function(){return _client.createSupportClient}}),Object.defineProperty(exports,"describeDevice",{enumerable:!0,get:function(){return _device.describeDevice}}),Object.defineProperty(exports,"toSupportApiError",{enumerable:!0,get:function(){return _client.toSupportApiError}}),Object.defineProperty(exports,"useCreateSupportTicket",{enumerable:!0,get:function(){return _SupportProvider.useCreateSupportTicket}}),Object.defineProperty(exports,"useSupportClient",{enumerable:!0,get:function(){return _SupportProvider.useSupportClient}}),Object.defineProperty(exports,"useSupportConfig",{enumerable:!0,get:function(){return _SupportProvider.useSupportConfig}}),Object.defineProperty(exports,"useSupportTicket",{enumerable:!0,get:function(){return _SupportProvider.useSupportTicket}}),Object.defineProperty(exports,"useSupportTickets",{enumerable:!0,get:function(){return _SupportProvider.useSupportTickets}});var _client=require("./client.js"),_NewSupportTicket=require("./components/NewSupportTicket.js"),_SupportContact=require("./components/SupportContact.js"),_SupportTicketDetail=require("./components/SupportTicketDetail.js"),_SupportTicketList=require("./components/SupportTicketList.js"),_device=require("./device.js"),_SupportProvider=require("./SupportProvider.js");
@@ -1,67 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.pickPhotos = exports.PhotoPermissionError = void 0;
7
- var _compressor = require("@webority/mobile-core/compressor");
8
- /** The one place `expo-image-picker` is named. */
9
- const picker = () => require('expo-image-picker');
10
- class PhotoPermissionError extends Error {
11
- constructor(source) {
12
- super(source === 'camera' ? 'Allow camera access in Settings to take a photo.' : 'Allow photo access in Settings to attach a photo.');
13
- this.name = 'PhotoPermissionError';
14
- this.source = source;
15
- }
16
- }
17
-
18
- // The fleet capture pass: quality 0.8, no editing, then bound to 1920 px.
19
- exports.PhotoPermissionError = PhotoPermissionError;
20
- const CAPTURE_OPTIONS = {
21
- mediaTypes: ['images'],
22
- quality: 0.8,
23
- allowsEditing: false
24
- };
25
- const MAX_SIDE = 1920;
26
-
27
- /**
28
- * Lets the person pick or take photos and returns them ready to upload,
29
- * compressed to fit the product's per-file limit. An empty list means they
30
- * cancelled.
31
- */
32
- const pickPhotos = async (source, options) => {
33
- if (options.maxCount <= 0) {
34
- return [];
35
- }
36
- const m = picker();
37
- const permission = source === 'camera' ? await m.requestCameraPermissionsAsync() : await m.requestMediaLibraryPermissionsAsync();
38
- if (!permission.granted) {
39
- throw new PhotoPermissionError(source);
40
- }
41
- const result = source === 'camera' ? await m.launchCameraAsync(CAPTURE_OPTIONS) : await m.launchImageLibraryAsync({
42
- ...CAPTURE_OPTIONS,
43
- allowsMultipleSelection: options.maxCount > 1,
44
- selectionLimit: options.maxCount
45
- });
46
- if (result.canceled || !result.assets) {
47
- return [];
48
- }
49
- const parts = [];
50
- for (const asset of result.assets.slice(0, options.maxCount)) {
51
- const compressed = await (0, _compressor.compressImage)(asset.uri, {
52
- maxSize: MAX_SIDE,
53
- maxBytes: options.maxBytes,
54
- fileSize: asset.fileSize ?? undefined,
55
- name: asset.fileName ?? `photo-${Date.now()}-${parts.length + 1}`
56
- });
57
- parts.push({
58
- uri: compressed.part.uri,
59
- name: compressed.part.name,
60
- type: compressed.part.type,
61
- size: compressed.size ?? undefined
62
- });
63
- }
64
- return parts;
65
- };
66
- exports.pickPhotos = pickPhotos;
67
- //# sourceMappingURL=photos.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.pickPhotos=exports.PhotoPermissionError=void 0;var _compressor=require("@webority/mobile-core/compressor");const picker=()=>require("expo-image-picker");class PhotoPermissionError extends Error{constructor(e){super("camera"===e?"Allow camera access in Settings to take a photo.":"Allow photo access in Settings to attach a photo."),this.name="PhotoPermissionError",this.source=e}}exports.PhotoPermissionError=PhotoPermissionError;const CAPTURE_OPTIONS={mediaTypes:["images"],quality:.8,allowsEditing:!1},MAX_SIDE=1920,pickPhotos=async(e,o)=>{if(o.maxCount<=0)return[];const r=picker();if(!("camera"===e?await r.requestCameraPermissionsAsync():await r.requestMediaLibraryPermissionsAsync()).granted)throw new PhotoPermissionError(e);const s="camera"===e?await r.launchCameraAsync(CAPTURE_OPTIONS):await r.launchImageLibraryAsync({...CAPTURE_OPTIONS,allowsMultipleSelection:o.maxCount>1,selectionLimit:o.maxCount});if(s.canceled||!s.assets)return[];const t=[];for(const e of s.assets.slice(0,o.maxCount)){const r=await(0,_compressor.compressImage)(e.uri,{maxSize:1920,maxBytes:o.maxBytes,fileSize:e.fileSize??void 0,name:e.fileName??`photo-${Date.now()}-${t.length+1}`});t.push({uri:r.part.uri,name:r.part.name,type:r.part.type,size:r.size??void 0})}return t};exports.pickPhotos=pickPhotos;
@@ -1,57 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.ReportIssueButton = void 0;
7
- var _mobileUi = require("@webority/mobile-ui");
8
- var _react = require("react");
9
- var _captureAndReport = require("./captureAndReport.js");
10
- var _jsxRuntime = require("react/jsx-runtime");
11
- /** A button that screenshots the current screen and hands a prefilled ticket draft to the app. */
12
- const ReportIssueButton = ({
13
- onReport,
14
- onCaptureError,
15
- screenName,
16
- appVersion,
17
- category,
18
- subject,
19
- title = 'Report an issue',
20
- accessibilityLabel = 'Report an issue with this screen',
21
- testID = 'support-report-issue',
22
- ...buttonProps
23
- }) => {
24
- const [capturing, setCapturing] = (0, _react.useState)(false);
25
- const press = async () => {
26
- setCapturing(true);
27
- try {
28
- onReport(await (0, _captureAndReport.captureAndReport)({
29
- screenName,
30
- appVersion,
31
- category,
32
- subject
33
- }));
34
- } catch (error) {
35
- onCaptureError?.(error);
36
- onReport({
37
- category,
38
- subject,
39
- pageUrl: screenName,
40
- appVersion
41
- });
42
- } finally {
43
- setCapturing(false);
44
- }
45
- };
46
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_mobileUi.Button, {
47
- leftIcon: "message-question",
48
- ...buttonProps,
49
- title: title,
50
- accessibilityLabel: accessibilityLabel,
51
- testID: testID,
52
- loading: capturing,
53
- onPress: () => void press()
54
- });
55
- };
56
- exports.ReportIssueButton = ReportIssueButton;
57
- //# sourceMappingURL=ReportIssueButton.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.ReportIssueButton=void 0;var _mobileUi=require("@webority/mobile-ui"),_react=require("react"),_captureAndReport=require("./captureAndReport.js"),_jsxRuntime=require("react/jsx-runtime");const ReportIssueButton=({onReport:e,onCaptureError:t,screenName:r,appVersion:s,category:o,subject:i,title:u="Report an issue",accessibilityLabel:a="Report an issue with this screen",testID:n="support-report-issue",...p})=>{const[c,l]=(0,_react.useState)(!1);return(0,_jsxRuntime.jsx)(_mobileUi.Button,{leftIcon:"message-question",...p,title:u,accessibilityLabel:a,testID:n,loading:c,onPress:()=>{(async()=>{l(!0);try{e(await(0,_captureAndReport.captureAndReport)({screenName:r,appVersion:s,category:o,subject:i}))}catch(u){t?.(u),e({category:o,subject:i,pageUrl:r,appVersion:s})}finally{l(!1)}})()}})};exports.ReportIssueButton=ReportIssueButton;
@@ -1,34 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.captureAndReport = void 0;
7
- var _device = require("../device.js");
8
- /** The one place `react-native-view-shot` is named, and only this subpath reaches it. */
9
- const viewShot = () => require('react-native-view-shot');
10
- /**
11
- * Captures what is on screen right now and returns a draft for NewSupportTicket
12
- * with the image under `screenshots`, plus the page, device and app version.
13
- */
14
- const captureAndReport = async options => {
15
- const uri = await viewShot().captureScreen({
16
- format: 'jpg',
17
- quality: 0.8,
18
- result: 'tmpfile'
19
- });
20
- return {
21
- category: options.category,
22
- subject: options.subject,
23
- pageUrl: options.screenName,
24
- userAgent: (0, _device.describeDevice)(),
25
- appVersion: options.appVersion,
26
- screenshots: [{
27
- uri,
28
- name: `screenshot-${Date.now()}.jpg`,
29
- type: 'image/jpeg'
30
- }]
31
- };
32
- };
33
- exports.captureAndReport = captureAndReport;
34
- //# sourceMappingURL=captureAndReport.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.captureAndReport=void 0;var _device=require("../device.js");const viewShot=()=>require("react-native-view-shot"),captureAndReport=async e=>{const t=await viewShot().captureScreen({format:"jpg",quality:.8,result:"tmpfile"});return{category:e.category,subject:e.subject,pageUrl:e.screenName,userAgent:(0,_device.describeDevice)(),appVersion:e.appVersion,screenshots:[{uri:t,name:`screenshot-${Date.now()}.jpg`,type:"image/jpeg"}]}};exports.captureAndReport=captureAndReport;