@webority/mobile-support 0.0.9
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/LICENSE +21 -0
- package/README.md +146 -0
- package/lib/commonjs/SupportProvider.js +335 -0
- package/lib/commonjs/client.js +164 -0
- package/lib/commonjs/clipboard.js +10 -0
- package/lib/commonjs/components/Attachments.js +246 -0
- package/lib/commonjs/components/NewSupportTicket.js +210 -0
- package/lib/commonjs/components/PhotoTray.js +141 -0
- package/lib/commonjs/components/SupportContact.js +134 -0
- package/lib/commonjs/components/SupportTicketDetail.js +348 -0
- package/lib/commonjs/components/SupportTicketList.js +279 -0
- package/lib/commonjs/device.js +29 -0
- package/lib/commonjs/files.js +37 -0
- package/lib/commonjs/format.js +29 -0
- package/lib/commonjs/index.js +97 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/photos.js +67 -0
- package/lib/commonjs/report/ReportIssueButton.js +57 -0
- package/lib/commonjs/report/captureAndReport.js +34 -0
- package/lib/commonjs/report/index.js +20 -0
- package/lib/commonjs/share/index.js +44 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/validation.js +45 -0
- package/lib/module/SupportProvider.js +323 -0
- package/lib/module/client.js +156 -0
- package/lib/module/clipboard.js +5 -0
- package/lib/module/components/Attachments.js +240 -0
- package/lib/module/components/NewSupportTicket.js +205 -0
- package/lib/module/components/PhotoTray.js +136 -0
- package/lib/module/components/SupportContact.js +129 -0
- package/lib/module/components/SupportTicketDetail.js +343 -0
- package/lib/module/components/SupportTicketList.js +273 -0
- package/lib/module/device.js +24 -0
- package/lib/module/files.js +31 -0
- package/lib/module/format.js +23 -0
- package/lib/module/index.js +10 -0
- package/lib/module/package.json +1 -0
- package/lib/module/photos.js +61 -0
- package/lib/module/report/ReportIssueButton.js +52 -0
- package/lib/module/report/captureAndReport.js +29 -0
- package/lib/module/report/index.js +12 -0
- package/lib/module/share/index.js +37 -0
- package/lib/module/types.js +2 -0
- package/lib/module/validation.js +38 -0
- package/lib/typescript/commonjs/SupportProvider.d.ts +82 -0
- package/lib/typescript/commonjs/client.d.ts +63 -0
- package/lib/typescript/commonjs/clipboard.d.ts +3 -0
- package/lib/typescript/commonjs/components/Attachments.d.ts +16 -0
- package/lib/typescript/commonjs/components/NewSupportTicket.d.ts +12 -0
- package/lib/typescript/commonjs/components/PhotoTray.d.ts +17 -0
- package/lib/typescript/commonjs/components/SupportContact.d.ts +7 -0
- package/lib/typescript/commonjs/components/SupportTicketDetail.d.ts +7 -0
- package/lib/typescript/commonjs/components/SupportTicketList.d.ts +28 -0
- package/lib/typescript/commonjs/device.d.ts +6 -0
- package/lib/typescript/commonjs/files.d.ts +10 -0
- package/lib/typescript/commonjs/format.d.ts +10 -0
- package/lib/typescript/commonjs/index.d.ts +15 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/photos.d.ts +16 -0
- package/lib/typescript/commonjs/report/ReportIssueButton.d.ts +14 -0
- package/lib/typescript/commonjs/report/captureAndReport.d.ts +15 -0
- package/lib/typescript/commonjs/report/index.d.ts +11 -0
- package/lib/typescript/commonjs/share/index.d.ts +10 -0
- package/lib/typescript/commonjs/types.d.ts +152 -0
- package/lib/typescript/commonjs/validation.d.ts +10 -0
- package/lib/typescript/module/SupportProvider.d.ts +82 -0
- package/lib/typescript/module/client.d.ts +63 -0
- package/lib/typescript/module/clipboard.d.ts +3 -0
- package/lib/typescript/module/components/Attachments.d.ts +16 -0
- package/lib/typescript/module/components/NewSupportTicket.d.ts +12 -0
- package/lib/typescript/module/components/PhotoTray.d.ts +17 -0
- package/lib/typescript/module/components/SupportContact.d.ts +7 -0
- package/lib/typescript/module/components/SupportTicketDetail.d.ts +7 -0
- package/lib/typescript/module/components/SupportTicketList.d.ts +28 -0
- package/lib/typescript/module/device.d.ts +6 -0
- package/lib/typescript/module/files.d.ts +10 -0
- package/lib/typescript/module/format.d.ts +10 -0
- package/lib/typescript/module/index.d.ts +15 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/photos.d.ts +16 -0
- package/lib/typescript/module/report/ReportIssueButton.d.ts +14 -0
- package/lib/typescript/module/report/captureAndReport.d.ts +15 -0
- package/lib/typescript/module/report/index.d.ts +11 -0
- package/lib/typescript/module/share/index.d.ts +10 -0
- package/lib/typescript/module/types.d.ts +152 -0
- package/lib/typescript/module/validation.d.ts +10 -0
- package/package.json +124 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
export { createSupportClient, SupportApiError, toSupportApiError } from "./client.js";
|
|
4
|
+
export { NewSupportTicket } from "./components/NewSupportTicket.js";
|
|
5
|
+
export { SupportContact } from "./components/SupportContact.js";
|
|
6
|
+
export { SupportTicketDetail } from "./components/SupportTicketDetail.js";
|
|
7
|
+
export { SupportTicketList } from "./components/SupportTicketList.js";
|
|
8
|
+
export { describeDevice } from "./device.js";
|
|
9
|
+
export { SupportProvider, useCreateSupportTicket, useSupportClient, useSupportConfig, useSupportTicket, useSupportTickets } from "./SupportProvider.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { compressImage } from '@webority/mobile-core/compressor';
|
|
4
|
+
/** The one place `expo-image-picker` is named. */
|
|
5
|
+
const picker = () => require('expo-image-picker');
|
|
6
|
+
export class PhotoPermissionError extends Error {
|
|
7
|
+
constructor(source) {
|
|
8
|
+
super(source === 'camera' ? 'Allow camera access in Settings to take a photo.' : 'Allow photo access in Settings to attach a photo.');
|
|
9
|
+
this.name = 'PhotoPermissionError';
|
|
10
|
+
this.source = source;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// The fleet capture pass: quality 0.8, no editing, then bound to 1920 px.
|
|
15
|
+
const CAPTURE_OPTIONS = {
|
|
16
|
+
mediaTypes: ['images'],
|
|
17
|
+
quality: 0.8,
|
|
18
|
+
allowsEditing: false
|
|
19
|
+
};
|
|
20
|
+
const MAX_SIDE = 1920;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Lets the person pick or take photos and returns them ready to upload,
|
|
24
|
+
* compressed to fit the product's per-file limit. An empty list means they
|
|
25
|
+
* cancelled.
|
|
26
|
+
*/
|
|
27
|
+
export const pickPhotos = async (source, options) => {
|
|
28
|
+
if (options.maxCount <= 0) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
const m = picker();
|
|
32
|
+
const permission = source === 'camera' ? await m.requestCameraPermissionsAsync() : await m.requestMediaLibraryPermissionsAsync();
|
|
33
|
+
if (!permission.granted) {
|
|
34
|
+
throw new PhotoPermissionError(source);
|
|
35
|
+
}
|
|
36
|
+
const result = source === 'camera' ? await m.launchCameraAsync(CAPTURE_OPTIONS) : await m.launchImageLibraryAsync({
|
|
37
|
+
...CAPTURE_OPTIONS,
|
|
38
|
+
allowsMultipleSelection: options.maxCount > 1,
|
|
39
|
+
selectionLimit: options.maxCount
|
|
40
|
+
});
|
|
41
|
+
if (result.canceled || !result.assets) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
const parts = [];
|
|
45
|
+
for (const asset of result.assets.slice(0, options.maxCount)) {
|
|
46
|
+
const compressed = await compressImage(asset.uri, {
|
|
47
|
+
maxSize: MAX_SIDE,
|
|
48
|
+
maxBytes: options.maxBytes,
|
|
49
|
+
fileSize: asset.fileSize ?? undefined,
|
|
50
|
+
name: asset.fileName ?? `photo-${Date.now()}-${parts.length + 1}`
|
|
51
|
+
});
|
|
52
|
+
parts.push({
|
|
53
|
+
uri: compressed.part.uri,
|
|
54
|
+
name: compressed.part.name,
|
|
55
|
+
type: compressed.part.type,
|
|
56
|
+
size: compressed.size ?? undefined
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return parts;
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=photos.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { Button } from '@webority/mobile-ui';
|
|
4
|
+
import { useState } from 'react';
|
|
5
|
+
import { captureAndReport } from "./captureAndReport.js";
|
|
6
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
7
|
+
/** A button that screenshots the current screen and hands a prefilled ticket draft to the app. */
|
|
8
|
+
export const ReportIssueButton = ({
|
|
9
|
+
onReport,
|
|
10
|
+
onCaptureError,
|
|
11
|
+
screenName,
|
|
12
|
+
appVersion,
|
|
13
|
+
category,
|
|
14
|
+
subject,
|
|
15
|
+
title = 'Report an issue',
|
|
16
|
+
accessibilityLabel = 'Report an issue with this screen',
|
|
17
|
+
testID = 'support-report-issue',
|
|
18
|
+
...buttonProps
|
|
19
|
+
}) => {
|
|
20
|
+
const [capturing, setCapturing] = useState(false);
|
|
21
|
+
const press = async () => {
|
|
22
|
+
setCapturing(true);
|
|
23
|
+
try {
|
|
24
|
+
onReport(await captureAndReport({
|
|
25
|
+
screenName,
|
|
26
|
+
appVersion,
|
|
27
|
+
category,
|
|
28
|
+
subject
|
|
29
|
+
}));
|
|
30
|
+
} catch (error) {
|
|
31
|
+
onCaptureError?.(error);
|
|
32
|
+
onReport({
|
|
33
|
+
category,
|
|
34
|
+
subject,
|
|
35
|
+
pageUrl: screenName,
|
|
36
|
+
appVersion
|
|
37
|
+
});
|
|
38
|
+
} finally {
|
|
39
|
+
setCapturing(false);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
return /*#__PURE__*/_jsx(Button, {
|
|
43
|
+
leftIcon: "message-question",
|
|
44
|
+
...buttonProps,
|
|
45
|
+
title: title,
|
|
46
|
+
accessibilityLabel: accessibilityLabel,
|
|
47
|
+
testID: testID,
|
|
48
|
+
loading: capturing,
|
|
49
|
+
onPress: () => void press()
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=ReportIssueButton.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { describeDevice } from "../device.js";
|
|
4
|
+
/** The one place `react-native-view-shot` is named, and only this subpath reaches it. */
|
|
5
|
+
const viewShot = () => require('react-native-view-shot');
|
|
6
|
+
/**
|
|
7
|
+
* Captures what is on screen right now and returns a draft for NewSupportTicket
|
|
8
|
+
* with the image under `screenshots`, plus the page, device and app version.
|
|
9
|
+
*/
|
|
10
|
+
export const captureAndReport = async options => {
|
|
11
|
+
const uri = await viewShot().captureScreen({
|
|
12
|
+
format: 'jpg',
|
|
13
|
+
quality: 0.8,
|
|
14
|
+
result: 'tmpfile'
|
|
15
|
+
});
|
|
16
|
+
return {
|
|
17
|
+
category: options.category,
|
|
18
|
+
subject: options.subject,
|
|
19
|
+
pageUrl: options.screenName,
|
|
20
|
+
userAgent: describeDevice(),
|
|
21
|
+
appVersion: options.appVersion,
|
|
22
|
+
screenshots: [{
|
|
23
|
+
uri,
|
|
24
|
+
name: `screenshot-${Date.now()}.jpg`,
|
|
25
|
+
type: 'image/jpeg'
|
|
26
|
+
}]
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=captureAndReport.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Report an issue. A separate entry point because it names react-native-view-shot,
|
|
5
|
+
* an optional peer: Metro resolves every require reachable from an import, so
|
|
6
|
+
* keeping it off the main barrel lets an app skip the package when it does not
|
|
7
|
+
* use this button.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export { captureAndReport } from "./captureAndReport.js";
|
|
11
|
+
export { ReportIssueButton } from "./ReportIssueButton.js";
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Opens a support attachment through the system share sheet, by way of
|
|
5
|
+
* mobile-core's `/share`. A separate entry point because that reaches
|
|
6
|
+
* expo-sharing, an optional peer: Metro resolves every require reachable from an
|
|
7
|
+
* import, so keeping it off the main barrel lets an app skip the package when it
|
|
8
|
+
* does not open files.
|
|
9
|
+
*/
|
|
10
|
+
import { shareFile } from '@webority/mobile-core/share';
|
|
11
|
+
export class SupportShareUnavailableError extends Error {
|
|
12
|
+
constructor() {
|
|
13
|
+
super('Opening files is not available on this device.');
|
|
14
|
+
this.name = 'SupportShareUnavailableError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Hands a downloaded attachment to the OS share sheet, where the person can open
|
|
20
|
+
* it in a viewer or save it. Pass it to `SupportProvider` as `openFile`.
|
|
21
|
+
*/
|
|
22
|
+
export const shareSupportFile = async file => {
|
|
23
|
+
try {
|
|
24
|
+
await shareFile(file.uri, {
|
|
25
|
+
mimeType: file.contentType,
|
|
26
|
+
dialogTitle: file.name
|
|
27
|
+
});
|
|
28
|
+
} catch (error) {
|
|
29
|
+
// By name, not instanceof: a second copy of mobile-core would fail instanceof.
|
|
30
|
+
const e = error;
|
|
31
|
+
if (e?.name === 'ShareError' && e.code === 'unavailable') {
|
|
32
|
+
throw new SupportShareUnavailableError();
|
|
33
|
+
}
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
export const validateCategory = (category, config) => config.categories.includes(category) ? undefined : 'Choose one of the listed categories.';
|
|
4
|
+
|
|
5
|
+
/** Limits come from GET /settings, so the product's own values are the only copy. */
|
|
6
|
+
|
|
7
|
+
export const validateSubject = (subject, limits) => {
|
|
8
|
+
const trimmed = subject.trim();
|
|
9
|
+
if (!trimmed) {
|
|
10
|
+
return 'Add a short subject.';
|
|
11
|
+
}
|
|
12
|
+
return trimmed.length > limits.subjectMaxLength ? `Keep the subject under ${limits.subjectMaxLength} characters.` : undefined;
|
|
13
|
+
};
|
|
14
|
+
export const validateBody = (body, limits) => {
|
|
15
|
+
const trimmed = body.trim();
|
|
16
|
+
if (!trimmed) {
|
|
17
|
+
return 'Write a message.';
|
|
18
|
+
}
|
|
19
|
+
return trimmed.length > limits.bodyMaxLength ? `Keep the message under ${limits.bodyMaxLength} characters.` : undefined;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** Checks every file on one message against the product's limits. The first problem wins. */
|
|
23
|
+
export const validateFiles = (files, config) => {
|
|
24
|
+
if (files.length > config.maxAttachmentsPerMessage) {
|
|
25
|
+
return `Attach up to ${config.maxAttachmentsPerMessage} files to one message.`;
|
|
26
|
+
}
|
|
27
|
+
for (const file of files) {
|
|
28
|
+
if (config.allowedContentTypes.length > 0 && !config.allowedContentTypes.includes(file.type)) {
|
|
29
|
+
return `${file.name} can't be attached. Use an image, a PDF or a text file.`;
|
|
30
|
+
}
|
|
31
|
+
if (file.size !== undefined && file.size > config.maxAttachmentBytes) {
|
|
32
|
+
const mb = Math.floor(config.maxAttachmentBytes / (1024 * 1024));
|
|
33
|
+
return `${file.name} is too large. The limit is ${mb} MB per file.`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import { SupportApiError, type SupportClient } from './client';
|
|
3
|
+
import type { NewSupportTicketInput, SupportAttachmentView, SupportContactInfo, SupportRatingInput, SupportReplyInput, SupportTicketCounts, SupportTicketDetail, SupportTicketStatus, SupportTicketSummary } from './types';
|
|
4
|
+
/** A downloaded attachment, ready for the OS to open. */
|
|
5
|
+
export interface SupportFileToOpen {
|
|
6
|
+
/** A `file://` URI in the app's cache. */
|
|
7
|
+
uri: string;
|
|
8
|
+
name: string;
|
|
9
|
+
contentType: string;
|
|
10
|
+
}
|
|
11
|
+
/** Hands a downloaded file to the OS. `shareSupportFile` from the `/share` entry is one. */
|
|
12
|
+
export type SupportFileOpener = (file: SupportFileToOpen) => Promise<void>;
|
|
13
|
+
type AttachmentOpener = (attachment: Pick<SupportAttachmentView, 'id' | 'fileName' | 'contentType'>) => Promise<void>;
|
|
14
|
+
export interface SupportProviderProps {
|
|
15
|
+
client: SupportClient;
|
|
16
|
+
/**
|
|
17
|
+
* Opens a file that is not an image (a PDF, a text file). Leave it out and those
|
|
18
|
+
* attachments show their name and size only.
|
|
19
|
+
*/
|
|
20
|
+
openFile?: SupportFileOpener;
|
|
21
|
+
children: React.ReactNode;
|
|
22
|
+
}
|
|
23
|
+
export declare const SupportProvider: React.FC<SupportProviderProps>;
|
|
24
|
+
export declare const useSupportClient: () => SupportClient;
|
|
25
|
+
/** @internal Used by the attachment thumbnails. */
|
|
26
|
+
export declare const useAttachmentLoader: () => (attachment: Pick<SupportAttachmentView, "id" | "contentType">) => Promise<string>;
|
|
27
|
+
/** @internal Used by the file rows; null when the app gave no `openFile`. */
|
|
28
|
+
export declare const useAttachmentOpener: () => AttachmentOpener | null;
|
|
29
|
+
export interface SupportConfigState {
|
|
30
|
+
config: SupportContactInfo | null;
|
|
31
|
+
loading: boolean;
|
|
32
|
+
error: SupportApiError | null;
|
|
33
|
+
reload: () => Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export declare const useSupportConfig: () => SupportConfigState;
|
|
36
|
+
export interface SupportTicketsOptions {
|
|
37
|
+
/** Tickets per page. The server defaults to 20 and caps at 100. */
|
|
38
|
+
pageSize?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface SupportTicketsState {
|
|
41
|
+
/** Every page loaded so far for the current status and search. */
|
|
42
|
+
tickets: SupportTicketSummary[];
|
|
43
|
+
/** All of the caller's tickets by status, whatever the filter. Null until first loaded. */
|
|
44
|
+
counts: SupportTicketCounts | null;
|
|
45
|
+
status: SupportTicketStatus | null;
|
|
46
|
+
search: string;
|
|
47
|
+
/** Null shows every status. Starts again from the first page. */
|
|
48
|
+
setStatus: (status: SupportTicketStatus | null) => void;
|
|
49
|
+
/** Matches the subject or the ticket number. Starts again from the first page. */
|
|
50
|
+
setSearch: (search: string) => void;
|
|
51
|
+
/** The first page for the current status and search is loading. */
|
|
52
|
+
loading: boolean;
|
|
53
|
+
refreshing: boolean;
|
|
54
|
+
loadingMore: boolean;
|
|
55
|
+
hasMore: boolean;
|
|
56
|
+
error: SupportApiError | null;
|
|
57
|
+
/** The last failed next page. The pages already loaded stay. */
|
|
58
|
+
loadMoreError: SupportApiError | null;
|
|
59
|
+
/** Reloads the counts and the first page. */
|
|
60
|
+
refresh: () => Promise<void>;
|
|
61
|
+
/** Loads the next page, when there is one and nothing else is loading. */
|
|
62
|
+
loadMore: () => Promise<void>;
|
|
63
|
+
createTicket: (input: NewSupportTicketInput) => Promise<SupportTicketDetail>;
|
|
64
|
+
}
|
|
65
|
+
export declare const useSupportTickets: (options?: SupportTicketsOptions) => SupportTicketsState;
|
|
66
|
+
/** Raises a ticket and tells every mounted ticket list to refetch. */
|
|
67
|
+
export declare const useCreateSupportTicket: () => ((input: NewSupportTicketInput) => Promise<SupportTicketDetail>);
|
|
68
|
+
export interface SupportTicketState {
|
|
69
|
+
ticket: SupportTicketDetail | null;
|
|
70
|
+
loading: boolean;
|
|
71
|
+
error: SupportApiError | null;
|
|
72
|
+
/** The last reply, reopen or rating failure, for showing beside the action. */
|
|
73
|
+
actionError: SupportApiError | null;
|
|
74
|
+
busy: boolean;
|
|
75
|
+
reload: () => Promise<void>;
|
|
76
|
+
reply: (input: SupportReplyInput) => Promise<boolean>;
|
|
77
|
+
reopen: () => Promise<boolean>;
|
|
78
|
+
rate: (input: SupportRatingInput) => Promise<boolean>;
|
|
79
|
+
}
|
|
80
|
+
export declare const useSupportTicket: (id: string) => SupportTicketState;
|
|
81
|
+
export {};
|
|
82
|
+
//# sourceMappingURL=SupportProvider.d.ts.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { NewSupportTicketInput, SupportAttachmentView, SupportContactInfo, SupportPage, SupportRatingInput, SupportReplyInput, SupportTicketCounts, SupportTicketDetail, SupportTicketQuery, SupportTicketSummary } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The slice of an HTTP client the support client needs. The app's signed-in
|
|
4
|
+
* axios instance (mobile-core's `userClient`, or its own) satisfies it as is, so
|
|
5
|
+
* whatever it adds to a request (bearer token, tenant header, refresh on 401)
|
|
6
|
+
* applies to support calls too. Declared structurally so no axios type leaks into
|
|
7
|
+
* this package's declarations.
|
|
8
|
+
*/
|
|
9
|
+
export interface SupportHttpRequestConfig {
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
timeout?: number;
|
|
12
|
+
responseType?: 'arraybuffer' | 'json';
|
|
13
|
+
}
|
|
14
|
+
export interface SupportHttpClient {
|
|
15
|
+
get<T = unknown>(url: string, config?: SupportHttpRequestConfig): Promise<{
|
|
16
|
+
data: T;
|
|
17
|
+
}>;
|
|
18
|
+
post<T = unknown>(url: string, body?: unknown, config?: SupportHttpRequestConfig): Promise<{
|
|
19
|
+
data: T;
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
22
|
+
export declare const OFFLINE_MESSAGE = "You are offline. Check your connection and try again.";
|
|
23
|
+
export declare const GENERIC_MESSAGE = "Something went wrong. Try again in a moment.";
|
|
24
|
+
/**
|
|
25
|
+
* A failed support call. `message` is written for the person and is shown as is;
|
|
26
|
+
* `code` is the backend's stable code, for branching only.
|
|
27
|
+
*/
|
|
28
|
+
export declare class SupportApiError extends Error {
|
|
29
|
+
readonly code: string;
|
|
30
|
+
readonly status: number | null;
|
|
31
|
+
constructor(code: string, message: string, status: number | null);
|
|
32
|
+
}
|
|
33
|
+
/** Turns whatever the HTTP client threw into a SupportApiError. */
|
|
34
|
+
export declare const toSupportApiError: (error: unknown) => SupportApiError;
|
|
35
|
+
export declare const encodeBase64: (buffer: ArrayBuffer) => string;
|
|
36
|
+
export interface SupportClient {
|
|
37
|
+
readonly basePath: string;
|
|
38
|
+
getConfig(): Promise<SupportContactInfo>;
|
|
39
|
+
/** One page of the caller's tickets, most recently updated first. */
|
|
40
|
+
listTickets(query?: SupportTicketQuery): Promise<SupportPage<SupportTicketSummary>>;
|
|
41
|
+
/** The caller's tickets counted by status, for labelling the status filters. */
|
|
42
|
+
getTicketCounts(): Promise<SupportTicketCounts>;
|
|
43
|
+
getTicket(id: string): Promise<SupportTicketDetail>;
|
|
44
|
+
createTicket(input: NewSupportTicketInput): Promise<SupportTicketDetail>;
|
|
45
|
+
reply(id: string, input: SupportReplyInput): Promise<SupportTicketDetail>;
|
|
46
|
+
reopen(id: string): Promise<SupportTicketDetail>;
|
|
47
|
+
rate(id: string, input: SupportRatingInput): Promise<SupportTicketDetail>;
|
|
48
|
+
/** An attachment's raw bytes, fetched through the same authenticated client. */
|
|
49
|
+
getAttachmentBytes(id: string): Promise<ArrayBuffer>;
|
|
50
|
+
/**
|
|
51
|
+
* An attachment's bytes as a `data:` URI an `<Image>` can show. Fetched through
|
|
52
|
+
* the same authenticated client as every other call.
|
|
53
|
+
*/
|
|
54
|
+
getAttachmentDataUri(attachment: Pick<SupportAttachmentView, 'id' | 'contentType'>): Promise<string>;
|
|
55
|
+
/**
|
|
56
|
+
* The person's yes to a support action an assistant prepared (raise a ticket, reply on
|
|
57
|
+
* one, reopen one). Only this call completes it. Resolves to the ticket it acted on; an
|
|
58
|
+
* expired or unknown token rejects with the code `action_expired`.
|
|
59
|
+
*/
|
|
60
|
+
confirmAction(token: string): Promise<SupportTicketDetail>;
|
|
61
|
+
}
|
|
62
|
+
export declare const createSupportClient: (http: SupportHttpClient, basePath?: string) => SupportClient;
|
|
63
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import type { SupportAttachmentView } from '../types';
|
|
3
|
+
/** A message's files: images as tappable thumbnails, anything else as a file row. */
|
|
4
|
+
export declare const AttachmentList: React.FC<{
|
|
5
|
+
attachments: SupportAttachmentView[];
|
|
6
|
+
onOpenImage: (uri: string, name: string) => void;
|
|
7
|
+
}>;
|
|
8
|
+
/** Full-screen view of one image with pinch to zoom. */
|
|
9
|
+
export declare const ImageViewer: React.FC<{
|
|
10
|
+
image: {
|
|
11
|
+
uri: string;
|
|
12
|
+
name: string;
|
|
13
|
+
} | null;
|
|
14
|
+
onClose: () => void;
|
|
15
|
+
}>;
|
|
16
|
+
//# sourceMappingURL=Attachments.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import type { SupportTicketDetail, SupportTicketDraft } from '../types';
|
|
3
|
+
export interface NewSupportTicketProps {
|
|
4
|
+
/** Called with the created ticket; navigate to its detail from here. */
|
|
5
|
+
onCreated: (ticket: SupportTicketDetail) => void;
|
|
6
|
+
onCancel?: () => void;
|
|
7
|
+
/** Prefill, typically from the report-an-issue button: screenshot, page and device. */
|
|
8
|
+
initialDraft?: SupportTicketDraft;
|
|
9
|
+
testID?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare const NewSupportTicket: React.FC<NewSupportTicketProps>;
|
|
12
|
+
//# sourceMappingURL=NewSupportTicket.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import type { SupportFilePart } from '../types';
|
|
3
|
+
export interface PhotoTrayProps {
|
|
4
|
+
photos: SupportFilePart[];
|
|
5
|
+
onChange: (photos: SupportFilePart[]) => void;
|
|
6
|
+
/** Photos this message may still take, after anything else attached to it. */
|
|
7
|
+
remaining: number;
|
|
8
|
+
maxBytes: number;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
/** A heading above the tray. */
|
|
11
|
+
label?: string;
|
|
12
|
+
/** False shows the photos with remove buttons only, for a prefilled screenshot. */
|
|
13
|
+
allowAdd?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** Photos on a message: thumbnails with remove buttons, plus library and camera buttons. */
|
|
16
|
+
export declare const PhotoTray: React.FC<PhotoTrayProps>;
|
|
17
|
+
//# sourceMappingURL=PhotoTray.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
export interface SupportContactProps {
|
|
3
|
+
testID?: string;
|
|
4
|
+
}
|
|
5
|
+
/** How to reach the support team: email with copy and compose, phone, and hours. */
|
|
6
|
+
export declare const SupportContact: React.FC<SupportContactProps>;
|
|
7
|
+
//# sourceMappingURL=SupportContact.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type StatusBadgeTone } from '@webority/mobile-ui';
|
|
2
|
+
import type React from 'react';
|
|
3
|
+
import type { SupportTicketCounts, SupportTicketStatus, SupportTicketSummary } from '../types';
|
|
4
|
+
export declare const STATUS_TONES: Record<SupportTicketStatus, StatusBadgeTone>;
|
|
5
|
+
declare const ALL = "all";
|
|
6
|
+
export interface StatusFilterOption {
|
|
7
|
+
value: SupportTicketStatus | typeof ALL;
|
|
8
|
+
label: string;
|
|
9
|
+
count: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* All, then each status that has tickets. The selected status stays even at
|
|
13
|
+
* zero, so the chip the person is on never vanishes under them.
|
|
14
|
+
*/
|
|
15
|
+
export declare const statusFilterOptions: (counts: SupportTicketCounts, selected: SupportTicketStatus | null) => StatusFilterOption[];
|
|
16
|
+
export interface SupportTicketListProps {
|
|
17
|
+
onOpenTicket: (ticket: SupportTicketSummary) => void;
|
|
18
|
+
/** Shows a New ticket button in the header and the empty state. */
|
|
19
|
+
onNewTicket?: () => void;
|
|
20
|
+
/** Content above the list, for example a SupportContact card. */
|
|
21
|
+
header?: React.ReactElement;
|
|
22
|
+
/** Tickets per page. Default 20. */
|
|
23
|
+
pageSize?: number;
|
|
24
|
+
testID?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare const SupportTicketList: React.FC<SupportTicketListProps>;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=SupportTicketList.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SupportAttachmentView } from './types';
|
|
2
|
+
/** A server-supplied name reduced to one path segment, so it cannot leave the cache folder. */
|
|
3
|
+
export declare const safeFileName: (name: string) => string;
|
|
4
|
+
/**
|
|
5
|
+
* Writes an attachment to the app's cache and returns its `file://` URI. The id
|
|
6
|
+
* prefix keeps two tickets' `report.pdf` apart. Attachments never change once
|
|
7
|
+
* stored, so a file already on disk is reused rather than fetched again.
|
|
8
|
+
*/
|
|
9
|
+
export declare const saveAttachmentToCache: (attachment: Pick<SupportAttachmentView, "id" | "fileName">, loadBytes: () => Promise<ArrayBuffer>) => Promise<string>;
|
|
10
|
+
//# sourceMappingURL=files.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A timestamp in the device's own time zone. The backend sends UTC; mobile-core's
|
|
3
|
+
* formatter goes through Intl without pinning a zone, so the reader sees their
|
|
4
|
+
* own clock.
|
|
5
|
+
*/
|
|
6
|
+
export declare const formatWhen: (utc: string | null | undefined) => string;
|
|
7
|
+
/** A byte count as a person reads it. */
|
|
8
|
+
export declare const formatBytes: (bytes: number) => string;
|
|
9
|
+
export declare const isImage: (contentType: string) => boolean;
|
|
10
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type { SupportClient, SupportHttpClient, SupportHttpRequestConfig } from './client';
|
|
2
|
+
export { createSupportClient, SupportApiError, toSupportApiError } from './client';
|
|
3
|
+
export type { NewSupportTicketProps } from './components/NewSupportTicket';
|
|
4
|
+
export { NewSupportTicket } from './components/NewSupportTicket';
|
|
5
|
+
export type { SupportContactProps } from './components/SupportContact';
|
|
6
|
+
export { SupportContact } from './components/SupportContact';
|
|
7
|
+
export type { SupportTicketDetailProps } from './components/SupportTicketDetail';
|
|
8
|
+
export { SupportTicketDetail } from './components/SupportTicketDetail';
|
|
9
|
+
export type { SupportTicketListProps } from './components/SupportTicketList';
|
|
10
|
+
export { SupportTicketList } from './components/SupportTicketList';
|
|
11
|
+
export { describeDevice } from './device';
|
|
12
|
+
export type { SupportConfigState, SupportFileOpener, SupportFileToOpen, SupportProviderProps, SupportTicketState, SupportTicketsOptions, SupportTicketsState } from './SupportProvider';
|
|
13
|
+
export { SupportProvider, useCreateSupportTicket, useSupportClient, useSupportConfig, useSupportTicket, useSupportTickets } from './SupportProvider';
|
|
14
|
+
export type { NewSupportTicketInput, SupportAttachmentKind, SupportAttachmentView, SupportContactInfo, SupportFilePart, SupportMessageAuthorKind, SupportMessageView, SupportPage, SupportRatingInput, SupportReplyInput, SupportSatisfaction, SupportTicketCounts, SupportTicketDetail as SupportTicketDetailView, SupportTicketDraft, SupportTicketPriority, SupportTicketQuery, SupportTicketStatus, SupportTicketSummary } from './types';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SupportFilePart } from './types';
|
|
2
|
+
export type PhotoSource = 'library' | 'camera';
|
|
3
|
+
export declare class PhotoPermissionError extends Error {
|
|
4
|
+
readonly source: PhotoSource;
|
|
5
|
+
constructor(source: PhotoSource);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Lets the person pick or take photos and returns them ready to upload,
|
|
9
|
+
* compressed to fit the product's per-file limit. An empty list means they
|
|
10
|
+
* cancelled.
|
|
11
|
+
*/
|
|
12
|
+
export declare const pickPhotos: (source: PhotoSource, options: {
|
|
13
|
+
maxCount: number;
|
|
14
|
+
maxBytes: number;
|
|
15
|
+
}) => Promise<SupportFilePart[]>;
|
|
16
|
+
//# sourceMappingURL=photos.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ButtonProps } from '@webority/mobile-ui';
|
|
2
|
+
import type React from 'react';
|
|
3
|
+
import type { SupportTicketDraft } from '../types';
|
|
4
|
+
import { type CaptureAndReportOptions } from './captureAndReport';
|
|
5
|
+
export interface ReportIssueButtonProps extends CaptureAndReportOptions, Omit<ButtonProps, 'onPress' | 'title' | 'children' | 'loading'> {
|
|
6
|
+
/** Open the new ticket screen with this draft, for example `navigation.navigate('NewTicket', { draft })`. */
|
|
7
|
+
onReport: (draft: SupportTicketDraft) => void;
|
|
8
|
+
/** Called when the screen could not be captured. The draft still opens, without a screenshot. */
|
|
9
|
+
onCaptureError?: (error: unknown) => void;
|
|
10
|
+
title?: string;
|
|
11
|
+
}
|
|
12
|
+
/** A button that screenshots the current screen and hands a prefilled ticket draft to the app. */
|
|
13
|
+
export declare const ReportIssueButton: React.FC<ReportIssueButtonProps>;
|
|
14
|
+
//# sourceMappingURL=ReportIssueButton.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SupportTicketDraft } from '../types';
|
|
2
|
+
export interface CaptureAndReportOptions {
|
|
3
|
+
/** The current screen or route name. Sent as the ticket's page. */
|
|
4
|
+
screenName: string;
|
|
5
|
+
/** The app's user-visible version, for example from its compiled appsettings. */
|
|
6
|
+
appVersion?: string;
|
|
7
|
+
category?: string;
|
|
8
|
+
subject?: string;
|
|
9
|
+
}
|
|
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
|
+
export declare const captureAndReport: (options: CaptureAndReportOptions) => Promise<SupportTicketDraft>;
|
|
15
|
+
//# sourceMappingURL=captureAndReport.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Report an issue. A separate entry point because it names react-native-view-shot,
|
|
3
|
+
* an optional peer: Metro resolves every require reachable from an import, so
|
|
4
|
+
* keeping it off the main barrel lets an app skip the package when it does not
|
|
5
|
+
* use this button.
|
|
6
|
+
*/
|
|
7
|
+
export type { CaptureAndReportOptions } from './captureAndReport';
|
|
8
|
+
export { captureAndReport } from './captureAndReport';
|
|
9
|
+
export type { ReportIssueButtonProps } from './ReportIssueButton';
|
|
10
|
+
export { ReportIssueButton } from './ReportIssueButton';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SupportFileOpener } from '../SupportProvider';
|
|
2
|
+
export declare class SupportShareUnavailableError extends Error {
|
|
3
|
+
constructor();
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Hands a downloaded attachment to the OS share sheet, where the person can open
|
|
7
|
+
* it in a viewer or save it. Pass it to `SupportProvider` as `openFile`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const shareSupportFile: SupportFileOpener;
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|