@webority/mobile-support 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,6 +47,10 @@ export const App = () => (
47
47
  );
48
48
  ```
49
49
 
50
+ Ticket times are written by mobile-core's `formatDateTime` ("07-Oct-2026, 2:30 PM"), in the zone
51
+ set with `configureFormatters` (IST unless the app changed it). Pass `timeZone` to
52
+ `SupportProvider` (`'viewer'`, `'UTC'` or an IANA name) to show another zone; it is then labelled.
53
+
50
54
  ## Screens
51
55
 
52
56
  The screens know nothing about navigation. Place them in your own navigator and pass callbacks.
@@ -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"),_format=require("./format.js"),_jsxRuntime=require("react/jsx-runtime");const SupportContext=(0,_react.createContext)(null),SupportProvider=({client:e,openFile:t,timeZone:r=_format.DEFAULT_TIME_ZONE,children:c})=>{const[u,o]=(0,_react.useState)(null),[a,n]=(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{o(await e.getConfig()),n(null)}catch(e){n((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]),m=(0,_react.useCallback)(()=>l(e=>e+1),[]),d=(0,_react.useMemo)(()=>({client:e,config:u,configError:a,configLoading:s,reloadConfig:_,loadAttachment:f,openAttachment:C,timeZone:r,revision:i,bumpRevision:m}),[e,u,a,s,_,f,C,r,i,m]);return(0,_jsxRuntime.jsx)(SupportContext.Provider,{value:d,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,o]=(0,_react.useState)(null),[a,n]=(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),[m,d]=(0,_react.useState)(!0),[x,h]=(0,_react.useState)(!1),[k,g]=(0,_react.useState)(!1),[T,y]=(0,_react.useState)(null),[A,b]=(0,_react.useState)(null),E=(0,_react.useRef)(!0),v=(0,_react.useRef)(0),w=(0,_react.useRef)(!1),R=(0,_react.useRef)(null);(0,_react.useEffect)(()=>(E.current=!0,()=>{E.current=!1}),[]);const P=(0,_react.useCallback)(e=>({status:u??void 0,search:a.trim()||void 0,page:e,pageSize:t}),[u,a,t]),M=(0,_react.useCallback)(async e=>{v.current+=1;const t=v.current,current=()=>E.current&&t===v.current;w.current=!1,g(!1),"refresh"===e?h(!0):"replace"===e&&(d(!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),b(null))}catch(e){current()&&y((0,_client.toSupportApiError)(e))}finally{current()&&(d(!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||m||x||!j)return;const e=v.current,current=()=>E.current&&e===v.current;w.current=!0,g(!0),b(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()&&b((0,_client.toSupportApiError)(e))}finally{current()&&(w.current=!1,g(!1))}},[r,P,S,j,m,x]),q=(0,_react.useCallback)(()=>M("refresh"),[M]),O=useCreateSupportTicket();return{tickets:s,counts:i,status:u,search:a,setStatus:o,setSearch:n,loading:m,refreshing:x,loadingMore:k,hasMore:j,error:T,loadMoreError:A,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),[o,a]=(0,_react.useState)(!0),[n,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()=>{a(!0);try{const r=await t.getTicket(e);_.current&&(u(r),s(null))}catch(e){_.current&&s((0,_client.toSupportApiError)(e))}finally{_.current&&a(!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]),m=(0,_react.useCallback)(r=>C(()=>t.reply(e,r)),[C,t,e]),d=(0,_react.useCallback)(()=>C(()=>t.reopen(e)),[C,t,e]),x=(0,_react.useCallback)(r=>C(()=>t.rate(e,r)),[C,t,e]);return{ticket:c,loading:o,error:n,actionError:p,busy:l,reload:f,reply:m,reopen:d,rate:x}};exports.useSupportTicket=useSupportTicket;
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,n]=(0,_react.useState)(null),[o,a]=(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{n(await e.getConfig()),a(null)}catch(e){a((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:o,configLoading:s,reloadConfig:_,loadAttachment:f,openAttachment:C,timeZone:r,revision:i,bumpRevision:d}),[e,u,o,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,n]=(0,_react.useState)(null),[o,a]=(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),[A,b]=(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:o.trim()||void 0,page:e,pageSize:t}),[u,o,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),b(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),b(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()&&b((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:o,setStatus:n,setSearch:a,loading:d,refreshing:x,loadingMore:k,hasMore:j,error:T,loadMoreError:A,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),[n,o]=(0,_react.useState)(!0),[a,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()=>{o(!0);try{const r=await t.getTicket(e);_.current&&(u(r),s(null))}catch(e){_.current&&s((0,_client.toSupportApiError)(e))}finally{_.current&&o(!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)(r=>C(()=>t.rate(e,r)),[C,t,e]);return{ticket:c,loading:n,error:a,actionError:p,busy:l,reload:f,reply:d,reopen:m,rate:x}};exports.useSupportTicket=useSupportTicket;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.SupportTicketDetail=void 0;var _mobileUi=require("@webority/mobile-ui"),_react=require("react"),_reactNative=require("react-native"),_format=require("../format.js"),_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,_format.formatWhen)(e.createdDateTimeUtc,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??"-"}),(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,_format.formatWhen)(e.createdDateTimeUtc,i)})]})})},SATISFACTION_TEXT={Satisfied:"You said we handled this well.",Unsatisfied:"You said we did not handle this well."},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:r,loading:o,error:l,actionError:c,busy:u,reload:d,reply:m,reopen:x,rate:p}=(0,_SupportProvider.useSupportTicket)(e),[_,j]=(0,_react.useState)(null),[b,h]=(0,_react.useState)(""),[g,y]=(0,_react.useState)([]),[R,S]=(0,_react.useState)(null),[v,T]=(0,_react.useState)(null),[f,U]=(0,_react.useState)("");if(!r)return(0,_jsxRuntime.jsx)(_reactNative.View,{style:s.center,testID:t,children:o?(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{accessibilityLabel:"Loading the ticket"}):(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"body",color:"error",align:"center",children:l?.message??"That ticket could not be found."}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Try again",variant:"ghost",onPress:()=>{d()}})]})});const w=r.messages.filter(e=>!e.isInternal);return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.root,testID:t,children:[(0,_jsxRuntime.jsxs)(_reactNative.ScrollView,{contentContainerStyle:s.content,keyboardShouldPersistTaps:"handled",children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.head,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"h3",accessibilityRole:"header",children:r.subject}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"secondary",children:`${r.number}, ${r.category}`}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.headRow,children:[(0,_jsxRuntime.jsx)(_mobileUi.StatusBadge,{label:r.statusLabel,tone:_SupportTicketList.STATUS_TONES[r.status]??"neutral",accessibilityLabel:`Status: ${r.statusLabel}`}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",children:`Opened ${(0,_format.formatWhen)(r.createdDateTimeUtc,n)}`})]})]}),r.canReopen&&(0,_jsxRuntime.jsxs)(_mobileUi.Card,{variant:"outlined",bodyStyle:s.section,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",children:`This ticket is ${r.statusLabel}. Reopen it if you still need help.`}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Reopen ticket",variant:"outline",size:"sm",loading:u,disabled:u,onPress:()=>{x()},testID:"support-reopen"})]}),r.canRate&&(0,_jsxRuntime.jsxs)(_mobileUi.Card,{variant:"outlined",bodyStyle:s.section,testID:"support-rating",children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"label",children:"How did we do?"}),r.satisfaction&&(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"secondary",children:SATISFACTION_TEXT[r.satisfaction]}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.ratingRow,children:[(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Satisfied",size:"sm",leftIcon:"like",variant:"Satisfied"===v?"solid":"outline",tone:"success",accessibilityState:{selected:"Satisfied"===v},onPress:()=>T("Satisfied"),testID:"support-rate-satisfied"}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Not satisfied",size:"sm",leftIcon:"dislike",variant:"Unsatisfied"===v?"solid":"outline",tone:"error",accessibilityState:{selected:"Unsatisfied"===v},onPress:()=>T("Unsatisfied"),testID:"support-rate-unsatisfied"})]}),v&&(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_mobileUi.Input,{label:"Anything to add? (optional)",value:f,onChangeText:U,multiline:!0,maxLength:a?.satisfactionCommentMaxLength,testID:"support-rating-comment"}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Send rating",size:"sm",loading:u,disabled:u,onPress:()=>{(async()=>{v&&await p({satisfaction:v,comment:f})&&(T(null),U(""))})()},testID:"support-rating-send"})]})]}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:s.thread,accessibilityLabel:"Conversation",children:w.map(e=>(0,_jsxRuntime.jsx)(MessageBubble,{message:e,styles:s,timeZone:n,onOpenImage:(e,t)=>j({uri:e,name:t})},e.id))}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.section,children:[(0,_jsxRuntime.jsx)(_mobileUi.Input,{label:"Your reply",placeholder:"Write a reply to the support team",value:b,onChangeText:h,multiline:!0,maxLength:a?.bodyMaxLength,error:R??void 0,testID:"support-reply-input"}),a&&(0,_jsxRuntime.jsx)(_PhotoTray.PhotoTray,{photos:g,onChange:y,remaining:a.maxAttachmentsPerMessage-g.length,maxBytes:a.maxAttachmentBytes,disabled:u}),r.canReopen&&(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"secondary",children:"Replying reopens this ticket."}),c&&(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"error",accessibilityLiveRegion:"polite",children:c.message}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Send reply",leftIcon:"send",loading:u,disabled:u,onPress:()=>{(async()=>{const e=b.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 m({body:e,files:g})&&(h(""),y([])))})()},testID:"support-reply-send"})]})]}),(0,_jsxRuntime.jsx)(_Attachments.ImageViewer,{image:_,onClose:()=>j(null)})]})};exports.SupportTicketDetail=SupportTicketDetail;const buildStyles=e=>_reactNative.StyleSheet.create({root:{flex:1},content:{padding:e.spacing.md,gap:e.spacing.md},center:{flex:1,alignItems:"center",justifyContent:"center",padding:e.spacing.xl,gap:e.spacing.sm},head:{gap:e.spacing.xxs},headRow:{flexDirection:"row",alignItems:"center",gap:e.spacing.sm,marginTop:e.spacing.xs},section:{gap:e.spacing.sm},ratingRow:{flexDirection:"row",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";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??"-"}),(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 said we handled this well.",Unsatisfied:"You said we did not handle this well."},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:r,loading:o,error:l,actionError:c,busy:m,reload:u,reply:d,reopen:x,rate:p}=(0,_SupportProvider.useSupportTicket)(e),[_,b]=(0,_react.useState)(null),[j,g]=(0,_react.useState)(""),[h,y]=(0,_react.useState)([]),[R,S]=(0,_react.useState)(null),[v,T]=(0,_react.useState)(null),[f,w]=(0,_react.useState)("");if(!r)return(0,_jsxRuntime.jsx)(_reactNative.View,{style:s.center,testID:t,children:o?(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{accessibilityLabel:"Loading the ticket"}):(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"body",color:"error",align:"center",children:l?.message??"That ticket could not be found."}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Try again",variant:"ghost",onPress:()=>{u()}})]})});const U=r.messages.filter(e=>!e.isInternal);return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.root,testID:t,children:[(0,_jsxRuntime.jsxs)(_reactNative.ScrollView,{contentContainerStyle:s.content,keyboardShouldPersistTaps:"handled",children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.head,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"h3",accessibilityRole:"header",children:r.subject}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"secondary",children:`${r.number}, ${r.category}`}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.headRow,children:[(0,_jsxRuntime.jsx)(_mobileUi.StatusBadge,{label:r.statusLabel,tone:_SupportTicketList.STATUS_TONES[r.status]??"neutral",accessibilityLabel:`Status: ${r.statusLabel}`}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",children:`Opened ${(0,_mobileCore.formatDateTime)(r.createdDateTimeUtc,{zone:n})}`})]})]}),r.canReopen&&(0,_jsxRuntime.jsxs)(_mobileUi.Card,{variant:"outlined",bodyStyle:s.section,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",children:`This ticket is ${r.statusLabel}. Reopen it if you still need help.`}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Reopen ticket",variant:"outline",size:"sm",loading:m,disabled:m,onPress:()=>{x()},testID:"support-reopen"})]}),r.canRate&&(0,_jsxRuntime.jsxs)(_mobileUi.Card,{variant:"outlined",bodyStyle:s.section,testID:"support-rating",children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"label",children:"How did we do?"}),r.satisfaction&&(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"secondary",children:SATISFACTION_TEXT[r.satisfaction]}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.ratingRow,children:[(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Satisfied",size:"sm",leftIcon:"like",variant:"Satisfied"===v?"solid":"outline",tone:"success",accessibilityState:{selected:"Satisfied"===v},onPress:()=>T("Satisfied"),testID:"support-rate-satisfied"}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Not satisfied",size:"sm",leftIcon:"dislike",variant:"Unsatisfied"===v?"solid":"outline",tone:"error",accessibilityState:{selected:"Unsatisfied"===v},onPress:()=>T("Unsatisfied"),testID:"support-rate-unsatisfied"})]}),v&&(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_mobileUi.Input,{label:"Anything to add? (optional)",value:f,onChangeText:w,multiline:!0,maxLength:a?.satisfactionCommentMaxLength,testID:"support-rating-comment"}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Send rating",size:"sm",loading:m,disabled:m,onPress:()=>{(async()=>{v&&await p({satisfaction:v,comment:f})&&(T(null),w(""))})()},testID:"support-rating-send"})]})]}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:s.thread,accessibilityLabel:"Conversation",children:U.map(e=>(0,_jsxRuntime.jsx)(MessageBubble,{message:e,styles:s,timeZone:n,onOpenImage:(e,t)=>b({uri:e,name:t})},e.id))}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.section,children:[(0,_jsxRuntime.jsx)(_mobileUi.Input,{label:"Your reply",placeholder:"Write a reply to the support team",value:j,onChangeText:g,multiline:!0,maxLength:a?.bodyMaxLength,error:R??void 0,testID:"support-reply-input"}),a&&(0,_jsxRuntime.jsx)(_PhotoTray.PhotoTray,{photos:h,onChange:y,remaining:a.maxAttachmentsPerMessage-h.length,maxBytes:a.maxAttachmentBytes,disabled:m}),r.canReopen&&(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"secondary",children:"Replying reopens this ticket."}),c&&(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"error",accessibilityLiveRegion:"polite",children:c.message}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Send reply",leftIcon:"send",loading:m,disabled:m,onPress:()=>{(async()=>{const e=j.trim(),t=a?(0,_validation.validateBody)(e,a)??(0,_validation.validateFiles)(h,a):e?void 0:"Write a message.";t?S(t):(S(null),await d({body:e,files:h})&&(g(""),y([])))})()},testID:"support-reply-send"})]})]}),(0,_jsxRuntime.jsx)(_Attachments.ImageViewer,{image:_,onClose:()=>b(null)})]})};exports.SupportTicketDetail=SupportTicketDetail;const buildStyles=e=>_reactNative.StyleSheet.create({root:{flex:1},content:{padding:e.spacing.md,gap:e.spacing.md},center:{flex:1,alignItems:"center",justifyContent:"center",padding:e.spacing.xl,gap:e.spacing.sm},head:{gap:e.spacing.xxs},headRow:{flexDirection:"row",alignItems:"center",gap:e.spacing.sm,marginTop:e.spacing.xs},section:{gap:e.spacing.sm},ratingRow:{flexDirection:"row",gap:e.spacing.sm},thread:{gap:e.spacing.sm},system:{paddingHorizontal:e.spacing.lg,paddingVertical:e.spacing.xxs},bubbleRow:{flexDirection:"row"},mineRow:{justifyContent:"flex-end"},theirsRow:{justifyContent:"flex-start"},bubble:{maxWidth:"85%",borderRadius:e.radius.lg,padding:e.spacing.sm,gap:e.spacing.xxs},mine:{backgroundColor:e.colors.primaryMuted},theirs:{backgroundColor:e.colors.background.secondary}});
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.statusFilterOptions=exports.SupportTicketList=exports.STATUS_TONES=void 0;var _mobileUi=require("@webority/mobile-ui"),_react=require("react"),_reactNative=require("react-native"),_format=require("../format.js"),_SupportProvider=require("../SupportProvider.js"),_jsxRuntime=require("react/jsx-runtime");const STATUS_TONES=exports.STATUS_TONES={Open:"warning",InProgress:"info",Resolved:"success",Closed:"neutral"},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",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,SupportTicketList=({onOpenTicket:e,onNewTicket:t,header:i,pageSize:s,testID:r="support-ticket-list"})=>{const o=(0,_mobileUi.useTheme)(),a=(0,_react.useMemo)(()=>buildStyles(o),[o]),{tickets:n,counts:l,status:c,search:u,setStatus:m,setSearch:p,loading:d,refreshing:x,loadingMore:_,error:b,loadMoreError:j,refresh:S,loadMore:h}=(0,_SupportProvider.useSupportTickets)({pageSize:s}),g=(0,_SupportProvider.useSupportTimeZone)(),v=(l?.total??0)>0,y=null!==c||""!==u.trim(),R=(0,_jsxRuntime.jsxs)(_reactNative.View,{style:a.header,children:[i,t&&(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"New ticket",leftIcon:"add",onPress:t,fullWidth:!0}),b&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:a.error,accessibilityLiveRegion:"polite",children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"error",children:b.message}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Try again",size:"sm",variant:"ghost",onPress:()=>{S()}})]}),l&&v&&(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_mobileUi.SearchBar,{value:u,onChangeText:p,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:a.filters,accessibilityRole:"tablist",accessibilityLabel:"Filter by status",children:statusFilterOptions(l,c).map(e=>(0,_jsxRuntime.jsx)(_mobileUi.Chip,{label:`${e.label} (${e.count})`,selected:(c??ALL)===e.value,onPress:()=>m(e.value===ALL?null:e.value),accessibilityLabel:`${e.label}, ${e.count} tickets`,testID:`support-filter-${e.value}`},e.value))})]})]});if(d&&null===l&&!b)return(0,_jsxRuntime.jsx)(_reactNative.View,{style:a.center,testID:r,children:(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{accessibilityLabel:"Loading your tickets"})});const T=_?(0,_jsxRuntime.jsx)(_reactNative.View,{style:a.footer,children:(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{size:"sm",accessibilityLabel:"Loading more tickets"})}):j?(0,_jsxRuntime.jsxs)(_reactNative.View,{style:[a.footer,a.error],accessibilityLiveRegion:"polite",children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"error",children:j.message}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Load more",size:"sm",variant:"ghost",onPress:()=>{h()}})]}):null;return(0,_jsxRuntime.jsx)(_reactNative.FlatList,{testID:r,data:n,keyExtractor:e=>e.id,contentContainerStyle:a.content,ListHeaderComponent:R,ListFooterComponent:T,onEndReached:()=>{h()},onEndReachedThreshold:.5,refreshControl:(0,_jsxRuntime.jsx)(_reactNative.RefreshControl,{refreshing:x,onRefresh:()=>{S()}}),ListEmptyComponent:d?(0,_jsxRuntime.jsx)(_reactNative.View,{style:a.footer,children:(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{accessibilityLabel:"Loading your tickets"})}):b?null:v?(0,_jsxRuntime.jsx)(_mobileUi.EmptyState,{title:"No tickets match",description:y?"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:({item:t})=>(0,_jsxRuntime.jsxs)(_mobileUi.Card,{onPress:()=>e(t),accessibilityLabel:`${t.subject}, ${t.statusLabel}, ticket ${t.number}`,bodyStyle:a.card,testID:`support-ticket-${t.id}`,children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:a.cardTop,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",children:t.number}),(0,_jsxRuntime.jsx)(_mobileUi.StatusBadge,{label:t.statusLabel,tone:STATUS_TONES[t.status]??"neutral",size:"sm"})]}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"title",numberOfLines:2,children:t.subject}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"secondary",children:t.category}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:a.cardBottom,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",children:`Updated ${(0,_format.formatWhen)(t.updatedDateTimeUtc,g)}`}),t.awaitingCustomer&&(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"warning",children:"Waiting for your reply"})]})]})})};exports.SupportTicketList=SupportTicketList;const buildStyles=e=>_reactNative.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
+ "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",Resolved:"success",Closed:"neutral"},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",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,SupportTicketList=({onOpenTicket:e,onNewTicket:t,header:i,pageSize:s,testID:r="support-ticket-list"})=>{const o=(0,_mobileUi.useTheme)(),a=(0,_react.useMemo)(()=>buildStyles(o),[o]),{tickets:n,counts:l,status:c,search:u,setStatus:m,setSearch:p,loading:d,refreshing:x,loadingMore:_,error:b,loadMoreError:j,refresh:S,loadMore:h}=(0,_SupportProvider.useSupportTickets)({pageSize:s}),g=(0,_SupportProvider.useSupportTimeZone)(),v=(l?.total??0)>0,y=null!==c||""!==u.trim(),R=(0,_jsxRuntime.jsxs)(_reactNative.View,{style:a.header,children:[i,t&&(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"New ticket",leftIcon:"add",onPress:t,fullWidth:!0}),b&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:a.error,accessibilityLiveRegion:"polite",children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"error",children:b.message}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Try again",size:"sm",variant:"ghost",onPress:()=>{S()}})]}),l&&v&&(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_mobileUi.SearchBar,{value:u,onChangeText:p,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:a.filters,accessibilityRole:"tablist",accessibilityLabel:"Filter by status",children:statusFilterOptions(l,c).map(e=>(0,_jsxRuntime.jsx)(_mobileUi.Chip,{label:`${e.label} (${e.count})`,selected:(c??ALL)===e.value,onPress:()=>m(e.value===ALL?null:e.value),accessibilityLabel:`${e.label}, ${e.count} tickets`,testID:`support-filter-${e.value}`},e.value))})]})]});if(d&&null===l&&!b)return(0,_jsxRuntime.jsx)(_reactNative.View,{style:a.center,testID:r,children:(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{accessibilityLabel:"Loading your tickets"})});const T=_?(0,_jsxRuntime.jsx)(_reactNative.View,{style:a.footer,children:(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{size:"sm",accessibilityLabel:"Loading more tickets"})}):j?(0,_jsxRuntime.jsxs)(_reactNative.View,{style:[a.footer,a.error],accessibilityLiveRegion:"polite",children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"error",children:j.message}),(0,_jsxRuntime.jsx)(_mobileUi.Button,{title:"Load more",size:"sm",variant:"ghost",onPress:()=>{h()}})]}):null;return(0,_jsxRuntime.jsx)(_reactNative.FlatList,{testID:r,data:n,keyExtractor:e=>e.id,contentContainerStyle:a.content,ListHeaderComponent:R,ListFooterComponent:T,onEndReached:()=>{h()},onEndReachedThreshold:.5,refreshControl:(0,_jsxRuntime.jsx)(_reactNative.RefreshControl,{refreshing:x,onRefresh:()=>{S()}}),ListEmptyComponent:d?(0,_jsxRuntime.jsx)(_reactNative.View,{style:a.footer,children:(0,_jsxRuntime.jsx)(_mobileUi.Spinner,{accessibilityLabel:"Loading your tickets"})}):b?null:v?(0,_jsxRuntime.jsx)(_mobileUi.EmptyState,{title:"No tickets match",description:y?"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:({item:t})=>(0,_jsxRuntime.jsxs)(_mobileUi.Card,{onPress:()=>e(t),accessibilityLabel:`${t.subject}, ${t.statusLabel}, ticket ${t.number}`,bodyStyle:a.card,testID:`support-ticket-${t.id}`,children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:a.cardTop,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",children:t.number}),(0,_jsxRuntime.jsx)(_mobileUi.StatusBadge,{label:t.statusLabel,tone:STATUS_TONES[t.status]??"neutral",size:"sm"})]}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"title",numberOfLines:2,children:t.subject}),(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"bodySmall",color:"secondary",children:t.category}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:a.cardBottom,children:[(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"tertiary",children:`Updated ${(0,_mobileCore.formatDateTime)(t.updatedDateTimeUtc,{zone:g})}`}),t.awaitingCustomer&&(0,_jsxRuntime.jsx)(_mobileUi.Text,{variant:"caption",color:"warning",children:"Waiting for your reply"})]})]})})};exports.SupportTicketList=SupportTicketList;const buildStyles=e=>_reactNative.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
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.isImage=exports.formatWhen=exports.formatBytes=exports.DEFAULT_TIME_ZONE=void 0;const DEFAULT_TIME_ZONE=exports.DEFAULT_TIME_ZONE="Asia/Kolkata",formatWhen=(t,e=DEFAULT_TIME_ZONE)=>{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()}`};exports.formatWhen=formatWhen;const formatBytes=t=>t>=1048576?`${(t/1048576).toFixed(1)} MB`:t>=1024?`${Math.round(t/1024)} KB`:`${t} bytes`;exports.formatBytes=formatBytes;const isImage=t=>t.startsWith("image/");exports.isImage=isImage;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.isImage=exports.formatBytes=void 0;const formatBytes=t=>t>=1048576?`${(t/1048576).toFixed(1)} MB`:t>=1024?`${Math.round(t/1024)} KB`:`${t} bytes`;exports.formatBytes=formatBytes;const isImage=t=>t.startsWith("image/");exports.isImage=isImage;
@@ -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{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
+ "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,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 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:s,configLoading:a,reloadConfig:f,loadAttachment:C,openAttachment:m,timeZone:r,revision:i,bumpRevision:h}),[e,o,s,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),[s,c]=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: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,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:s,setStatus:u,setSearch:c,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,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 C=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(()=>{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(r=>m(()=>t.rate(e,r)),[m,t,e]);return{ticket:n,loading:u,error:c,actionError:l,busy:p,reload:C,reply:h,reopen:d,rate:g}};
@@ -1 +1 @@
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
+ "use strict";import{formatDateTime}from"@webority/mobile-core";import{Button,Card,Input,Spinner,StatusBadge,Text,useTheme}from"@webority/mobile-ui";import{useMemo,useState}from"react";import{ScrollView,StyleSheet,View}from"react-native";import{useSupportConfig,useSupportTicket,useSupportTimeZone}from"../SupportProvider.js";import{validateBody,validateFiles}from"../validation.js";import{AttachmentList,ImageViewer}from"./Attachments.js";import{PhotoTray}from"./PhotoTray.js";import{STATUS_TONES}from"./SupportTicketList.js";import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";const MessageBubble=({message:e,styles:t,timeZone:s,onOpenImage:i})=>{if("System"===e.authorKind)return _jsx(View,{style:t.system,testID:`support-message-${e.id}`,children:_jsx(Text,{variant:"caption",color:"tertiary",align:"center",children:`${e.body}, ${formatDateTime(e.createdDateTimeUtc,{zone:s})}`})});const a="Customer"===e.authorKind;return _jsx(View,{style:[t.bubbleRow,a?t.mineRow:t.theirsRow],testID:`support-message-${e.id}`,children:_jsxs(View,{style:[t.bubble,a?t.mine:t.theirs],children:[_jsx(Text,{variant:"label",color:a?"primary":"secondary",children:e.authorName??"-"}),_jsx(Text,{variant:"body",selectable:!0,children:e.body}),_jsx(AttachmentList,{attachments:e.attachments,onOpenImage:i}),_jsx(Text,{variant:"caption",color:"tertiary",children:formatDateTime(e.createdDateTimeUtc,{zone:s})})]})})},SATISFACTION_TEXT={Satisfied:"You 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(""),[S,j]=useState([]),[T,f]=useState(null),[_,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 D=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 ${formatDateTime(o.createdDateTimeUtc,{zone: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"===_?"solid":"outline",tone:"success",accessibilityState:{selected:"Satisfied"===_},onPress:()=>w("Satisfied"),testID:"support-rate-satisfied"}),_jsx(Button,{title:"Not satisfied",size:"sm",leftIcon:"dislike",variant:"Unsatisfied"===_?"solid":"outline",tone:"error",accessibilityState:{selected:"Unsatisfied"===_},onPress:()=>w("Unsatisfied"),testID:"support-rate-unsatisfied"})]}),_&&_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()=>{_&&await g({satisfaction:_,comment:v})&&(w(null),I(""))})()},testID:"support-rating-send"})]})]}),_jsx(View,{style:i.thread,accessibilityLabel:"Conversation",children:D.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:T??void 0,testID:"support-reply-input"}),a&&_jsx(PhotoTray,{photos:S,onChange:j,remaining:a.maxAttachmentsPerMessage-S.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(S,a):e?void 0:"Write a message.";t?f(t):(f(null),await m({body:e,files:S})&&(b(""),j([])))})()},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
- "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
+ "use strict";import{formatDateTime}from"@webority/mobile-core";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{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:x,loadingMore:h,error:g,loadMoreError:b,refresh:y,loadMore:S}=useSupportTickets({pageSize:r}),j=useSupportTimeZone(),_=(l?.total??0)>0,T=null!==c||""!==u.trim(),f=_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:()=>{y()}})]}),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=h?_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:()=>{S()}})]}):null;return _jsx(FlatList,{testID:i,data:n,keyExtractor:e=>e.id,contentContainerStyle:a.content,ListHeaderComponent:f,ListFooterComponent:v,onEndReached:()=>{S()},onEndReachedThreshold:.5,refreshControl:_jsx(RefreshControl,{refreshing:x,onRefresh:()=>{y()}}),ListEmptyComponent:m?_jsx(View,{style:a.footer,children:_jsx(Spinner,{accessibilityLabel:"Loading your tickets"})}):g?null:_jsx(EmptyState,_?{title:"No tickets match",description:T?"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 ${formatDateTime(t.updatedDateTimeUtc,{zone: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
- "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
+ "use strict";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,3 +1,4 @@
1
+ import type { ZoneOption } from '@webority/mobile-core';
1
2
  import type React from 'react';
2
3
  import { SupportApiError, type SupportClient } from './client';
3
4
  import type { NewSupportTicketInput, SupportAttachmentView, SupportContactInfo, SupportRatingInput, SupportReplyInput, SupportTicketCounts, SupportTicketDetail, SupportTicketStatus, SupportTicketSummary } from './types';
@@ -18,8 +19,11 @@ export interface SupportProviderProps {
18
19
  * attachments show their name and size only.
19
20
  */
20
21
  openFile?: SupportFileOpener;
21
- /** IANA time zone ticket times show in. Defaults to `Asia/Kolkata` (IST). */
22
- timeZone?: string;
22
+ /**
23
+ * The zone ticket times show in: `'IST'`, `'viewer'`, `'UTC'` or an IANA name. Defaults to
24
+ * the zone set with mobile-core's `configureFormatters` (IST unless the app changed it).
25
+ */
26
+ timeZone?: ZoneOption;
23
27
  children: React.ReactNode;
24
28
  }
25
29
  export declare const SupportProvider: React.FC<SupportProviderProps>;
@@ -29,7 +33,7 @@ export declare const useAttachmentLoader: () => (attachment: Pick<SupportAttachm
29
33
  /** @internal Used by the file rows; null when the app gave no `openFile`. */
30
34
  export declare const useAttachmentOpener: () => AttachmentOpener | null;
31
35
  /** @internal Used by the ticket screens to format times. */
32
- export declare const useSupportTimeZone: () => string;
36
+ export declare const useSupportTimeZone: () => ZoneOption | undefined;
33
37
  export interface SupportConfigState {
34
38
  config: SupportContactInfo | null;
35
39
  loading: boolean;
@@ -1,11 +1,3 @@
1
- /** The zone ticket times show in unless the app passes `timeZone` to `SupportProvider`. */
2
- export declare const DEFAULT_TIME_ZONE = "Asia/Kolkata";
3
- /**
4
- * A timestamp as `07-Oct-2026, 2:30 PM` in an IANA time zone. The backend sends UTC.
5
- * Built from `formatToParts`, because the assembled Intl string varies by engine
6
- * (month spelling, a narrow no-break space before AM/PM).
7
- */
8
- export declare const formatWhen: (utc: string | null | undefined, timeZone?: string) => string;
9
1
  /** A byte count as a person reads it. */
10
2
  export declare const formatBytes: (bytes: number) => string;
11
3
  export declare const isImage: (contentType: string) => boolean;
@@ -1,3 +1,4 @@
1
+ import type { ZoneOption } from '@webority/mobile-core';
1
2
  import type React from 'react';
2
3
  import { SupportApiError, type SupportClient } from './client.js';
3
4
  import type { NewSupportTicketInput, SupportAttachmentView, SupportContactInfo, SupportRatingInput, SupportReplyInput, SupportTicketCounts, SupportTicketDetail, SupportTicketStatus, SupportTicketSummary } from './types.js';
@@ -18,8 +19,11 @@ export interface SupportProviderProps {
18
19
  * attachments show their name and size only.
19
20
  */
20
21
  openFile?: SupportFileOpener;
21
- /** IANA time zone ticket times show in. Defaults to `Asia/Kolkata` (IST). */
22
- timeZone?: string;
22
+ /**
23
+ * The zone ticket times show in: `'IST'`, `'viewer'`, `'UTC'` or an IANA name. Defaults to
24
+ * the zone set with mobile-core's `configureFormatters` (IST unless the app changed it).
25
+ */
26
+ timeZone?: ZoneOption;
23
27
  children: React.ReactNode;
24
28
  }
25
29
  export declare const SupportProvider: React.FC<SupportProviderProps>;
@@ -29,7 +33,7 @@ export declare const useAttachmentLoader: () => (attachment: Pick<SupportAttachm
29
33
  /** @internal Used by the file rows; null when the app gave no `openFile`. */
30
34
  export declare const useAttachmentOpener: () => AttachmentOpener | null;
31
35
  /** @internal Used by the ticket screens to format times. */
32
- export declare const useSupportTimeZone: () => string;
36
+ export declare const useSupportTimeZone: () => ZoneOption | undefined;
33
37
  export interface SupportConfigState {
34
38
  config: SupportContactInfo | null;
35
39
  loading: boolean;
@@ -1,11 +1,3 @@
1
- /** The zone ticket times show in unless the app passes `timeZone` to `SupportProvider`. */
2
- export declare const DEFAULT_TIME_ZONE = "Asia/Kolkata";
3
- /**
4
- * A timestamp as `07-Oct-2026, 2:30 PM` in an IANA time zone. The backend sends UTC.
5
- * Built from `formatToParts`, because the assembled Intl string varies by engine
6
- * (month spelling, a narrow no-break space before AM/PM).
7
- */
8
- export declare const formatWhen: (utc: string | null | undefined, timeZone?: string) => string;
9
1
  /** A byte count as a person reads it. */
10
2
  export declare const formatBytes: (bytes: number) => string;
11
3
  export declare const isImage: (contentType: string) => boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority/mobile-support",
3
- "version": "0.2.1",
3
+ "version": "0.3.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",
@@ -70,8 +70,8 @@
70
70
  "typecheck": "tsc --noEmit"
71
71
  },
72
72
  "peerDependencies": {
73
- "@webority/mobile-core": ">=0.2.0 <0.3.0",
74
- "@webority/mobile-ui": ">=0.2.0 <0.3.0",
73
+ "@webority/mobile-core": ">=0.3.0 <0.4.0",
74
+ "@webority/mobile-ui": ">=0.3.0 <0.4.0",
75
75
  "expo": ">=57.0.0",
76
76
  "expo-clipboard": ">=57.0.0",
77
77
  "expo-file-system": ">=57.0.0",