@webority/mobile-support 0.1.0 → 0.2.1

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.
@@ -1 +1 @@
1
- import{createContext as t,useCallback as e,useContext as r,useEffect as n,useMemo as o,useRef as c,useState as i}from"react";import{toSupportApiError as a}from"./client.js";import{saveAttachmentToCache as s}from"./files.js";import{DEFAULT_TIME_ZONE as u}from"./format.js";import{jsx as l}from"react/jsx-runtime";const p=t(null);export const SupportProvider=({client:t,openFile:r,timeZone:m=u,children:f})=>{const[d,g]=i(null),[h,y]=i(null),[S,k]=i(!0),[w,T]=i(0),v=c(new Map),x=e(async()=>{k(!0);try{g(await t.getConfig()),y(null)}catch(t){y(a(t))}finally{k(!1)}},[t]);n(()=>{v.current.clear(),x()},[x]);const A=e(e=>{const r=v.current.get(e.id);if(r)return r;const n=t.getAttachmentDataUri(e);return v.current.set(e.id,n),n.catch(()=>v.current.delete(e.id)),n},[t]),C=o(()=>r?async e=>{const n=await s(e,()=>t.getAttachmentBytes(e.id));await r({uri:n,name:e.fileName,contentType:e.contentType})}:null,[t,r]),b=e(()=>T(t=>t+1),[]),P=o(()=>({client:t,config:d,configError:h,configLoading:S,reloadConfig:x,loadAttachment:A,openAttachment:C,timeZone:m,revision:w,bumpRevision:b}),[t,d,h,S,x,A,C,m,w,b]);return l(p.Provider,{value:P,children:f})};const useSupportContext=()=>{const t=r(p);if(!t)throw new Error("[@webority/mobile-support] Support screens and hooks need a <SupportProvider client={...}> above them.");return t};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:t,configLoading:e,configError:r,reloadConfig:n}=useSupportContext();return{config:t,loading:e,error:r,reload:n}};export const useSupportTickets=(t={})=>{const r=t.pageSize??20,{client:o,revision:s}=useSupportContext(),[u,l]=i(null),[p,m]=i(""),[f,d]=i([]),[g,h]=i(null),[y,S]=i(0),[k,w]=i(0),[T,v]=i(!0),[x,A]=i(!1),[C,b]=i(!1),[P,j]=i(null),[E,M]=i(null),Z=c(!0),L=c(0),R=c(!1),z=c(null);n(()=>(Z.current=!0,()=>{Z.current=!1}),[]);const q=e(t=>({status:u??void 0,search:p.trim()||void 0,page:t,pageSize:r}),[u,p,r]),B=e(async t=>{L.current+=1;const e=L.current,current=()=>Z.current&&e===L.current;R.current=!1,b(!1),"refresh"===t?A(!0):"replace"===t&&(v(!0),d([]));const r="replace"!==t||z.current!==o;try{const[t,e]=await Promise.all([o.listTickets(q(1)),r?o.getTicketCounts():null]);current()&&(d(t.items),S(t.page),w(t.totalPages),e&&(h(e),z.current=o),j(null),M(null))}catch(t){current()&&j(a(t))}finally{current()&&(v(!1),A(!1))}},[o,q]),D=c(s);n(()=>{const t=D.current===s?"replace":"quiet";D.current=s,B(t)},[B,s]);const F=y<k,N=e(async()=>{if(R.current||T||x||!F)return;const t=L.current,current=()=>Z.current&&t===L.current;R.current=!0,b(!0),M(null);try{const t=await o.listTickets(q(y+1));current()&&(d(e=>{const r=new Set(e.map(t=>t.id));return[...e,...t.items.filter(t=>!r.has(t.id))]}),S(t.page),w(t.totalPages))}catch(t){current()&&M(a(t))}finally{current()&&(R.current=!1,b(!1))}},[o,q,y,F,T,x]),O=e(()=>B("refresh"),[B]),U=useCreateSupportTicket();return{tickets:f,counts:g,status:u,search:p,setStatus:l,setSearch:m,loading:T,refreshing:x,loadingMore:C,hasMore:F,error:P,loadMoreError:E,refresh:O,loadMore:N,createTicket:U}};export const useCreateSupportTicket=()=>{const{client:t,bumpRevision:r}=useSupportContext();return e(async e=>{const n=await t.createTicket(e);return r(),n},[t,r])};export const useSupportTicket=t=>{const{client:r,bumpRevision:o}=useSupportContext(),[s,u]=i(null),[l,p]=i(!0),[m,f]=i(null),[d,g]=i(null),[h,y]=i(!1),S=c(!0);n(()=>(S.current=!0,()=>{S.current=!1}),[]);const k=e(async()=>{p(!0);try{const e=await r.getTicket(t);S.current&&(u(e),f(null))}catch(t){S.current&&f(a(t))}finally{S.current&&p(!1)}},[r,t]);n(()=>{k()},[k]);const w=e(async t=>{y(!0),g(null);try{const e=await t();return S.current&&u(e),o(),!0}catch(t){return S.current&&g(a(t)),!1}finally{S.current&&y(!1)}},[o]),T=e(e=>w(()=>r.reply(t,e)),[w,r,t]),v=e(()=>w(()=>r.reopen(t)),[w,r,t]),x=e(e=>w(()=>r.rate(t,e)),[w,r,t]);return{ticket:s,loading:l,error:m,actionError:d,busy:h,reload:k,reply:T,reopen:v,rate:x}};
1
+ "use strict";import{createContext,useCallback,useContext,useEffect,useMemo,useRef,useState}from"react";import{toSupportApiError}from"./client.js";import{saveAttachmentToCache}from"./files.js";import{DEFAULT_TIME_ZONE}from"./format.js";import{jsx as _jsx}from"react/jsx-runtime";const SupportContext=createContext(null);export const SupportProvider=({client:e,openFile:t,timeZone:r=DEFAULT_TIME_ZONE,children:n})=>{const[o,u]=useState(null),[s,c]=useState(null),[a,l]=useState(!0),[i,p]=useState(0),S=useRef(new Map),f=useCallback(async()=>{l(!0);try{u(await e.getConfig()),c(null)}catch(e){c(toSupportApiError(e))}finally{l(!1)}},[e]);useEffect(()=>{S.current.clear(),f()},[f]);const m=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]),C=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:s,configLoading:a,reloadConfig:f,loadAttachment:m,openAttachment:C,timeZone:r,revision:i,bumpRevision:h}),[e,o,s,a,f,m,C,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),[s,c]=useState(""),[a,l]=useState([]),[i,p]=useState(null),[S,f]=useState(0),[m,C]=useState(0),[h,d]=useState(!0),[g,x]=useState(!1),[k,E]=useState(!1),[y,b]=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 _=useCallback(e=>({status:o??void 0,search:s.trim()||void 0,page:e,pageSize:t}),[o,s,t]),j=useCallback(async e=>{w.current+=1;const t=w.current,current=()=>v.current&&t===w.current;R.current=!1,E(!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(_(1)),n?r.getTicketCounts():null]);current()&&(l(e.items),f(e.page),C(e.totalPages),t&&(p(t),M.current=r),b(null),T(null))}catch(e){current()&&b(toSupportApiError(e))}finally{current()&&(d(!1),x(!1))}},[r,_]),P=useRef(n);useEffect(()=>{const e=P.current===n?"replace":"quiet";P.current=n,j(e)},[j,n]);const Z=S<m,L=useCallback(async()=>{if(R.current||h||g||!Z)return;const e=w.current,current=()=>v.current&&e===w.current;R.current=!0,E(!0),T(null);try{const e=await r.listTickets(_(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),C(e.totalPages))}catch(e){current()&&T(toSupportApiError(e))}finally{current()&&(R.current=!1,E(!1))}},[r,_,S,Z,h,g]),D=useCallback(()=>j("refresh"),[j]),F=useCreateSupportTicket();return{tickets:a,counts:i,status:o,search:s,setStatus:u,setSearch:c,loading:h,refreshing:g,loadingMore:k,hasMore:Z,error:y,loadMoreError:A,refresh:D,loadMore:L,createTicket:F}};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,s]=useState(!0),[c,a]=useState(null),[l,i]=useState(null),[p,S]=useState(!1),f=useRef(!0);useEffect(()=>(f.current=!0,()=>{f.current=!1}),[]);const m=useCallback(async()=>{s(!0);try{const r=await t.getTicket(e);f.current&&(o(r),a(null))}catch(e){f.current&&a(toSupportApiError(e))}finally{f.current&&s(!1)}},[t,e]);useEffect(()=>{m()},[m]);const C=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=>C(()=>t.reply(e,r)),[C,t,e]),d=useCallback(()=>C(()=>t.reopen(e)),[C,t,e]),g=useCallback(r=>C(()=>t.rate(e,r)),[C,t,e]);return{ticket:n,loading:u,error:c,actionError:l,busy:p,reload:m,reply:h,reopen:d,rate:g}};
@@ -1 +1 @@
1
- import{encodeBase64 as e}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,o=t.response?.data;if(o&&"string"==typeof o.message&&o.message.trim()){const e="string"==typeof o.code?o.code:"unknown";return new SupportApiError(e,o.message,r)}return t.response?new SupportApiError("unknown",GENERIC_MESSAGE,r):new SupportApiError("network",OFFLINE_MESSAGE,null)};const t=3e5,r={"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)};export const createSupportClient=(o,n="/api/support")=>{const s=n.replace(/\/+$/,""),ticketUrl=e=>`${s}/tickets/${encodeURIComponent(e)}`,call=async e=>{try{return(await e()).data}catch(e){throw toSupportApiError(e)}},getBytes=e=>call(()=>o.get(`${s}/attachments/${encodeURIComponent(e)}`,{responseType:"arraybuffer"}));return{basePath:s,getConfig:()=>call(()=>o.get(`${s}/settings`)),listTickets:(e={})=>call(()=>o.get(`${s}/tickets${(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("&")}`:""})(e)}`)),getTicketCounts:()=>call(()=>o.get(`${s}/tickets/counts`)),getTicket:e=>call(()=>o.get(ticketUrl(e))),createTicket:e=>{const n=new FormData;return n.append("category",e.category),n.append("subject",e.subject),n.append("body",e.body),appendText(n,"pageUrl",e.pageUrl),appendText(n,"userAgent",e.userAgent),appendText(n,"appVersion",e.appVersion),appendParts(n,"files",e.files),appendParts(n,"screenshots",e.screenshots),call(()=>o.post(`${s}/tickets`,n,{headers:r,timeout:t}))},reply:(e,n)=>{const s=new FormData;return s.append("body",n.body),appendParts(s,"files",n.files),appendParts(s,"screenshots",n.screenshots),call(()=>o.post(`${ticketUrl(e)}/replies`,s,{headers:r,timeout:t}))},reopen:e=>call(()=>o.post(`${ticketUrl(e)}/reopen`)),rate:(e,t)=>call(()=>o.post(`${ticketUrl(e)}/rating`,{satisfaction:t.satisfaction,comment:t.comment?.trim()?t.comment.trim():null})),getAttachmentBytes:getBytes,getAttachmentDataUri:async t=>{const r=await getBytes(t.id);return`data:${t.contentType};base64,${e(r)}`},confirmAction:e=>call(()=>o.post(`${s}/actions/confirm`,{token:e}))}};
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.message&&n.message.trim()){const e="string"==typeof n.code?n.code:"unknown";return new SupportApiError(e,n.message,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 +1 @@
1
- export const copyText=e=>require("expo-clipboard").setStringAsync(e);
1
+ "use strict";export const copyText=e=>require("expo-clipboard").setStringAsync(e);
@@ -1 +1 @@
1
- import{AppIcon as e,IconButton as t,Spinner as i,Text as r,ToastHost as a,toast as s,useTheme as o,ZoomableImage as n}from"@webority/mobile-ui";import{useEffect as l,useMemo as c,useState as m}from"react";import{Image as p,Modal as d,Pressable as u,StyleSheet as b,View as g}from"react-native";import{GestureHandlerRootView as h}from"react-native-gesture-handler";import{formatBytes as f,isImage as y}from"../format.js";import{useAttachmentLoader as w,useAttachmentOpener as v}from"../SupportProvider.js";import{jsx as x,jsxs as I}from"react/jsx-runtime";const AttachmentThumb=({attachment:e,onOpen:t})=>{const a=o(),s=c(()=>buildStyles(a),[a]),n=w(),[d,b]=m({status:"loading"});return l(()=>{let t=!1;return n(e).then(e=>!t&&b({status:"ready",uri:e}),()=>!t&&b({status:"error"})),()=>{t=!0}},[n,e]),"loading"===d.status?x(g,{style:s.thumb,testID:`support-attachment-${e.id}`,children:x(i,{size:"sm",accessibilityLabel:`Loading ${e.fileName}`})}):"error"===d.status?x(g,{style:s.thumb,accessible:!0,accessibilityLabel:`${e.fileName} could not be loaded`,testID:`support-attachment-${e.id}`,children:x(r,{variant:"caption",color:"tertiary",align:"center",children:"Could not load"})}):x(u,{onPress:()=>t(d.uri,e.fileName),accessibilityRole:"imagebutton",accessibilityLabel:`Open ${e.fileName}`,testID:`support-attachment-${e.id}`,children:x(p,{source:{uri:d.uri},style:s.thumb,resizeMode:"cover"})})},FileRow=({attachment:t})=>{const a=o(),n=c(()=>buildStyles(a),[a]),l=v(),[p,d]=m(!1),b=`${t.fileName}, ${f(t.sizeBytes)}`;if(!l)return x(r,{variant:"caption",color:"secondary",testID:`support-file-${t.id}`,children:b});return I(u,{onPress:()=>{(async()=>{d(!0);try{await l(t)}catch{s.error(`Could not open ${t.fileName}. Try again.`)}finally{d(!1)}})()},disabled:p,accessibilityRole:"button",accessibilityLabel:`Open ${t.fileName}`,accessibilityState:{busy:p,disabled:p},style:n.fileRow,testID:`support-file-${t.id}`,children:[x(e,{name:"document",size:16,color:a.colors.text.link}),x(r,{variant:"caption",color:"link",children:b}),p&&x(i,{size:"sm",accessibilityLabel:`Opening ${t.fileName}`})]})};export const AttachmentList=({attachments:e,onOpenImage:t})=>{const i=o(),r=c(()=>buildStyles(i),[i]);if(0===e.length)return null;const a=e.filter(e=>y(e.contentType)),s=e.filter(e=>!y(e.contentType));return I(g,{style:r.list,children:[a.length>0&&x(g,{style:r.thumbRow,children:a.map(e=>x(AttachmentThumb,{attachment:e,onOpen:t},e.id))}),s.map(e=>x(FileRow,{attachment:e},e.id))]})};export const ImageViewer=({image:i,onClose:r})=>{const s=o(),l=c(()=>buildStyles(s),[s]);return x(d,{visible:null!==i,onRequestClose:r,animationType:"fade",transparent:!1,children:I(h,{style:l.viewer,testID:"support-image-viewer",children:[i&&x(n,{accessibilityLabel:i.name,style:l.viewerImageWrap,children:x(p,{source:{uri:i.uri},style:l.viewerImage,resizeMode:"contain"})}),x(g,{style:l.viewerClose,children:x(t,{icon:({color:t,size:i})=>x(e,{name:"close-circle",color:t,size:i}),accessibilityLabel:"Close image",onPress:r,variant:"glass"})}),x(a,{})]})})};const buildStyles=e=>b.create({list:{gap:e.spacing.xs,marginTop:e.spacing.sm},fileRow:{flexDirection:"row",alignItems:"center",gap:e.spacing.xxs,alignSelf:"flex-start"},thumbRow:{flexDirection:"row",flexWrap:"wrap",gap:e.spacing.xs},thumb:{width:84,height:84,borderRadius:e.radius.sm,backgroundColor:e.colors.background.secondary,alignItems:"center",justifyContent:"center",overflow:"hidden"},viewer:{flex:1,backgroundColor:e.colors.background.inverse},viewerImageWrap:{flex:1},viewerImage:{flex:1,width:"100%"},viewerClose:{position:"absolute",top:e.spacing["2xl"],right:e.spacing.md}});
1
+ "use strict";import{AppIcon,IconButton,Spinner,Text,ToastHost,toast,useTheme,ZoomableImage}from"@webority/mobile-ui";import{useEffect,useMemo,useState}from"react";import{Image,Modal,Pressable,StyleSheet,View}from"react-native";import{GestureHandlerRootView}from"react-native-gesture-handler";import{formatBytes,isImage}from"../format.js";import{useAttachmentLoader,useAttachmentOpener}from"../SupportProvider.js";import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";const AttachmentThumb=({attachment:e,onOpen:t})=>{const s=useTheme(),i=useMemo(()=>buildStyles(s),[s]),a=useAttachmentLoader(),[r,o]=useState({status:"loading"});return useEffect(()=>{let t=!1;return a(e).then(e=>!t&&o({status:"ready",uri:e}),()=>!t&&o({status:"error"})),()=>{t=!0}},[a,e]),"loading"===r.status?_jsx(View,{style:i.thumb,testID:`support-attachment-${e.id}`,children:_jsx(Spinner,{size:"sm",accessibilityLabel:`Loading ${e.fileName}`})}):"error"===r.status?_jsx(View,{style:i.thumb,accessible:!0,accessibilityLabel:`${e.fileName} could not be loaded`,testID:`support-attachment-${e.id}`,children:_jsx(Text,{variant:"caption",color:"tertiary",align:"center",children:"Could not load"})}):_jsx(Pressable,{onPress:()=>t(r.uri,e.fileName),accessibilityRole:"imagebutton",accessibilityLabel:`Open ${e.fileName}`,testID:`support-attachment-${e.id}`,children:_jsx(Image,{source:{uri:r.uri},style:i.thumb,resizeMode:"cover"})})},FileRow=({attachment:e})=>{const t=useTheme(),s=useMemo(()=>buildStyles(t),[t]),i=useAttachmentOpener(),[a,r]=useState(!1),o=`${e.fileName}, ${formatBytes(e.sizeBytes)}`;if(!i)return _jsx(Text,{variant:"caption",color:"secondary",testID:`support-file-${e.id}`,children:o});return _jsxs(Pressable,{onPress:()=>{(async()=>{r(!0);try{await i(e)}catch{toast.error(`Could not open ${e.fileName}. Try again.`)}finally{r(!1)}})()},disabled:a,accessibilityRole:"button",accessibilityLabel:`Open ${e.fileName}`,accessibilityState:{busy:a,disabled:a},style:s.fileRow,testID:`support-file-${e.id}`,children:[_jsx(AppIcon,{name:"document",size:16,color:t.colors.text.link}),_jsx(Text,{variant:"caption",color:"link",children:o}),a&&_jsx(Spinner,{size:"sm",accessibilityLabel:`Opening ${e.fileName}`})]})};export const AttachmentList=({attachments:e,onOpenImage:t})=>{const s=useTheme(),i=useMemo(()=>buildStyles(s),[s]);if(0===e.length)return null;const a=e.filter(e=>isImage(e.contentType)),r=e.filter(e=>!isImage(e.contentType));return _jsxs(View,{style:i.list,children:[a.length>0&&_jsx(View,{style:i.thumbRow,children:a.map(e=>_jsx(AttachmentThumb,{attachment:e,onOpen:t},e.id))}),r.map(e=>_jsx(FileRow,{attachment:e},e.id))]})};export const ImageViewer=({image:e,onClose:t})=>{const s=useTheme(),i=useMemo(()=>buildStyles(s),[s]);return _jsx(Modal,{visible:null!==e,onRequestClose:t,animationType:"fade",transparent:!1,children:_jsxs(GestureHandlerRootView,{style:i.viewer,testID:"support-image-viewer",children:[e&&_jsx(ZoomableImage,{accessibilityLabel:e.name,style:i.viewerImageWrap,children:_jsx(Image,{source:{uri:e.uri},style:i.viewerImage,resizeMode:"contain"})}),_jsx(View,{style:i.viewerClose,children:_jsx(IconButton,{icon:({color:e,size:t})=>_jsx(AppIcon,{name:"close-circle",color:e,size:t}),accessibilityLabel:"Close image",onPress:t,variant:"glass"})}),_jsx(ToastHost,{})]})})};const THUMB=84,buildStyles=e=>StyleSheet.create({list:{gap:e.spacing.xs,marginTop:e.spacing.sm},fileRow:{flexDirection:"row",alignItems:"center",gap:e.spacing.xxs,alignSelf:"flex-start"},thumbRow:{flexDirection:"row",flexWrap:"wrap",gap:e.spacing.xs},thumb:{width:84,height:84,borderRadius:e.radius.sm,backgroundColor:e.colors.background.secondary,alignItems:"center",justifyContent:"center",overflow:"hidden"},viewer:{flex:1,backgroundColor:e.colors.background.inverse},viewerImageWrap:{flex:1},viewerImage:{flex:1,width:"100%"},viewerClose:{position:"absolute",top:e.spacing["2xl"],right:e.spacing.md}});
@@ -1 +1 @@
1
- import{Button as e,Input as t,Select as r,Spinner as o,Text as a,useForm as n,useTheme as i}from"@webority/mobile-ui";import{useMemo as s,useState as l}from"react";import{ScrollView as c,StyleSheet as g,View as d}from"react-native";import{toSupportApiError as u}from"../client.js";import{useCreateSupportTicket as p,useSupportConfig as m}from"../SupportProvider.js";import{validateBody as h,validateCategory as y,validateFiles as b,validateSubject as x}from"../validation.js";import{PhotoTray as f}from"./PhotoTray.js";import{jsx as v,jsxs as j,Fragment as S}from"react/jsx-runtime";const TicketForm=({config:o,styles:i,onCreated:s,onCancel:g,initialDraft:m})=>{const S=p(),[C,w]=l([]),[P,A]=l(m?.screenshots??[]),[D,T]=l(null),[B,I]=l(null),L=n({initialValues:{category:m?.category??"",subject:m?.subject??"",body:m?.body??""},validate:{category:e=>y(e,o),subject:e=>x(e,o),body:e=>h(e,o)},onSubmit:async e=>{const t=b([...P,...C],o);if(T(t??null),!t){I(null);try{const t=await S({category:e.category,subject:e.subject.trim(),body:e.body.trim(),pageUrl:m?.pageUrl,userAgent:m?.userAgent,appVersion:m?.appVersion,files:C,screenshots:P});s(t)}catch(e){I(u(e).message)}}}}),k=L.getFieldProps("category"),M=L.getFieldProps("subject"),F=L.getFieldProps("body"),V=P.length+C.length;return j(c,{contentContainerStyle:i.content,keyboardShouldPersistTaps:"handled",children:[v(a,{variant:"bodySmall",color:"secondary",children:"Tell us what you need. We reply by email and in this ticket."}),v(r,{label:"Category",placeholder:"Choose a category",required:!0,options:o.categories.map(e=>({label:e,value:e})),value:k.value||void 0,onChange:e=>k.setValue(String(e),!0),error:k.error,testID:"support-new-category"}),v(t,{label:"Subject",placeholder:"A short summary",required:!0,maxLength:o.subjectMaxLength,value:M.value,onChangeText:M.onChangeText,onBlur:M.onBlur,error:M.error,testID:"support-new-subject"}),v(t,{label:"Message",placeholder:"What happened, and what did you expect?",required:!0,multiline:!0,maxLength:o.bodyMaxLength,value:F.value,onChangeText:F.onChangeText,onBlur:F.onBlur,error:F.error,testID:"support-new-body"}),P.length>0&&v(f,{label:"Screenshot",photos:P,onChange:A,remaining:0,allowAdd:!1,maxBytes:o.maxAttachmentBytes,disabled:L.isSubmitting}),v(f,{label:"Photos",photos:C,onChange:w,remaining:o.maxAttachmentsPerMessage-V,maxBytes:o.maxAttachmentBytes,disabled:L.isSubmitting}),v(a,{variant:"caption",color:"tertiary",children:`Attach up to ${o.maxAttachmentsPerMessage} files.`}),(D||B)&&v(a,{variant:"bodySmall",color:"error",accessibilityLiveRegion:"polite",children:D??B}),j(d,{style:i.actions,children:[g&&v(e,{title:"Cancel",variant:"ghost",tone:"neutral",onPress:g,disabled:L.isSubmitting}),v(e,{title:"Send ticket",loading:L.isSubmitting,disabled:L.isSubmitting,onPress:L.submit,testID:"support-new-submit"})]})]})};export const NewSupportTicket=t=>{const r=i(),n=s(()=>buildStyles(r),[r]),{config:l,loading:c,error:g,reload:u}=m(),p=t.testID??"support-new-ticket";return v(d,l?{style:n.root,testID:p,children:v(TicketForm,{...t,config:l,styles:n})}:{style:n.center,testID:p,children:c?v(o,{accessibilityLabel:"Loading"}):j(S,{children:[v(a,{variant:"body",color:"error",align:"center",children:g?.message??"Support is not available right now."}),v(e,{title:"Try again",variant:"ghost",onPress:()=>{u()}})]})})};const buildStyles=e=>g.create({root:{flex:1},content:{padding:e.spacing.md,gap:e.spacing.md},center:{flex:1,alignItems:"center",justifyContent:"center",padding:e.spacing.xl,gap:e.spacing.sm},actions:{flexDirection:"row",justifyContent:"flex-end",gap:e.spacing.sm}});
1
+ "use strict";import{Button,Input,Select,Spinner,Text,useForm,useTheme}from"@webority/mobile-ui";import{useMemo,useState}from"react";import{ScrollView,StyleSheet,View}from"react-native";import{toSupportApiError}from"../client.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})=>{const s=useCreateSupportTicket(),[n,i]=useState([]),[l,c]=useState(a?.screenshots??[]),[u,d]=useState(null),[p,g]=useState(null),m=useForm({initialValues:{category:a?.category??"",subject:a?.subject??"",body:a?.body??""},validate:{category:t=>validateCategory(t,e),subject:t=>validateSubject(t,e),body:t=>validateBody(t,e)},onSubmit:async t=>{const r=validateFiles([...l,...n],e);if(d(r??null),!r){g(null);try{const e=await s({category:t.category,subject:t.subject.trim(),body:t.body.trim(),pageUrl:a?.pageUrl,userAgent:a?.userAgent,appVersion:a?.appVersion,files:n,screenshots:l});o(e)}catch(e){g(toSupportApiError(e).message)}}}}),h=m.getFieldProps("category"),y=m.getFieldProps("subject"),b=m.getFieldProps("body"),x=l.length+n.length;return _jsxs(ScrollView,{contentContainerStyle:t.content,keyboardShouldPersistTaps:"handled",children:[_jsx(Text,{variant:"bodySmall",color:"secondary",children:"Tell us what you need. We reply by email and in this ticket."}),_jsx(Select,{label:"Category",placeholder:"Choose a category",required:!0,options:e.categories.map(e=>({label:e,value:e})),value:h.value||void 0,onChange:e=>h.setValue(String(e),!0),error:h.error,testID:"support-new-category"}),_jsx(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"}),_jsx(Input,{label:"Message",placeholder:"What happened, and what did you expect?",required:!0,multiline:!0,maxLength:e.bodyMaxLength,value:b.value,onChangeText:b.onChangeText,onBlur:b.onBlur,error:b.error,testID:"support-new-body"}),l.length>0&&_jsx(PhotoTray,{label:"Screenshot",photos:l,onChange:c,remaining:0,allowAdd:!1,maxBytes:e.maxAttachmentBytes,disabled:m.isSubmitting}),_jsx(PhotoTray,{label:"Photos",photos:n,onChange:i,remaining:e.maxAttachmentsPerMessage-x,maxBytes:e.maxAttachmentBytes,disabled:m.isSubmitting}),_jsx(Text,{variant:"caption",color:"tertiary",children:`Attach up to ${e.maxAttachmentsPerMessage} files.`}),(u||p)&&_jsx(Text,{variant:"bodySmall",color:"error",accessibilityLiveRegion:"polite",children:u??p}),_jsxs(View,{style:t.actions,children:[r&&_jsx(Button,{title:"Cancel",variant:"ghost",tone:"neutral",onPress:r,disabled:m.isSubmitting}),_jsx(Button,{title:"Send ticket",loading:m.isSubmitting,disabled:m.isSubmitting,onPress:m.submit,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(),i=e.testID??"support-new-ticket";return _jsx(View,r?{style:o.root,testID:i,children:_jsx(TicketForm,{...e,config:r,styles:o})}:{style:o.center,testID:i,children:a?_jsx(Spinner,{accessibilityLabel:"Loading"}):_jsxs(_Fragment,{children:[_jsx(Text,{variant:"body",color:"error",align:"center",children:s?.message??"Support is not available right now."}),_jsx(Button,{title:"Try again",variant:"ghost",onPress:()=>{n()}})]})})};const buildStyles=e=>StyleSheet.create({root:{flex:1},content:{padding:e.spacing.md,gap:e.spacing.md},center:{flex:1,alignItems:"center",justifyContent:"center",padding:e.spacing.xl,gap:e.spacing.sm},actions:{flexDirection:"row",justifyContent:"flex-end",gap:e.spacing.sm}});
@@ -1 +1 @@
1
- import{AppIcon as e,Button as o,IconButton as r,Text as a,useTheme as i}from"@webority/mobile-ui";import{useMemo as t,useState as l}from"react";import{Image as s,StyleSheet as n,View as c}from"react-native";import{pickPhotos as m}from"../photos.js";import{jsx as d,jsxs as h}from"react/jsx-runtime";export const PhotoTray=({photos:n,onChange:u,remaining:p,maxBytes:b,disabled:y,label:g,allowAdd:x=!0})=>{const f=i(),w=t(()=>buildStyles(f),[f]),[v,P]=l(null),[z,L]=l(null),add=async e=>{L(null),P(e);try{const o=await m(e,{maxCount:p,maxBytes:b});o.length>0&&u([...n,...o])}catch(e){L(e instanceof Error&&"PhotoPermissionError"===e.name?e.message:"That photo could not be added. Try another one.")}finally{P(null)}},T=p<=0;return h(c,{style:w.root,children:[g?d(a,{variant:"label",color:"secondary",children:g}):null,n.length>0&&d(c,{style:w.row,children:n.map((o,a)=>h(c,{style:w.item,children:[d(s,{source:{uri:o.uri},style:w.thumb,accessibilityLabel:o.name}),d(c,{style:w.remove,children:d(r,{size:"sm",variant:"glass",icon:({color:o,size:r})=>d(e,{name:"trash",color:o,size:r}),accessibilityLabel:`Remove ${o.name}`,disabled:y,onPress:()=>u(n.filter((e,o)=>o!==a))})})]},`${o.uri}-${a}`))}),x&&h(c,{style:w.actions,children:[d(o,{size:"sm",variant:"outline",tone:"neutral",leftIcon:"gallery",title:"Add photo",accessibilityLabel:"Add a photo from your library",loading:"library"===v,disabled:y||T||null!==v,onPress:()=>{add("library")}}),d(o,{size:"sm",variant:"outline",tone:"neutral",leftIcon:"camera",title:"Take photo",accessibilityLabel:"Take a photo with your camera",loading:"camera"===v,disabled:y||T||null!==v,onPress:()=>{add("camera")}})]}),z?d(a,{variant:"caption",color:"error",accessibilityLiveRegion:"polite",children:z}):null]})};const buildStyles=e=>n.create({root:{gap:e.spacing.xs},row:{flexDirection:"row",flexWrap:"wrap",gap:e.spacing.xs},item:{width:72,height:72},thumb:{width:72,height:72,borderRadius:e.radius.sm,backgroundColor:e.colors.background.secondary},remove:{position:"absolute",top:e.spacing.xxs,right:e.spacing.xxs},actions:{flexDirection:"row",gap:e.spacing.sm}});
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:t,remaining:o,maxBytes:s,disabled:i,label:a,allowAdd:r=!0})=>{const l=useTheme(),n=useMemo(()=>buildStyles(l),[l]),[c,m]=useState(null),[u,h]=useState(null),add=async i=>{h(null),m(i);try{const a=await pickPhotos(i,{maxCount:o,maxBytes:s});a.length>0&&t([...e,...a])}catch(e){h(e instanceof Error&&"PhotoPermissionError"===e.name?e.message:"That photo could not be added. Try another one.")}finally{m(null)}},d=o<=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((o,s)=>_jsxs(View,{style:n.item,children:[_jsx(Image,{source:{uri:o.uri},style:n.thumb,accessibilityLabel:o.name}),_jsx(View,{style:n.remove,children:_jsx(IconButton,{size:"sm",variant:"glass",icon:({color:e,size:t})=>_jsx(AppIcon,{name:"trash",color:e,size:t}),accessibilityLabel:`Remove ${o.name}`,disabled:i,onPress:()=>t(e.filter((e,t)=>t!==s))})})]},`${o.uri}-${s}`))}),r&&_jsxs(View,{style:n.actions,children:[_jsx(Button,{size:"sm",variant:"outline",tone:"neutral",leftIcon:"gallery",title:"Add photo",accessibilityLabel:"Add a photo from your library",loading:"library"===c,disabled:i||d||null!==c,onPress:()=>{add("library")}}),_jsx(Button,{size:"sm",variant:"outline",tone:"neutral",leftIcon:"camera",title:"Take photo",accessibilityLabel:"Take a photo with your camera",loading:"camera"===c,disabled:i||d||null!==c,onPress:()=>{add("camera")}})]}),u?_jsx(Text,{variant:"caption",color:"error",accessibilityLiveRegion:"polite",children:u}):null]})};const THUMB=72,buildStyles=e=>StyleSheet.create({root:{gap:e.spacing.xs},row:{flexDirection:"row",flexWrap:"wrap",gap:e.spacing.xs},item:{width:72,height:72},thumb:{width:72,height:72,borderRadius:e.radius.sm,backgroundColor:e.colors.background.secondary},remove:{position:"absolute",top:e.spacing.xxs,right:e.spacing.xxs},actions:{flexDirection:"row",gap:e.spacing.sm}});
@@ -1 +1 @@
1
- import{Button as t,Card as e,Spinner as a,Text as o,toast as r,useTheme as i}from"@webority/mobile-ui";import{useMemo as l}from"react";import{Linking as s,StyleSheet as n,View as c}from"react-native";import{copyText as p}from"../clipboard.js";import{useSupportConfig as d}from"../SupportProvider.js";import{jsx as m,jsxs as u}from"react/jsx-runtime";export const SupportContact=({testID:n="support-contact"})=>{const y=i(),b=l(()=>buildStyles(y),[y]),{config:h,loading:v,error:f}=d();if(!h)return m(e,{variant:"outlined",testID:n,children:v?m(a,{size:"sm",accessibilityLabel:"Loading contact details"}):m(o,{variant:"bodySmall",color:"error",children:f?.message??"Contact details are not available right now."})});const g=h.supportEmail,I=h.supportPhone?.trim(),w=h.supportHours?.trim(),open=(t,e)=>{s.openURL(t).catch(()=>r.error(e))};return u(e,{variant:"outlined",bodyStyle:b.body,testID:n,children:[m(o,{variant:"title",accessibilityRole:"header",children:`Contact ${h.supportName||h.productName}`}),u(c,{style:b.row,children:[m(o,{variant:"label",color:"secondary",children:"Email"}),m(o,{variant:"body",selectable:!0,testID:"support-contact-email",children:g||"-"})]}),g?u(c,{style:b.actions,children:[m(t,{title:"Copy",size:"sm",variant:"outline",tone:"neutral",leftIcon:"copy",accessibilityLabel:`Copy ${g}`,onPress:()=>{(async()=>{try{await p(g),r.success("Email address copied.")}catch{r.error("Could not copy the email address.")}})()},testID:"support-contact-copy"}),m(t,{title:"Send email",size:"sm",variant:"outline",leftIcon:"mail",accessibilityLabel:`Send an email to ${g}`,onPress:()=>open(`mailto:${g}`,"No email app is set up on this device."),testID:"support-contact-mail"})]}):null,I?u(c,{style:b.row,children:[m(o,{variant:"label",color:"secondary",children:"Phone"}),m(t,{title:I,size:"sm",variant:"link",leftIcon:"call",accessibilityLabel:`Call ${I}`,onPress:()=>open(`tel:${I.replace(/[^\d+]/g,"")}`,"This device cannot make calls."),testID:"support-contact-phone"})]}):null,w?u(c,{style:b.row,children:[m(o,{variant:"label",color:"secondary",children:"Hours"}),m(o,{variant:"bodySmall",children:w})]}):null]})};const buildStyles=t=>n.create({body:{gap:t.spacing.sm},row:{gap:t.spacing.xxs,alignItems:"flex-start"},actions:{flexDirection:"row",gap:t.spacing.sm}});
1
+ "use strict";import{Button,Card,Spinner,Text,toast,useTheme}from"@webority/mobile-ui";import{useMemo}from"react";import{Linking,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";export const SupportContact=({testID:t="support-contact"})=>{const e=useTheme(),s=useMemo(()=>buildStyles(e),[e]),{config:o,loading:i,error:a}=useSupportConfig();if(!o)return _jsx(Card,{variant:"outlined",testID:t,children:i?_jsx(Spinner,{size:"sm",accessibilityLabel:"Loading contact details"}):_jsx(Text,{variant:"bodySmall",color:"error",children:a?.message??"Contact details are not available right now."})});const r=o.supportEmail,l=o.supportPhone?.trim(),n=o.supportHours?.trim(),open=(t,e)=>{Linking.openURL(t).catch(()=>toast.error(e))};return _jsxs(Card,{variant:"outlined",bodyStyle:s.body,testID:t,children:[_jsx(Text,{variant:"title",accessibilityRole:"header",children:`Contact ${o.supportName||o.productName}`}),_jsxs(View,{style:s.row,children:[_jsx(Text,{variant:"label",color:"secondary",children:"Email"}),_jsx(Text,{variant:"body",selectable:!0,testID:"support-contact-email",children:r||"-"})]}),r?_jsxs(View,{style:s.actions,children:[_jsx(Button,{title:"Copy",size:"sm",variant:"outline",tone:"neutral",leftIcon:"copy",accessibilityLabel:`Copy ${r}`,onPress:()=>{(async()=>{try{await copyText(r),toast.success("Email address copied.")}catch{toast.error("Could not copy the email address.")}})()},testID:"support-contact-copy"}),_jsx(Button,{title:"Send email",size:"sm",variant:"outline",leftIcon:"mail",accessibilityLabel:`Send an email to ${r}`,onPress:()=>open(`mailto:${r}`,"No email app is set up on this device."),testID:"support-contact-mail"})]}):null,l?_jsxs(View,{style:s.row,children:[_jsx(Text,{variant:"label",color:"secondary",children:"Phone"}),_jsx(Button,{title: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:s.row,children:[_jsx(Text,{variant:"label",color:"secondary",children:"Hours"}),_jsx(Text,{variant:"bodySmall",children:n})]}):null]})};const buildStyles=t=>StyleSheet.create({body:{gap:t.spacing.sm},row:{gap:t.spacing.xxs,alignItems:"flex-start"},actions:{flexDirection:"row",gap:t.spacing.sm}});
@@ -1 +1 @@
1
- import{Button as e,Card as t,Input as i,Spinner as a,StatusBadge as n,Text as s,useTheme as r}from"@webority/mobile-ui";import{useMemo as o,useState as l}from"react";import{ScrollView as c,StyleSheet as d,View as p}from"react-native";import{formatWhen as m}from"../format.js";import{useSupportConfig as g,useSupportTicket as h,useSupportTimeZone as u}from"../SupportProvider.js";import{validateBody as y,validateFiles as b}from"../validation.js";import{AttachmentList as f,ImageViewer as x}from"./Attachments.js";import{PhotoTray as v}from"./PhotoTray.js";import{STATUS_TONES as I}from"./SupportTicketList.js";import{jsx as w,jsxs as S,Fragment as D}from"react/jsx-runtime";const MessageBubble=({message:e,styles:t,timeZone:i,onOpenImage:a})=>{if("System"===e.authorKind)return w(p,{style:t.system,testID:`support-message-${e.id}`,children:w(s,{variant:"caption",color:"tertiary",align:"center",children:`${e.body}, ${m(e.createdDateTimeUtc,i)}`})});const n="Customer"===e.authorKind;return w(p,{style:[t.bubbleRow,n?t.mineRow:t.theirsRow],testID:`support-message-${e.id}`,children:S(p,{style:[t.bubble,n?t.mine:t.theirs],children:[w(s,{variant:"label",color:n?"primary":"secondary",children:e.authorName??"-"}),w(s,{variant:"body",selectable:!0,children:e.body}),w(f,{attachments:e.attachments,onOpenImage:a}),w(s,{variant:"caption",color:"tertiary",children:m(e.createdDateTimeUtc,i)})]})})},R={Satisfied:"You said we handled this well.",Unsatisfied:"You said we did not handle this well."};export const SupportTicketDetail=({ticketId:d,testID:f="support-ticket-detail"})=>{const k=r(),j=o(()=>buildStyles(k),[k]),{config:C}=g(),L=u(),{ticket:T,loading:P,error:$,actionError:U,busy:z,reload:A,reply:M,reopen:O,rate:W}=h(d),[Y,B]=l(null),[H,K]=l(""),[N,Z]=l([]),[E,F]=l(null),[V,q]=l(null),[G,J]=l("");if(!T)return w(p,{style:j.center,testID:f,children:P?w(a,{accessibilityLabel:"Loading the ticket"}):S(D,{children:[w(s,{variant:"body",color:"error",align:"center",children:$?.message??"That ticket could not be found."}),w(e,{title:"Try again",variant:"ghost",onPress:()=>{A()}})]})});const Q=T.messages.filter(e=>!e.isInternal);return S(p,{style:j.root,testID:f,children:[S(c,{contentContainerStyle:j.content,keyboardShouldPersistTaps:"handled",children:[S(p,{style:j.head,children:[w(s,{variant:"h3",accessibilityRole:"header",children:T.subject}),w(s,{variant:"bodySmall",color:"secondary",children:`${T.number}, ${T.category}`}),S(p,{style:j.headRow,children:[w(n,{label:T.statusLabel,tone:I[T.status]??"neutral",accessibilityLabel:`Status: ${T.statusLabel}`}),w(s,{variant:"caption",color:"tertiary",children:`Opened ${m(T.createdDateTimeUtc,L)}`})]})]}),T.canReopen&&S(t,{variant:"outlined",bodyStyle:j.section,children:[w(s,{variant:"bodySmall",children:`This ticket is ${T.statusLabel}. Reopen it if you still need help.`}),w(e,{title:"Reopen ticket",variant:"outline",size:"sm",loading:z,disabled:z,onPress:()=>{O()},testID:"support-reopen"})]}),T.canRate&&S(t,{variant:"outlined",bodyStyle:j.section,testID:"support-rating",children:[w(s,{variant:"label",children:"How did we do?"}),T.satisfaction&&w(s,{variant:"bodySmall",color:"secondary",children:R[T.satisfaction]}),S(p,{style:j.ratingRow,children:[w(e,{title:"Satisfied",size:"sm",leftIcon:"like",variant:"Satisfied"===V?"solid":"outline",tone:"success",accessibilityState:{selected:"Satisfied"===V},onPress:()=>q("Satisfied"),testID:"support-rate-satisfied"}),w(e,{title:"Not satisfied",size:"sm",leftIcon:"dislike",variant:"Unsatisfied"===V?"solid":"outline",tone:"error",accessibilityState:{selected:"Unsatisfied"===V},onPress:()=>q("Unsatisfied"),testID:"support-rate-unsatisfied"})]}),V&&S(D,{children:[w(i,{label:"Anything to add? (optional)",value:G,onChangeText:J,multiline:!0,maxLength:C?.satisfactionCommentMaxLength,testID:"support-rating-comment"}),w(e,{title:"Send rating",size:"sm",loading:z,disabled:z,onPress:()=>{(async()=>{V&&await W({satisfaction:V,comment:G})&&(q(null),J(""))})()},testID:"support-rating-send"})]})]}),w(p,{style:j.thread,accessibilityLabel:"Conversation",children:Q.map(e=>w(MessageBubble,{message:e,styles:j,timeZone:L,onOpenImage:(e,t)=>B({uri:e,name:t})},e.id))}),S(p,{style:j.section,children:[w(i,{label:"Your reply",placeholder:"Write a reply to the support team",value:H,onChangeText:K,multiline:!0,maxLength:C?.bodyMaxLength,error:E??void 0,testID:"support-reply-input"}),C&&w(v,{photos:N,onChange:Z,remaining:C.maxAttachmentsPerMessage-N.length,maxBytes:C.maxAttachmentBytes,disabled:z}),T.canReopen&&w(s,{variant:"caption",color:"secondary",children:"Replying reopens this ticket."}),U&&w(s,{variant:"bodySmall",color:"error",accessibilityLiveRegion:"polite",children:U.message}),w(e,{title:"Send reply",leftIcon:"send",loading:z,disabled:z,onPress:()=>{(async()=>{const e=H.trim(),t=C?y(e,C)??b(N,C):e?void 0:"Write a message.";t?F(t):(F(null),await M({body:e,files:N})&&(K(""),Z([])))})()},testID:"support-reply-send"})]})]}),w(x,{image:Y,onClose:()=>B(null)})]})};const buildStyles=e=>d.create({root:{flex:1},content:{padding:e.spacing.md,gap:e.spacing.md},center:{flex:1,alignItems:"center",justifyContent:"center",padding:e.spacing.xl,gap:e.spacing.sm},head:{gap:e.spacing.xxs},headRow:{flexDirection:"row",alignItems:"center",gap:e.spacing.sm,marginTop:e.spacing.xs},section:{gap:e.spacing.sm},ratingRow:{flexDirection:"row",gap:e.spacing.sm},thread:{gap:e.spacing.sm},system:{paddingHorizontal:e.spacing.lg,paddingVertical:e.spacing.xxs},bubbleRow:{flexDirection:"row"},mineRow:{justifyContent:"flex-end"},theirsRow:{justifyContent:"flex-start"},bubble:{maxWidth:"85%",borderRadius:e.radius.lg,padding:e.spacing.sm,gap:e.spacing.xxs},mine:{backgroundColor:e.colors.primaryMuted},theirs:{backgroundColor:e.colors.background.secondary}});
1
+ "use strict";import{Button,Card,Input,Spinner,StatusBadge,Text,useTheme}from"@webority/mobile-ui";import{useMemo,useState}from"react";import{ScrollView,StyleSheet,View}from"react-native";import{formatWhen}from"../format.js";import{useSupportConfig,useSupportTicket,useSupportTimeZone}from"../SupportProvider.js";import{validateBody,validateFiles}from"../validation.js";import{AttachmentList,ImageViewer}from"./Attachments.js";import{PhotoTray}from"./PhotoTray.js";import{STATUS_TONES}from"./SupportTicketList.js";import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";const MessageBubble=({message:e,styles:t,timeZone:s,onOpenImage:i})=>{if("System"===e.authorKind)return _jsx(View,{style:t.system,testID:`support-message-${e.id}`,children:_jsx(Text,{variant:"caption",color:"tertiary",align:"center",children:`${e.body}, ${formatWhen(e.createdDateTimeUtc,s)}`})});const a="Customer"===e.authorKind;return _jsx(View,{style:[t.bubbleRow,a?t.mineRow:t.theirsRow],testID:`support-message-${e.id}`,children:_jsxs(View,{style:[t.bubble,a?t.mine:t.theirs],children:[_jsx(Text,{variant:"label",color:a?"primary":"secondary",children:e.authorName??"-"}),_jsx(Text,{variant:"body",selectable:!0,children:e.body}),_jsx(AttachmentList,{attachments:e.attachments,onOpenImage:i}),_jsx(Text,{variant:"caption",color:"tertiary",children:formatWhen(e.createdDateTimeUtc,s)})]})})},SATISFACTION_TEXT={Satisfied:"You said we handled this well.",Unsatisfied:"You said we did not handle this well."};export const SupportTicketDetail=({ticketId:e,testID:t="support-ticket-detail"})=>{const s=useTheme(),i=useMemo(()=>buildStyles(s),[s]),{config:a}=useSupportConfig(),n=useSupportTimeZone(),{ticket:o,loading:r,error:l,actionError:c,busy:d,reload:p,reply:m,reopen:u,rate:g}=useSupportTicket(e),[x,h]=useState(null),[y,b]=useState(""),[j,S]=useState([]),[f,_]=useState(null),[T,w]=useState(null),[v,I]=useState("");if(!o)return _jsx(View,{style:i.center,testID:t,children:r?_jsx(Spinner,{accessibilityLabel:"Loading the ticket"}):_jsxs(_Fragment,{children:[_jsx(Text,{variant:"body",color:"error",align:"center",children:l?.message??"That ticket could not be found."}),_jsx(Button,{title:"Try again",variant:"ghost",onPress:()=>{p()}})]})});const C=o.messages.filter(e=>!e.isInternal);return _jsxs(View,{style:i.root,testID:t,children:[_jsxs(ScrollView,{contentContainerStyle:i.content,keyboardShouldPersistTaps:"handled",children:[_jsxs(View,{style:i.head,children:[_jsx(Text,{variant:"h3",accessibilityRole:"header",children:o.subject}),_jsx(Text,{variant:"bodySmall",color:"secondary",children:`${o.number}, ${o.category}`}),_jsxs(View,{style:i.headRow,children:[_jsx(StatusBadge,{label:o.statusLabel,tone:STATUS_TONES[o.status]??"neutral",accessibilityLabel:`Status: ${o.statusLabel}`}),_jsx(Text,{variant:"caption",color:"tertiary",children:`Opened ${formatWhen(o.createdDateTimeUtc,n)}`})]})]}),o.canReopen&&_jsxs(Card,{variant:"outlined",bodyStyle:i.section,children:[_jsx(Text,{variant:"bodySmall",children:`This ticket is ${o.statusLabel}. Reopen it if you still need help.`}),_jsx(Button,{title:"Reopen ticket",variant:"outline",size:"sm",loading:d,disabled:d,onPress:()=>{u()},testID:"support-reopen"})]}),o.canRate&&_jsxs(Card,{variant:"outlined",bodyStyle:i.section,testID:"support-rating",children:[_jsx(Text,{variant:"label",children:"How did we do?"}),o.satisfaction&&_jsx(Text,{variant:"bodySmall",color:"secondary",children:SATISFACTION_TEXT[o.satisfaction]}),_jsxs(View,{style:i.ratingRow,children:[_jsx(Button,{title:"Satisfied",size:"sm",leftIcon:"like",variant:"Satisfied"===T?"solid":"outline",tone:"success",accessibilityState:{selected:"Satisfied"===T},onPress:()=>w("Satisfied"),testID:"support-rate-satisfied"}),_jsx(Button,{title:"Not satisfied",size:"sm",leftIcon:"dislike",variant:"Unsatisfied"===T?"solid":"outline",tone:"error",accessibilityState:{selected:"Unsatisfied"===T},onPress:()=>w("Unsatisfied"),testID:"support-rate-unsatisfied"})]}),T&&_jsxs(_Fragment,{children:[_jsx(Input,{label:"Anything to add? (optional)",value:v,onChangeText:I,multiline:!0,maxLength:a?.satisfactionCommentMaxLength,testID:"support-rating-comment"}),_jsx(Button,{title:"Send rating",size:"sm",loading:d,disabled:d,onPress:()=>{(async()=>{T&&await g({satisfaction:T,comment:v})&&(w(null),I(""))})()},testID:"support-rating-send"})]})]}),_jsx(View,{style:i.thread,accessibilityLabel:"Conversation",children:C.map(e=>_jsx(MessageBubble,{message:e,styles:i,timeZone:n,onOpenImage:(e,t)=>h({uri:e,name:t})},e.id))}),_jsxs(View,{style:i.section,children:[_jsx(Input,{label:"Your reply",placeholder:"Write a reply to the support team",value:y,onChangeText:b,multiline:!0,maxLength:a?.bodyMaxLength,error:f??void 0,testID:"support-reply-input"}),a&&_jsx(PhotoTray,{photos:j,onChange:S,remaining:a.maxAttachmentsPerMessage-j.length,maxBytes:a.maxAttachmentBytes,disabled:d}),o.canReopen&&_jsx(Text,{variant:"caption",color:"secondary",children:"Replying reopens this ticket."}),c&&_jsx(Text,{variant:"bodySmall",color:"error",accessibilityLiveRegion:"polite",children:c.message}),_jsx(Button,{title:"Send reply",leftIcon:"send",loading:d,disabled:d,onPress:()=>{(async()=>{const e=y.trim(),t=a?validateBody(e,a)??validateFiles(j,a):e?void 0:"Write a message.";t?_(t):(_(null),await m({body:e,files:j})&&(b(""),S([])))})()},testID:"support-reply-send"})]})]}),_jsx(ImageViewer,{image:x,onClose:()=>h(null)})]})};const buildStyles=e=>StyleSheet.create({root:{flex:1},content:{padding:e.spacing.md,gap:e.spacing.md},center:{flex:1,alignItems:"center",justifyContent:"center",padding:e.spacing.xl,gap:e.spacing.sm},head:{gap:e.spacing.xxs},headRow:{flexDirection:"row",alignItems:"center",gap:e.spacing.sm,marginTop:e.spacing.xs},section:{gap:e.spacing.sm},ratingRow:{flexDirection:"row",gap:e.spacing.sm},thread:{gap:e.spacing.sm},system:{paddingHorizontal:e.spacing.lg,paddingVertical:e.spacing.xxs},bubbleRow:{flexDirection:"row"},mineRow:{justifyContent:"flex-end"},theirsRow:{justifyContent:"flex-start"},bubble:{maxWidth:"85%",borderRadius:e.radius.lg,padding:e.spacing.sm,gap:e.spacing.xxs},mine:{backgroundColor:e.colors.primaryMuted},theirs:{backgroundColor:e.colors.background.secondary}});
@@ -1 +1 @@
1
- import{Button as e,Card as t,Chip as r,EmptyState as i,SearchBar as o,Spinner as s,StatusBadge as a,Text as n,useTheme as l}from"@webority/mobile-ui";import{useMemo as c}from"react";import{FlatList as d,RefreshControl as p,ScrollView as u,StyleSheet as m,View as g}from"react-native";import{formatWhen as b}from"../format.js";import{useSupportTickets as h,useSupportTimeZone as y}from"../SupportProvider.js";import{jsx as f,jsxs as v,Fragment as S}from"react/jsx-runtime";export const STATUS_TONES={Open:"warning",InProgress:"info",Resolved:"success",Closed:"neutral"};const k=[{value:"Open",label:"Open",count:"open"},{value:"InProgress",label:"In progress",count:"inProgress"},{value:"Resolved",label:"Resolved",count:"resolved"},{value:"Closed",label:"Closed",count:"closed"}],x="all";export const statusFilterOptions=(e,t)=>[{value:x,label:"All",count:e.total},...k.filter(r=>e[r.count]>0||r.value===t).map(t=>({value:t.value,label:t.label,count:e[t.count]}))];export const SupportTicketList=({onOpenTicket:m,onNewTicket:k,header:L,pageSize:T,testID:I="support-ticket-list"})=>{const w=l(),C=c(()=>buildStyles(w),[w]),{tickets:j,counts:P,status:R,search:$,setStatus:D,setSearch:O,loading:z,refreshing:E,loadingMore:N,error:F,loadMoreError:M,refresh:U,loadMore:A}=h({pageSize:T}),B=y(),H=(P?.total??0)>0,W=null!==R||""!==$.trim(),_=v(g,{style:C.header,children:[L,k&&f(e,{title:"New ticket",leftIcon:"add",onPress:k,fullWidth:!0}),F&&v(g,{style:C.error,accessibilityLiveRegion:"polite",children:[f(n,{variant:"bodySmall",color:"error",children:F.message}),f(e,{title:"Try again",size:"sm",variant:"ghost",onPress:()=>{U()}})]}),P&&H&&v(S,{children:[f(o,{value:$,onChangeText:O,debounceMs:400,placeholder:"Search by subject or ticket number",accessibilityLabel:"Search tickets",testID:"support-ticket-search"}),f(u,{horizontal:!0,showsHorizontalScrollIndicator:!1,contentContainerStyle:C.filters,accessibilityRole:"tablist",accessibilityLabel:"Filter by status",children:statusFilterOptions(P,R).map(e=>f(r,{label:`${e.label} (${e.count})`,selected:(R??x)===e.value,onPress:()=>D(e.value===x?null:e.value),accessibilityLabel:`${e.label}, ${e.count} tickets`,testID:`support-filter-${e.value}`},e.value))})]})]});if(z&&null===P&&!F)return f(g,{style:C.center,testID:I,children:f(s,{accessibilityLabel:"Loading your tickets"})});const V=N?f(g,{style:C.footer,children:f(s,{size:"sm",accessibilityLabel:"Loading more tickets"})}):M?v(g,{style:[C.footer,C.error],accessibilityLiveRegion:"polite",children:[f(n,{variant:"bodySmall",color:"error",children:M.message}),f(e,{title:"Load more",size:"sm",variant:"ghost",onPress:()=>{A()}})]}):null;return f(d,{testID:I,data:j,keyExtractor:e=>e.id,contentContainerStyle:C.content,ListHeaderComponent:_,ListFooterComponent:V,onEndReached:()=>{A()},onEndReachedThreshold:.5,refreshControl:f(p,{refreshing:E,onRefresh:()=>{U()}}),ListEmptyComponent:z?f(g,{style:C.footer,children:f(s,{accessibilityLabel:"Loading your tickets"})}):F?null:f(i,H?{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:k?{label:"New ticket",onPress:k}:void 0}),renderItem:({item:e})=>v(t,{onPress:()=>m(e),accessibilityLabel:`${e.subject}, ${e.statusLabel}, ticket ${e.number}`,bodyStyle:C.card,testID:`support-ticket-${e.id}`,children:[v(g,{style:C.cardTop,children:[f(n,{variant:"caption",color:"tertiary",children:e.number}),f(a,{label:e.statusLabel,tone:STATUS_TONES[e.status]??"neutral",size:"sm"})]}),f(n,{variant:"title",numberOfLines:2,children:e.subject}),f(n,{variant:"bodySmall",color:"secondary",children:e.category}),v(g,{style:C.cardBottom,children:[f(n,{variant:"caption",color:"tertiary",children:`Updated ${b(e.updatedDateTimeUtc,B)}`}),e.awaitingCustomer&&f(n,{variant:"caption",color:"warning",children:"Waiting for your reply"})]})]})})};const buildStyles=e=>m.create({content:{padding:e.spacing.md,gap:e.spacing.sm},header:{gap:e.spacing.sm,marginBottom:e.spacing.xs},filters:{gap:e.spacing.xs},error:{gap:e.spacing.xs,alignItems:"flex-start"},center:{flex:1,alignItems:"center",justifyContent:"center",padding:e.spacing.xl},footer:{paddingVertical:e.spacing.md,alignItems:"center"},card:{gap:e.spacing.xxs},cardTop:{flexDirection:"row",justifyContent:"space-between",alignItems:"center"},cardBottom:{flexDirection:"row",justifyContent:"space-between",marginTop:e.spacing.xxs}});
1
+ "use strict";import{Button,Card,Chip,EmptyState,SearchBar,Spinner,StatusBadge,Text,useTheme}from"@webority/mobile-ui";import{useMemo}from"react";import{FlatList,RefreshControl,ScrollView,StyleSheet,View}from"react-native";import{formatWhen}from"../format.js";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",Resolved:"success",Closed:"neutral"};const STATUS_FILTERS=[{value:"Open",label:"Open",count:"open"},{value:"InProgress",label:"In progress",count:"inProgress"},{value:"Resolved",label:"Resolved",count:"resolved"},{value:"Closed",label:"Closed",count:"closed"}],ALL="all";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;export const SupportTicketList=({onOpenTicket:e,onNewTicket:t,header:s,pageSize:r,testID:i="support-ticket-list"})=>{const o=useTheme(),a=useMemo(()=>buildStyles(o),[o]),{tickets:n,counts:l,status:c,search:u,setStatus:d,setSearch:p,loading:m,refreshing:h,loadingMore:x,error:g,loadMoreError:b,refresh:S,loadMore:y}=useSupportTickets({pageSize:r}),j=useSupportTimeZone(),_=(l?.total??0)>0,f=null!==c||""!==u.trim(),T=_jsxs(View,{style:a.header,children:[s,t&&_jsx(Button,{title:"New ticket",leftIcon:"add",onPress:t,fullWidth:!0}),g&&_jsxs(View,{style:a.error,accessibilityLiveRegion:"polite",children:[_jsx(Text,{variant:"bodySmall",color:"error",children:g.message}),_jsx(Button,{title:"Try again",size:"sm",variant:"ghost",onPress:()=>{S()}})]}),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:a.filters,accessibilityRole:"tablist",accessibilityLabel:"Filter by status",children:statusFilterOptions(l,c).map(e=>_jsx(Chip,{label:`${e.label} (${e.count})`,selected:(c??ALL)===e.value,onPress:()=>d(e.value===ALL?null:e.value),accessibilityLabel:`${e.label}, ${e.count} tickets`,testID:`support-filter-${e.value}`},e.value))})]})]});if(m&&null===l&&!g)return _jsx(View,{style:a.center,testID:i,children:_jsx(Spinner,{accessibilityLabel:"Loading your tickets"})});const v=x?_jsx(View,{style:a.footer,children:_jsx(Spinner,{size:"sm",accessibilityLabel:"Loading more tickets"})}):b?_jsxs(View,{style:[a.footer,a.error],accessibilityLiveRegion:"polite",children:[_jsx(Text,{variant:"bodySmall",color:"error",children:b.message}),_jsx(Button,{title:"Load more",size:"sm",variant:"ghost",onPress:()=>{y()}})]}):null;return _jsx(FlatList,{testID:i,data:n,keyExtractor:e=>e.id,contentContainerStyle:a.content,ListHeaderComponent:T,ListFooterComponent:v,onEndReached:()=>{y()},onEndReachedThreshold:.5,refreshControl:_jsx(RefreshControl,{refreshing:h,onRefresh:()=>{S()}}),ListEmptyComponent:m?_jsx(View,{style:a.footer,children:_jsx(Spinner,{accessibilityLabel:"Loading your tickets"})}):g?null:_jsx(EmptyState,_?{title:"No tickets match",description:f?"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:({item:t})=>_jsxs(Card,{onPress:()=>e(t),accessibilityLabel:`${t.subject}, ${t.statusLabel}, ticket ${t.number}`,bodyStyle:a.card,testID:`support-ticket-${t.id}`,children:[_jsxs(View,{style:a.cardTop,children:[_jsx(Text,{variant:"caption",color:"tertiary",children:t.number}),_jsx(StatusBadge,{label:t.statusLabel,tone:STATUS_TONES[t.status]??"neutral",size:"sm"})]}),_jsx(Text,{variant:"title",numberOfLines:2,children:t.subject}),_jsx(Text,{variant:"bodySmall",color:"secondary",children:t.category}),_jsxs(View,{style:a.cardBottom,children:[_jsx(Text,{variant:"caption",color:"tertiary",children:`Updated ${formatWhen(t.updatedDateTimeUtc,j)}`}),t.awaitingCustomer&&_jsx(Text,{variant:"caption",color:"warning",children:"Waiting for your reply"})]})]})})};const buildStyles=e=>StyleSheet.create({content:{padding:e.spacing.md,gap:e.spacing.sm},header:{gap:e.spacing.sm,marginBottom:e.spacing.xs},filters:{gap:e.spacing.xs},error:{gap:e.spacing.xs,alignItems:"flex-start"},center:{flex:1,alignItems:"center",justifyContent:"center",padding:e.spacing.xl},footer:{paddingVertical:e.spacing.md,alignItems:"center"},card:{gap:e.spacing.xxs},cardTop:{flexDirection:"row",justifyContent:"space-between",alignItems:"center"},cardBottom:{flexDirection:"row",justifyContent:"space-between",marginTop:e.spacing.xxs}});
@@ -1 +1 @@
1
- import{getDeviceContext as o}from"@webority/mobile-core";import{Platform as e}from"react-native";const i={ios:"iOS",android:"Android"};export const describeDevice=()=>{const r=o(),t=[`${i[e.OS]??e.OS} ${r?.osVersion??String(e.Version??"")}`.trim()];return r?.deviceModel&&t.push(r.deviceModel),t.join("; ")};
1
+ "use strict";import{getDeviceContext}from"@webority/mobile-core";import{Platform}from"react-native";const PLATFORM_NAMES={ios:"iOS",android:"Android"};export const describeDevice=()=>{const e=getDeviceContext(),o=[`${PLATFORM_NAMES[Platform.OS]??Platform.OS} ${e?.osVersion??String(Platform.Version??"")}`.trim()];return e?.deviceModel&&o.push(e.deviceModel),o.join("; ")};
@@ -1 +1 @@
1
- import{File as e,Paths as t}from"expo-file-system";export const safeFileName=e=>{const t=e.split(/[/\\]/).pop()??"",r=Array.from(t).filter(e=>(e.codePointAt(0)??0)>31).join("").trim();return""===r||"."===r||".."===r?"attachment":r};export const saveAttachmentToCache=async(r,i)=>{const a=new e(t.cache,"support-attachments",`${r.id}-${safeFileName(r.fileName)}`);if(a.exists&&a.size>0)return a.uri;const n=await i();return a.create({intermediates:!0,overwrite:!0}),a.write(new Uint8Array(n)),a.uri};
1
+ "use strict";import{File,Paths}from"expo-file-system";const FOLDER="support-attachments";export const safeFileName=t=>{const e=t.split(/[/\\]/).pop()??"",r=Array.from(e).filter(t=>(t.codePointAt(0)??0)>31).join("").trim();return""===r||"."===r||".."===r?"attachment":r};export const saveAttachmentToCache=async(t,e)=>{const r=new File(Paths.cache,FOLDER,`${t.id}-${safeFileName(t.fileName)}`);if(r.exists&&r.size>0)return r.uri;const i=await e();return r.create({intermediates:!0,overwrite:!0}),r.write(new Uint8Array(i)),r.uri};
@@ -1 +1 @@
1
- export const DEFAULT_TIME_ZONE="Asia/Kolkata";export const formatWhen=(t,e="Asia/Kolkata")=>{if(!t)return"-";const o=new Date(t);if(Number.isNaN(o.getTime()))return"-";const r={};for(const t of new Intl.DateTimeFormat("en-US",{timeZone:e,day:"2-digit",month:"short",year:"numeric",hour:"numeric",minute:"2-digit",hour12:!0}).formatToParts(o))r[t.type]=t.value;return`${r.day}-${r.month}-${r.year}, ${r.hour}:${r.minute} ${(r.dayPeriod??"").toUpperCase()}`};export const formatBytes=t=>t>=1048576?`${(t/1048576).toFixed(1)} MB`:t>=1024?`${Math.round(t/1024)} KB`:`${t} bytes`;export const isImage=t=>t.startsWith("image/");
1
+ "use strict";export const DEFAULT_TIME_ZONE="Asia/Kolkata";export const formatWhen=(t,e="Asia/Kolkata")=>{if(!t)return"-";const o=new Date(t);if(Number.isNaN(o.getTime()))return"-";const r={};for(const t of new Intl.DateTimeFormat("en-US",{timeZone:e,day:"2-digit",month:"short",year:"numeric",hour:"numeric",minute:"2-digit",hour12:!0}).formatToParts(o))r[t.type]=t.value;return`${r.day}-${r.month}-${r.year}, ${r.hour}:${r.minute} ${(r.dayPeriod??"").toUpperCase()}`};export const formatBytes=t=>t>=1048576?`${(t/1048576).toFixed(1)} MB`:t>=1024?`${Math.round(t/1024)} KB`:`${t} bytes`;export const isImage=t=>t.startsWith("image/");
@@ -1 +1 @@
1
- export{createSupportClient,SupportApiError,toSupportApiError}from"./client.js";export{NewSupportTicket}from"./components/NewSupportTicket.js";export{SupportContact}from"./components/SupportContact.js";export{SupportTicketDetail}from"./components/SupportTicketDetail.js";export{SupportTicketList}from"./components/SupportTicketList.js";export{describeDevice}from"./device.js";export{SupportProvider,useCreateSupportTicket,useSupportClient,useSupportConfig,useSupportTicket,useSupportTickets}from"./SupportProvider.js";
1
+ "use strict";export{createSupportClient,SupportApiError,toSupportApiError}from"./client.js";export{NewSupportTicket}from"./components/NewSupportTicket.js";export{SupportContact}from"./components/SupportContact.js";export{SupportTicketDetail}from"./components/SupportTicketDetail.js";export{SupportTicketList}from"./components/SupportTicketList.js";export{describeDevice}from"./device.js";export{SupportProvider,useCreateSupportTicket,useSupportClient,useSupportConfig,useSupportTicket,useSupportTickets}from"./SupportProvider.js";
@@ -1 +1 @@
1
- import{compressImage as e}from"@webority/mobile-core/compressor";export class PhotoPermissionError extends Error{constructor(e){super("camera"===e?"Allow camera access in Settings to take a photo.":"Allow photo access in Settings to attach a photo."),this.name="PhotoPermissionError",this.source=e}}const t={mediaTypes:["images"],quality:.8,allowsEditing:!1};export const pickPhotos=async(a,o)=>{if(o.maxCount<=0)return[];const s=require("expo-image-picker");if(!("camera"===a?await s.requestCameraPermissionsAsync():await s.requestMediaLibraryPermissionsAsync()).granted)throw new PhotoPermissionError(a);const r="camera"===a?await s.launchCameraAsync(t):await s.launchImageLibraryAsync({...t,allowsMultipleSelection:o.maxCount>1,selectionLimit:o.maxCount});if(r.canceled||!r.assets)return[];const i=[];for(const t of r.assets.slice(0,o.maxCount)){const a=await e(t.uri,{maxSize:1920,maxBytes:o.maxBytes,fileSize:t.fileSize??void 0,name:t.fileName??`photo-${Date.now()}-${i.length+1}`});i.push({uri:a.part.uri,name:a.part.name,type:a.part.type,size:a.size??void 0})}return i};
1
+ "use strict";import{compressImage}from"@webority/mobile-core/compressor";const picker=()=>require("expo-image-picker");export class PhotoPermissionError extends Error{constructor(e){super("camera"===e?"Allow camera access in Settings to take a photo.":"Allow photo access in Settings to attach a photo."),this.name="PhotoPermissionError",this.source=e}}const CAPTURE_OPTIONS={mediaTypes:["images"],quality:.8,allowsEditing:!1},MAX_SIDE=1920;export const pickPhotos=async(e,t)=>{if(t.maxCount<=0)return[];const s=picker();if(!("camera"===e?await s.requestCameraPermissionsAsync():await s.requestMediaLibraryPermissionsAsync()).granted)throw new PhotoPermissionError(e);const o="camera"===e?await s.launchCameraAsync(CAPTURE_OPTIONS):await s.launchImageLibraryAsync({...CAPTURE_OPTIONS,allowsMultipleSelection:t.maxCount>1,selectionLimit:t.maxCount});if(o.canceled||!o.assets)return[];const a=[];for(const e of o.assets.slice(0,t.maxCount)){const s=await compressImage(e.uri,{maxSize:1920,maxBytes:t.maxBytes,fileSize:e.fileSize??void 0,name:e.fileName??`photo-${Date.now()}-${a.length+1}`});a.push({uri:s.part.uri,name:s.part.name,type:s.part.type,size:s.size??void 0})}return a};
@@ -1 +1 @@
1
- import{Button as e}from"@webority/mobile-ui";import{useState as t}from"react";import{captureAndReport as r}from"./captureAndReport.js";import{jsx as s}from"react/jsx-runtime";export const ReportIssueButton=({onReport:o,onCaptureError:i,screenName:a,appVersion:c,category:n,subject:p,title:u="Report an issue",accessibilityLabel:m="Report an issue with this screen",testID:l="support-report-issue",...b})=>{const[y,f]=t(!1);return s(e,{leftIcon:"message-question",...b,title:u,accessibilityLabel:m,testID:l,loading:y,onPress:()=>{(async()=>{f(!0);try{o(await r({screenName:a,appVersion:c,category:n,subject:p}))}catch(e){i?.(e),o({category:n,subject:p,pageUrl:a,appVersion:c})}finally{f(!1)}})()}})};
1
+ "use strict";import{Button}from"@webority/mobile-ui";import{useState}from"react";import{captureAndReport}from"./captureAndReport.js";import{jsx as _jsx}from"react/jsx-runtime";export const ReportIssueButton=({onReport:t,onCaptureError:e,screenName:r,appVersion:s,category:o,subject:a,title:i="Report an issue",accessibilityLabel:n="Report an issue with this screen",testID:p="support-report-issue",...c})=>{const[u,m]=useState(!1);return _jsx(Button,{leftIcon:"message-question",...c,title:i,accessibilityLabel:n,testID:p,loading:u,onPress:()=>{(async()=>{m(!0);try{t(await captureAndReport({screenName:r,appVersion:s,category:o,subject:a}))}catch(i){e?.(i),t({category:o,subject:a,pageUrl:r,appVersion:s})}finally{m(!1)}})()}})};
@@ -1 +1 @@
1
- import{describeDevice as e}from"../device.js";export const captureAndReport=async t=>{const r=await require("react-native-view-shot").captureScreen({format:"jpg",quality:.8,result:"tmpfile"});return{category:t.category,subject:t.subject,pageUrl:t.screenName,userAgent:e(),appVersion:t.appVersion,screenshots:[{uri:r,name:`screenshot-${Date.now()}.jpg`,type:"image/jpeg"}]}};
1
+ "use strict";import{describeDevice}from"../device.js";const viewShot=()=>require("react-native-view-shot");export const captureAndReport=async e=>{const t=await viewShot().captureScreen({format:"jpg",quality:.8,result:"tmpfile"});return{category:e.category,subject:e.subject,pageUrl:e.screenName,userAgent:describeDevice(),appVersion:e.appVersion,screenshots:[{uri:t,name:`screenshot-${Date.now()}.jpg`,type:"image/jpeg"}]}};
@@ -1 +1 @@
1
- export{captureAndReport}from"./captureAndReport.js";export{ReportIssueButton}from"./ReportIssueButton.js";
1
+ "use strict";export{captureAndReport}from"./captureAndReport.js";export{ReportIssueButton}from"./ReportIssueButton.js";
@@ -1 +1 @@
1
- import{shareFile as r}from"@webority/mobile-core/share";export class SupportShareUnavailableError extends Error{constructor(){super("Opening files is not available on this device."),this.name="SupportShareUnavailableError"}}export const shareSupportFile=async e=>{try{await r(e.uri,{mimeType:e.contentType,dialogTitle:e.name})}catch(r){const e=r;if("ShareError"===e?.name&&"unavailable"===e.code)throw new SupportShareUnavailableError;throw r}};
1
+ "use strict";import{shareFile}from"@webority/mobile-core/share";export class SupportShareUnavailableError extends Error{constructor(){super("Opening files is not available on this device."),this.name="SupportShareUnavailableError"}}export const shareSupportFile=async r=>{try{await shareFile(r.uri,{mimeType:r.contentType,dialogTitle:r.name})}catch(r){const e=r;if("ShareError"===e?.name&&"unavailable"===e.code)throw new SupportShareUnavailableError;throw r}};
@@ -0,0 +1 @@
1
+ "use strict";
@@ -1 +1 @@
1
- export const validateCategory=(e,t)=>t.categories.includes(e)?void 0:"Choose one of the listed categories.";export const validateSubject=(e,t)=>{const a=e.trim();return a?a.length>t.subjectMaxLength?`Keep the subject under ${t.subjectMaxLength} characters.`:void 0:"Add a short subject."};export const validateBody=(e,t)=>{const a=e.trim();return a?a.length>t.bodyMaxLength?`Keep the message under ${t.bodyMaxLength} characters.`:void 0:"Write a message."};export const validateFiles=(e,t)=>{if(e.length>t.maxAttachmentsPerMessage)return`Attach up to ${t.maxAttachmentsPerMessage} files to one message.`;for(const a of e){if(t.allowedContentTypes.length>0&&!t.allowedContentTypes.includes(a.type))return`${a.name} can't be attached. Use an image, a PDF or a text file.`;if(void 0!==a.size&&a.size>t.maxAttachmentBytes){const e=Math.floor(t.maxAttachmentBytes/1048576);return`${a.name} is too large. The limit is ${e} MB per file.`}}};
1
+ "use strict";export const validateCategory=(e,t)=>t.categories.includes(e)?void 0:"Choose one of the listed categories.";export const validateSubject=(e,t)=>{const a=e.trim();return a?a.length>t.subjectMaxLength?`Keep the subject under ${t.subjectMaxLength} characters.`:void 0:"Add a short subject."};export const validateBody=(e,t)=>{const a=e.trim();return a?a.length>t.bodyMaxLength?`Keep the message under ${t.bodyMaxLength} characters.`:void 0:"Write a message."};export const validateFiles=(e,t)=>{if(e.length>t.maxAttachmentsPerMessage)return`Attach up to ${t.maxAttachmentsPerMessage} files to one message.`;for(const a of e){if(t.allowedContentTypes.length>0&&!t.allowedContentTypes.includes(a.type))return`${a.name} can't be attached. Use an image, a PDF or a text file.`;if(void 0!==a.size&&a.size>t.maxAttachmentBytes){const e=Math.floor(t.maxAttachmentBytes/1048576);return`${a.name} is too large. The limit is ${e} MB per file.`}}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/mobile-support",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
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",
@@ -70,8 +70,8 @@
70
70
  "typecheck": "tsc --noEmit"
71
71
  },
72
72
  "peerDependencies": {
73
- "@webority/mobile-core": ">=0.1.0 <0.2.0",
74
- "@webority/mobile-ui": ">=0.1.0 <0.2.0",
73
+ "@webority/mobile-core": ">=0.2.0 <0.3.0",
74
+ "@webority/mobile-ui": ">=0.2.0 <0.3.0",
75
75
  "expo": ">=57.0.0",
76
76
  "expo-clipboard": ">=57.0.0",
77
77
  "expo-file-system": ">=57.0.0",