@webority/mobile-support 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -6
- package/lib/commonjs/components/Attachments.js +1 -1
- package/lib/commonjs/components/NewSupportTicket.js +1 -1
- package/lib/commonjs/components/SupportTicketDetail.js +1 -1
- package/lib/module/components/Attachments.js +1 -1
- package/lib/module/components/NewSupportTicket.js +1 -1
- package/lib/module/components/SupportTicketDetail.js +1 -1
- package/lib/typescript/commonjs/SupportProvider.d.ts +1 -1
- package/lib/typescript/commonjs/components/NewSupportTicket.d.ts +4 -0
- package/lib/typescript/module/SupportProvider.d.ts +1 -1
- package/lib/typescript/module/components/NewSupportTicket.d.ts +4 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -76,6 +76,8 @@ const SupportTicketScreen = ({ route }) => <SupportTicketDetail ticketId={route.
|
|
|
76
76
|
const NewSupportTicketScreen = ({ navigation, route }) => (
|
|
77
77
|
<NewSupportTicket
|
|
78
78
|
initialDraft={route.params?.draft}
|
|
79
|
+
defaultCategory="Bug report"
|
|
80
|
+
appVersion={AppSettings.VERSION_DISPLAY}
|
|
79
81
|
onCancel={() => navigation.goBack()}
|
|
80
82
|
onCreated={(t) => navigation.replace('SupportTicket', { id: t.id })}
|
|
81
83
|
/>
|
|
@@ -88,11 +90,17 @@ const NewSupportTicketScreen = ({ navigation, route }) => (
|
|
|
88
90
|
does pull to refresh. Empty state, retry on failure, and a Load more retry when a later page
|
|
89
91
|
fails without losing the rows already shown.
|
|
90
92
|
- **SupportTicketDetail**: the thread as bubbles, status events as small centred lines, image
|
|
91
|
-
thumbnails that open full screen, a reply box with photos, Reopen when `canReopen
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
thumbnails that open full screen, a reply box with photos, and Reopen when `canReopen`. When
|
|
94
|
+
`canRate`, it asks "Did this solve your problem?" with an optional comment; once answered, it
|
|
95
|
+
shows the answer and the comment instead. A message with no author name reads "You" or
|
|
96
|
+
"Support team". Replying to a resolved or closed ticket reopens it.
|
|
94
97
|
- **NewSupportTicket**: category, subject, message and photos, checked against the limits the
|
|
95
|
-
product sends in `GET /settings` before anything is uploaded.
|
|
98
|
+
product sends in `GET /settings` before anything is uploaded. `defaultCategory` preselects a
|
|
99
|
+
category the product lists (a draft's own category wins; one the product does not list starts
|
|
100
|
+
unselected). `appVersion` is recorded when the draft carries none, and the device line
|
|
101
|
+
(`describeDevice()`) is recorded on every ticket.
|
|
102
|
+
- Both forms keep the focused field above the keyboard (mobile-ui's `KeyboardAwareScrollView`,
|
|
103
|
+
which needs the app's `KeyboardProvider` at the root).
|
|
96
104
|
- **SupportContact**: the support email with copy and compose, the phone number, and the hours.
|
|
97
105
|
|
|
98
106
|
## Report an issue
|
|
@@ -107,8 +115,10 @@ import { ReportIssueButton } from '@webority/mobile-support/report';
|
|
|
107
115
|
/>;
|
|
108
116
|
```
|
|
109
117
|
|
|
110
|
-
The button captures the screen, then hands your app a draft with the
|
|
111
|
-
`screenshots`, the screen name as the page, the device line and the app version.
|
|
118
|
+
The button captures the screen as soon as it is pressed, then hands your app a draft with the
|
|
119
|
+
image under `screenshots`, the screen name as the page, the device line and the app version. The
|
|
120
|
+
person can remove the screenshot on the form before sending. An app that wants no capture opens
|
|
121
|
+
`NewSupportTicket` directly; the person can still attach their own screenshots as photos.
|
|
112
122
|
`captureAndReport(options)` does the same without the button. The device line reads the
|
|
113
123
|
platform and OS version, plus the model when the app has called mobile-core's
|
|
114
124
|
`setDeviceContext`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.ImageViewer=exports.AttachmentList=void 0;var _mobileUi=require("@webority/mobile-ui"),_react=require("react"),_reactNative=require("react-native"),_reactNativeGestureHandler=require("react-native-gesture-handler"),_format=require("../format.js"),_SupportProvider=require("../SupportProvider.js"),_jsxRuntime=require("react/jsx-runtime");const AttachmentThumb=({attachment:e,onOpen:t})=>{const i=(0,_mobileUi.useTheme)(),s=(0,_react.useMemo)(()=>buildStyles(i),[i]),a=(0,_SupportProvider.useAttachmentLoader)(),[r,n]=(0,_react.useState)({status:"loading"});return(0,_react.useEffect)(()=>{let t=!1;return a(e).then(e=>!t&&n({status:"ready",uri:e}),()=>!t&&n({status:"error"})),()=>{t=!0}},[a,e]),"loading"===r.status?(0,_jsxRuntime.jsx)(_reactNative.View,{style:s.thumb,testID:`support-attachment-${e.id}`,children:(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{size:"sm",accessibilityLabel:`Loading ${e.fileName}`})}):"error"===r.status?(0,_jsxRuntime.jsx)(_reactNative.View,{style:s.thumb,accessible:!0,accessibilityLabel:`${e.fileName} could not be loaded`,testID:`support-attachment-${e.id}`,children:(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",align:"center",children:"Could not load"})}):(0,_jsxRuntime.jsx)(_reactNative.Pressable,{onPress:()=>t(r.uri,e.fileName),accessibilityRole:"imagebutton",accessibilityLabel:`Open ${e.fileName}`,testID:`support-attachment-${e.id}`,children:(0,_jsxRuntime.jsx)(_reactNative.Image,{source:{uri:r.uri},style:s.thumb,resizeMode:"cover"})})},FileRow=({attachment:e})=>{const t=(0,_mobileUi.useTheme)(),i=(0,_react.useMemo)(()=>buildStyles(t),[t]),s=(0,_SupportProvider.useAttachmentOpener)(),[a,r]=(0,_react.useState)(!1),n=`${e.fileName}, ${(0,_format.formatBytes)(e.sizeBytes)}`;if(!s)return(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"secondary",testID:`support-file-${e.id}`,children:n});return(0,_jsxRuntime.jsxs)(_reactNative.Pressable,{onPress:()=>{(async()=>{r(!0);try{await s(e)}catch{_mobileUi.toast.error(`Could not open ${e.fileName}. Try again.`)}finally{r(!1)}})()},disabled:a,accessibilityRole:"button",accessibilityLabel:`Open ${e.fileName}`,accessibilityState:{busy:a,disabled:a},style:i.fileRow,testID:`support-file-${e.id}`,children:[(0,_jsxRuntime.jsx)(_mobileUi.AppIcon,{name:"document",size:16,color:t.colors.text.link}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"link",children:n}),a&&(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{size:"sm",accessibilityLabel:`Opening ${e.fileName}`})]})},AttachmentList=({attachments:e,onOpenImage:t})=>{const i=(0,_mobileUi.useTheme)(),s=(0,_react.useMemo)(()=>buildStyles(i),[i]);if(0===e.length)return null;const a=e.filter(e=>(0,_format.isImage)(e.contentType)),r=e.filter(e=>!(0,_format.isImage)(e.contentType));return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.list,children:[a.length>0&&(0,_jsxRuntime.jsx)(_reactNative.View,{style:s.thumbRow,children:a.map(e=>(0,_jsxRuntime.jsx)(AttachmentThumb,{attachment:e,onOpen:t},e.id))}),r.map(e=>(0,_jsxRuntime.jsx)(FileRow,{attachment:e},e.id))]})};exports.AttachmentList=AttachmentList;const ImageViewer=({image:e,onClose:t})=>{const i=(0,_mobileUi.useTheme)(),s=(0,_react.useMemo)(()=>buildStyles(i),[i]);return(0,_jsxRuntime.jsx)(_reactNative.Modal,{visible:null!==e,onRequestClose:t,animationType:"fade",transparent:!1,children:(0,_jsxRuntime.jsxs)(_reactNativeGestureHandler.GestureHandlerRootView,{style:s.viewer,testID:"support-image-viewer",children:[e&&(0,_jsxRuntime.jsx)(_mobileUi.ZoomableImage,{accessibilityLabel:e.name,style:s.viewerImageWrap,children:(0,_jsxRuntime.jsx)(_reactNative.Image,{source:{uri:e.uri},style:s.viewerImage,resizeMode:"contain"})}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:s.viewerClose,children:(0,_jsxRuntime.jsx)(_mobileUi.IconButton,{icon:({color:e,size:t})=>(0,_jsxRuntime.jsx)(_mobileUi.AppIcon,{name:"close-circle",color:e,size:t}),accessibilityLabel:"Close image",onPress:t,variant:"glass"})}),(0,_jsxRuntime.jsx)(_mobileUi.ToastHost,{})]})})};exports.ImageViewer=ImageViewer;const THUMB=84,buildStyles=e=>_reactNative.StyleSheet.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
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.ImageViewer=exports.AttachmentList=void 0;var _mobileUi=require("@webority/mobile-ui"),_react=require("react"),_reactNative=require("react-native"),_reactNativeGestureHandler=require("react-native-gesture-handler"),_format=require("../format.js"),_SupportProvider=require("../SupportProvider.js"),_jsxRuntime=require("react/jsx-runtime");const AttachmentThumb=({attachment:e,onOpen:t})=>{const i=(0,_mobileUi.useTheme)(),s=(0,_react.useMemo)(()=>buildStyles(i),[i]),a=(0,_SupportProvider.useAttachmentLoader)(),[r,n]=(0,_react.useState)({status:"loading"});return(0,_react.useEffect)(()=>{let t=!1;return a(e).then(e=>!t&&n({status:"ready",uri:e}),()=>!t&&n({status:"error"})),()=>{t=!0}},[a,e]),"loading"===r.status?(0,_jsxRuntime.jsx)(_reactNative.View,{style:s.thumb,testID:`support-attachment-${e.id}`,children:(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{size:"sm",accessibilityLabel:`Loading ${e.fileName}`})}):"error"===r.status?(0,_jsxRuntime.jsx)(_reactNative.View,{style:s.thumb,accessible:!0,accessibilityLabel:`${e.fileName} could not be loaded`,testID:`support-attachment-${e.id}`,children:(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",align:"center",children:"Could not load"})}):(0,_jsxRuntime.jsx)(_reactNative.Pressable,{onPress:()=>t(r.uri,e.fileName),accessibilityRole:"imagebutton",accessibilityLabel:`Open ${e.fileName}`,testID:`support-attachment-${e.id}`,children:(0,_jsxRuntime.jsx)(_reactNative.Image,{source:{uri:r.uri},style:s.thumb,resizeMode:"cover"})})},FileRow=({attachment:e})=>{const t=(0,_mobileUi.useTheme)(),i=(0,_react.useMemo)(()=>buildStyles(t),[t]),s=(0,_SupportProvider.useAttachmentOpener)(),[a,r]=(0,_react.useState)(!1),n=`${e.fileName}, ${(0,_format.formatBytes)(e.sizeBytes)}`;if(!s)return(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"secondary",testID:`support-file-${e.id}`,children:n});return(0,_jsxRuntime.jsxs)(_reactNative.Pressable,{onPress:()=>{(async()=>{r(!0);try{await s(e)}catch{_mobileUi.toast.error(`Could not open ${e.fileName}. Try again.`)}finally{r(!1)}})()},disabled:a,accessibilityRole:"button",accessibilityLabel:`Open ${e.fileName}`,accessibilityState:{busy:a,disabled:a},"aria-busy":a,"aria-disabled":a,style:i.fileRow,testID:`support-file-${e.id}`,children:[(0,_jsxRuntime.jsx)(_mobileUi.AppIcon,{name:"document",size:16,color:t.colors.text.link}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"link",children:n}),a&&(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{size:"sm",accessibilityLabel:`Opening ${e.fileName}`})]})},AttachmentList=({attachments:e,onOpenImage:t})=>{const i=(0,_mobileUi.useTheme)(),s=(0,_react.useMemo)(()=>buildStyles(i),[i]);if(0===e.length)return null;const a=e.filter(e=>(0,_format.isImage)(e.contentType)),r=e.filter(e=>!(0,_format.isImage)(e.contentType));return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.list,children:[a.length>0&&(0,_jsxRuntime.jsx)(_reactNative.View,{style:s.thumbRow,children:a.map(e=>(0,_jsxRuntime.jsx)(AttachmentThumb,{attachment:e,onOpen:t},e.id))}),r.map(e=>(0,_jsxRuntime.jsx)(FileRow,{attachment:e},e.id))]})};exports.AttachmentList=AttachmentList;const ImageViewer=({image:e,onClose:t})=>{const i=(0,_mobileUi.useTheme)(),s=(0,_react.useMemo)(()=>buildStyles(i),[i]);return(0,_jsxRuntime.jsx)(_reactNative.Modal,{visible:null!==e,onRequestClose:t,animationType:"fade",transparent:!1,children:(0,_jsxRuntime.jsxs)(_reactNativeGestureHandler.GestureHandlerRootView,{style:s.viewer,testID:"support-image-viewer",children:[e&&(0,_jsxRuntime.jsx)(_mobileUi.ZoomableImage,{accessibilityLabel:e.name,style:s.viewerImageWrap,children:(0,_jsxRuntime.jsx)(_reactNative.Image,{source:{uri:e.uri},style:s.viewerImage,resizeMode:"contain"})}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:s.viewerClose,children:(0,_jsxRuntime.jsx)(_mobileUi.IconButton,{icon:({color:e,size:t})=>(0,_jsxRuntime.jsx)(_mobileUi.AppIcon,{name:"close-circle",color:e,size:t}),accessibilityLabel:"Close image",onPress:t,variant:"glass"})}),(0,_jsxRuntime.jsx)(_mobileUi.ToastHost,{})]})})};exports.ImageViewer=ImageViewer;const THUMB=84,buildStyles=e=>_reactNative.StyleSheet.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 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.NewSupportTicket=void 0;var _mobileUi=require("@webority/mobile-ui"),_react=require("react"),_reactNative=require("react-native"),_client=require("../client.js"),_SupportProvider=require("../SupportProvider.js"),_validation=require("../validation.js"),_PhotoTray=require("./PhotoTray.js"),_jsxRuntime=require("react/jsx-runtime");const TicketForm=({config:e,styles:t,onCreated:i,onCancel:r,initialDraft:a})=>{const
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.NewSupportTicket=void 0;var _mobileUi=require("@webority/mobile-ui"),_react=require("react"),_reactNative=require("react-native"),_client=require("../client.js"),_device=require("../device.js"),_SupportProvider=require("../SupportProvider.js"),_validation=require("../validation.js"),_PhotoTray=require("./PhotoTray.js"),_jsxRuntime=require("react/jsx-runtime");const TicketForm=({config:e,styles:t,onCreated:i,onCancel:r,initialDraft:o,defaultCategory:a,appVersion:s})=>{const n=(0,_SupportProvider.useCreateSupportTicket)(),listed=t=>t&&e.categories.includes(t)?t:void 0,[l,u]=(0,_react.useState)([]),[c,d]=(0,_react.useState)(o?.screenshots??[]),[m,p]=(0,_react.useState)(null),[g,x]=(0,_react.useState)(null),b=(0,_mobileUi.useForm)({initialValues:{category:listed(o?.category)??listed(a)??"",subject:o?.subject??"",body:o?.body??""},validate:{category:t=>(0,_validation.validateCategory)(t,e),subject:t=>(0,_validation.validateSubject)(t,e),body:t=>(0,_validation.validateBody)(t,e)},onSubmit:async t=>{const r=(0,_validation.validateFiles)([...c,...l],e);if(p(r??null),!r){x(null);try{const e=await n({category:t.category,subject:t.subject.trim(),body:t.body.trim(),pageUrl:o?.pageUrl,userAgent:o?.userAgent??(0,_device.describeDevice)(),appVersion:o?.appVersion??s,files:l,screenshots:c});i(e)}catch(e){x((0,_client.toSupportApiError)(e).message)}}}}),_=b.getFieldProps("category"),j=b.getFieldProps("subject"),y=b.getFieldProps("body"),h=c.length+l.length;return(0,_jsxRuntime.jsxs)(_mobileUi.KeyboardAwareScrollView,{contentContainerStyle:t.content,safeAreaEdges:[],children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"secondary",children:"Tell us what you need. We reply by email and in this ticket."}),(0,_jsxRuntime.jsx)(_mobileUi.Select,{label:"Category",placeholder:"Choose a category",required:!0,options:e.categories.map(e=>({label:e,value:e})),value:_.value||void 0,onChange:e=>_.setValue(String(e),!0),error:_.error,testID:"support-new-category"}),(0,_jsxRuntime.jsx)(_mobileUi.Input,{label:"Subject",placeholder:"A short summary",required:!0,maxLength:e.subjectMaxLength,value:j.value,onChangeText:j.onChangeText,onBlur:j.onBlur,error:j.error,testID:"support-new-subject"}),(0,_jsxRuntime.jsx)(_mobileUi.Input,{label:"Message",placeholder:"What happened, and what did you expect?",required:!0,multiline:!0,maxLength:e.bodyMaxLength,value:y.value,onChangeText:y.onChangeText,onBlur:y.onBlur,error:y.error,testID:"support-new-body"}),c.length>0&&(0,_jsxRuntime.jsx)(_PhotoTray.PhotoTray,{label:"Screenshot",photos:c,onChange:d,remaining:0,allowAdd:!1,maxBytes:e.maxAttachmentBytes,disabled:b.isSubmitting}),(0,_jsxRuntime.jsx)(_PhotoTray.PhotoTray,{label:"Photos",photos:l,onChange:u,remaining:e.maxAttachmentsPerMessage-h,maxBytes:e.maxAttachmentBytes,disabled:b.isSubmitting}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",children:`Attach up to ${e.maxAttachmentsPerMessage} files.`}),(m||g)&&(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"error",accessibilityLiveRegion:"polite",children:m??g}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:t.actions,children:[r&&(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Cancel",variant:"ghost",tone:"neutral",onPress:r,disabled:b.isSubmitting}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Send ticket",loading:b.isSubmitting,disabled:b.isSubmitting,onPress:b.submit,testID:"support-new-submit"})]})]})},NewSupportTicket=e=>{const t=(0,_mobileUi.useTheme)(),i=(0,_react.useMemo)(()=>buildStyles(t),[t]),{config:r,loading:o,error:a,reload:s}=(0,_SupportProvider.useSupportConfig)(),n=e.testID??"support-new-ticket";return r?(0,_jsxRuntime.jsx)(_reactNative.View,{style:i.root,testID:n,children:(0,_jsxRuntime.jsx)(TicketForm,{...e,config:r,styles:i})}):(0,_jsxRuntime.jsx)(_reactNative.View,{style:i.center,testID:n,children:o?(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{accessibilityLabel:"Loading"}):(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"body",color:"error",align:"center",children:a?.message??"Support is not available right now."}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Try again",variant:"ghost",onPress:()=>{s()}})]})})};exports.NewSupportTicket=NewSupportTicket;const buildStyles=e=>_reactNative.StyleSheet.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 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.SupportTicketDetail=void 0;var _mobileCore=require("@webority/mobile-core"),_mobileUi=require("@webority/mobile-ui"),_react=require("react"),_reactNative=require("react-native"),_SupportProvider=require("../SupportProvider.js"),_validation=require("../validation.js"),_Attachments=require("./Attachments.js"),_PhotoTray=require("./PhotoTray.js"),_SupportTicketList=require("./SupportTicketList.js"),_jsxRuntime=require("react/jsx-runtime");const MessageBubble=({message:e,styles:t,timeZone:i,onOpenImage:s})=>{if("System"===e.authorKind)return(0,_jsxRuntime.jsx)(_reactNative.View,{style:t.system,testID:`support-message-${e.id}`,children:(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",align:"center",children:`${e.body}, ${(0,_mobileCore.formatDateTime)(e.createdDateTimeUtc,{zone:i})}`})});const a="Customer"===e.authorKind;return(0,_jsxRuntime.jsx)(_reactNative.View,{style:[t.bubbleRow,a?t.mineRow:t.theirsRow],testID:`support-message-${e.id}`,children:(0,_jsxRuntime.jsxs)(_reactNative.View,{style:[t.bubble,a?t.mine:t.theirs],children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"label",color:a?"primary":"secondary",children:e.authorName??"
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.SupportTicketDetail=void 0;var _mobileCore=require("@webority/mobile-core"),_mobileUi=require("@webority/mobile-ui"),_react=require("react"),_reactNative=require("react-native"),_SupportProvider=require("../SupportProvider.js"),_validation=require("../validation.js"),_Attachments=require("./Attachments.js"),_PhotoTray=require("./PhotoTray.js"),_SupportTicketList=require("./SupportTicketList.js"),_jsxRuntime=require("react/jsx-runtime");const MessageBubble=({message:e,styles:t,timeZone:i,onOpenImage:s})=>{if("System"===e.authorKind)return(0,_jsxRuntime.jsx)(_reactNative.View,{style:t.system,testID:`support-message-${e.id}`,children:(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",align:"center",children:`${e.body}, ${(0,_mobileCore.formatDateTime)(e.createdDateTimeUtc,{zone:i})}`})});const a="Customer"===e.authorKind;return(0,_jsxRuntime.jsx)(_reactNative.View,{style:[t.bubbleRow,a?t.mineRow:t.theirsRow],testID:`support-message-${e.id}`,children:(0,_jsxRuntime.jsxs)(_reactNative.View,{style:[t.bubble,a?t.mine:t.theirs],children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"label",color:a?"primary":"secondary",children:e.authorName??(a?"You":"Support team")}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"body",selectable:!0,children:e.body}),(0,_jsxRuntime.jsx)(_Attachments.AttachmentList,{attachments:e.attachments,onOpenImage:s}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",children:(0,_mobileCore.formatDateTime)(e.createdDateTimeUtc,{zone:i})})]})})},SATISFACTION_TEXT={Satisfied:"You told us this solved your problem.",Unsatisfied:"You told us this did not solve your problem."},SupportTicketDetail=({ticketId:e,testID:t="support-ticket-detail"})=>{const i=(0,_mobileUi.useTheme)(),s=(0,_react.useMemo)(()=>buildStyles(i),[i]),{config:a}=(0,_SupportProvider.useSupportConfig)(),n=(0,_SupportProvider.useSupportTimeZone)(),{ticket:o,loading:r,error:l,actionError:c,busy:m,reload:u,reply:d,reopen:x,rate:p}=(0,_SupportProvider.useSupportTicket)(e),[_,b]=(0,_react.useState)(null),[j,g]=(0,_react.useState)(""),[y,h]=(0,_react.useState)([]),[R,v]=(0,_react.useState)(null),[S,T]=(0,_react.useState)(null),[f,U]=(0,_react.useState)("");if(!o)return(0,_jsxRuntime.jsx)(_reactNative.View,{style:s.center,testID:t,children:r?(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{accessibilityLabel:"Loading the ticket"}):(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"body",color:"error",align:"center",children:l?.message??"That ticket could not be found."}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Try again",variant:"ghost",onPress:()=>{u()}})]})});const w=o.messages.filter(e=>!e.isInternal);return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.root,testID:t,children:[(0,_jsxRuntime.jsxs)(_mobileUi.KeyboardAwareScrollView,{contentContainerStyle:s.content,safeAreaEdges:[],children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.head,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"h3",accessibilityRole:"header",children:o.subject}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"secondary",children:`${o.number}, ${o.category}`}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.headRow,children:[(0,_jsxRuntime.jsx)(_mobileUi.StatusBadge,{label:o.statusLabel,tone:_SupportTicketList.STATUS_TONES[o.status]??"neutral",accessibilityLabel:`Status: ${o.statusLabel}`}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",children:`Opened ${(0,_mobileCore.formatDateTime)(o.createdDateTimeUtc,{zone:n})}`})]})]}),o.canReopen&&(0,_jsxRuntime.jsxs)(_mobileUi.Card,{variant:"outlined",bodyStyle:s.section,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",children:`This ticket is ${o.statusLabel}. Reopen it if you still need help.`}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Reopen ticket",variant:"outline",size:"sm",loading:m,disabled:m,onPress:()=>{x()},testID:"support-reopen"})]}),o.satisfaction?(0,_jsxRuntime.jsxs)(_mobileUi.Card,{variant:"outlined",bodyStyle:s.section,testID:"support-rating",children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",accessibilityLiveRegion:"polite",children:SATISFACTION_TEXT[o.satisfaction]}),o.satisfactionComment?(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"secondary",children:`Your comment: ${o.satisfactionComment}`}):null]}):o.canRate?(0,_jsxRuntime.jsxs)(_mobileUi.Card,{variant:"outlined",bodyStyle:s.section,testID:"support-rating",children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"label",children:"Did this solve your problem?"}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.ratingRow,children:[(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Yes, it is solved",size:"sm",leftIcon:"like",variant:"Satisfied"===S?"solid":"outline",tone:"success",accessibilityState:{selected:"Satisfied"===S},"aria-selected":"Satisfied"===S,onPress:()=>T("Satisfied"),testID:"support-rate-satisfied"}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"No, it is not",size:"sm",leftIcon:"dislike",variant:"Unsatisfied"===S?"solid":"outline",tone:"error",accessibilityState:{selected:"Unsatisfied"===S},"aria-selected":"Unsatisfied"===S,onPress:()=>T("Unsatisfied"),testID:"support-rate-unsatisfied"})]}),S&&(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_mobileUi.Input,{label:"Anything to add? (optional)",helperText:"Your comment goes to the support team.",value:f,onChangeText:U,multiline:!0,maxLength:a?.satisfactionCommentMaxLength,testID:"support-rating-comment"}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Send answer",size:"sm",loading:m,disabled:m,onPress:()=>{(async()=>{S&&await p({satisfaction:S,comment:f})&&(T(null),U(""))})()},testID:"support-rating-send"})]})]}):null,(0,_jsxRuntime.jsx)(_reactNative.View,{style:s.thread,accessibilityLabel:"Conversation",children:w.map(e=>(0,_jsxRuntime.jsx)(MessageBubble,{message:e,styles:s,timeZone:n,onOpenImage:(e,t)=>b({uri:e,name:t})},e.id))}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.section,children:[(0,_jsxRuntime.jsx)(_mobileUi.Input,{label:"Your reply",placeholder:"Write a reply to the support team",value:j,onChangeText:g,multiline:!0,maxLength:a?.bodyMaxLength,error:R??void 0,testID:"support-reply-input"}),a&&(0,_jsxRuntime.jsx)(_PhotoTray.PhotoTray,{photos:y,onChange:h,remaining:a.maxAttachmentsPerMessage-y.length,maxBytes:a.maxAttachmentBytes,disabled:m}),o.canReopen&&(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"secondary",children:"Replying reopens this ticket."}),c&&(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"error",accessibilityLiveRegion:"polite",children:c.message}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Send reply",leftIcon:"send",loading:m,disabled:m,onPress:()=>{(async()=>{const e=j.trim(),t=a?(0,_validation.validateBody)(e,a)??(0,_validation.validateFiles)(y,a):e?void 0:"Write a message.";t?v(t):(v(null),await d({body:e,files:y})&&(g(""),h([])))})()},testID:"support-reply-send"})]})]}),(0,_jsxRuntime.jsx)(_Attachments.ImageViewer,{image:_,onClose:()=>b(null)})]})};exports.SupportTicketDetail=SupportTicketDetail;const buildStyles=e=>_reactNative.StyleSheet.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},head:{gap:e.spacing.xxs},headRow:{flexDirection:"row",alignItems:"center",gap:e.spacing.sm,marginTop:e.spacing.xs},section:{gap:e.spacing.sm},ratingRow:{flexDirection:"row",flexWrap:"wrap",gap:e.spacing.sm},thread:{gap:e.spacing.sm},system:{paddingHorizontal:e.spacing.lg,paddingVertical:e.spacing.xxs},bubbleRow:{flexDirection:"row"},mineRow:{justifyContent:"flex-end"},theirsRow:{justifyContent:"flex-start"},bubble:{maxWidth:"85%",borderRadius:e.radius.lg,padding:e.spacing.sm,gap:e.spacing.xxs},mine:{backgroundColor:e.colors.primaryMuted},theirs:{backgroundColor:e.colors.background.secondary}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{AppIcon,IconButton,Spinner,Text,ToastHost,toast,useTheme,ZoomableImage}from"@webority/mobile-ui";import{useEffect,useMemo,useState}from"react";import{Image,Modal,Pressable,StyleSheet,View}from"react-native";import{GestureHandlerRootView}from"react-native-gesture-handler";import{formatBytes,isImage}from"../format.js";import{useAttachmentLoader,useAttachmentOpener}from"../SupportProvider.js";import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";const AttachmentThumb=({attachment:e,onOpen:t})=>{const s=useTheme(),i=useMemo(()=>buildStyles(s),[s]),a=useAttachmentLoader(),[r,o]=useState({status:"loading"});return useEffect(()=>{let t=!1;return a(e).then(e=>!t&&o({status:"ready",uri:e}),()=>!t&&o({status:"error"})),()=>{t=!0}},[a,e]),"loading"===r.status?_jsx(View,{style:i.thumb,testID:`support-attachment-${e.id}`,children:_jsx(Spinner,{size:"sm",accessibilityLabel:`Loading ${e.fileName}`})}):"error"===r.status?_jsx(View,{style:i.thumb,accessible:!0,accessibilityLabel:`${e.fileName} could not be loaded`,testID:`support-attachment-${e.id}`,children:_jsx(Text,{variant:"caption",color:"tertiary",align:"center",children:"Could not load"})}):_jsx(Pressable,{onPress:()=>t(r.uri,e.fileName),accessibilityRole:"imagebutton",accessibilityLabel:`Open ${e.fileName}`,testID:`support-attachment-${e.id}`,children:_jsx(Image,{source:{uri:r.uri},style:i.thumb,resizeMode:"cover"})})},FileRow=({attachment:e})=>{const t=useTheme(),s=useMemo(()=>buildStyles(t),[t]),i=useAttachmentOpener(),[a,r]=useState(!1),o=`${e.fileName}, ${formatBytes(e.sizeBytes)}`;if(!i)return _jsx(Text,{variant:"caption",color:"secondary",testID:`support-file-${e.id}`,children:o});return _jsxs(Pressable,{onPress:()=>{(async()=>{r(!0);try{await i(e)}catch{toast.error(`Could not open ${e.fileName}. Try again.`)}finally{r(!1)}})()},disabled:a,accessibilityRole:"button",accessibilityLabel:`Open ${e.fileName}`,accessibilityState:{busy:a,disabled:a},style:s.fileRow,testID:`support-file-${e.id}`,children:[_jsx(AppIcon,{name:"document",size:16,color:t.colors.text.link}),_jsx(Text,{variant:"caption",color:"link",children:o}),a&&_jsx(Spinner,{size:"sm",accessibilityLabel:`Opening ${e.fileName}`})]})};export const AttachmentList=({attachments:e,onOpenImage:t})=>{const s=useTheme(),i=useMemo(()=>buildStyles(s),[s]);if(0===e.length)return null;const a=e.filter(e=>isImage(e.contentType)),r=e.filter(e=>!isImage(e.contentType));return _jsxs(View,{style:i.list,children:[a.length>0&&_jsx(View,{style:i.thumbRow,children:a.map(e=>_jsx(AttachmentThumb,{attachment:e,onOpen:t},e.id))}),r.map(e=>_jsx(FileRow,{attachment:e},e.id))]})};export const ImageViewer=({image:e,onClose:t})=>{const s=useTheme(),i=useMemo(()=>buildStyles(s),[s]);return _jsx(Modal,{visible:null!==e,onRequestClose:t,animationType:"fade",transparent:!1,children:_jsxs(GestureHandlerRootView,{style:i.viewer,testID:"support-image-viewer",children:[e&&_jsx(ZoomableImage,{accessibilityLabel:e.name,style:i.viewerImageWrap,children:_jsx(Image,{source:{uri:e.uri},style:i.viewerImage,resizeMode:"contain"})}),_jsx(View,{style:i.viewerClose,children:_jsx(IconButton,{icon:({color:e,size:t})=>_jsx(AppIcon,{name:"close-circle",color:e,size:t}),accessibilityLabel:"Close image",onPress:t,variant:"glass"})}),_jsx(ToastHost,{})]})})};const THUMB=84,buildStyles=e=>StyleSheet.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
|
+
"use strict";import{AppIcon,IconButton,Spinner,Text,ToastHost,toast,useTheme,ZoomableImage}from"@webority/mobile-ui";import{useEffect,useMemo,useState}from"react";import{Image,Modal,Pressable,StyleSheet,View}from"react-native";import{GestureHandlerRootView}from"react-native-gesture-handler";import{formatBytes,isImage}from"../format.js";import{useAttachmentLoader,useAttachmentOpener}from"../SupportProvider.js";import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";const AttachmentThumb=({attachment:e,onOpen:t})=>{const s=useTheme(),i=useMemo(()=>buildStyles(s),[s]),a=useAttachmentLoader(),[r,o]=useState({status:"loading"});return useEffect(()=>{let t=!1;return a(e).then(e=>!t&&o({status:"ready",uri:e}),()=>!t&&o({status:"error"})),()=>{t=!0}},[a,e]),"loading"===r.status?_jsx(View,{style:i.thumb,testID:`support-attachment-${e.id}`,children:_jsx(Spinner,{size:"sm",accessibilityLabel:`Loading ${e.fileName}`})}):"error"===r.status?_jsx(View,{style:i.thumb,accessible:!0,accessibilityLabel:`${e.fileName} could not be loaded`,testID:`support-attachment-${e.id}`,children:_jsx(Text,{variant:"caption",color:"tertiary",align:"center",children:"Could not load"})}):_jsx(Pressable,{onPress:()=>t(r.uri,e.fileName),accessibilityRole:"imagebutton",accessibilityLabel:`Open ${e.fileName}`,testID:`support-attachment-${e.id}`,children:_jsx(Image,{source:{uri:r.uri},style:i.thumb,resizeMode:"cover"})})},FileRow=({attachment:e})=>{const t=useTheme(),s=useMemo(()=>buildStyles(t),[t]),i=useAttachmentOpener(),[a,r]=useState(!1),o=`${e.fileName}, ${formatBytes(e.sizeBytes)}`;if(!i)return _jsx(Text,{variant:"caption",color:"secondary",testID:`support-file-${e.id}`,children:o});return _jsxs(Pressable,{onPress:()=>{(async()=>{r(!0);try{await i(e)}catch{toast.error(`Could not open ${e.fileName}. Try again.`)}finally{r(!1)}})()},disabled:a,accessibilityRole:"button",accessibilityLabel:`Open ${e.fileName}`,accessibilityState:{busy:a,disabled:a},"aria-busy":a,"aria-disabled":a,style:s.fileRow,testID:`support-file-${e.id}`,children:[_jsx(AppIcon,{name:"document",size:16,color:t.colors.text.link}),_jsx(Text,{variant:"caption",color:"link",children:o}),a&&_jsx(Spinner,{size:"sm",accessibilityLabel:`Opening ${e.fileName}`})]})};export const AttachmentList=({attachments:e,onOpenImage:t})=>{const s=useTheme(),i=useMemo(()=>buildStyles(s),[s]);if(0===e.length)return null;const a=e.filter(e=>isImage(e.contentType)),r=e.filter(e=>!isImage(e.contentType));return _jsxs(View,{style:i.list,children:[a.length>0&&_jsx(View,{style:i.thumbRow,children:a.map(e=>_jsx(AttachmentThumb,{attachment:e,onOpen:t},e.id))}),r.map(e=>_jsx(FileRow,{attachment:e},e.id))]})};export const ImageViewer=({image:e,onClose:t})=>{const s=useTheme(),i=useMemo(()=>buildStyles(s),[s]);return _jsx(Modal,{visible:null!==e,onRequestClose:t,animationType:"fade",transparent:!1,children:_jsxs(GestureHandlerRootView,{style:i.viewer,testID:"support-image-viewer",children:[e&&_jsx(ZoomableImage,{accessibilityLabel:e.name,style:i.viewerImageWrap,children:_jsx(Image,{source:{uri:e.uri},style:i.viewerImage,resizeMode:"contain"})}),_jsx(View,{style:i.viewerClose,children:_jsx(IconButton,{icon:({color:e,size:t})=>_jsx(AppIcon,{name:"close-circle",color:e,size:t}),accessibilityLabel:"Close image",onPress:t,variant:"glass"})}),_jsx(ToastHost,{})]})})};const THUMB=84,buildStyles=e=>StyleSheet.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 +1 @@
|
|
|
1
|
-
"use strict";import{Button,Input,Select,Spinner,Text,useForm,useTheme}from"@webority/mobile-ui";import{useMemo,useState}from"react";import{
|
|
1
|
+
"use strict";import{Button,Input,KeyboardAwareScrollView,Select,Spinner,Text,useForm,useTheme}from"@webority/mobile-ui";import{useMemo,useState}from"react";import{StyleSheet,View}from"react-native";import{toSupportApiError}from"../client.js";import{describeDevice}from"../device.js";import{useCreateSupportTicket,useSupportConfig}from"../SupportProvider.js";import{validateBody,validateCategory,validateFiles,validateSubject}from"../validation.js";import{PhotoTray}from"./PhotoTray.js";import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";const TicketForm=({config:e,styles:t,onCreated:r,onCancel:o,initialDraft:a,defaultCategory:s,appVersion:i})=>{const n=useCreateSupportTicket(),listed=t=>t&&e.categories.includes(t)?t:void 0,[l,c]=useState([]),[u,d]=useState(a?.screenshots??[]),[p,g]=useState(null),[m,y]=useState(null),h=useForm({initialValues:{category:listed(a?.category)??listed(s)??"",subject:a?.subject??"",body:a?.body??""},validate:{category:t=>validateCategory(t,e),subject:t=>validateSubject(t,e),body:t=>validateBody(t,e)},onSubmit:async t=>{const o=validateFiles([...u,...l],e);if(g(o??null),!o){y(null);try{const e=await n({category:t.category,subject:t.subject.trim(),body:t.body.trim(),pageUrl:a?.pageUrl,userAgent:a?.userAgent??describeDevice(),appVersion:a?.appVersion??i,files:l,screenshots:u});r(e)}catch(e){y(toSupportApiError(e).message)}}}}),b=h.getFieldProps("category"),x=h.getFieldProps("subject"),j=h.getFieldProps("body"),S=u.length+l.length;return _jsxs(KeyboardAwareScrollView,{contentContainerStyle:t.content,safeAreaEdges:[],children:[_jsx(Text,{variant:"bodySmall",color:"secondary",children:"Tell us what you need. We reply by email and in this ticket."}),_jsx(Select,{label:"Category",placeholder:"Choose a category",required:!0,options:e.categories.map(e=>({label:e,value:e})),value:b.value||void 0,onChange:e=>b.setValue(String(e),!0),error:b.error,testID:"support-new-category"}),_jsx(Input,{label:"Subject",placeholder:"A short summary",required:!0,maxLength:e.subjectMaxLength,value:x.value,onChangeText:x.onChangeText,onBlur:x.onBlur,error:x.error,testID:"support-new-subject"}),_jsx(Input,{label:"Message",placeholder:"What happened, and what did you expect?",required:!0,multiline:!0,maxLength:e.bodyMaxLength,value:j.value,onChangeText:j.onChangeText,onBlur:j.onBlur,error:j.error,testID:"support-new-body"}),u.length>0&&_jsx(PhotoTray,{label:"Screenshot",photos:u,onChange:d,remaining:0,allowAdd:!1,maxBytes:e.maxAttachmentBytes,disabled:h.isSubmitting}),_jsx(PhotoTray,{label:"Photos",photos:l,onChange:c,remaining:e.maxAttachmentsPerMessage-S,maxBytes:e.maxAttachmentBytes,disabled:h.isSubmitting}),_jsx(Text,{variant:"caption",color:"tertiary",children:`Attach up to ${e.maxAttachmentsPerMessage} files.`}),(p||m)&&_jsx(Text,{variant:"bodySmall",color:"error",accessibilityLiveRegion:"polite",children:p??m}),_jsxs(View,{style:t.actions,children:[o&&_jsx(Button,{title:"Cancel",variant:"ghost",tone:"neutral",onPress:o,disabled:h.isSubmitting}),_jsx(Button,{title:"Send ticket",loading:h.isSubmitting,disabled:h.isSubmitting,onPress:h.submit,testID:"support-new-submit"})]})]})};export const NewSupportTicket=e=>{const t=useTheme(),r=useMemo(()=>buildStyles(t),[t]),{config:o,loading:a,error:s,reload:i}=useSupportConfig(),n=e.testID??"support-new-ticket";return _jsx(View,o?{style:r.root,testID:n,children:_jsx(TicketForm,{...e,config:o,styles:r})}:{style:r.center,testID:n,children:a?_jsx(Spinner,{accessibilityLabel:"Loading"}):_jsxs(_Fragment,{children:[_jsx(Text,{variant:"body",color:"error",align:"center",children:s?.message??"Support is not available right now."}),_jsx(Button,{title:"Try again",variant:"ghost",onPress:()=>{i()}})]})})};const buildStyles=e=>StyleSheet.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 +1 @@
|
|
|
1
|
-
"use strict";import{formatDateTime}from"@webority/mobile-core";import{Button,Card,Input,Spinner,StatusBadge,Text,useTheme}from"@webority/mobile-ui";import{useMemo,useState}from"react";import{
|
|
1
|
+
"use strict";import{formatDateTime}from"@webority/mobile-core";import{Button,Card,Input,KeyboardAwareScrollView,Spinner,StatusBadge,Text,useTheme}from"@webority/mobile-ui";import{useMemo,useState}from"react";import{StyleSheet,View}from"react-native";import{useSupportConfig,useSupportTicket,useSupportTimeZone}from"../SupportProvider.js";import{validateBody,validateFiles}from"../validation.js";import{AttachmentList,ImageViewer}from"./Attachments.js";import{PhotoTray}from"./PhotoTray.js";import{STATUS_TONES}from"./SupportTicketList.js";import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";const MessageBubble=({message:e,styles:t,timeZone:s,onOpenImage:i})=>{if("System"===e.authorKind)return _jsx(View,{style:t.system,testID:`support-message-${e.id}`,children:_jsx(Text,{variant:"caption",color:"tertiary",align:"center",children:`${e.body}, ${formatDateTime(e.createdDateTimeUtc,{zone:s})}`})});const a="Customer"===e.authorKind;return _jsx(View,{style:[t.bubbleRow,a?t.mineRow:t.theirsRow],testID:`support-message-${e.id}`,children:_jsxs(View,{style:[t.bubble,a?t.mine:t.theirs],children:[_jsx(Text,{variant:"label",color:a?"primary":"secondary",children:e.authorName??(a?"You":"Support team")}),_jsx(Text,{variant:"body",selectable:!0,children:e.body}),_jsx(AttachmentList,{attachments:e.attachments,onOpenImage:i}),_jsx(Text,{variant:"caption",color:"tertiary",children:formatDateTime(e.createdDateTimeUtc,{zone:s})})]})})},SATISFACTION_TEXT={Satisfied:"You told us this solved your problem.",Unsatisfied:"You told us this did not solve your problem."};export const SupportTicketDetail=({ticketId:e,testID:t="support-ticket-detail"})=>{const s=useTheme(),i=useMemo(()=>buildStyles(s),[s]),{config:a}=useSupportConfig(),o=useSupportTimeZone(),{ticket:n,loading:r,error:l,actionError:c,busy:d,reload:m,reply:p,reopen:u,rate:g}=useSupportTicket(e),[x,y]=useState(null),[h,b]=useState(""),[S,j]=useState([]),[f,T]=useState(null),[_,v]=useState(null),[w,I]=useState("");if(!n)return _jsx(View,{style:i.center,testID:t,children:r?_jsx(Spinner,{accessibilityLabel:"Loading the ticket"}):_jsxs(_Fragment,{children:[_jsx(Text,{variant:"body",color:"error",align:"center",children:l?.message??"That ticket could not be found."}),_jsx(Button,{title:"Try again",variant:"ghost",onPress:()=>{m()}})]})});const D=n.messages.filter(e=>!e.isInternal);return _jsxs(View,{style:i.root,testID:t,children:[_jsxs(KeyboardAwareScrollView,{contentContainerStyle:i.content,safeAreaEdges:[],children:[_jsxs(View,{style:i.head,children:[_jsx(Text,{variant:"h3",accessibilityRole:"header",children:n.subject}),_jsx(Text,{variant:"bodySmall",color:"secondary",children:`${n.number}, ${n.category}`}),_jsxs(View,{style:i.headRow,children:[_jsx(StatusBadge,{label:n.statusLabel,tone:STATUS_TONES[n.status]??"neutral",accessibilityLabel:`Status: ${n.statusLabel}`}),_jsx(Text,{variant:"caption",color:"tertiary",children:`Opened ${formatDateTime(n.createdDateTimeUtc,{zone:o})}`})]})]}),n.canReopen&&_jsxs(Card,{variant:"outlined",bodyStyle:i.section,children:[_jsx(Text,{variant:"bodySmall",children:`This ticket is ${n.statusLabel}. Reopen it if you still need help.`}),_jsx(Button,{title:"Reopen ticket",variant:"outline",size:"sm",loading:d,disabled:d,onPress:()=>{u()},testID:"support-reopen"})]}),n.satisfaction?_jsxs(Card,{variant:"outlined",bodyStyle:i.section,testID:"support-rating",children:[_jsx(Text,{variant:"bodySmall",accessibilityLiveRegion:"polite",children:SATISFACTION_TEXT[n.satisfaction]}),n.satisfactionComment?_jsx(Text,{variant:"bodySmall",color:"secondary",children:`Your comment: ${n.satisfactionComment}`}):null]}):n.canRate?_jsxs(Card,{variant:"outlined",bodyStyle:i.section,testID:"support-rating",children:[_jsx(Text,{variant:"label",children:"Did this solve your problem?"}),_jsxs(View,{style:i.ratingRow,children:[_jsx(Button,{title:"Yes, it is solved",size:"sm",leftIcon:"like",variant:"Satisfied"===_?"solid":"outline",tone:"success",accessibilityState:{selected:"Satisfied"===_},"aria-selected":"Satisfied"===_,onPress:()=>v("Satisfied"),testID:"support-rate-satisfied"}),_jsx(Button,{title:"No, it is not",size:"sm",leftIcon:"dislike",variant:"Unsatisfied"===_?"solid":"outline",tone:"error",accessibilityState:{selected:"Unsatisfied"===_},"aria-selected":"Unsatisfied"===_,onPress:()=>v("Unsatisfied"),testID:"support-rate-unsatisfied"})]}),_&&_jsxs(_Fragment,{children:[_jsx(Input,{label:"Anything to add? (optional)",helperText:"Your comment goes to the support team.",value:w,onChangeText:I,multiline:!0,maxLength:a?.satisfactionCommentMaxLength,testID:"support-rating-comment"}),_jsx(Button,{title:"Send answer",size:"sm",loading:d,disabled:d,onPress:()=>{(async()=>{_&&await g({satisfaction:_,comment:w})&&(v(null),I(""))})()},testID:"support-rating-send"})]})]}):null,_jsx(View,{style:i.thread,accessibilityLabel:"Conversation",children:D.map(e=>_jsx(MessageBubble,{message:e,styles:i,timeZone:o,onOpenImage:(e,t)=>y({uri:e,name:t})},e.id))}),_jsxs(View,{style:i.section,children:[_jsx(Input,{label:"Your reply",placeholder:"Write a reply to the support team",value:h,onChangeText:b,multiline:!0,maxLength:a?.bodyMaxLength,error:f??void 0,testID:"support-reply-input"}),a&&_jsx(PhotoTray,{photos:S,onChange:j,remaining:a.maxAttachmentsPerMessage-S.length,maxBytes:a.maxAttachmentBytes,disabled:d}),n.canReopen&&_jsx(Text,{variant:"caption",color:"secondary",children:"Replying reopens this ticket."}),c&&_jsx(Text,{variant:"bodySmall",color:"error",accessibilityLiveRegion:"polite",children:c.message}),_jsx(Button,{title:"Send reply",leftIcon:"send",loading:d,disabled:d,onPress:()=>{(async()=>{const e=h.trim(),t=a?validateBody(e,a)??validateFiles(S,a):e?void 0:"Write a message.";t?T(t):(T(null),await p({body:e,files:S})&&(b(""),j([])))})()},testID:"support-reply-send"})]})]}),_jsx(ImageViewer,{image:x,onClose:()=>y(null)})]})};const buildStyles=e=>StyleSheet.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},head:{gap:e.spacing.xxs},headRow:{flexDirection:"row",alignItems:"center",gap:e.spacing.sm,marginTop:e.spacing.xs},section:{gap:e.spacing.sm},ratingRow:{flexDirection:"row",flexWrap:"wrap",gap:e.spacing.sm},thread:{gap:e.spacing.sm},system:{paddingHorizontal:e.spacing.lg,paddingVertical:e.spacing.xxs},bubbleRow:{flexDirection:"row"},mineRow:{justifyContent:"flex-end"},theirsRow:{justifyContent:"flex-start"},bubble:{maxWidth:"85%",borderRadius:e.radius.lg,padding:e.spacing.sm,gap:e.spacing.xxs},mine:{backgroundColor:e.colors.primaryMuted},theirs:{backgroundColor:e.colors.background.secondary}});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ZoneOption } from '@webority/mobile-core';
|
|
2
2
|
import type React from 'react';
|
|
3
|
-
import { SupportApiError, type SupportClient } from './client';
|
|
3
|
+
import { type SupportApiError, type SupportClient } from './client';
|
|
4
4
|
import type { NewSupportTicketInput, SupportAttachmentView, SupportContactInfo, SupportRatingInput, SupportReplyInput, SupportTicketCounts, SupportTicketDetail, SupportTicketStatus, SupportTicketSummary } from './types';
|
|
5
5
|
/** A downloaded attachment, ready for the OS to open. */
|
|
6
6
|
export interface SupportFileToOpen {
|
|
@@ -6,6 +6,10 @@ export interface NewSupportTicketProps {
|
|
|
6
6
|
onCancel?: () => void;
|
|
7
7
|
/** Prefill, typically from the report-an-issue button: screenshot, page and device. */
|
|
8
8
|
initialDraft?: SupportTicketDraft;
|
|
9
|
+
/** Preselected when the product's categories list it and the draft names none. */
|
|
10
|
+
defaultCategory?: string;
|
|
11
|
+
/** The app's user-visible version, recorded on the ticket when the draft carries none. */
|
|
12
|
+
appVersion?: string;
|
|
9
13
|
testID?: string;
|
|
10
14
|
}
|
|
11
15
|
export declare const NewSupportTicket: React.FC<NewSupportTicketProps>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ZoneOption } from '@webority/mobile-core';
|
|
2
2
|
import type React from 'react';
|
|
3
|
-
import { SupportApiError, type SupportClient } from './client.js';
|
|
3
|
+
import { type SupportApiError, type SupportClient } from './client.js';
|
|
4
4
|
import type { NewSupportTicketInput, SupportAttachmentView, SupportContactInfo, SupportRatingInput, SupportReplyInput, SupportTicketCounts, SupportTicketDetail, SupportTicketStatus, SupportTicketSummary } from './types.js';
|
|
5
5
|
/** A downloaded attachment, ready for the OS to open. */
|
|
6
6
|
export interface SupportFileToOpen {
|
|
@@ -6,6 +6,10 @@ export interface NewSupportTicketProps {
|
|
|
6
6
|
onCancel?: () => void;
|
|
7
7
|
/** Prefill, typically from the report-an-issue button: screenshot, page and device. */
|
|
8
8
|
initialDraft?: SupportTicketDraft;
|
|
9
|
+
/** Preselected when the product's categories list it and the draft names none. */
|
|
10
|
+
defaultCategory?: string;
|
|
11
|
+
/** The app's user-visible version, recorded on the ticket when the draft carries none. */
|
|
12
|
+
appVersion?: string;
|
|
9
13
|
testID?: string;
|
|
10
14
|
}
|
|
11
15
|
export declare const NewSupportTicket: React.FC<NewSupportTicketProps>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/mobile-support",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Customer support tickets for Webority React Native apps: the support API client, hooks, ticket screens, contact card and a report-an-issue button that attaches a screenshot.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -114,8 +114,8 @@
|
|
|
114
114
|
]
|
|
115
115
|
},
|
|
116
116
|
"devDependencies": {
|
|
117
|
-
"@webority/mobile-
|
|
118
|
-
"@webority/mobile-
|
|
117
|
+
"@webority/mobile-ui": "0.9.0",
|
|
118
|
+
"@webority/mobile-core": "0.9.0"
|
|
119
119
|
},
|
|
120
120
|
"scripts": {
|
|
121
121
|
"build": "bob build && node ../../scripts/minify-lib.mjs",
|