@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.
Files changed (45) hide show
  1. package/lib/commonjs/SupportProvider.js +1 -335
  2. package/lib/commonjs/client.js +1 -149
  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 -143
  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/format.d.ts +6 -4
  43. package/lib/typescript/module/SupportProvider.d.ts +4 -0
  44. package/lib/typescript/module/format.d.ts +6 -4
  45. package/package.json +8 -6
@@ -1,240 +1 @@
1
- "use strict";
2
-
3
- import { AppIcon, IconButton, Spinner, Text, toast, useTheme, ZoomableImage } from '@webority/mobile-ui';
4
- import { useEffect, useMemo, useState } from 'react';
5
- import { Image, Modal, Pressable, StyleSheet, View } from 'react-native';
6
- import { GestureHandlerRootView } from 'react-native-gesture-handler';
7
- import { formatBytes, isImage } from "../format.js";
8
- import { useAttachmentLoader, useAttachmentOpener } from "../SupportProvider.js";
9
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
- const AttachmentThumb = ({
11
- attachment,
12
- onOpen
13
- }) => {
14
- const theme = useTheme();
15
- const styles = useMemo(() => buildStyles(theme), [theme]);
16
- const load = useAttachmentLoader();
17
- const [state, setState] = useState({
18
- status: 'loading'
19
- });
20
- useEffect(() => {
21
- let cancelled = false;
22
- load(attachment).then(uri => !cancelled && setState({
23
- status: 'ready',
24
- uri
25
- }), () => !cancelled && setState({
26
- status: 'error'
27
- }));
28
- return () => {
29
- cancelled = true;
30
- };
31
- }, [load, attachment]);
32
- if (state.status === 'loading') {
33
- return /*#__PURE__*/_jsx(View, {
34
- style: styles.thumb,
35
- testID: `support-attachment-${attachment.id}`,
36
- children: /*#__PURE__*/_jsx(Spinner, {
37
- size: "sm",
38
- accessibilityLabel: `Loading ${attachment.fileName}`
39
- })
40
- });
41
- }
42
- if (state.status === 'error') {
43
- return /*#__PURE__*/_jsx(View, {
44
- style: styles.thumb,
45
- accessible: true,
46
- accessibilityLabel: `${attachment.fileName} could not be loaded`,
47
- testID: `support-attachment-${attachment.id}`,
48
- children: /*#__PURE__*/_jsx(Text, {
49
- variant: "caption",
50
- color: "tertiary",
51
- align: "center",
52
- children: "Could not load"
53
- })
54
- });
55
- }
56
- return /*#__PURE__*/_jsx(Pressable, {
57
- onPress: () => onOpen(state.uri, attachment.fileName),
58
- accessibilityRole: "imagebutton",
59
- accessibilityLabel: `Open ${attachment.fileName}`,
60
- testID: `support-attachment-${attachment.id}`,
61
- children: /*#__PURE__*/_jsx(Image, {
62
- source: {
63
- uri: state.uri
64
- },
65
- style: styles.thumb,
66
- resizeMode: "cover"
67
- })
68
- });
69
- };
70
-
71
- /**
72
- * A file that is not an image. Tappable when the app gave SupportProvider an
73
- * `openFile`: it is downloaded through the signed-in client, then handed to the OS.
74
- */
75
- const FileRow = ({
76
- attachment
77
- }) => {
78
- const theme = useTheme();
79
- const styles = useMemo(() => buildStyles(theme), [theme]);
80
- const open = useAttachmentOpener();
81
- const [opening, setOpening] = useState(false);
82
- const label = `${attachment.fileName}, ${formatBytes(attachment.sizeBytes)}`;
83
- if (!open) {
84
- return /*#__PURE__*/_jsx(Text, {
85
- variant: "caption",
86
- color: "secondary",
87
- testID: `support-file-${attachment.id}`,
88
- children: label
89
- });
90
- }
91
- const onPress = async () => {
92
- setOpening(true);
93
- try {
94
- await open(attachment);
95
- } catch {
96
- toast.error(`Could not open ${attachment.fileName}. Try again.`);
97
- } finally {
98
- setOpening(false);
99
- }
100
- };
101
- return /*#__PURE__*/_jsxs(Pressable, {
102
- onPress: () => void onPress(),
103
- disabled: opening,
104
- accessibilityRole: "button",
105
- accessibilityLabel: `Open ${attachment.fileName}`,
106
- accessibilityState: {
107
- busy: opening,
108
- disabled: opening
109
- },
110
- style: styles.fileRow,
111
- testID: `support-file-${attachment.id}`,
112
- children: [/*#__PURE__*/_jsx(AppIcon, {
113
- name: "document",
114
- size: 16,
115
- color: theme.colors.text.link
116
- }), /*#__PURE__*/_jsx(Text, {
117
- variant: "caption",
118
- color: "link",
119
- children: label
120
- }), opening && /*#__PURE__*/_jsx(Spinner, {
121
- size: "sm",
122
- accessibilityLabel: `Opening ${attachment.fileName}`
123
- })]
124
- });
125
- };
126
-
127
- /** A message's files: images as tappable thumbnails, anything else as a file row. */
128
- export const AttachmentList = ({
129
- attachments,
130
- onOpenImage
131
- }) => {
132
- const theme = useTheme();
133
- const styles = useMemo(() => buildStyles(theme), [theme]);
134
- if (attachments.length === 0) {
135
- return null;
136
- }
137
- const images = attachments.filter(a => isImage(a.contentType));
138
- const others = attachments.filter(a => !isImage(a.contentType));
139
- return /*#__PURE__*/_jsxs(View, {
140
- style: styles.list,
141
- children: [images.length > 0 && /*#__PURE__*/_jsx(View, {
142
- style: styles.thumbRow,
143
- children: images.map(a => /*#__PURE__*/_jsx(AttachmentThumb, {
144
- attachment: a,
145
- onOpen: onOpenImage
146
- }, a.id))
147
- }), others.map(a => /*#__PURE__*/_jsx(FileRow, {
148
- attachment: a
149
- }, a.id))]
150
- });
151
- };
152
-
153
- /** Full-screen view of one image with pinch to zoom. */
154
- export const ImageViewer = ({
155
- image,
156
- onClose
157
- }) => {
158
- const theme = useTheme();
159
- const styles = useMemo(() => buildStyles(theme), [theme]);
160
- return /*#__PURE__*/_jsx(Modal, {
161
- visible: image !== null,
162
- onRequestClose: onClose,
163
- animationType: "fade",
164
- transparent: false,
165
- children: /*#__PURE__*/_jsxs(GestureHandlerRootView, {
166
- style: styles.viewer,
167
- testID: "support-image-viewer",
168
- children: [image && /*#__PURE__*/_jsx(ZoomableImage, {
169
- accessibilityLabel: image.name,
170
- style: styles.viewerImageWrap,
171
- children: /*#__PURE__*/_jsx(Image, {
172
- source: {
173
- uri: image.uri
174
- },
175
- style: styles.viewerImage,
176
- resizeMode: "contain"
177
- })
178
- }), /*#__PURE__*/_jsx(View, {
179
- style: styles.viewerClose,
180
- children: /*#__PURE__*/_jsx(IconButton, {
181
- icon: ({
182
- color,
183
- size
184
- }) => /*#__PURE__*/_jsx(AppIcon, {
185
- name: "close-circle",
186
- color: color,
187
- size: size
188
- }),
189
- accessibilityLabel: "Close image",
190
- onPress: onClose,
191
- variant: "glass"
192
- })
193
- })]
194
- })
195
- });
196
- };
197
- const THUMB = 84;
198
- const buildStyles = theme => StyleSheet.create({
199
- list: {
200
- gap: theme.spacing.xs,
201
- marginTop: theme.spacing.sm
202
- },
203
- fileRow: {
204
- flexDirection: 'row',
205
- alignItems: 'center',
206
- gap: theme.spacing.xxs,
207
- alignSelf: 'flex-start'
208
- },
209
- thumbRow: {
210
- flexDirection: 'row',
211
- flexWrap: 'wrap',
212
- gap: theme.spacing.xs
213
- },
214
- thumb: {
215
- width: THUMB,
216
- height: THUMB,
217
- borderRadius: theme.radius.sm,
218
- backgroundColor: theme.colors.background.secondary,
219
- alignItems: 'center',
220
- justifyContent: 'center',
221
- overflow: 'hidden'
222
- },
223
- viewer: {
224
- flex: 1,
225
- backgroundColor: theme.colors.background.inverse
226
- },
227
- viewerImageWrap: {
228
- flex: 1
229
- },
230
- viewerImage: {
231
- flex: 1,
232
- width: '100%'
233
- },
234
- viewerClose: {
235
- position: 'absolute',
236
- top: theme.spacing['2xl'],
237
- right: theme.spacing.md
238
- }
239
- });
240
- //# sourceMappingURL=Attachments.js.map
1
+ import{AppIcon as e,IconButton as t,Spinner as i,Text as r,ToastHost as a,toast as s,useTheme as o,ZoomableImage as n}from"@webority/mobile-ui";import{useEffect as l,useMemo as c,useState as m}from"react";import{Image as p,Modal as d,Pressable as u,StyleSheet as b,View as g}from"react-native";import{GestureHandlerRootView as h}from"react-native-gesture-handler";import{formatBytes as f,isImage as y}from"../format.js";import{useAttachmentLoader as w,useAttachmentOpener as v}from"../SupportProvider.js";import{jsx as x,jsxs as I}from"react/jsx-runtime";const AttachmentThumb=({attachment:e,onOpen:t})=>{const a=o(),s=c(()=>buildStyles(a),[a]),n=w(),[d,b]=m({status:"loading"});return l(()=>{let t=!1;return n(e).then(e=>!t&&b({status:"ready",uri:e}),()=>!t&&b({status:"error"})),()=>{t=!0}},[n,e]),"loading"===d.status?x(g,{style:s.thumb,testID:`support-attachment-${e.id}`,children:x(i,{size:"sm",accessibilityLabel:`Loading ${e.fileName}`})}):"error"===d.status?x(g,{style:s.thumb,accessible:!0,accessibilityLabel:`${e.fileName} could not be loaded`,testID:`support-attachment-${e.id}`,children:x(r,{variant:"caption",color:"tertiary",align:"center",children:"Could not load"})}):x(u,{onPress:()=>t(d.uri,e.fileName),accessibilityRole:"imagebutton",accessibilityLabel:`Open ${e.fileName}`,testID:`support-attachment-${e.id}`,children:x(p,{source:{uri:d.uri},style:s.thumb,resizeMode:"cover"})})},FileRow=({attachment:t})=>{const a=o(),n=c(()=>buildStyles(a),[a]),l=v(),[p,d]=m(!1),b=`${t.fileName}, ${f(t.sizeBytes)}`;if(!l)return x(r,{variant:"caption",color:"secondary",testID:`support-file-${t.id}`,children:b});return I(u,{onPress:()=>{(async()=>{d(!0);try{await l(t)}catch{s.error(`Could not open ${t.fileName}. Try again.`)}finally{d(!1)}})()},disabled:p,accessibilityRole:"button",accessibilityLabel:`Open ${t.fileName}`,accessibilityState:{busy:p,disabled:p},style:n.fileRow,testID:`support-file-${t.id}`,children:[x(e,{name:"document",size:16,color:a.colors.text.link}),x(r,{variant:"caption",color:"link",children:b}),p&&x(i,{size:"sm",accessibilityLabel:`Opening ${t.fileName}`})]})};export const AttachmentList=({attachments:e,onOpenImage:t})=>{const i=o(),r=c(()=>buildStyles(i),[i]);if(0===e.length)return null;const a=e.filter(e=>y(e.contentType)),s=e.filter(e=>!y(e.contentType));return I(g,{style:r.list,children:[a.length>0&&x(g,{style:r.thumbRow,children:a.map(e=>x(AttachmentThumb,{attachment:e,onOpen:t},e.id))}),s.map(e=>x(FileRow,{attachment:e},e.id))]})};export const ImageViewer=({image:i,onClose:r})=>{const s=o(),l=c(()=>buildStyles(s),[s]);return x(d,{visible:null!==i,onRequestClose:r,animationType:"fade",transparent:!1,children:I(h,{style:l.viewer,testID:"support-image-viewer",children:[i&&x(n,{accessibilityLabel:i.name,style:l.viewerImageWrap,children:x(p,{source:{uri:i.uri},style:l.viewerImage,resizeMode:"contain"})}),x(g,{style:l.viewerClose,children:x(t,{icon:({color:t,size:i})=>x(e,{name:"close-circle",color:t,size:i}),accessibilityLabel:"Close image",onPress:r,variant:"glass"})}),x(a,{})]})})};const buildStyles=e=>b.create({list:{gap:e.spacing.xs,marginTop:e.spacing.sm},fileRow:{flexDirection:"row",alignItems:"center",gap:e.spacing.xxs,alignSelf:"flex-start"},thumbRow:{flexDirection:"row",flexWrap:"wrap",gap:e.spacing.xs},thumb:{width:84,height:84,borderRadius:e.radius.sm,backgroundColor:e.colors.background.secondary,alignItems:"center",justifyContent:"center",overflow:"hidden"},viewer:{flex:1,backgroundColor:e.colors.background.inverse},viewerImageWrap:{flex:1},viewerImage:{flex:1,width:"100%"},viewerClose:{position:"absolute",top:e.spacing["2xl"],right:e.spacing.md}});
@@ -1,205 +1 @@
1
- "use strict";
2
-
3
- import { Button, Input, Select, Spinner, Text, useForm, useTheme } from '@webority/mobile-ui';
4
- import { useMemo, useState } from 'react';
5
- import { ScrollView, StyleSheet, View } from 'react-native';
6
- import { toSupportApiError } from "../client.js";
7
- import { useCreateSupportTicket, useSupportConfig } from "../SupportProvider.js";
8
- import { validateBody, validateCategory, validateFiles, validateSubject } from "../validation.js";
9
- import { PhotoTray } from "./PhotoTray.js";
10
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
11
- const TicketForm = ({
12
- config,
13
- styles,
14
- onCreated,
15
- onCancel,
16
- initialDraft
17
- }) => {
18
- const createTicket = useCreateSupportTicket();
19
- const [photos, setPhotos] = useState([]);
20
- const [screenshots, setScreenshots] = useState(initialDraft?.screenshots ?? []);
21
- const [filesError, setFilesError] = useState(null);
22
- const [submitError, setSubmitError] = useState(null);
23
- const form = useForm({
24
- initialValues: {
25
- category: initialDraft?.category ?? '',
26
- subject: initialDraft?.subject ?? '',
27
- body: initialDraft?.body ?? ''
28
- },
29
- validate: {
30
- category: v => validateCategory(v, config),
31
- subject: v => validateSubject(v, config),
32
- body: v => validateBody(v, config)
33
- },
34
- onSubmit: async values => {
35
- const fileProblem = validateFiles([...screenshots, ...photos], config);
36
- setFilesError(fileProblem ?? null);
37
- if (fileProblem) {
38
- return;
39
- }
40
- setSubmitError(null);
41
- try {
42
- const created = await createTicket({
43
- category: values.category,
44
- subject: values.subject.trim(),
45
- body: values.body.trim(),
46
- pageUrl: initialDraft?.pageUrl,
47
- userAgent: initialDraft?.userAgent,
48
- appVersion: initialDraft?.appVersion,
49
- files: photos,
50
- screenshots
51
- });
52
- onCreated(created);
53
- } catch (e) {
54
- setSubmitError(toSupportApiError(e).message);
55
- }
56
- }
57
- });
58
- const category = form.getFieldProps('category');
59
- const subject = form.getFieldProps('subject');
60
- const body = form.getFieldProps('body');
61
- const attached = screenshots.length + photos.length;
62
- return /*#__PURE__*/_jsxs(ScrollView, {
63
- contentContainerStyle: styles.content,
64
- keyboardShouldPersistTaps: "handled",
65
- children: [/*#__PURE__*/_jsx(Text, {
66
- variant: "bodySmall",
67
- color: "secondary",
68
- children: "Tell us what you need. We reply by email and in this ticket."
69
- }), /*#__PURE__*/_jsx(Select, {
70
- label: "Category",
71
- placeholder: "Choose a category",
72
- required: true,
73
- options: config.categories.map(c => ({
74
- label: c,
75
- value: c
76
- })),
77
- value: category.value || undefined,
78
- onChange: v => category.setValue(String(v), true),
79
- error: category.error,
80
- testID: "support-new-category"
81
- }), /*#__PURE__*/_jsx(Input, {
82
- label: "Subject",
83
- placeholder: "A short summary",
84
- required: true,
85
- maxLength: config.subjectMaxLength,
86
- value: subject.value,
87
- onChangeText: subject.onChangeText,
88
- onBlur: subject.onBlur,
89
- error: subject.error,
90
- testID: "support-new-subject"
91
- }), /*#__PURE__*/_jsx(Input, {
92
- label: "Message",
93
- placeholder: "What happened, and what did you expect?",
94
- required: true,
95
- multiline: true,
96
- maxLength: config.bodyMaxLength,
97
- value: body.value,
98
- onChangeText: body.onChangeText,
99
- onBlur: body.onBlur,
100
- error: body.error,
101
- testID: "support-new-body"
102
- }), screenshots.length > 0 && /*#__PURE__*/_jsx(PhotoTray, {
103
- label: "Screenshot",
104
- photos: screenshots,
105
- onChange: setScreenshots,
106
- remaining: 0,
107
- allowAdd: false,
108
- maxBytes: config.maxAttachmentBytes,
109
- disabled: form.isSubmitting
110
- }), /*#__PURE__*/_jsx(PhotoTray, {
111
- label: "Photos",
112
- photos: photos,
113
- onChange: setPhotos,
114
- remaining: config.maxAttachmentsPerMessage - attached,
115
- maxBytes: config.maxAttachmentBytes,
116
- disabled: form.isSubmitting
117
- }), /*#__PURE__*/_jsx(Text, {
118
- variant: "caption",
119
- color: "tertiary",
120
- children: `Attach up to ${config.maxAttachmentsPerMessage} files.`
121
- }), (filesError || submitError) && /*#__PURE__*/_jsx(Text, {
122
- variant: "bodySmall",
123
- color: "error",
124
- accessibilityLiveRegion: "polite",
125
- children: filesError ?? submitError
126
- }), /*#__PURE__*/_jsxs(View, {
127
- style: styles.actions,
128
- children: [onCancel && /*#__PURE__*/_jsx(Button, {
129
- title: "Cancel",
130
- variant: "ghost",
131
- tone: "neutral",
132
- onPress: onCancel,
133
- disabled: form.isSubmitting
134
- }), /*#__PURE__*/_jsx(Button, {
135
- title: "Send ticket",
136
- loading: form.isSubmitting,
137
- disabled: form.isSubmitting,
138
- onPress: form.submit,
139
- testID: "support-new-submit"
140
- })]
141
- })]
142
- });
143
- };
144
- export const NewSupportTicket = props => {
145
- const theme = useTheme();
146
- const styles = useMemo(() => buildStyles(theme), [theme]);
147
- const {
148
- config,
149
- loading,
150
- error,
151
- reload
152
- } = useSupportConfig();
153
- const testID = props.testID ?? 'support-new-ticket';
154
- if (!config) {
155
- return /*#__PURE__*/_jsx(View, {
156
- style: styles.center,
157
- testID: testID,
158
- children: loading ? /*#__PURE__*/_jsx(Spinner, {
159
- accessibilityLabel: "Loading"
160
- }) : /*#__PURE__*/_jsxs(_Fragment, {
161
- children: [/*#__PURE__*/_jsx(Text, {
162
- variant: "body",
163
- color: "error",
164
- align: "center",
165
- children: error?.message ?? 'Support is not available right now.'
166
- }), /*#__PURE__*/_jsx(Button, {
167
- title: "Try again",
168
- variant: "ghost",
169
- onPress: () => void reload()
170
- })]
171
- })
172
- });
173
- }
174
- return /*#__PURE__*/_jsx(View, {
175
- style: styles.root,
176
- testID: testID,
177
- children: /*#__PURE__*/_jsx(TicketForm, {
178
- ...props,
179
- config: config,
180
- styles: styles
181
- })
182
- });
183
- };
184
- const buildStyles = theme => StyleSheet.create({
185
- root: {
186
- flex: 1
187
- },
188
- content: {
189
- padding: theme.spacing.md,
190
- gap: theme.spacing.md
191
- },
192
- center: {
193
- flex: 1,
194
- alignItems: 'center',
195
- justifyContent: 'center',
196
- padding: theme.spacing.xl,
197
- gap: theme.spacing.sm
198
- },
199
- actions: {
200
- flexDirection: 'row',
201
- justifyContent: 'flex-end',
202
- gap: theme.spacing.sm
203
- }
204
- });
205
- //# sourceMappingURL=NewSupportTicket.js.map
1
+ import{Button as e,Input as t,Select as r,Spinner as o,Text as a,useForm as n,useTheme as i}from"@webority/mobile-ui";import{useMemo as s,useState as l}from"react";import{ScrollView as c,StyleSheet as g,View as d}from"react-native";import{toSupportApiError as u}from"../client.js";import{useCreateSupportTicket as p,useSupportConfig as m}from"../SupportProvider.js";import{validateBody as h,validateCategory as y,validateFiles as b,validateSubject as x}from"../validation.js";import{PhotoTray as f}from"./PhotoTray.js";import{jsx as v,jsxs as j,Fragment as S}from"react/jsx-runtime";const TicketForm=({config:o,styles:i,onCreated:s,onCancel:g,initialDraft:m})=>{const S=p(),[C,w]=l([]),[P,A]=l(m?.screenshots??[]),[D,T]=l(null),[B,I]=l(null),L=n({initialValues:{category:m?.category??"",subject:m?.subject??"",body:m?.body??""},validate:{category:e=>y(e,o),subject:e=>x(e,o),body:e=>h(e,o)},onSubmit:async e=>{const t=b([...P,...C],o);if(T(t??null),!t){I(null);try{const t=await S({category:e.category,subject:e.subject.trim(),body:e.body.trim(),pageUrl:m?.pageUrl,userAgent:m?.userAgent,appVersion:m?.appVersion,files:C,screenshots:P});s(t)}catch(e){I(u(e).message)}}}}),k=L.getFieldProps("category"),M=L.getFieldProps("subject"),F=L.getFieldProps("body"),V=P.length+C.length;return j(c,{contentContainerStyle:i.content,keyboardShouldPersistTaps:"handled",children:[v(a,{variant:"bodySmall",color:"secondary",children:"Tell us what you need. We reply by email and in this ticket."}),v(r,{label:"Category",placeholder:"Choose a category",required:!0,options:o.categories.map(e=>({label:e,value:e})),value:k.value||void 0,onChange:e=>k.setValue(String(e),!0),error:k.error,testID:"support-new-category"}),v(t,{label:"Subject",placeholder:"A short summary",required:!0,maxLength:o.subjectMaxLength,value:M.value,onChangeText:M.onChangeText,onBlur:M.onBlur,error:M.error,testID:"support-new-subject"}),v(t,{label:"Message",placeholder:"What happened, and what did you expect?",required:!0,multiline:!0,maxLength:o.bodyMaxLength,value:F.value,onChangeText:F.onChangeText,onBlur:F.onBlur,error:F.error,testID:"support-new-body"}),P.length>0&&v(f,{label:"Screenshot",photos:P,onChange:A,remaining:0,allowAdd:!1,maxBytes:o.maxAttachmentBytes,disabled:L.isSubmitting}),v(f,{label:"Photos",photos:C,onChange:w,remaining:o.maxAttachmentsPerMessage-V,maxBytes:o.maxAttachmentBytes,disabled:L.isSubmitting}),v(a,{variant:"caption",color:"tertiary",children:`Attach up to ${o.maxAttachmentsPerMessage} files.`}),(D||B)&&v(a,{variant:"bodySmall",color:"error",accessibilityLiveRegion:"polite",children:D??B}),j(d,{style:i.actions,children:[g&&v(e,{title:"Cancel",variant:"ghost",tone:"neutral",onPress:g,disabled:L.isSubmitting}),v(e,{title:"Send ticket",loading:L.isSubmitting,disabled:L.isSubmitting,onPress:L.submit,testID:"support-new-submit"})]})]})};export const NewSupportTicket=t=>{const r=i(),n=s(()=>buildStyles(r),[r]),{config:l,loading:c,error:g,reload:u}=m(),p=t.testID??"support-new-ticket";return v(d,l?{style:n.root,testID:p,children:v(TicketForm,{...t,config:l,styles:n})}:{style:n.center,testID:p,children:c?v(o,{accessibilityLabel:"Loading"}):j(S,{children:[v(a,{variant:"body",color:"error",align:"center",children:g?.message??"Support is not available right now."}),v(e,{title:"Try again",variant:"ghost",onPress:()=>{u()}})]})})};const buildStyles=e=>g.create({root:{flex:1},content:{padding:e.spacing.md,gap:e.spacing.md},center:{flex:1,alignItems:"center",justifyContent:"center",padding:e.spacing.xl,gap:e.spacing.sm},actions:{flexDirection:"row",justifyContent:"flex-end",gap:e.spacing.sm}});
@@ -1,136 +1 @@
1
- "use strict";
2
-
3
- import { AppIcon, Button, IconButton, Text, useTheme } from '@webority/mobile-ui';
4
- import { useMemo, useState } from 'react';
5
- import { Image, StyleSheet, View } from 'react-native';
6
- import { pickPhotos } from "../photos.js";
7
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
- /** Photos on a message: thumbnails with remove buttons, plus library and camera buttons. */
9
- export const PhotoTray = ({
10
- photos,
11
- onChange,
12
- remaining,
13
- maxBytes,
14
- disabled,
15
- label,
16
- allowAdd = true
17
- }) => {
18
- const theme = useTheme();
19
- const styles = useMemo(() => buildStyles(theme), [theme]);
20
- const [picking, setPicking] = useState(null);
21
- const [error, setError] = useState(null);
22
- const add = async source => {
23
- setError(null);
24
- setPicking(source);
25
- try {
26
- const picked = await pickPhotos(source, {
27
- maxCount: remaining,
28
- maxBytes
29
- });
30
- if (picked.length > 0) {
31
- onChange([...photos, ...picked]);
32
- }
33
- } catch (e) {
34
- setError(e instanceof Error && e.name === 'PhotoPermissionError' ? e.message : 'That photo could not be added. Try another one.');
35
- } finally {
36
- setPicking(null);
37
- }
38
- };
39
- const full = remaining <= 0;
40
- return /*#__PURE__*/_jsxs(View, {
41
- style: styles.root,
42
- children: [label ? /*#__PURE__*/_jsx(Text, {
43
- variant: "label",
44
- color: "secondary",
45
- children: label
46
- }) : null, photos.length > 0 && /*#__PURE__*/_jsx(View, {
47
- style: styles.row,
48
- children: photos.map((photo, index) => /*#__PURE__*/_jsxs(View, {
49
- style: styles.item,
50
- children: [/*#__PURE__*/_jsx(Image, {
51
- source: {
52
- uri: photo.uri
53
- },
54
- style: styles.thumb,
55
- accessibilityLabel: photo.name
56
- }), /*#__PURE__*/_jsx(View, {
57
- style: styles.remove,
58
- children: /*#__PURE__*/_jsx(IconButton, {
59
- size: "sm",
60
- variant: "glass",
61
- icon: ({
62
- color,
63
- size
64
- }) => /*#__PURE__*/_jsx(AppIcon, {
65
- name: "trash",
66
- color: color,
67
- size: size
68
- }),
69
- accessibilityLabel: `Remove ${photo.name}`,
70
- disabled: disabled,
71
- onPress: () => onChange(photos.filter((_, i) => i !== index))
72
- })
73
- })]
74
- }, `${photo.uri}-${index}`))
75
- }), allowAdd && /*#__PURE__*/_jsxs(View, {
76
- style: styles.actions,
77
- children: [/*#__PURE__*/_jsx(Button, {
78
- size: "sm",
79
- variant: "outline",
80
- tone: "neutral",
81
- leftIcon: "gallery",
82
- title: "Add photo",
83
- accessibilityLabel: "Add a photo from your library",
84
- loading: picking === 'library',
85
- disabled: disabled || full || picking !== null,
86
- onPress: () => void add('library')
87
- }), /*#__PURE__*/_jsx(Button, {
88
- size: "sm",
89
- variant: "outline",
90
- tone: "neutral",
91
- leftIcon: "camera",
92
- title: "Take photo",
93
- accessibilityLabel: "Take a photo with your camera",
94
- loading: picking === 'camera',
95
- disabled: disabled || full || picking !== null,
96
- onPress: () => void add('camera')
97
- })]
98
- }), error ? /*#__PURE__*/_jsx(Text, {
99
- variant: "caption",
100
- color: "error",
101
- accessibilityLiveRegion: "polite",
102
- children: error
103
- }) : null]
104
- });
105
- };
106
- const THUMB = 72;
107
- const buildStyles = theme => StyleSheet.create({
108
- root: {
109
- gap: theme.spacing.xs
110
- },
111
- row: {
112
- flexDirection: 'row',
113
- flexWrap: 'wrap',
114
- gap: theme.spacing.xs
115
- },
116
- item: {
117
- width: THUMB,
118
- height: THUMB
119
- },
120
- thumb: {
121
- width: THUMB,
122
- height: THUMB,
123
- borderRadius: theme.radius.sm,
124
- backgroundColor: theme.colors.background.secondary
125
- },
126
- remove: {
127
- position: 'absolute',
128
- top: theme.spacing.xxs,
129
- right: theme.spacing.xxs
130
- },
131
- actions: {
132
- flexDirection: 'row',
133
- gap: theme.spacing.sm
134
- }
135
- });
136
- //# sourceMappingURL=PhotoTray.js.map
1
+ import{AppIcon as e,Button as o,IconButton as r,Text as a,useTheme as i}from"@webority/mobile-ui";import{useMemo as t,useState as l}from"react";import{Image as s,StyleSheet as n,View as c}from"react-native";import{pickPhotos as m}from"../photos.js";import{jsx as d,jsxs as h}from"react/jsx-runtime";export const PhotoTray=({photos:n,onChange:u,remaining:p,maxBytes:b,disabled:y,label:g,allowAdd:x=!0})=>{const f=i(),w=t(()=>buildStyles(f),[f]),[v,P]=l(null),[z,L]=l(null),add=async e=>{L(null),P(e);try{const o=await m(e,{maxCount:p,maxBytes:b});o.length>0&&u([...n,...o])}catch(e){L(e instanceof Error&&"PhotoPermissionError"===e.name?e.message:"That photo could not be added. Try another one.")}finally{P(null)}},T=p<=0;return h(c,{style:w.root,children:[g?d(a,{variant:"label",color:"secondary",children:g}):null,n.length>0&&d(c,{style:w.row,children:n.map((o,a)=>h(c,{style:w.item,children:[d(s,{source:{uri:o.uri},style:w.thumb,accessibilityLabel:o.name}),d(c,{style:w.remove,children:d(r,{size:"sm",variant:"glass",icon:({color:o,size:r})=>d(e,{name:"trash",color:o,size:r}),accessibilityLabel:`Remove ${o.name}`,disabled:y,onPress:()=>u(n.filter((e,o)=>o!==a))})})]},`${o.uri}-${a}`))}),x&&h(c,{style:w.actions,children:[d(o,{size:"sm",variant:"outline",tone:"neutral",leftIcon:"gallery",title:"Add photo",accessibilityLabel:"Add a photo from your library",loading:"library"===v,disabled:y||T||null!==v,onPress:()=>{add("library")}}),d(o,{size:"sm",variant:"outline",tone:"neutral",leftIcon:"camera",title:"Take photo",accessibilityLabel:"Take a photo with your camera",loading:"camera"===v,disabled:y||T||null!==v,onPress:()=>{add("camera")}})]}),z?d(a,{variant:"caption",color:"error",accessibilityLiveRegion:"polite",children:z}):null]})};const buildStyles=e=>n.create({root:{gap:e.spacing.xs},row:{flexDirection:"row",flexWrap:"wrap",gap:e.spacing.xs},item:{width:72,height:72},thumb:{width:72,height:72,borderRadius:e.radius.sm,backgroundColor:e.colors.background.secondary},remove:{position:"absolute",top:e.spacing.xxs,right:e.spacing.xxs},actions:{flexDirection:"row",gap:e.spacing.sm}});