@webority/mobile-support 0.106.0 → 0.108.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/SupportProvider.js +1 -1
- package/lib/commonjs/client.js +1 -1
- package/lib/commonjs/components/NewSupportTicket.js +1 -1
- package/lib/commonjs/components/PhotoTray.js +1 -1
- package/lib/commonjs/components/SupportContact.js +1 -1
- package/lib/commonjs/components/SupportTicketDetail.js +1 -1
- package/lib/commonjs/components/SupportTicketList.js +1 -1
- package/lib/commonjs/report/ReportIssueButton.js +1 -1
- package/lib/module/SupportProvider.js +1 -1
- package/lib/module/client.js +1 -1
- package/lib/module/components/NewSupportTicket.js +1 -1
- package/lib/module/components/PhotoTray.js +1 -1
- package/lib/module/components/SupportContact.js +1 -1
- package/lib/module/components/SupportTicketDetail.js +1 -1
- package/lib/module/components/SupportTicketList.js +1 -1
- package/lib/module/report/ReportIssueButton.js +1 -1
- package/lib/typescript/commonjs/SupportProvider.d.ts +1 -0
- package/lib/typescript/commonjs/client.d.ts +2 -0
- package/lib/typescript/commonjs/components/SupportTicketList.d.ts +3 -3
- package/lib/typescript/commonjs/report/ReportIssueButton.d.ts +1 -1
- package/lib/typescript/commonjs/types.d.ts +1 -1
- package/lib/typescript/module/SupportProvider.d.ts +1 -0
- package/lib/typescript/module/client.d.ts +2 -0
- package/lib/typescript/module/components/SupportTicketList.d.ts +3 -3
- package/lib/typescript/module/report/ReportIssueButton.d.ts +1 -1
- package/lib/typescript/module/types.d.ts +1 -1
- package/package.json +8 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.useSupportTimeZone=exports.useSupportTickets=exports.useSupportTicket=exports.useSupportConfig=exports.useSupportClient=exports.useCreateSupportTicket=exports.useAttachmentOpener=exports.useAttachmentLoader=exports.SupportProvider=void 0;var _react=require("react"),_client=require("./client.js"),_files=require("./files.js"),_jsxRuntime=require("react/jsx-runtime");const SupportContext=(0,_react.createContext)(null),SupportProvider=({client:e,openFile:t,timeZone:r,children:c})=>{const[u,
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.useSupportTimeZone=exports.useSupportTickets=exports.useSupportTicket=exports.useSupportConfig=exports.useSupportClient=exports.useCreateSupportTicket=exports.useAttachmentOpener=exports.useAttachmentLoader=exports.SupportProvider=void 0;var _react=require("react"),_client=require("./client.js"),_files=require("./files.js"),_jsxRuntime=require("react/jsx-runtime");const SupportContext=(0,_react.createContext)(null),SupportProvider=({client:e,openFile:t,timeZone:r,children:c})=>{const[u,a]=(0,_react.useState)(null),[n,o]=(0,_react.useState)(null),[s,p]=(0,_react.useState)(!0),[i,l]=(0,_react.useState)(0),S=(0,_react.useRef)(new Map),_=(0,_react.useCallback)(async()=>{p(!0);try{a(await e.getConfig()),o(null)}catch(e){o((0,_client.toSupportApiError)(e))}finally{p(!1)}},[e]);(0,_react.useEffect)(()=>{S.current.clear(),_()},[_]);const f=(0,_react.useCallback)(t=>{const r=S.current.get(t.id);if(r)return r;const c=e.getAttachmentDataUri(t);return S.current.set(t.id,c),c.catch(()=>S.current.delete(t.id)),c},[e]),C=(0,_react.useMemo)(()=>t?async r=>{const c=await(0,_files.saveAttachmentToCache)(r,()=>e.getAttachmentBytes(r.id));await t({uri:c,name:r.fileName,contentType:r.contentType})}:null,[e,t]),d=(0,_react.useCallback)(()=>l(e=>e+1),[]),m=(0,_react.useMemo)(()=>({client:e,config:u,configError:n,configLoading:s,reloadConfig:_,loadAttachment:f,openAttachment:C,timeZone:r,revision:i,bumpRevision:d}),[e,u,n,s,_,f,C,r,i,d]);return(0,_jsxRuntime.jsx)(SupportContext.Provider,{value:m,children:c})};exports.SupportProvider=SupportProvider;const useSupportContext=()=>{const e=(0,_react.useContext)(SupportContext);if(!e)throw new Error("[@webority/mobile-support] Support screens and hooks need a <SupportProvider client={...}> above them.");return e},useSupportClient=()=>useSupportContext().client;exports.useSupportClient=useSupportClient;const useAttachmentLoader=()=>useSupportContext().loadAttachment;exports.useAttachmentLoader=useAttachmentLoader;const useAttachmentOpener=()=>useSupportContext().openAttachment;exports.useAttachmentOpener=useAttachmentOpener;const useSupportTimeZone=()=>useSupportContext().timeZone;exports.useSupportTimeZone=useSupportTimeZone;const useSupportConfig=()=>{const{config:e,configLoading:t,configError:r,reloadConfig:c}=useSupportContext();return{config:e,loading:t,error:r,reload:c}};exports.useSupportConfig=useSupportConfig;const DEFAULT_PAGE_SIZE=20,useSupportTickets=(e={})=>{const t=e.pageSize??20,{client:r,revision:c}=useSupportContext(),[u,a]=(0,_react.useState)(null),[n,o]=(0,_react.useState)(""),[s,p]=(0,_react.useState)([]),[i,l]=(0,_react.useState)(null),[S,_]=(0,_react.useState)(0),[f,C]=(0,_react.useState)(0),[d,m]=(0,_react.useState)(!0),[x,h]=(0,_react.useState)(!1),[k,g]=(0,_react.useState)(!1),[T,y]=(0,_react.useState)(null),[b,A]=(0,_react.useState)(null),v=(0,_react.useRef)(!0),E=(0,_react.useRef)(0),w=(0,_react.useRef)(!1),R=(0,_react.useRef)(null);(0,_react.useEffect)(()=>(v.current=!0,()=>{v.current=!1}),[]);const P=(0,_react.useCallback)(e=>({status:u??void 0,search:n.trim()||void 0,page:e,pageSize:t}),[u,n,t]),M=(0,_react.useCallback)(async e=>{E.current+=1;const t=E.current,current=()=>v.current&&t===E.current;w.current=!1,g(!1),"refresh"===e?h(!0):"replace"===e&&(m(!0),p([]));const c="replace"!==e||R.current!==r;try{const[e,t]=await Promise.all([r.listTickets(P(1)),c?r.getTicketCounts():null]);current()&&(p(e.items),_(e.page),C(e.totalPages),t&&(l(t),R.current=r),y(null),A(null))}catch(e){current()&&y((0,_client.toSupportApiError)(e))}finally{current()&&(m(!1),h(!1))}},[r,P]),Z=(0,_react.useRef)(c);(0,_react.useEffect)(()=>{const e=Z.current===c?"replace":"quiet";Z.current=c,M(e)},[M,c]);const j=S<f,L=(0,_react.useCallback)(async()=>{if(w.current||d||x||!j)return;const e=E.current,current=()=>v.current&&e===E.current;w.current=!0,g(!0),A(null);try{const e=await r.listTickets(P(S+1));current()&&(p(t=>{const r=new Set(t.map(e=>e.id));return[...t,...e.items.filter(e=>!r.has(e.id))]}),_(e.page),C(e.totalPages))}catch(e){current()&&A((0,_client.toSupportApiError)(e))}finally{current()&&(w.current=!1,g(!1))}},[r,P,S,j,d,x]),q=(0,_react.useCallback)(()=>M("refresh"),[M]),O=useCreateSupportTicket();return{tickets:s,counts:i,status:u,search:n,setStatus:a,setSearch:o,loading:d,refreshing:x,loadingMore:k,hasMore:j,error:T,loadMoreError:b,refresh:q,loadMore:L,createTicket:O}};exports.useSupportTickets=useSupportTickets;const useCreateSupportTicket=()=>{const{client:e,bumpRevision:t}=useSupportContext();return(0,_react.useCallback)(async r=>{const c=await e.createTicket(r);return t(),c},[e,t])};exports.useCreateSupportTicket=useCreateSupportTicket;const useSupportTicket=e=>{const{client:t,bumpRevision:r}=useSupportContext(),[c,u]=(0,_react.useState)(null),[a,n]=(0,_react.useState)(!0),[o,s]=(0,_react.useState)(null),[p,i]=(0,_react.useState)(null),[l,S]=(0,_react.useState)(!1),_=(0,_react.useRef)(!0);(0,_react.useEffect)(()=>(_.current=!0,()=>{_.current=!1}),[]);const f=(0,_react.useCallback)(async()=>{n(!0);try{const r=await t.getTicket(e);_.current&&(u(r),s(null))}catch(e){_.current&&s((0,_client.toSupportApiError)(e))}finally{_.current&&n(!1)}},[t,e]);(0,_react.useEffect)(()=>{f()},[f]);const C=(0,_react.useCallback)(async e=>{S(!0),i(null);try{const t=await e();return _.current&&u(t),r(),!0}catch(e){return _.current&&i((0,_client.toSupportApiError)(e)),!1}finally{_.current&&S(!1)}},[r]),d=(0,_react.useCallback)(r=>C(()=>t.reply(e,r)),[C,t,e]),m=(0,_react.useCallback)(()=>C(()=>t.reopen(e)),[C,t,e]),x=(0,_react.useCallback)(()=>C(()=>t.cancel(e)),[C,t,e]),h=(0,_react.useCallback)(r=>C(()=>t.rate(e,r)),[C,t,e]);return{ticket:c,loading:a,error:o,actionError:p,busy:l,reload:f,reply:d,reopen:m,cancel:x,rate:h}};exports.useSupportTicket=useSupportTicket;
|
package/lib/commonjs/client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.toSupportApiError=exports.createSupportClient=exports.SupportApiError=exports.OFFLINE_MESSAGE=exports.GENERIC_MESSAGE=void 0;var _mobileCore=require("@webority/mobile-core");const OFFLINE_MESSAGE=exports.OFFLINE_MESSAGE="You are offline. Check your connection and try again.",GENERIC_MESSAGE=exports.GENERIC_MESSAGE="Something went wrong. Try again in a moment.";class SupportApiError extends Error{constructor(e,t,r){super(t),this.name="SupportApiError",this.code=e,this.status=r}}exports.SupportApiError=SupportApiError;const toSupportApiError=e=>{if(e instanceof SupportApiError)return e;const t=e??{};if("RequestOfflineError"===t.name)return new SupportApiError("offline",OFFLINE_MESSAGE,null);const r="number"==typeof t.response?.status?t.response.status:null,o=t.response?.data;if(o&&"string"==typeof o.detail&&o.detail.trim()){const e="string"==typeof o.code?o.code:"unknown";return new SupportApiError(e,o.detail,r)}return t.response?new SupportApiError("unknown",GENERIC_MESSAGE,r):new SupportApiError("network",OFFLINE_MESSAGE,null)};exports.toSupportApiError=toSupportApiError;const UPLOAD_TIMEOUT_MS=3e5,MULTIPART={"Content-Type":"multipart/form-data"},appendParts=(e,t,r)=>{for(const o of r??[])e.append(t,{uri:o.uri,name:o.name,type:o.type})},appendText=(e,t,r)=>{void 0!==r&&""!==r&&e.append(t,r)},queryString=e=>{const t=[],add=(e,r)=>{void 0!==r&&""!==r&&t.push(`${e}=${encodeURIComponent(String(r))}`)};return add("status",e.status),add("search",e.search?.trim()),add("page",e.page),add("pageSize",e.pageSize),t.length>0?`?${t.join("&")}`:""},createSupportClient=(e,t="/api/support")=>{const r=t.replace(/\/+$/,""),ticketUrl=e=>`${r}/tickets/${encodeURIComponent(e)}`,call=async e=>{try{return(await e()).data}catch(e){throw toSupportApiError(e)}},getBytes=t=>call(()=>e.get(`${r}/attachments/${encodeURIComponent(t)}`,{responseType:"arraybuffer"}));return{basePath:r,getConfig:()=>call(()=>e.get(`${r}/settings`)),listTickets:(t={})=>call(()=>e.get(`${r}/tickets${queryString(t)}`)),getTicketCounts:()=>call(()=>e.get(`${r}/tickets/counts`)),getTicket:t=>call(()=>e.get(ticketUrl(t))),createTicket:t=>{const o=new FormData;return o.append("category",t.category),o.append("subject",t.subject),o.append("body",t.body),appendText(o,"pageUrl",t.pageUrl),appendText(o,"userAgent",t.userAgent),appendText(o,"appVersion",t.appVersion),appendParts(o,"files",t.files),appendParts(o,"screenshots",t.screenshots),call(()=>e.post(`${r}/tickets`,o,{headers:MULTIPART,timeout:3e5}))},reply:(t,r)=>{const o=new FormData;return o.append("body",r.body),appendParts(o,"files",r.files),appendParts(o,"screenshots",r.screenshots),call(()=>e.post(`${ticketUrl(t)}/replies`,o,{headers:MULTIPART,timeout:3e5}))},reopen:t=>call(()=>e.post(`${ticketUrl(t)}/reopen`)),rate:(t,r)=>call(()=>e.post(`${ticketUrl(t)}/rating`,{satisfaction:r.satisfaction,comment:r.comment?.trim()?r.comment.trim():null})),getAttachmentBytes:getBytes,getAttachmentDataUri:async e=>{const t=await getBytes(e.id);return`data:${e.contentType};base64,${(0,_mobileCore.encodeBase64)(t)}`},confirmAction:t=>call(()=>e.post(`${r}/actions/confirm`,{token:t}))}};exports.createSupportClient=createSupportClient;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.toSupportApiError=exports.createSupportClient=exports.SupportApiError=exports.OFFLINE_MESSAGE=exports.GENERIC_MESSAGE=void 0;var _mobileCore=require("@webority/mobile-core");const OFFLINE_MESSAGE=exports.OFFLINE_MESSAGE="You are offline. Check your connection and try again.",GENERIC_MESSAGE=exports.GENERIC_MESSAGE="Something went wrong. Try again in a moment.";class SupportApiError extends Error{constructor(e,t,r){super(t),this.name="SupportApiError",this.code=e,this.status=r}}exports.SupportApiError=SupportApiError;const toSupportApiError=e=>{if(e instanceof SupportApiError)return e;const t=e??{};if("RequestOfflineError"===t.name)return new SupportApiError("offline",OFFLINE_MESSAGE,null);const r="number"==typeof t.response?.status?t.response.status:null,o=t.response?.data;if(o&&"string"==typeof o.detail&&o.detail.trim()){const e="string"==typeof o.code?o.code:"unknown";return new SupportApiError(e,o.detail,r)}return t.response?new SupportApiError("unknown",GENERIC_MESSAGE,r):new SupportApiError("network",OFFLINE_MESSAGE,null)};exports.toSupportApiError=toSupportApiError;const UPLOAD_TIMEOUT_MS=3e5,MULTIPART={"Content-Type":"multipart/form-data"},appendParts=(e,t,r)=>{for(const o of r??[])e.append(t,{uri:o.uri,name:o.name,type:o.type})},appendText=(e,t,r)=>{void 0!==r&&""!==r&&e.append(t,r)},queryString=e=>{const t=[],add=(e,r)=>{void 0!==r&&""!==r&&t.push(`${e}=${encodeURIComponent(String(r))}`)};return add("status",e.status),add("search",e.search?.trim()),add("page",e.page),add("pageSize",e.pageSize),t.length>0?`?${t.join("&")}`:""},createSupportClient=(e,t="/api/support")=>{const r=t.replace(/\/+$/,""),ticketUrl=e=>`${r}/tickets/${encodeURIComponent(e)}`,call=async e=>{try{return(await e()).data}catch(e){throw toSupportApiError(e)}},getBytes=t=>call(()=>e.get(`${r}/attachments/${encodeURIComponent(t)}`,{responseType:"arraybuffer"}));return{basePath:r,getConfig:()=>call(()=>e.get(`${r}/settings`)),listTickets:(t={})=>call(()=>e.get(`${r}/tickets${queryString(t)}`)),getTicketCounts:()=>call(()=>e.get(`${r}/tickets/counts`)),getTicket:t=>call(()=>e.get(ticketUrl(t))),createTicket:t=>{const o=new FormData;return o.append("category",t.category),o.append("subject",t.subject),o.append("body",t.body),appendText(o,"pageUrl",t.pageUrl),appendText(o,"userAgent",t.userAgent),appendText(o,"appVersion",t.appVersion),appendParts(o,"files",t.files),appendParts(o,"screenshots",t.screenshots),call(()=>e.post(`${r}/tickets`,o,{headers:MULTIPART,timeout:3e5}))},reply:(t,r)=>{const o=new FormData;return o.append("body",r.body),appendParts(o,"files",r.files),appendParts(o,"screenshots",r.screenshots),call(()=>e.post(`${ticketUrl(t)}/replies`,o,{headers:MULTIPART,timeout:3e5}))},reopen:t=>call(()=>e.post(`${ticketUrl(t)}/reopen`)),cancel:t=>call(()=>e.post(`${ticketUrl(t)}/cancel`)),rate:(t,r)=>call(()=>e.post(`${ticketUrl(t)}/rating`,{satisfaction:r.satisfaction,comment:r.comment?.trim()?r.comment.trim():null})),getAttachmentBytes:getBytes,getAttachmentDataUri:async e=>{const t=await getBytes(e.id);return`data:${e.contentType};base64,${(0,_mobileCore.encodeBase64)(t)}`},confirmAction:t=>call(()=>e.post(`${r}/actions/confirm`,{token:t}))}};exports.createSupportClient=createSupportClient;
|
|
@@ -1 +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"),_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:
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.NewSupportTicket=void 0;var _mobileUi=require("@webority/mobile-ui"),_react=require("react"),_reactHookForm=require("react-hook-form"),_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:o,onCancel:r,initialDraft:i,defaultCategory:a,appVersion:n})=>{const s=(0,_SupportProvider.useCreateSupportTicket)(),listed=t=>t&&e.categories.includes(t)?t:void 0,[l,c]=(0,_react.useState)([]),[u,d]=(0,_react.useState)(i?.screenshots??[]),[m,p]=(0,_react.useState)(null),[x,_]=(0,_react.useState)(null),b=(0,_reactHookForm.useForm)({defaultValues:{category:listed(i?.category)??listed(a)??"",subject:i?.subject??"",body:i?.body??""}}),g=b.handleSubmit(async t=>{const r=(0,_validation.validateFiles)([...u,...l],e);if(p(r??null),!r){_(null);try{const e=await s({category:t.category,subject:t.subject.trim(),body:t.body.trim(),pageUrl:i?.pageUrl,userAgent:i?.userAgent??(0,_device.describeDevice)(),appVersion:i?.appVersion??n,files:l,screenshots:u});o(e)}catch(e){_((0,_client.toSupportApiError)(e).message)}}}),h=b.formState.isSubmitting,j=(0,_mobileUi.useHookFormField)({control:b.control,name:"category",rules:{validate:t=>(0,_validation.validateCategory)(t,e)}}),y=(0,_mobileUi.useHookFormField)({control:b.control,name:"subject",rules:{validate:t=>(0,_validation.validateSubject)(t,e)}}),v=(0,_mobileUi.useHookFormField)({control:b.control,name:"body",rules:{validate:t=>(0,_validation.validateBody)(t,e)}}),S=u.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",options:e.categories.map(e=>({label:e,value:e})),value:j.value||void 0,onChange:e=>j.onChange(String(e)),error:j.error,testID:"support-new-category"}),(0,_jsxRuntime.jsx)(_mobileUi.Input,{label:"Subject",placeholder:"A short summary",required:!0,maxLength:e.subjectMaxLength,value:y.value,onChangeText:y.onChangeText,onBlur:y.onBlur,error:y.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:v.value,onChangeText:v.onChangeText,onBlur:v.onBlur,error:v.error,testID:"support-new-body"}),u.length>0&&(0,_jsxRuntime.jsx)(_PhotoTray.PhotoTray,{label:"Screenshot",photos:u,onChange:d,remaining:0,allowAdd:!1,maxBytes:e.maxAttachmentBytes,disabled:h}),(0,_jsxRuntime.jsx)(_PhotoTray.PhotoTray,{label:"Photos",photos:l,onChange:c,remaining:e.maxAttachmentsPerMessage-S,maxBytes:e.maxAttachmentBytes,disabled:h}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",children:`Attach up to ${e.maxAttachmentsPerMessage} files.`}),(m||x)&&(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"danger",accessibilityLiveRegion:"polite",children:m??x}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:t.actions,children:[r&&(0,_jsxRuntime.jsx)(_mobileUi.Button,{label:"Cancel",variant:"ghost",tone:"neutral",onPress:r,disabled:h}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{label:"Send ticket",loading:h,disabled:h,onPress:()=>{g()},testID:"support-new-submit"})]})]})},NewSupportTicket=e=>{const t=(0,_mobileUi.useTheme)(),o=(0,_react.useMemo)(()=>buildStyles(t),[t]),{config:r,loading:i,error:a,reload:n}=(0,_SupportProvider.useSupportConfig)(),s=e.testID??"support-new-ticket";return r?(0,_jsxRuntime.jsx)(_reactNative.View,{style:o.root,testID:s,children:(0,_jsxRuntime.jsx)(TicketForm,{...e,config:r,styles:o})}):(0,_jsxRuntime.jsx)(_reactNative.View,{style:o.center,testID:s,children:i?(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{accessibilityLabel:"Loading"}):(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"body",color:"danger",align:"center",children:a?.message??"Support is not available right now."}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{label:"Try again",variant:"ghost",onPress:()=>{n()}})]})})};exports.NewSupportTicket=NewSupportTicket;const buildStyles=e=>_reactNative.StyleSheet.create({root:{flex:1},content:{padding:e.space.md,gap:e.space.md},center:{flex:1,alignItems:"center",justifyContent:"center",padding:e.space.xl,gap:e.space.sm},actions:{flexDirection:"row",justifyContent:"flex-end",gap:e.space.sm}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.PhotoTray=void 0;var _mobileUi=require("@webority/mobile-ui"),_react=require("react"),_reactNative=require("react-native"),_photos=require("../photos.js"),_jsxRuntime=require("react/jsx-runtime");const PhotoTray=({photos:e,onChange:t,remaining:i,maxBytes:o,disabled:r,label:
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.PhotoTray=void 0;var _mobileUi=require("@webority/mobile-ui"),_react=require("react"),_reactNative=require("react-native"),_photos=require("../photos.js"),_jsxRuntime=require("react/jsx-runtime");const PhotoTray=({photos:e,onChange:t,remaining:i,maxBytes:o,disabled:r,label:a,allowAdd:s=!0})=>{const l=(0,_mobileUi.useTheme)(),n=(0,_react.useMemo)(()=>buildStyles(l),[l]),[c,u]=(0,_react.useState)(null),[m,d]=(0,_react.useState)(null),add=async r=>{d(null),u(r);try{const a=await(0,_photos.pickPhotos)(r,{maxCount:i,maxBytes:o});a.length>0&&t([...e,...a])}catch(e){d(e instanceof Error&&"PhotoPermissionError"===e.name?e.message:"That photo could not be added. Try another one.")}finally{u(null)}},b=i<=0;return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:n.root,children:[a?(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"label",color:"secondary",children:a}):null,e.length>0&&(0,_jsxRuntime.jsx)(_reactNative.View,{style:n.row,children:e.map((i,o)=>(0,_jsxRuntime.jsxs)(_reactNative.View,{style:n.item,children:[(0,_jsxRuntime.jsx)(_reactNative.Image,{source:{uri:i.uri},style:n.thumb,accessibilityLabel:i.name}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:n.remove,children:(0,_jsxRuntime.jsx)(_mobileUi.IconButton,{size:"sm",variant:"glass",style:n.removeButton,icon:({color:e})=>(0,_jsxRuntime.jsx)(_mobileUi.AppIcon,{name:"trash",color:r?e:l.colors["on-scrim"],size:REMOVE_ICON}),accessibilityLabel:`Remove ${i.name}`,disabled:r,onPress:()=>t(e.filter((e,t)=>t!==o))})})]},`${i.uri}-${o}`))}),s&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:n.actions,children:[(0,_jsxRuntime.jsx)(_mobileUi.Button,{size:"sm",variant:"outline",tone:"neutral",leftIcon:"gallery",label:"Add photo",accessibilityLabel:"Add a photo from your library",loading:"library"===c,disabled:r||b||null!==c,onPress:()=>{add("library")}}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{size:"sm",variant:"outline",tone:"neutral",leftIcon:"camera",label:"Take photo",accessibilityLabel:"Take a photo with your camera",loading:"camera"===c,disabled:r||b||null!==c,onPress:()=>{add("camera")}})]}),m?(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"danger",accessibilityLiveRegion:"polite",children:m}):null]})};exports.PhotoTray=PhotoTray;const THUMB=72,REMOVE_SIZE=24,REMOVE_ICON=14,buildStyles=e=>_reactNative.StyleSheet.create({root:{gap:e.space.xs},row:{flexDirection:"row",flexWrap:"wrap",gap:e.space.xs},item:{width:72,height:72},thumb:{width:72,height:72,borderRadius:e.radius.sm,backgroundColor:e.colors["bg-subtle"]},remove:{position:"absolute",top:e.space.xxs,right:e.space.xxs},removeButton:{width:24,height:24,borderRadius:12,backgroundColor:e.colors.scrim,borderColor:e.colors["on-scrim"]},actions:{flexDirection:"row",gap:e.space.sm}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.SupportContact=void 0;var _mobileUi=require("@webority/mobile-ui"),_react=require("react"),_reactNative=require("react-native"),_clipboard=require("../clipboard.js"),_SupportProvider=require("../SupportProvider.js"),_jsxRuntime=require("react/jsx-runtime");const COPY_TOAST_KEY="support-contact-copy",SupportContact=({testID:e="support-contact"})=>{const t=(0,_mobileUi.useTheme)(),i=(0,_react.useMemo)(()=>buildStyles(t),[t]),{config:
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.SupportContact=void 0;var _mobileUi=require("@webority/mobile-ui"),_react=require("react"),_reactNative=require("react-native"),_clipboard=require("../clipboard.js"),_SupportProvider=require("../SupportProvider.js"),_jsxRuntime=require("react/jsx-runtime");const COPY_TOAST_KEY="support-contact-copy",SupportContact=({testID:e="support-contact"})=>{const t=(0,_mobileUi.useTheme)(),i=(0,_react.useMemo)(()=>buildStyles(t),[t]),{config:a,loading:o,error:r}=(0,_SupportProvider.useSupportConfig)();if(!a)return(0,_jsxRuntime.jsx)(_mobileUi.Card,{variant:"outline",testID:e,children:o?(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{size:"sm",accessibilityLabel:"Loading contact details"}):(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"danger",children:r?.message??"Contact details are not available right now."})});const s=a.supportEmail,l=a.supportPhone?.trim(),n=a.supportHours?.trim(),open=(e,t)=>{_reactNative.Linking.openURL(e).catch(()=>_mobileUi.toast.error(t))};return(0,_jsxRuntime.jsxs)(_mobileUi.Card,{variant:"outline",bodyStyle:i.body,testID:e,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"title",accessibilityRole:"header",children:`Contact ${a.supportName||a.productName}`}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:i.row,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"label",color:"secondary",children:"Email"}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"body",selectable:!0,testID:"support-contact-email",children:s||"-"})]}),s?(0,_jsxRuntime.jsxs)(_reactNative.View,{style:i.actions,children:[(0,_jsxRuntime.jsx)(_mobileUi.Button,{label:"Copy",size:"sm",variant:"outline",tone:"neutral",leftIcon:"copy",accessibilityLabel:`Copy ${s}`,onPress:()=>{(async()=>{try{await(0,_clipboard.copyText)(s),"android"===_reactNative.Platform.OS&&Number(_reactNative.Platform.Version)>=33||_mobileUi.toast.success("Email address copied.",{key:COPY_TOAST_KEY})}catch{_mobileUi.toast.error("Could not copy the email address.",{key:COPY_TOAST_KEY})}})()},testID:"support-contact-copy"}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{label:"Send email",size:"sm",variant:"outline",leftIcon:"mail",accessibilityLabel:`Send an email to ${s}`,onPress:()=>open(`mailto:${s}`,"No email app is set up on this device."),testID:"support-contact-mail"})]}):null,l?(0,_jsxRuntime.jsxs)(_reactNative.View,{style:i.row,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"label",color:"secondary",children:"Phone"}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{label:l,size:"sm",variant:"link",leftIcon:"call",accessibilityLabel:`Call ${l}`,onPress:()=>open(`tel:${l.replace(/[^\d+]/g,"")}`,"This device cannot make calls."),testID:"support-contact-phone"})]}):null,n?(0,_jsxRuntime.jsxs)(_reactNative.View,{style:i.row,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"label",color:"secondary",children:"Hours"}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",children:n})]}):null]})};exports.SupportContact=SupportContact;const buildStyles=e=>_reactNative.StyleSheet.create({body:{gap:e.space.sm},row:{gap:e.space.xxs,alignItems:"flex-start"},actions:{flexDirection:"row",gap:e.space.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??(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:
|
|
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:u,reload:m,reply:d,reopen:x,cancel:p,rate:_}=(0,_SupportProvider.useSupportTicket)(e),[b,j]=(0,_react.useState)(null),[h,y]=(0,_react.useState)(""),[g,R]=(0,_react.useState)([]),[v,S]=(0,_react.useState)(null),[T,f]=(0,_react.useState)(null),[U,w]=(0,_react.useState)(""),[I,C]=(0,_react.useState)(!1);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:"danger",align:"center",children:l?.message??"That ticket could not be found."}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{label:"Try again",variant:"ghost",onPress:()=>{m()}})]})});const D=o.messages.filter(e=>!e.isInternal),k="Open"===o.status||"InProgress"===o.status||"WaitingOnCustomer"===o.status;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})}`})]})]}),"WaitingOnCustomer"===o.status&&(0,_jsxRuntime.jsx)(_mobileUi.Card,{variant:"outline",bodyStyle:s.section,testID:"support-waiting",children:(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",children:"We need your answer to carry on. Reply below and the ticket goes back to our team."})}),o.canReopen&&(0,_jsxRuntime.jsxs)(_mobileUi.Card,{variant:"outline",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,{label:"Reopen ticket",variant:"outline",size:"sm",loading:u,disabled:u,onPress:()=>{x()},testID:"support-reopen"})]}),k&&(0,_jsxRuntime.jsxs)(_mobileUi.Card,{variant:"outline",bodyStyle:s.section,children:[(0,_jsxRuntime.jsx)(_mobileUi.Button,{label:"Withdraw request",variant:"outline",size:"sm",loading:u,disabled:u,onPress:()=>C(!0),testID:"support-withdraw"}),(0,_jsxRuntime.jsx)(_mobileUi.Dialog,{visible:I,onClose:()=>C(!1),title:"Withdraw this request?",message:"Our team will stop working on this ticket. You can open a new ticket later if you still need help.",tone:"danger",actions:[{label:"Cancel",onPress:()=>C(!1),tone:"neutral"},{label:"Withdraw request",onPress:()=>{p()},tone:"danger"}]})]}),o.satisfaction?(0,_jsxRuntime.jsxs)(_mobileUi.Card,{variant:"outline",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:"outline",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,{label:"Yes, it is solved",size:"sm",leftIcon:"like",variant:"Satisfied"===T?"solid":"outline",tone:"success",accessibilityState:{selected:"Satisfied"===T},"aria-selected":"Satisfied"===T,onPress:()=>f("Satisfied"),testID:"support-rate-satisfied"}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{label:"No, it is not",size:"sm",leftIcon:"dislike",variant:"Unsatisfied"===T?"solid":"outline",tone:"danger",accessibilityState:{selected:"Unsatisfied"===T},"aria-selected":"Unsatisfied"===T,onPress:()=>f("Unsatisfied"),testID:"support-rate-unsatisfied"})]}),T&&(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:U,onChangeText:w,multiline:!0,maxLength:a?.satisfactionCommentMaxLength,testID:"support-rating-comment"}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{label:"Send answer",size:"sm",loading:u,disabled:u,onPress:()=>{(async()=>{T&&await _({satisfaction:T,comment:U})&&(f(null),w(""))})()},testID:"support-rating-send"})]})]}):null,(0,_jsxRuntime.jsx)(_reactNative.View,{style:s.thread,accessibilityLabel:"Conversation",children:D.map(e=>(0,_jsxRuntime.jsx)(MessageBubble,{message:e,styles:s,timeZone:n,onOpenImage:(e,t)=>j({uri:e,name:t})},e.id))}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.section,children:[(0,_jsxRuntime.jsx)(_mobileUi.Input,{label:"Your reply",required:!0,placeholder:"Write a reply to the support team",value:h,onChangeText:y,multiline:!0,maxLength:a?.bodyMaxLength,error:v??void 0,testID:"support-reply-input"}),a&&(0,_jsxRuntime.jsx)(_PhotoTray.PhotoTray,{photos:g,onChange:R,remaining:a.maxAttachmentsPerMessage-g.length,maxBytes:a.maxAttachmentBytes,disabled:u}),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:"danger",accessibilityLiveRegion:"polite",children:c.message}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{label:"Send reply",leftIcon:"send",loading:u,disabled:u,onPress:()=>{(async()=>{const e=h.trim(),t=a?(0,_validation.validateBody)(e,a)??(0,_validation.validateFiles)(g,a):e?void 0:"Write a message.";t?S(t):(S(null),await d({body:e,files:g})&&(y(""),R([])))})()},testID:"support-reply-send"})]})]}),(0,_jsxRuntime.jsx)(_Attachments.ImageViewer,{image:b,onClose:()=>j(null)})]})};exports.SupportTicketDetail=SupportTicketDetail;const buildStyles=e=>_reactNative.StyleSheet.create({root:{flex:1},content:{padding:e.space.md,gap:e.space.md},center:{flex:1,alignItems:"center",justifyContent:"center",padding:e.space.xl,gap:e.space.sm},head:{gap:e.space.xxs},headRow:{flexDirection:"row",alignItems:"center",gap:e.space.sm,marginTop:e.space.xs},section:{gap:e.space.sm},ratingRow:{flexDirection:"row",flexWrap:"wrap",gap:e.space.sm},thread:{gap:e.space.sm},system:{paddingHorizontal:e.space.lg,paddingVertical:e.space.xxs},bubbleRow:{flexDirection:"row"},mineRow:{justifyContent:"flex-end"},theirsRow:{justifyContent:"flex-start"},bubble:{maxWidth:"85%",borderRadius:e.radius.lg,padding:e.space.sm,gap:e.space.xxs},mine:{backgroundColor:e.colors["primary-soft"]},theirs:{backgroundColor:e.colors["bg-subtle"]}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.statusFilterOptions=exports.SupportTicketList=exports.STATUS_TONES=void 0;var _mobileCore=require("@webority/mobile-core"),_mobileUi=require("@webority/mobile-ui"),_react=require("react"),_reactNative=require("react-native"),_SupportProvider=require("../SupportProvider.js"),_jsxRuntime=require("react/jsx-runtime");const STATUS_TONES=exports.STATUS_TONES={Open:"warning",InProgress:"info",WaitingOnCustomer:"warning",Resolved:"success",Closed:"neutral"},STATUS_FILTERS=[{value:"Open",label:"Open",count:"open"},{value:"InProgress",label:"In progress",count:"inProgress"},{value:"WaitingOnCustomer",label:"Waiting on you",count:"waitingOnCustomer"},{value:"Resolved",label:"Resolved",count:"resolved"},{value:"Closed",label:"Closed",count:"closed"}],ALL="all",statusFilterOptions=(e,t)=>[{value:ALL,label:"All",count:e.total},...STATUS_FILTERS.filter(i=>e[i.count]>0||i.value===t).map(t=>({value:t.value,label:t.label,count:e[t.count]}))];exports.statusFilterOptions=statusFilterOptions;const SEARCH_DEBOUNCE_MS=400,ticketKey=e=>e.id,TicketRow=(0,_react.memo)(function TicketRow({item:e,timeZone:t,onOpen:i,styles:s}){return(0,_jsxRuntime.jsxs)(_mobileUi.Card,{onPress:()=>i(e),accessibilityLabel:`${e.subject}, ${e.statusLabel}, ticket ${e.number}`,bodyStyle:s.card,testID:`support-ticket-${e.id}`,children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.cardTop,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",children:e.number}),(0,_jsxRuntime.jsx)(_mobileUi.StatusBadge,{label:e.statusLabel,tone:STATUS_TONES[e.status]??"neutral",size:"sm"})]}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"title",numberOfLines:2,children:e.subject}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"secondary",children:e.category}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.cardBottom,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",children:`Updated ${(0,_mobileCore.formatDateTime)(e.updatedDateTimeUtc,{zone:t})}`}),e.awaitingCustomer&&(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"warning",children:"Waiting for your reply"})]})]})}),SupportTicketList=({onOpenTicket:e,onNewTicket:t,header:i,pageSize:s,testID:r="support-ticket-list"})=>{const
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.statusFilterOptions=exports.SupportTicketList=exports.STATUS_TONES=void 0;var _mobileCore=require("@webority/mobile-core"),_mobileUi=require("@webority/mobile-ui"),_react=require("react"),_reactNative=require("react-native"),_SupportProvider=require("../SupportProvider.js"),_jsxRuntime=require("react/jsx-runtime");const STATUS_TONES=exports.STATUS_TONES={Open:"warning",InProgress:"info",WaitingOnCustomer:"warning",Resolved:"success",Closed:"neutral",Cancelled:"neutral"},STATUS_FILTERS=[{value:"Open",label:"Open",count:"open"},{value:"InProgress",label:"In progress",count:"inProgress"},{value:"WaitingOnCustomer",label:"Waiting on you",count:"waitingOnCustomer"},{value:"Resolved",label:"Resolved",count:"resolved"},{value:"Closed",label:"Closed",count:"closed"},{value:"Cancelled",label:"Cancelled"}],ALL="all",statusFilterOptions=(e,t)=>[{value:ALL,label:"All",count:e.total},...STATUS_FILTERS.filter(i=>void 0===i.count||e[i.count]>0||i.value===t).map(t=>({value:t.value,label:t.label,count:void 0===t.count?void 0:e[t.count]}))];exports.statusFilterOptions=statusFilterOptions;const chipLabel=e=>void 0===e.count?e.label:`${e.label} (${e.count})`,SEARCH_DEBOUNCE_MS=400,ticketKey=e=>e.id,TicketRow=(0,_react.memo)(function TicketRow({item:e,timeZone:t,onOpen:i,styles:s}){return(0,_jsxRuntime.jsxs)(_mobileUi.Card,{onPress:()=>i(e),accessibilityLabel:`${e.subject}, ${e.statusLabel}, ticket ${e.number}`,bodyStyle:s.card,testID:`support-ticket-${e.id}`,children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.cardTop,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",children:e.number}),(0,_jsxRuntime.jsx)(_mobileUi.StatusBadge,{label:e.statusLabel,tone:STATUS_TONES[e.status]??"neutral",size:"sm"})]}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"title",numberOfLines:2,children:e.subject}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"secondary",children:e.category}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.cardBottom,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",children:`Updated ${(0,_mobileCore.formatDateTime)(e.updatedDateTimeUtc,{zone:t})}`}),e.awaitingCustomer&&(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"warning",children:"Waiting for your reply"})]})]})}),SupportTicketList=({onOpenTicket:e,onNewTicket:t,header:i,pageSize:s,testID:r="support-ticket-list"})=>{const a=(0,_mobileUi.useTheme)(),o=(0,_react.useMemo)(()=>buildStyles(a),[a]),{tickets:n,counts:l,status:c,search:u,setStatus:m,setSearch:d,loading:p,refreshing:x,loadingMore:b,error:_,loadMoreError:j,refresh:v,loadMore:y}=(0,_SupportProvider.useSupportTickets)({pageSize:s}),h=(0,_SupportProvider.useSupportTimeZone)(),S=(0,_react.useCallback)(({item:t})=>(0,_jsxRuntime.jsx)(TicketRow,{item:t,timeZone:h,onOpen:e,styles:o}),[h,e,o]),R=(0,_react.useCallback)(()=>{y()},[y]),g=(0,_react.useCallback)(()=>{v()},[v]),T=(0,_react.useMemo)(()=>(0,_jsxRuntime.jsx)(_reactNative.RefreshControl,{refreshing:x,onRefresh:g}),[x,g]),L=(l?.total??0)>0,k=null!==c||""!==u.trim(),f=(0,_jsxRuntime.jsxs)(_reactNative.View,{style:o.header,children:[i,t&&(0,_jsxRuntime.jsx)(_mobileUi.Button,{label:"New ticket",leftIcon:"add",onPress:t,fullWidth:!0}),_&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:o.error,accessibilityLiveRegion:"polite",children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"danger",children:_.message}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{label:"Try again",size:"sm",variant:"ghost",onPress:()=>{v()}})]}),l&&L&&(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_mobileUi.SearchBar,{value:u,onChangeText:d,debounceMs:400,placeholder:"Search by subject or ticket number",accessibilityLabel:"Search tickets",testID:"support-ticket-search"}),(0,_jsxRuntime.jsx)(_reactNative.ScrollView,{horizontal:!0,showsHorizontalScrollIndicator:!1,contentContainerStyle:o.filters,accessibilityRole:"tablist",accessibilityLabel:"Filter by status",children:statusFilterOptions(l,c).map(e=>(0,_jsxRuntime.jsx)(_mobileUi.Chip,{label:chipLabel(e),selected:(c??ALL)===e.value,onPress:()=>m(e.value===ALL?null:e.value),accessibilityLabel:void 0===e.count?e.label:`${e.label}, ${e.count} tickets`,testID:`support-filter-${e.value}`},e.value))})]})]});if(p&&null===l&&!_)return(0,_jsxRuntime.jsx)(_reactNative.View,{style:o.center,testID:r,children:(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{accessibilityLabel:"Loading your tickets"})});const w=b?(0,_jsxRuntime.jsx)(_reactNative.View,{style:o.footer,children:(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{size:"sm",accessibilityLabel:"Loading more tickets"})}):j?(0,_jsxRuntime.jsxs)(_reactNative.View,{style:[o.footer,o.error],accessibilityLiveRegion:"polite",children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"danger",children:j.message}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{label:"Load more",size:"sm",variant:"ghost",onPress:()=>{y()}})]}):null;return(0,_jsxRuntime.jsx)(_reactNative.FlatList,{testID:r,data:n,keyExtractor:ticketKey,contentContainerStyle:o.content,ListHeaderComponent:f,ListFooterComponent:w,onEndReached:R,onEndReachedThreshold:.5,refreshControl:T,ListEmptyComponent:p?(0,_jsxRuntime.jsx)(_reactNative.View,{style:o.footer,children:(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{accessibilityLabel:"Loading your tickets"})}):_?null:L?(0,_jsxRuntime.jsx)(_mobileUi.EmptyState,{title:"No tickets match",description:k?"Try another status or search.":void 0}):(0,_jsxRuntime.jsx)(_mobileUi.EmptyState,{title:"No tickets yet",description:"Raise a ticket and it will show here with its status.",action:t?{label:"New ticket",onPress:t}:void 0}),renderItem:S})};exports.SupportTicketList=SupportTicketList;const buildStyles=e=>_reactNative.StyleSheet.create({content:{padding:e.space.md,gap:e.space.sm},header:{gap:e.space.sm,marginBottom:e.space.xs},filters:{gap:e.space.xs},error:{gap:e.space.xs,alignItems:"flex-start"},center:{flex:1,alignItems:"center",justifyContent:"center",padding:e.space.xl},footer:{paddingVertical:e.space.md,alignItems:"center"},card:{gap:e.space.xxs},cardTop:{flexDirection:"row",justifyContent:"space-between",alignItems:"center"},cardBottom:{flexDirection:"row",justifyContent:"space-between",marginTop:e.space.xxs}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.ReportIssueButton=void 0;var _mobileUi=require("@webority/mobile-ui"),_react=require("react"),_reactNative=require("react-native"),_captureAndReport=require("./captureAndReport.js"),_jsxRuntime=require("react/jsx-runtime");const ReportIssueButton=({onReport:e,onCaptureError:t,captureScreenshot:s=!1,screenName:r,appVersion:i,category:a,subject:o,title:n="Report an issue",accessibilityLabel:u="Report an issue with this screen",testID:c="support-report-issue",...p})=>{const l=(0,_mobileUi.useTheme)(),[d
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.ReportIssueButton=void 0;var _mobileUi=require("@webority/mobile-ui"),_react=require("react"),_reactNative=require("react-native"),_captureAndReport=require("./captureAndReport.js"),_jsxRuntime=require("react/jsx-runtime");const ReportIssueButton=({onReport:e,onCaptureError:t,captureScreenshot:s=!1,screenName:r,appVersion:i,category:a,subject:o,title:n="Report an issue",accessibilityLabel:u="Report an issue with this screen",testID:c="support-report-issue",...p})=>{const l=(0,_mobileUi.useTheme)(),[b,d]=(0,_react.useState)(!1),draftWithoutScreenshot=()=>({category:a,subject:o,pageUrl:r,appVersion:i}),_=(0,_jsxRuntime.jsx)(_mobileUi.Button,{leftIcon:"message-question",...p,label:n,accessibilityLabel:u,testID:c,disabled:p.disabled||b,onPress:()=>e(draftWithoutScreenshot())});return s?(0,_jsxRuntime.jsxs)(_reactNative.View,{style:{gap:l.space.sm},children:[_,(0,_jsxRuntime.jsx)(_mobileUi.Button,{variant:"outline",label:"Add a screenshot",accessibilityLabel:"Add a screenshot",testID:`${c}-screenshot`,loading:b,disabled:p.disabled,onPress:()=>{(async()=>{d(!0);try{e(await(0,_captureAndReport.captureAndReport)({screenName:r,appVersion:i,category:a,subject:o}))}catch(s){t?.(s),e(draftWithoutScreenshot())}finally{d(!1)}})()}})]}):_};exports.ReportIssueButton=ReportIssueButton;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{createContext,useCallback,useContext,useEffect,useMemo,useRef,useState}from"react";import{toSupportApiError}from"./client.js";import{saveAttachmentToCache}from"./files.js";import{jsx as _jsx}from"react/jsx-runtime";const SupportContext=createContext(null);export const SupportProvider=({client:e,openFile:t,timeZone:r,children:n})=>{const[o,u]=useState(null),[s
|
|
1
|
+
"use strict";import{createContext,useCallback,useContext,useEffect,useMemo,useRef,useState}from"react";import{toSupportApiError}from"./client.js";import{saveAttachmentToCache}from"./files.js";import{jsx as _jsx}from"react/jsx-runtime";const SupportContext=createContext(null);export const SupportProvider=({client:e,openFile:t,timeZone:r,children:n})=>{const[o,u]=useState(null),[c,s]=useState(null),[a,l]=useState(!0),[i,p]=useState(0),S=useRef(new Map),f=useCallback(async()=>{l(!0);try{u(await e.getConfig()),s(null)}catch(e){s(toSupportApiError(e))}finally{l(!1)}},[e]);useEffect(()=>{S.current.clear(),f()},[f]);const C=useCallback(t=>{const r=S.current.get(t.id);if(r)return r;const n=e.getAttachmentDataUri(t);return S.current.set(t.id,n),n.catch(()=>S.current.delete(t.id)),n},[e]),m=useMemo(()=>t?async r=>{const n=await saveAttachmentToCache(r,()=>e.getAttachmentBytes(r.id));await t({uri:n,name:r.fileName,contentType:r.contentType})}:null,[e,t]),h=useCallback(()=>p(e=>e+1),[]),d=useMemo(()=>({client:e,config:o,configError:c,configLoading:a,reloadConfig:f,loadAttachment:C,openAttachment:m,timeZone:r,revision:i,bumpRevision:h}),[e,o,c,a,f,C,m,r,i,h]);return _jsx(SupportContext.Provider,{value:d,children:n})};const useSupportContext=()=>{const e=useContext(SupportContext);if(!e)throw new Error("[@webority/mobile-support] Support screens and hooks need a <SupportProvider client={...}> above them.");return e};export const useSupportClient=()=>useSupportContext().client;export const useAttachmentLoader=()=>useSupportContext().loadAttachment;export const useAttachmentOpener=()=>useSupportContext().openAttachment;export const useSupportTimeZone=()=>useSupportContext().timeZone;export const useSupportConfig=()=>{const{config:e,configLoading:t,configError:r,reloadConfig:n}=useSupportContext();return{config:e,loading:t,error:r,reload:n}};const DEFAULT_PAGE_SIZE=20;export const useSupportTickets=(e={})=>{const t=e.pageSize??20,{client:r,revision:n}=useSupportContext(),[o,u]=useState(null),[c,s]=useState(""),[a,l]=useState([]),[i,p]=useState(null),[S,f]=useState(0),[C,m]=useState(0),[h,d]=useState(!0),[g,x]=useState(!1),[k,y]=useState(!1),[b,E]=useState(null),[A,T]=useState(null),v=useRef(!0),w=useRef(0),R=useRef(!1),M=useRef(null);useEffect(()=>(v.current=!0,()=>{v.current=!1}),[]);const P=useCallback(e=>({status:o??void 0,search:c.trim()||void 0,page:e,pageSize:t}),[o,c,t]),j=useCallback(async e=>{w.current+=1;const t=w.current,current=()=>v.current&&t===w.current;R.current=!1,y(!1),"refresh"===e?x(!0):"replace"===e&&(d(!0),l([]));const n="replace"!==e||M.current!==r;try{const[e,t]=await Promise.all([r.listTickets(P(1)),n?r.getTicketCounts():null]);current()&&(l(e.items),f(e.page),m(e.totalPages),t&&(p(t),M.current=r),E(null),T(null))}catch(e){current()&&E(toSupportApiError(e))}finally{current()&&(d(!1),x(!1))}},[r,P]),Z=useRef(n);useEffect(()=>{const e=Z.current===n?"replace":"quiet";Z.current=n,j(e)},[j,n]);const L=S<C,_=useCallback(async()=>{if(R.current||h||g||!L)return;const e=w.current,current=()=>v.current&&e===w.current;R.current=!0,y(!0),T(null);try{const e=await r.listTickets(P(S+1));current()&&(l(t=>{const r=new Set(t.map(e=>e.id));return[...t,...e.items.filter(e=>!r.has(e.id))]}),f(e.page),m(e.totalPages))}catch(e){current()&&T(toSupportApiError(e))}finally{current()&&(R.current=!1,y(!1))}},[r,P,S,L,h,g]),z=useCallback(()=>j("refresh"),[j]),D=useCreateSupportTicket();return{tickets:a,counts:i,status:o,search:c,setStatus:u,setSearch:s,loading:h,refreshing:g,loadingMore:k,hasMore:L,error:b,loadMoreError:A,refresh:z,loadMore:_,createTicket:D}};export const useCreateSupportTicket=()=>{const{client:e,bumpRevision:t}=useSupportContext();return useCallback(async r=>{const n=await e.createTicket(r);return t(),n},[e,t])};export const useSupportTicket=e=>{const{client:t,bumpRevision:r}=useSupportContext(),[n,o]=useState(null),[u,c]=useState(!0),[s,a]=useState(null),[l,i]=useState(null),[p,S]=useState(!1),f=useRef(!0);useEffect(()=>(f.current=!0,()=>{f.current=!1}),[]);const C=useCallback(async()=>{c(!0);try{const r=await t.getTicket(e);f.current&&(o(r),a(null))}catch(e){f.current&&a(toSupportApiError(e))}finally{f.current&&c(!1)}},[t,e]);useEffect(()=>{C()},[C]);const m=useCallback(async e=>{S(!0),i(null);try{const t=await e();return f.current&&o(t),r(),!0}catch(e){return f.current&&i(toSupportApiError(e)),!1}finally{f.current&&S(!1)}},[r]),h=useCallback(r=>m(()=>t.reply(e,r)),[m,t,e]),d=useCallback(()=>m(()=>t.reopen(e)),[m,t,e]),g=useCallback(()=>m(()=>t.cancel(e)),[m,t,e]),x=useCallback(r=>m(()=>t.rate(e,r)),[m,t,e]);return{ticket:n,loading:u,error:s,actionError:l,busy:p,reload:C,reply:h,reopen:d,cancel:g,rate:x}};
|
package/lib/module/client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{encodeBase64}from"@webority/mobile-core";export const OFFLINE_MESSAGE="You are offline. Check your connection and try again.";export const GENERIC_MESSAGE="Something went wrong. Try again in a moment.";export class SupportApiError extends Error{constructor(e,t,r){super(t),this.name="SupportApiError",this.code=e,this.status=r}}export const toSupportApiError=e=>{if(e instanceof SupportApiError)return e;const t=e??{};if("RequestOfflineError"===t.name)return new SupportApiError("offline",OFFLINE_MESSAGE,null);const r="number"==typeof t.response?.status?t.response.status:null,n=t.response?.data;if(n&&"string"==typeof n.detail&&n.detail.trim()){const e="string"==typeof n.code?n.code:"unknown";return new SupportApiError(e,n.detail,r)}return t.response?new SupportApiError("unknown",GENERIC_MESSAGE,r):new SupportApiError("network",OFFLINE_MESSAGE,null)};const UPLOAD_TIMEOUT_MS=3e5,MULTIPART={"Content-Type":"multipart/form-data"},appendParts=(e,t,r)=>{for(const n of r??[])e.append(t,{uri:n.uri,name:n.name,type:n.type})},appendText=(e,t,r)=>{void 0!==r&&""!==r&&e.append(t,r)},queryString=e=>{const t=[],add=(e,r)=>{void 0!==r&&""!==r&&t.push(`${e}=${encodeURIComponent(String(r))}`)};return add("status",e.status),add("search",e.search?.trim()),add("page",e.page),add("pageSize",e.pageSize),t.length>0?`?${t.join("&")}`:""};export const createSupportClient=(e,t="/api/support")=>{const r=t.replace(/\/+$/,""),ticketUrl=e=>`${r}/tickets/${encodeURIComponent(e)}`,call=async e=>{try{return(await e()).data}catch(e){throw toSupportApiError(e)}},getBytes=t=>call(()=>e.get(`${r}/attachments/${encodeURIComponent(t)}`,{responseType:"arraybuffer"}));return{basePath:r,getConfig:()=>call(()=>e.get(`${r}/settings`)),listTickets:(t={})=>call(()=>e.get(`${r}/tickets${queryString(t)}`)),getTicketCounts:()=>call(()=>e.get(`${r}/tickets/counts`)),getTicket:t=>call(()=>e.get(ticketUrl(t))),createTicket:t=>{const n=new FormData;return n.append("category",t.category),n.append("subject",t.subject),n.append("body",t.body),appendText(n,"pageUrl",t.pageUrl),appendText(n,"userAgent",t.userAgent),appendText(n,"appVersion",t.appVersion),appendParts(n,"files",t.files),appendParts(n,"screenshots",t.screenshots),call(()=>e.post(`${r}/tickets`,n,{headers:MULTIPART,timeout:3e5}))},reply:(t,r)=>{const n=new FormData;return n.append("body",r.body),appendParts(n,"files",r.files),appendParts(n,"screenshots",r.screenshots),call(()=>e.post(`${ticketUrl(t)}/replies`,n,{headers:MULTIPART,timeout:3e5}))},reopen:t=>call(()=>e.post(`${ticketUrl(t)}/reopen`)),rate:(t,r)=>call(()=>e.post(`${ticketUrl(t)}/rating`,{satisfaction:r.satisfaction,comment:r.comment?.trim()?r.comment.trim():null})),getAttachmentBytes:getBytes,getAttachmentDataUri:async e=>{const t=await getBytes(e.id);return`data:${e.contentType};base64,${encodeBase64(t)}`},confirmAction:t=>call(()=>e.post(`${r}/actions/confirm`,{token:t}))}};
|
|
1
|
+
"use strict";import{encodeBase64}from"@webority/mobile-core";export const OFFLINE_MESSAGE="You are offline. Check your connection and try again.";export const GENERIC_MESSAGE="Something went wrong. Try again in a moment.";export class SupportApiError extends Error{constructor(e,t,r){super(t),this.name="SupportApiError",this.code=e,this.status=r}}export const toSupportApiError=e=>{if(e instanceof SupportApiError)return e;const t=e??{};if("RequestOfflineError"===t.name)return new SupportApiError("offline",OFFLINE_MESSAGE,null);const r="number"==typeof t.response?.status?t.response.status:null,n=t.response?.data;if(n&&"string"==typeof n.detail&&n.detail.trim()){const e="string"==typeof n.code?n.code:"unknown";return new SupportApiError(e,n.detail,r)}return t.response?new SupportApiError("unknown",GENERIC_MESSAGE,r):new SupportApiError("network",OFFLINE_MESSAGE,null)};const UPLOAD_TIMEOUT_MS=3e5,MULTIPART={"Content-Type":"multipart/form-data"},appendParts=(e,t,r)=>{for(const n of r??[])e.append(t,{uri:n.uri,name:n.name,type:n.type})},appendText=(e,t,r)=>{void 0!==r&&""!==r&&e.append(t,r)},queryString=e=>{const t=[],add=(e,r)=>{void 0!==r&&""!==r&&t.push(`${e}=${encodeURIComponent(String(r))}`)};return add("status",e.status),add("search",e.search?.trim()),add("page",e.page),add("pageSize",e.pageSize),t.length>0?`?${t.join("&")}`:""};export const createSupportClient=(e,t="/api/support")=>{const r=t.replace(/\/+$/,""),ticketUrl=e=>`${r}/tickets/${encodeURIComponent(e)}`,call=async e=>{try{return(await e()).data}catch(e){throw toSupportApiError(e)}},getBytes=t=>call(()=>e.get(`${r}/attachments/${encodeURIComponent(t)}`,{responseType:"arraybuffer"}));return{basePath:r,getConfig:()=>call(()=>e.get(`${r}/settings`)),listTickets:(t={})=>call(()=>e.get(`${r}/tickets${queryString(t)}`)),getTicketCounts:()=>call(()=>e.get(`${r}/tickets/counts`)),getTicket:t=>call(()=>e.get(ticketUrl(t))),createTicket:t=>{const n=new FormData;return n.append("category",t.category),n.append("subject",t.subject),n.append("body",t.body),appendText(n,"pageUrl",t.pageUrl),appendText(n,"userAgent",t.userAgent),appendText(n,"appVersion",t.appVersion),appendParts(n,"files",t.files),appendParts(n,"screenshots",t.screenshots),call(()=>e.post(`${r}/tickets`,n,{headers:MULTIPART,timeout:3e5}))},reply:(t,r)=>{const n=new FormData;return n.append("body",r.body),appendParts(n,"files",r.files),appendParts(n,"screenshots",r.screenshots),call(()=>e.post(`${ticketUrl(t)}/replies`,n,{headers:MULTIPART,timeout:3e5}))},reopen:t=>call(()=>e.post(`${ticketUrl(t)}/reopen`)),cancel:t=>call(()=>e.post(`${ticketUrl(t)}/cancel`)),rate:(t,r)=>call(()=>e.post(`${ticketUrl(t)}/rating`,{satisfaction:r.satisfaction,comment:r.comment?.trim()?r.comment.trim():null})),getAttachmentBytes:getBytes,getAttachmentDataUri:async e=>{const t=await getBytes(e.id);return`data:${e.contentType};base64,${encodeBase64(t)}`},confirmAction:t=>call(()=>e.post(`${r}/actions/confirm`,{token:t}))}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{Button,Input,KeyboardAwareScrollView,Select,Spinner,Text,
|
|
1
|
+
"use strict";import{Button,Input,KeyboardAwareScrollView,Select,Spinner,Text,useHookFormField,useTheme}from"@webority/mobile-ui";import{useMemo,useState}from"react";import{useForm}from"react-hook-form";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:o,onCancel:r,initialDraft:a,defaultCategory:s,appVersion:n})=>{const l=useCreateSupportTicket(),listed=t=>t&&e.categories.includes(t)?t:void 0,[i,c]=useState([]),[u,d]=useState(a?.screenshots??[]),[p,m]=useState(null),[g,h]=useState(null),y=useForm({defaultValues:{category:listed(a?.category)??listed(s)??"",subject:a?.subject??"",body:a?.body??""}}),b=y.handleSubmit(async t=>{const r=validateFiles([...u,...i],e);if(m(r??null),!r){h(null);try{const e=await l({category:t.category,subject:t.subject.trim(),body:t.body.trim(),pageUrl:a?.pageUrl,userAgent:a?.userAgent??describeDevice(),appVersion:a?.appVersion??n,files:i,screenshots:u});o(e)}catch(e){h(toSupportApiError(e).message)}}}),x=y.formState.isSubmitting,j=useHookFormField({control:y.control,name:"category",rules:{validate:t=>validateCategory(t,e)}}),S=useHookFormField({control:y.control,name:"subject",rules:{validate:t=>validateSubject(t,e)}}),v=useHookFormField({control:y.control,name:"body",rules:{validate:t=>validateBody(t,e)}}),f=u.length+i.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",options:e.categories.map(e=>({label:e,value:e})),value:j.value||void 0,onChange:e=>j.onChange(String(e)),error:j.error,testID:"support-new-category"}),_jsx(Input,{label:"Subject",placeholder:"A short summary",required:!0,maxLength:e.subjectMaxLength,value:S.value,onChangeText:S.onChangeText,onBlur:S.onBlur,error:S.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:v.value,onChangeText:v.onChangeText,onBlur:v.onBlur,error:v.error,testID:"support-new-body"}),u.length>0&&_jsx(PhotoTray,{label:"Screenshot",photos:u,onChange:d,remaining:0,allowAdd:!1,maxBytes:e.maxAttachmentBytes,disabled:x}),_jsx(PhotoTray,{label:"Photos",photos:i,onChange:c,remaining:e.maxAttachmentsPerMessage-f,maxBytes:e.maxAttachmentBytes,disabled:x}),_jsx(Text,{variant:"caption",color:"tertiary",children:`Attach up to ${e.maxAttachmentsPerMessage} files.`}),(p||g)&&_jsx(Text,{variant:"bodySmall",color:"danger",accessibilityLiveRegion:"polite",children:p??g}),_jsxs(View,{style:t.actions,children:[r&&_jsx(Button,{label:"Cancel",variant:"ghost",tone:"neutral",onPress:r,disabled:x}),_jsx(Button,{label:"Send ticket",loading:x,disabled:x,onPress:()=>{b()},testID:"support-new-submit"})]})]})};export const NewSupportTicket=e=>{const t=useTheme(),o=useMemo(()=>buildStyles(t),[t]),{config:r,loading:a,error:s,reload:n}=useSupportConfig(),l=e.testID??"support-new-ticket";return _jsx(View,r?{style:o.root,testID:l,children:_jsx(TicketForm,{...e,config:r,styles:o})}:{style:o.center,testID:l,children:a?_jsx(Spinner,{accessibilityLabel:"Loading"}):_jsxs(_Fragment,{children:[_jsx(Text,{variant:"body",color:"danger",align:"center",children:s?.message??"Support is not available right now."}),_jsx(Button,{label:"Try again",variant:"ghost",onPress:()=>{n()}})]})})};const buildStyles=e=>StyleSheet.create({root:{flex:1},content:{padding:e.space.md,gap:e.space.md},center:{flex:1,alignItems:"center",justifyContent:"center",padding:e.space.xl,gap:e.space.sm},actions:{flexDirection:"row",justifyContent:"flex-end",gap:e.space.sm}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{AppIcon,Button,IconButton,Text,useTheme}from"@webority/mobile-ui";import{useMemo,useState}from"react";import{Image,StyleSheet,View}from"react-native";import{pickPhotos}from"../photos.js";import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";export const PhotoTray=({photos:e,onChange:o,remaining:t,maxBytes:s,disabled:r,label:
|
|
1
|
+
"use strict";import{AppIcon,Button,IconButton,Text,useTheme}from"@webority/mobile-ui";import{useMemo,useState}from"react";import{Image,StyleSheet,View}from"react-native";import{pickPhotos}from"../photos.js";import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";export const PhotoTray=({photos:e,onChange:o,remaining:t,maxBytes:s,disabled:r,label:a,allowAdd:i=!0})=>{const l=useTheme(),n=useMemo(()=>buildStyles(l),[l]),[c,m]=useState(null),[u,d]=useState(null),add=async r=>{d(null),m(r);try{const a=await pickPhotos(r,{maxCount:t,maxBytes:s});a.length>0&&o([...e,...a])}catch(e){d(e instanceof Error&&"PhotoPermissionError"===e.name?e.message:"That photo could not be added. Try another one.")}finally{m(null)}},h=t<=0;return _jsxs(View,{style:n.root,children:[a?_jsx(Text,{variant:"label",color:"secondary",children:a}):null,e.length>0&&_jsx(View,{style:n.row,children:e.map((t,s)=>_jsxs(View,{style:n.item,children:[_jsx(Image,{source:{uri:t.uri},style:n.thumb,accessibilityLabel:t.name}),_jsx(View,{style:n.remove,children:_jsx(IconButton,{size:"sm",variant:"glass",style:n.removeButton,icon:({color:e})=>_jsx(AppIcon,{name:"trash",color:r?e:l.colors["on-scrim"],size:REMOVE_ICON}),accessibilityLabel:`Remove ${t.name}`,disabled:r,onPress:()=>o(e.filter((e,o)=>o!==s))})})]},`${t.uri}-${s}`))}),i&&_jsxs(View,{style:n.actions,children:[_jsx(Button,{size:"sm",variant:"outline",tone:"neutral",leftIcon:"gallery",label:"Add photo",accessibilityLabel:"Add a photo from your library",loading:"library"===c,disabled:r||h||null!==c,onPress:()=>{add("library")}}),_jsx(Button,{size:"sm",variant:"outline",tone:"neutral",leftIcon:"camera",label:"Take photo",accessibilityLabel:"Take a photo with your camera",loading:"camera"===c,disabled:r||h||null!==c,onPress:()=>{add("camera")}})]}),u?_jsx(Text,{variant:"caption",color:"danger",accessibilityLiveRegion:"polite",children:u}):null]})};const THUMB=72,REMOVE_SIZE=24,REMOVE_ICON=14,buildStyles=e=>StyleSheet.create({root:{gap:e.space.xs},row:{flexDirection:"row",flexWrap:"wrap",gap:e.space.xs},item:{width:72,height:72},thumb:{width:72,height:72,borderRadius:e.radius.sm,backgroundColor:e.colors["bg-subtle"]},remove:{position:"absolute",top:e.space.xxs,right:e.space.xxs},removeButton:{width:24,height:24,borderRadius:12,backgroundColor:e.colors.scrim,borderColor:e.colors["on-scrim"]},actions:{flexDirection:"row",gap:e.space.sm}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{Button,Card,Spinner,Text,toast,useTheme}from"@webority/mobile-ui";import{useMemo}from"react";import{Linking,Platform,StyleSheet,View}from"react-native";import{copyText}from"../clipboard.js";import{useSupportConfig}from"../SupportProvider.js";import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";const COPY_TOAST_KEY="support-contact-copy";export const SupportContact=({testID:
|
|
1
|
+
"use strict";import{Button,Card,Spinner,Text,toast,useTheme}from"@webority/mobile-ui";import{useMemo}from"react";import{Linking,Platform,StyleSheet,View}from"react-native";import{copyText}from"../clipboard.js";import{useSupportConfig}from"../SupportProvider.js";import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";const COPY_TOAST_KEY="support-contact-copy";export const SupportContact=({testID:e="support-contact"})=>{const t=useTheme(),o=useMemo(()=>buildStyles(t),[t]),{config:s,loading:a,error:r}=useSupportConfig();if(!s)return _jsx(Card,{variant:"outline",testID:e,children:a?_jsx(Spinner,{size:"sm",accessibilityLabel:"Loading contact details"}):_jsx(Text,{variant:"bodySmall",color:"danger",children:r?.message??"Contact details are not available right now."})});const i=s.supportEmail,l=s.supportPhone?.trim(),n=s.supportHours?.trim(),open=(e,t)=>{Linking.openURL(e).catch(()=>toast.error(t))};return _jsxs(Card,{variant:"outline",bodyStyle:o.body,testID:e,children:[_jsx(Text,{variant:"title",accessibilityRole:"header",children:`Contact ${s.supportName||s.productName}`}),_jsxs(View,{style:o.row,children:[_jsx(Text,{variant:"label",color:"secondary",children:"Email"}),_jsx(Text,{variant:"body",selectable:!0,testID:"support-contact-email",children:i||"-"})]}),i?_jsxs(View,{style:o.actions,children:[_jsx(Button,{label:"Copy",size:"sm",variant:"outline",tone:"neutral",leftIcon:"copy",accessibilityLabel:`Copy ${i}`,onPress:()=>{(async()=>{try{await copyText(i),"android"===Platform.OS&&Number(Platform.Version)>=33||toast.success("Email address copied.",{key:COPY_TOAST_KEY})}catch{toast.error("Could not copy the email address.",{key:COPY_TOAST_KEY})}})()},testID:"support-contact-copy"}),_jsx(Button,{label:"Send email",size:"sm",variant:"outline",leftIcon:"mail",accessibilityLabel:`Send an email to ${i}`,onPress:()=>open(`mailto:${i}`,"No email app is set up on this device."),testID:"support-contact-mail"})]}):null,l?_jsxs(View,{style:o.row,children:[_jsx(Text,{variant:"label",color:"secondary",children:"Phone"}),_jsx(Button,{label:l,size:"sm",variant:"link",leftIcon:"call",accessibilityLabel:`Call ${l}`,onPress:()=>open(`tel:${l.replace(/[^\d+]/g,"")}`,"This device cannot make calls."),testID:"support-contact-phone"})]}):null,n?_jsxs(View,{style:o.row,children:[_jsx(Text,{variant:"label",color:"secondary",children:"Hours"}),_jsx(Text,{variant:"bodySmall",children:n})]}):null]})};const buildStyles=e=>StyleSheet.create({body:{gap:e.space.sm},row:{gap:e.space.xxs,alignItems:"flex-start"},actions:{flexDirection:"row",gap:e.space.sm}});
|
|
@@ -1 +1 @@
|
|
|
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:
|
|
1
|
+
"use strict";import{formatDateTime}from"@webority/mobile-core";import{Button,Card,Dialog,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:a})=>{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 i="Customer"===e.authorKind;return _jsx(View,{style:[t.bubbleRow,i?t.mineRow:t.theirsRow],testID:`support-message-${e.id}`,children:_jsxs(View,{style:[t.bubble,i?t.mine:t.theirs],children:[_jsx(Text,{variant:"label",color:i?"primary":"secondary",children:e.authorName??(i?"You":"Support team")}),_jsx(Text,{variant:"body",selectable:!0,children:e.body}),_jsx(AttachmentList,{attachments:e.attachments,onOpenImage:a}),_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(),a=useMemo(()=>buildStyles(s),[s]),{config:i}=useSupportConfig(),o=useSupportTimeZone(),{ticket:n,loading:r,error:l,actionError:c,busy:d,reload:u,reply:p,reopen:m,cancel:x,rate:h}=useSupportTicket(e),[y,g]=useState(null),[b,j]=useState(""),[S,f]=useState([]),[_,T]=useState(null),[w,v]=useState(null),[I,D]=useState(""),[C,k]=useState(!1);if(!n)return _jsx(View,{style:a.center,testID:t,children:r?_jsx(Spinner,{accessibilityLabel:"Loading the ticket"}):_jsxs(_Fragment,{children:[_jsx(Text,{variant:"body",color:"danger",align:"center",children:l?.message??"That ticket could not be found."}),_jsx(Button,{label:"Try again",variant:"ghost",onPress:()=>{u()}})]})});const R=n.messages.filter(e=>!e.isInternal),B="Open"===n.status||"InProgress"===n.status||"WaitingOnCustomer"===n.status;return _jsxs(View,{style:a.root,testID:t,children:[_jsxs(KeyboardAwareScrollView,{contentContainerStyle:a.content,safeAreaEdges:[],children:[_jsxs(View,{style:a.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:a.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})}`})]})]}),"WaitingOnCustomer"===n.status&&_jsx(Card,{variant:"outline",bodyStyle:a.section,testID:"support-waiting",children:_jsx(Text,{variant:"bodySmall",children:"We need your answer to carry on. Reply below and the ticket goes back to our team."})}),n.canReopen&&_jsxs(Card,{variant:"outline",bodyStyle:a.section,children:[_jsx(Text,{variant:"bodySmall",children:`This ticket is ${n.statusLabel}. Reopen it if you still need help.`}),_jsx(Button,{label:"Reopen ticket",variant:"outline",size:"sm",loading:d,disabled:d,onPress:()=>{m()},testID:"support-reopen"})]}),B&&_jsxs(Card,{variant:"outline",bodyStyle:a.section,children:[_jsx(Button,{label:"Withdraw request",variant:"outline",size:"sm",loading:d,disabled:d,onPress:()=>k(!0),testID:"support-withdraw"}),_jsx(Dialog,{visible:C,onClose:()=>k(!1),title:"Withdraw this request?",message:"Our team will stop working on this ticket. You can open a new ticket later if you still need help.",tone:"danger",actions:[{label:"Cancel",onPress:()=>k(!1),tone:"neutral"},{label:"Withdraw request",onPress:()=>{x()},tone:"danger"}]})]}),n.satisfaction?_jsxs(Card,{variant:"outline",bodyStyle:a.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:"outline",bodyStyle:a.section,testID:"support-rating",children:[_jsx(Text,{variant:"label",children:"Did this solve your problem?"}),_jsxs(View,{style:a.ratingRow,children:[_jsx(Button,{label:"Yes, it is solved",size:"sm",leftIcon:"like",variant:"Satisfied"===w?"solid":"outline",tone:"success",accessibilityState:{selected:"Satisfied"===w},"aria-selected":"Satisfied"===w,onPress:()=>v("Satisfied"),testID:"support-rate-satisfied"}),_jsx(Button,{label:"No, it is not",size:"sm",leftIcon:"dislike",variant:"Unsatisfied"===w?"solid":"outline",tone:"danger",accessibilityState:{selected:"Unsatisfied"===w},"aria-selected":"Unsatisfied"===w,onPress:()=>v("Unsatisfied"),testID:"support-rate-unsatisfied"})]}),w&&_jsxs(_Fragment,{children:[_jsx(Input,{label:"Anything to add? (optional)",helperText:"Your comment goes to the support team.",value:I,onChangeText:D,multiline:!0,maxLength:i?.satisfactionCommentMaxLength,testID:"support-rating-comment"}),_jsx(Button,{label:"Send answer",size:"sm",loading:d,disabled:d,onPress:()=>{(async()=>{w&&await h({satisfaction:w,comment:I})&&(v(null),D(""))})()},testID:"support-rating-send"})]})]}):null,_jsx(View,{style:a.thread,accessibilityLabel:"Conversation",children:R.map(e=>_jsx(MessageBubble,{message:e,styles:a,timeZone:o,onOpenImage:(e,t)=>g({uri:e,name:t})},e.id))}),_jsxs(View,{style:a.section,children:[_jsx(Input,{label:"Your reply",required:!0,placeholder:"Write a reply to the support team",value:b,onChangeText:j,multiline:!0,maxLength:i?.bodyMaxLength,error:_??void 0,testID:"support-reply-input"}),i&&_jsx(PhotoTray,{photos:S,onChange:f,remaining:i.maxAttachmentsPerMessage-S.length,maxBytes:i.maxAttachmentBytes,disabled:d}),n.canReopen&&_jsx(Text,{variant:"caption",color:"secondary",children:"Replying reopens this ticket."}),c&&_jsx(Text,{variant:"bodySmall",color:"danger",accessibilityLiveRegion:"polite",children:c.message}),_jsx(Button,{label:"Send reply",leftIcon:"send",loading:d,disabled:d,onPress:()=>{(async()=>{const e=b.trim(),t=i?validateBody(e,i)??validateFiles(S,i):e?void 0:"Write a message.";t?T(t):(T(null),await p({body:e,files:S})&&(j(""),f([])))})()},testID:"support-reply-send"})]})]}),_jsx(ImageViewer,{image:y,onClose:()=>g(null)})]})};const buildStyles=e=>StyleSheet.create({root:{flex:1},content:{padding:e.space.md,gap:e.space.md},center:{flex:1,alignItems:"center",justifyContent:"center",padding:e.space.xl,gap:e.space.sm},head:{gap:e.space.xxs},headRow:{flexDirection:"row",alignItems:"center",gap:e.space.sm,marginTop:e.space.xs},section:{gap:e.space.sm},ratingRow:{flexDirection:"row",flexWrap:"wrap",gap:e.space.sm},thread:{gap:e.space.sm},system:{paddingHorizontal:e.space.lg,paddingVertical:e.space.xxs},bubbleRow:{flexDirection:"row"},mineRow:{justifyContent:"flex-end"},theirsRow:{justifyContent:"flex-start"},bubble:{maxWidth:"85%",borderRadius:e.radius.lg,padding:e.space.sm,gap:e.space.xxs},mine:{backgroundColor:e.colors["primary-soft"]},theirs:{backgroundColor:e.colors["bg-subtle"]}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{formatDateTime}from"@webority/mobile-core";import{Button,Card,Chip,EmptyState,SearchBar,Spinner,StatusBadge,Text,useTheme}from"@webority/mobile-ui";import{memo,useCallback,useMemo}from"react";import{FlatList,RefreshControl,ScrollView,StyleSheet,View}from"react-native";import{useSupportTickets,useSupportTimeZone}from"../SupportProvider.js";import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";export const STATUS_TONES={Open:"warning",InProgress:"info",WaitingOnCustomer:"warning",Resolved:"success",Closed:"neutral"};const STATUS_FILTERS=[{value:"Open",label:"Open",count:"open"},{value:"InProgress",label:"In progress",count:"inProgress"},{value:"WaitingOnCustomer",label:"Waiting on you",count:"waitingOnCustomer"},{value:"Resolved",label:"Resolved",count:"resolved"},{value:"Closed",label:"Closed",count:"closed"}],ALL="all";export const statusFilterOptions=(e,t)=>[{value:ALL,label:"All",count:e.total},...STATUS_FILTERS.filter(s=>e[s.count]>0||s.value===t).map(t=>({value:t.value,label:t.label,count:e[t.count]}))];const SEARCH_DEBOUNCE_MS=400,ticketKey=e=>e.id,TicketRow=memo(function TicketRow({item:e,timeZone:t,onOpen:s,styles:
|
|
1
|
+
"use strict";import{formatDateTime}from"@webority/mobile-core";import{Button,Card,Chip,EmptyState,SearchBar,Spinner,StatusBadge,Text,useTheme}from"@webority/mobile-ui";import{memo,useCallback,useMemo}from"react";import{FlatList,RefreshControl,ScrollView,StyleSheet,View}from"react-native";import{useSupportTickets,useSupportTimeZone}from"../SupportProvider.js";import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";export const STATUS_TONES={Open:"warning",InProgress:"info",WaitingOnCustomer:"warning",Resolved:"success",Closed:"neutral",Cancelled:"neutral"};const STATUS_FILTERS=[{value:"Open",label:"Open",count:"open"},{value:"InProgress",label:"In progress",count:"inProgress"},{value:"WaitingOnCustomer",label:"Waiting on you",count:"waitingOnCustomer"},{value:"Resolved",label:"Resolved",count:"resolved"},{value:"Closed",label:"Closed",count:"closed"},{value:"Cancelled",label:"Cancelled"}],ALL="all";export const statusFilterOptions=(e,t)=>[{value:ALL,label:"All",count:e.total},...STATUS_FILTERS.filter(s=>void 0===s.count||e[s.count]>0||s.value===t).map(t=>({value:t.value,label:t.label,count:void 0===t.count?void 0:e[t.count]}))];const chipLabel=e=>void 0===e.count?e.label:`${e.label} (${e.count})`,SEARCH_DEBOUNCE_MS=400,ticketKey=e=>e.id,TicketRow=memo(function TicketRow({item:e,timeZone:t,onOpen:s,styles:o}){return _jsxs(Card,{onPress:()=>s(e),accessibilityLabel:`${e.subject}, ${e.statusLabel}, ticket ${e.number}`,bodyStyle:o.card,testID:`support-ticket-${e.id}`,children:[_jsxs(View,{style:o.cardTop,children:[_jsx(Text,{variant:"caption",color:"tertiary",children:e.number}),_jsx(StatusBadge,{label:e.statusLabel,tone:STATUS_TONES[e.status]??"neutral",size:"sm"})]}),_jsx(Text,{variant:"title",numberOfLines:2,children:e.subject}),_jsx(Text,{variant:"bodySmall",color:"secondary",children:e.category}),_jsxs(View,{style:o.cardBottom,children:[_jsx(Text,{variant:"caption",color:"tertiary",children:`Updated ${formatDateTime(e.updatedDateTimeUtc,{zone:t})}`}),e.awaitingCustomer&&_jsx(Text,{variant:"caption",color:"warning",children:"Waiting for your reply"})]})]})});export const SupportTicketList=({onOpenTicket:e,onNewTicket:t,header:s,pageSize:o,testID:i="support-ticket-list"})=>{const a=useTheme(),r=useMemo(()=>buildStyles(a),[a]),{tickets:l,counts:n,status:c,search:u,setStatus:d,setSearch:p,loading:m,refreshing:b,loadingMore:x,error:h,loadMoreError:y,refresh:g,loadMore:S}=useSupportTickets({pageSize:o}),j=useSupportTimeZone(),T=useCallback(({item:t})=>_jsx(TicketRow,{item:t,timeZone:j,onOpen:e,styles:r}),[j,e,r]),_=useCallback(()=>{S()},[S]),v=useCallback(()=>{g()},[g]),f=useMemo(()=>_jsx(RefreshControl,{refreshing:b,onRefresh:v}),[b,v]),L=(n?.total??0)>0,w=null!==c||""!==u.trim(),C=_jsxs(View,{style:r.header,children:[s,t&&_jsx(Button,{label:"New ticket",leftIcon:"add",onPress:t,fullWidth:!0}),h&&_jsxs(View,{style:r.error,accessibilityLiveRegion:"polite",children:[_jsx(Text,{variant:"bodySmall",color:"danger",children:h.message}),_jsx(Button,{label:"Try again",size:"sm",variant:"ghost",onPress:()=>{g()}})]}),n&&L&&_jsxs(_Fragment,{children:[_jsx(SearchBar,{value:u,onChangeText:p,debounceMs:400,placeholder:"Search by subject or ticket number",accessibilityLabel:"Search tickets",testID:"support-ticket-search"}),_jsx(ScrollView,{horizontal:!0,showsHorizontalScrollIndicator:!1,contentContainerStyle:r.filters,accessibilityRole:"tablist",accessibilityLabel:"Filter by status",children:statusFilterOptions(n,c).map(e=>_jsx(Chip,{label:chipLabel(e),selected:(c??ALL)===e.value,onPress:()=>d(e.value===ALL?null:e.value),accessibilityLabel:void 0===e.count?e.label:`${e.label}, ${e.count} tickets`,testID:`support-filter-${e.value}`},e.value))})]})]});if(m&&null===n&&!h)return _jsx(View,{style:r.center,testID:i,children:_jsx(Spinner,{accessibilityLabel:"Loading your tickets"})});const k=x?_jsx(View,{style:r.footer,children:_jsx(Spinner,{size:"sm",accessibilityLabel:"Loading more tickets"})}):y?_jsxs(View,{style:[r.footer,r.error],accessibilityLiveRegion:"polite",children:[_jsx(Text,{variant:"bodySmall",color:"danger",children:y.message}),_jsx(Button,{label:"Load more",size:"sm",variant:"ghost",onPress:()=>{S()}})]}):null;return _jsx(FlatList,{testID:i,data:l,keyExtractor:ticketKey,contentContainerStyle:r.content,ListHeaderComponent:C,ListFooterComponent:k,onEndReached:_,onEndReachedThreshold:.5,refreshControl:f,ListEmptyComponent:m?_jsx(View,{style:r.footer,children:_jsx(Spinner,{accessibilityLabel:"Loading your tickets"})}):h?null:_jsx(EmptyState,L?{title:"No tickets match",description:w?"Try another status or search.":void 0}:{title:"No tickets yet",description:"Raise a ticket and it will show here with its status.",action:t?{label:"New ticket",onPress:t}:void 0}),renderItem:T})};const buildStyles=e=>StyleSheet.create({content:{padding:e.space.md,gap:e.space.sm},header:{gap:e.space.sm,marginBottom:e.space.xs},filters:{gap:e.space.xs},error:{gap:e.space.xs,alignItems:"flex-start"},center:{flex:1,alignItems:"center",justifyContent:"center",padding:e.space.xl},footer:{paddingVertical:e.space.md,alignItems:"center"},card:{gap:e.space.xxs},cardTop:{flexDirection:"row",justifyContent:"space-between",alignItems:"center"},cardBottom:{flexDirection:"row",justifyContent:"space-between",marginTop:e.space.xxs}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";import{Button,useTheme}from"@webority/mobile-ui";import{useState}from"react";import{View}from"react-native";import{captureAndReport}from"./captureAndReport.js";import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";export const ReportIssueButton=({onReport:e,onCaptureError:t,captureScreenshot:s=!1,screenName:r,appVersion:a,category:o,subject:i,title:n="Report an issue",accessibilityLabel:c="Report an issue with this screen",testID:p="support-report-issue",...u})=>{const l=useTheme(),[m,
|
|
1
|
+
"use strict";import{Button,useTheme}from"@webority/mobile-ui";import{useState}from"react";import{View}from"react-native";import{captureAndReport}from"./captureAndReport.js";import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";export const ReportIssueButton=({onReport:e,onCaptureError:t,captureScreenshot:s=!1,screenName:r,appVersion:a,category:o,subject:i,title:n="Report an issue",accessibilityLabel:c="Report an issue with this screen",testID:p="support-report-issue",...u})=>{const l=useTheme(),[m,b]=useState(!1),draftWithoutScreenshot=()=>({category:o,subject:i,pageUrl:r,appVersion:a}),d=_jsx(Button,{leftIcon:"message-question",...u,label:n,accessibilityLabel:c,testID:p,disabled:u.disabled||m,onPress:()=>e(draftWithoutScreenshot())});return s?_jsxs(View,{style:{gap:l.space.sm},children:[d,_jsx(Button,{variant:"outline",label:"Add a screenshot",accessibilityLabel:"Add a screenshot",testID:`${p}-screenshot`,loading:m,disabled:u.disabled,onPress:()=>{(async()=>{b(!0);try{e(await captureAndReport({screenName:r,appVersion:a,category:o,subject:i}))}catch(s){t?.(s),e(draftWithoutScreenshot())}finally{b(!1)}})()}})]}):d};
|
|
@@ -83,6 +83,7 @@ export interface SupportTicketState {
|
|
|
83
83
|
reload: () => Promise<void>;
|
|
84
84
|
reply: (input: SupportReplyInput) => Promise<boolean>;
|
|
85
85
|
reopen: () => Promise<boolean>;
|
|
86
|
+
cancel: () => Promise<boolean>;
|
|
86
87
|
rate: (input: SupportRatingInput) => Promise<boolean>;
|
|
87
88
|
}
|
|
88
89
|
export declare const useSupportTicket: (id: string) => SupportTicketState;
|
|
@@ -43,6 +43,8 @@ export interface SupportClient {
|
|
|
43
43
|
createTicket(input: NewSupportTicketInput): Promise<SupportTicketDetail>;
|
|
44
44
|
reply(id: string, input: SupportReplyInput): Promise<SupportTicketDetail>;
|
|
45
45
|
reopen(id: string): Promise<SupportTicketDetail>;
|
|
46
|
+
/** Withdraws the ticket; resolves to the updated ticket. */
|
|
47
|
+
cancel(id: string): Promise<SupportTicketDetail>;
|
|
46
48
|
rate(id: string, input: SupportRatingInput): Promise<SupportTicketDetail>;
|
|
47
49
|
/** An attachment's raw bytes, fetched through the same authenticated client. */
|
|
48
50
|
getAttachmentBytes(id: string): Promise<ArrayBuffer>;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Tone } from "@webority/mobile-ui";
|
|
2
2
|
import type React from "react";
|
|
3
3
|
import type { SupportTicketCounts, SupportTicketStatus, SupportTicketSummary } from "../types";
|
|
4
|
-
export declare const STATUS_TONES: Record<SupportTicketStatus,
|
|
4
|
+
export declare const STATUS_TONES: Record<SupportTicketStatus, Tone>;
|
|
5
5
|
declare const ALL = "all";
|
|
6
6
|
export interface StatusFilterOption {
|
|
7
7
|
value: SupportTicketStatus | typeof ALL;
|
|
8
8
|
label: string;
|
|
9
|
-
count
|
|
9
|
+
count?: number;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* All, then each status that has tickets. The selected status stays even at
|
|
@@ -2,7 +2,7 @@ import { type ButtonProps } from "@webority/mobile-ui";
|
|
|
2
2
|
import type React from "react";
|
|
3
3
|
import type { SupportTicketDraft } from "../types";
|
|
4
4
|
import { type CaptureAndReportOptions } from "./captureAndReport";
|
|
5
|
-
export interface ReportIssueButtonProps extends CaptureAndReportOptions, Omit<ButtonProps, "onPress" | "
|
|
5
|
+
export interface ReportIssueButtonProps extends CaptureAndReportOptions, Omit<ButtonProps, "onPress" | "label" | "loading"> {
|
|
6
6
|
/** Open the new ticket screen with this draft, for example `navigation.navigate('NewTicket', { draft })`. */
|
|
7
7
|
onReport: (draft: SupportTicketDraft) => void;
|
|
8
8
|
/**
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* of the backend's view records and enums travel as their member names, so these
|
|
4
4
|
* mirror `Webority.Support/SupportViews.cs` and `SupportEnums.cs` exactly.
|
|
5
5
|
*/
|
|
6
|
-
export type SupportTicketStatus = "Open" | "InProgress" | "WaitingOnCustomer" | "Resolved" | "Closed";
|
|
6
|
+
export type SupportTicketStatus = "Open" | "InProgress" | "WaitingOnCustomer" | "Resolved" | "Closed" | "Cancelled";
|
|
7
7
|
export type SupportTicketPriority = "Low" | "Normal" | "High" | "Urgent";
|
|
8
8
|
export type SupportMessageAuthorKind = "Customer" | "Agent" | "System";
|
|
9
9
|
export type SupportAttachmentKind = "Screenshot" | "File";
|
|
@@ -83,6 +83,7 @@ export interface SupportTicketState {
|
|
|
83
83
|
reload: () => Promise<void>;
|
|
84
84
|
reply: (input: SupportReplyInput) => Promise<boolean>;
|
|
85
85
|
reopen: () => Promise<boolean>;
|
|
86
|
+
cancel: () => Promise<boolean>;
|
|
86
87
|
rate: (input: SupportRatingInput) => Promise<boolean>;
|
|
87
88
|
}
|
|
88
89
|
export declare const useSupportTicket: (id: string) => SupportTicketState;
|
|
@@ -43,6 +43,8 @@ export interface SupportClient {
|
|
|
43
43
|
createTicket(input: NewSupportTicketInput): Promise<SupportTicketDetail>;
|
|
44
44
|
reply(id: string, input: SupportReplyInput): Promise<SupportTicketDetail>;
|
|
45
45
|
reopen(id: string): Promise<SupportTicketDetail>;
|
|
46
|
+
/** Withdraws the ticket; resolves to the updated ticket. */
|
|
47
|
+
cancel(id: string): Promise<SupportTicketDetail>;
|
|
46
48
|
rate(id: string, input: SupportRatingInput): Promise<SupportTicketDetail>;
|
|
47
49
|
/** An attachment's raw bytes, fetched through the same authenticated client. */
|
|
48
50
|
getAttachmentBytes(id: string): Promise<ArrayBuffer>;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Tone } from "@webority/mobile-ui";
|
|
2
2
|
import type React from "react";
|
|
3
3
|
import type { SupportTicketCounts, SupportTicketStatus, SupportTicketSummary } from "../types.js";
|
|
4
|
-
export declare const STATUS_TONES: Record<SupportTicketStatus,
|
|
4
|
+
export declare const STATUS_TONES: Record<SupportTicketStatus, Tone>;
|
|
5
5
|
declare const ALL = "all";
|
|
6
6
|
export interface StatusFilterOption {
|
|
7
7
|
value: SupportTicketStatus | typeof ALL;
|
|
8
8
|
label: string;
|
|
9
|
-
count
|
|
9
|
+
count?: number;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* All, then each status that has tickets. The selected status stays even at
|
|
@@ -2,7 +2,7 @@ import { type ButtonProps } from "@webority/mobile-ui";
|
|
|
2
2
|
import type React from "react";
|
|
3
3
|
import type { SupportTicketDraft } from "../types.js";
|
|
4
4
|
import { type CaptureAndReportOptions } from "./captureAndReport.js";
|
|
5
|
-
export interface ReportIssueButtonProps extends CaptureAndReportOptions, Omit<ButtonProps, "onPress" | "
|
|
5
|
+
export interface ReportIssueButtonProps extends CaptureAndReportOptions, Omit<ButtonProps, "onPress" | "label" | "loading"> {
|
|
6
6
|
/** Open the new ticket screen with this draft, for example `navigation.navigate('NewTicket', { draft })`. */
|
|
7
7
|
onReport: (draft: SupportTicketDraft) => void;
|
|
8
8
|
/**
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* of the backend's view records and enums travel as their member names, so these
|
|
4
4
|
* mirror `Webority.Support/SupportViews.cs` and `SupportEnums.cs` exactly.
|
|
5
5
|
*/
|
|
6
|
-
export type SupportTicketStatus = "Open" | "InProgress" | "WaitingOnCustomer" | "Resolved" | "Closed";
|
|
6
|
+
export type SupportTicketStatus = "Open" | "InProgress" | "WaitingOnCustomer" | "Resolved" | "Closed" | "Cancelled";
|
|
7
7
|
export type SupportTicketPriority = "Low" | "Normal" | "High" | "Urgent";
|
|
8
8
|
export type SupportMessageAuthorKind = "Customer" | "Agent" | "System";
|
|
9
9
|
export type SupportAttachmentKind = "Screenshot" | "File";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/mobile-support",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.108.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",
|
|
@@ -69,11 +69,12 @@
|
|
|
69
69
|
"expo-image-picker": ">=57.0.0",
|
|
70
70
|
"expo-sharing": ">=57.0.0",
|
|
71
71
|
"react": "^19.1.0",
|
|
72
|
+
"react-hook-form": "^7.55.0",
|
|
72
73
|
"react-native": ">=0.81.0",
|
|
73
74
|
"react-native-gesture-handler": "^2.21.0 || ^3.0.0",
|
|
74
75
|
"react-native-view-shot": ">=4.0.0",
|
|
75
|
-
"@webority/mobile-core": "0.
|
|
76
|
-
"@webority/mobile-ui": "0.
|
|
76
|
+
"@webority/mobile-core": "0.108.0",
|
|
77
|
+
"@webority/mobile-ui": "0.108.0"
|
|
77
78
|
},
|
|
78
79
|
"peerDependenciesMeta": {
|
|
79
80
|
"expo-image-manipulator": {
|
|
@@ -128,6 +129,7 @@
|
|
|
128
129
|
"expo-file-system": "57.0.6",
|
|
129
130
|
"expo-image-manipulator": "57.0.14",
|
|
130
131
|
"jest": "29.7.0",
|
|
132
|
+
"react-hook-form": "7.89.0",
|
|
131
133
|
"react": "19.2.8",
|
|
132
134
|
"react-native": "0.86.3",
|
|
133
135
|
"react-native-builder-bob": "0.43.0",
|
|
@@ -135,9 +137,9 @@
|
|
|
135
137
|
"react-test-renderer": "19.2.8",
|
|
136
138
|
"rimraf": "6.1.3",
|
|
137
139
|
"typescript": "5.9.3",
|
|
138
|
-
"@webority/mobile-
|
|
139
|
-
"@webority/mobile-
|
|
140
|
-
"@webority/mobile-
|
|
140
|
+
"@webority/mobile-core": "0.108.0",
|
|
141
|
+
"@webority/mobile-dev": "0.0.0",
|
|
142
|
+
"@webority/mobile-ui": "0.108.0"
|
|
141
143
|
},
|
|
142
144
|
"scripts": {
|
|
143
145
|
"build": "bob build && node ../mobile-dev/scripts/minify-lib.mjs",
|