@planningcenter/chat-react-native 3.38.0-rc.0 → 3.38.0-rc.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/build/components/conversation/message_list.d.ts +10 -0
  2. package/build/components/conversation/message_list.d.ts.map +1 -0
  3. package/build/components/conversation/message_list.js +13 -0
  4. package/build/components/conversation/message_list.js.map +1 -0
  5. package/build/components/conversations/conversations.d.ts.map +1 -1
  6. package/build/components/conversations/conversations.js +6 -16
  7. package/build/components/conversations/conversations.js.map +1 -1
  8. package/build/components/conversations/conversations_blank_state.d.ts +8 -0
  9. package/build/components/conversations/conversations_blank_state.d.ts.map +1 -0
  10. package/build/components/conversations/conversations_blank_state.js +25 -0
  11. package/build/components/conversations/conversations_blank_state.js.map +1 -0
  12. package/build/components/display/conversation_avatar.d.ts +2 -1
  13. package/build/components/display/conversation_avatar.d.ts.map +1 -1
  14. package/build/components/display/conversation_avatar.js +6 -5
  15. package/build/components/display/conversation_avatar.js.map +1 -1
  16. package/build/components/display/emoji_avatar.d.ts +3 -1
  17. package/build/components/display/emoji_avatar.d.ts.map +1 -1
  18. package/build/components/display/emoji_avatar.js +2 -2
  19. package/build/components/display/emoji_avatar.js.map +1 -1
  20. package/build/components/display/icon_avatar.d.ts +3 -1
  21. package/build/components/display/icon_avatar.d.ts.map +1 -1
  22. package/build/components/display/icon_avatar.js +2 -2
  23. package/build/components/display/icon_avatar.js.map +1 -1
  24. package/build/hooks/groups/use_group_chat_conversation_payload.d.ts.map +1 -1
  25. package/build/hooks/groups/use_group_chat_conversation_payload.js +1 -0
  26. package/build/hooks/groups/use_group_chat_conversation_payload.js.map +1 -1
  27. package/build/hooks/index.d.ts +1 -0
  28. package/build/hooks/index.d.ts.map +1 -1
  29. package/build/hooks/index.js +1 -0
  30. package/build/hooks/index.js.map +1 -1
  31. package/build/hooks/use_preview_avatar_diameter.d.ts +2 -0
  32. package/build/hooks/use_preview_avatar_diameter.d.ts.map +1 -0
  33. package/build/hooks/use_preview_avatar_diameter.js +11 -0
  34. package/build/hooks/use_preview_avatar_diameter.js.map +1 -0
  35. package/build/jest.js +1 -1
  36. package/build/jest.js.map +1 -1
  37. package/build/screens/age_check/age_check_underage_screen.js +1 -1
  38. package/build/screens/age_check/age_check_underage_screen.js.map +1 -1
  39. package/build/screens/avatar_picker/avatar_picker_screen.d.ts.map +1 -1
  40. package/build/screens/avatar_picker/avatar_picker_screen.js +11 -9
  41. package/build/screens/avatar_picker/avatar_picker_screen.js.map +1 -1
  42. package/build/screens/avatar_picker/avatar_preview.d.ts.map +1 -1
  43. package/build/screens/avatar_picker/avatar_preview.js +13 -5
  44. package/build/screens/avatar_picker/avatar_preview.js.map +1 -1
  45. package/build/screens/avatar_picker/emoji_tab.d.ts.map +1 -1
  46. package/build/screens/avatar_picker/emoji_tab.js +3 -7
  47. package/build/screens/avatar_picker/emoji_tab.js.map +1 -1
  48. package/build/screens/avatar_picker/upload_tab.d.ts.map +1 -1
  49. package/build/screens/avatar_picker/upload_tab.js +2 -1
  50. package/build/screens/avatar_picker/upload_tab.js.map +1 -1
  51. package/build/screens/conversation_details_screen.d.ts.map +1 -1
  52. package/build/screens/conversation_details_screen.js +5 -2
  53. package/build/screens/conversation_details_screen.js.map +1 -1
  54. package/build/screens/conversation_filter_recipients/components/header_row.d.ts.map +1 -1
  55. package/build/screens/conversation_filter_recipients/components/header_row.js +3 -2
  56. package/build/screens/conversation_filter_recipients/components/header_row.js.map +1 -1
  57. package/build/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.d.ts.map +1 -1
  58. package/build/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.js +47 -18
  59. package/build/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.js.map +1 -1
  60. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts +2 -1
  61. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts.map +1 -1
  62. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js +23 -26
  63. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js.map +1 -1
  64. package/build/screens/conversation_filter_recipients/types.d.ts +1 -1
  65. package/build/screens/conversation_filter_recipients/types.d.ts.map +1 -1
  66. package/build/screens/conversation_filter_recipients/types.js.map +1 -1
  67. package/build/screens/conversation_screen.d.ts.map +1 -1
  68. package/build/screens/conversation_screen.js +3 -7
  69. package/build/screens/conversation_screen.js.map +1 -1
  70. package/build/screens/conversation_select_recipients/components/recipient_link_row.d.ts +1 -1
  71. package/build/screens/conversation_select_recipients/components/recipient_link_row.d.ts.map +1 -1
  72. package/build/screens/conversation_select_recipients/components/recipient_link_row.js +3 -3
  73. package/build/screens/conversation_select_recipients/components/recipient_link_row.js.map +1 -1
  74. package/build/screens/conversation_select_recipients/components/team_recipient_row.d.ts.map +1 -1
  75. package/build/screens/conversation_select_recipients/components/team_recipient_row.js +1 -1
  76. package/build/screens/conversation_select_recipients/components/team_recipient_row.js.map +1 -1
  77. package/build/screens/team_conversation_screen.d.ts.map +1 -1
  78. package/build/screens/team_conversation_screen.js +24 -1
  79. package/build/screens/team_conversation_screen.js.map +1 -1
  80. package/build/utils/client/client.d.ts +1 -1
  81. package/build/utils/client/client.d.ts.map +1 -1
  82. package/build/utils/client/client.js +7 -6
  83. package/build/utils/client/client.js.map +1 -1
  84. package/build/utils/client/instrumented_fetch.js +3 -5
  85. package/build/utils/client/instrumented_fetch.js.map +1 -1
  86. package/package.json +4 -4
  87. package/src/__tests__/hooks/use_group_chat_conversation_payload.test.tsx +50 -0
  88. package/src/__tests__/jest.ts +1 -1
  89. package/src/__tests__/utils/client.ts +32 -0
  90. package/src/components/conversation/__tests__/message_list.test.tsx +14 -0
  91. package/src/components/conversation/message_list.tsx +42 -0
  92. package/src/components/conversations/conversations.tsx +9 -16
  93. package/src/components/conversations/conversations_blank_state.tsx +42 -0
  94. package/src/components/display/conversation_avatar.tsx +7 -5
  95. package/src/components/display/emoji_avatar.tsx +10 -2
  96. package/src/components/display/icon_avatar.tsx +10 -2
  97. package/src/hooks/groups/use_group_chat_conversation_payload.ts +1 -0
  98. package/src/hooks/index.ts +1 -0
  99. package/src/hooks/use_preview_avatar_diameter.ts +12 -0
  100. package/src/jest.ts +1 -1
  101. package/src/screens/age_check/age_check_underage_screen.tsx +1 -1
  102. package/src/screens/avatar_picker/avatar_picker_screen.tsx +25 -9
  103. package/src/screens/avatar_picker/avatar_preview.tsx +14 -5
  104. package/src/screens/avatar_picker/emoji_tab.tsx +3 -6
  105. package/src/screens/avatar_picker/upload_tab.tsx +2 -0
  106. package/src/screens/conversation_details_screen.tsx +10 -1
  107. package/src/screens/conversation_filter_recipients/components/header_row.tsx +3 -2
  108. package/src/screens/conversation_filter_recipients/hooks/__tests__/use_service_types_with_teams.test.ts +108 -0
  109. package/src/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.tsx +46 -19
  110. package/src/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.ts +31 -29
  111. package/src/screens/conversation_filter_recipients/types.tsx +1 -1
  112. package/src/screens/conversation_screen.tsx +5 -14
  113. package/src/screens/conversation_select_recipients/components/recipient_link_row.tsx +6 -4
  114. package/src/screens/conversation_select_recipients/components/team_recipient_row.tsx +2 -1
  115. package/src/screens/team_conversation_screen.tsx +33 -1
  116. package/src/utils/client/__tests__/instrumented_fetch.test.ts +9 -5
  117. package/src/utils/client/client.ts +9 -7
  118. package/src/utils/client/instrumented_fetch.ts +3 -6
@@ -1 +1 @@
1
- {"version":3,"file":"recipient_link_row.js","sourceRoot":"","sources":["../../../../src/screens/conversation_select_recipients/components/recipient_link_row.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AAWzD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,EAC/B,OAAO,EACP,kBAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,KAAK,EACL,QAAQ,GACc,EAAE,EAAE;IAC1B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAE1B,OAAO,CACL,CAAC,iBAAiB,CAChB,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAClB,OAAO,CAAC,CAAC,OAAO,CAAC,CACjB,iBAAiB,CAAC,MAAM,CACxB,kBAAkB,CAAC,CAAC,kBAAkB,CAAC,CACvC,iBAAiB,CAAC,CAAC,iBAAiB,CAAC,CAErC;MAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CACjC;QAAA,CAAC,QAAQ,IAAI,CACX,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,EAAG,CACpF,CACD;QAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAC1B;UAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAC1C;YAAA,CAAC,KAAK,CACR;UAAA,EAAE,IAAI,CACN;UAAA,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CACxC;YAAA,CAAC,QAAQ,CACX;UAAA,EAAE,IAAI,CACR;QAAA,EAAE,IAAI,CACN;QAAA,CAAC,QAAQ,CAAC,EAAE,KAAK,KAAK,IAAI,CACxB,CAAC,IAAI,CAAC,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAG,CACnE,CACH;MAAA,EAAE,IAAI,CACR;IAAA,EAAE,iBAAiB,CAAC,CACrB,CAAA;AACH,CAAC,CAAA;AAED,MAAM,YAAY,GAAG,EAAE,GAAG,CAAC,CAAA;AAC3B,MAAM,eAAe,GAAG,EAAE,CAAA;AAC1B,MAAM,gBAAgB,GAAG,eAAe,GAAG,YAAY,CAAA;AAEvD,MAAM,SAAS,GAAG,GAAG,EAAE;IACrB,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAA;IAExB,OAAO,UAAU,CAAC,MAAM,CAAC;QACvB,GAAG,EAAE;YACH,WAAW,EAAE,EAAE;SAChB;QACD,cAAc,EAAE;YACd,aAAa,EAAE,KAAK;YACpB,UAAU,EAAE,QAAQ;YACpB,GAAG,EAAE,EAAE;YACP,eAAe,EAAE,EAAE;YACnB,YAAY,EAAE,EAAE;YAChB,iBAAiB,EAAE,CAAC;YACpB,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,uBAAuB;SAClD;QACD,KAAK,EAAE;YACL,KAAK,EAAE,eAAe;YACtB,MAAM,EAAE,gBAAgB;YACxB,YAAY,EAAE,CAAC;SAChB;QACD,KAAK,EAAE;YACL,UAAU,EAAE,wBAAwB;YACpC,UAAU,EAAE,CAAC;SACd;QACD,OAAO,EAAE;YACP,UAAU,EAAE,CAAC;YACb,GAAG,EAAE,CAAC;SACP;QACD,IAAI,EAAE;YACJ,UAAU,EAAE,MAAM;YAClB,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,wBAAwB;SAC7C;KACF,CAAC,CAAA;AACJ,CAAC,CAAA","sourcesContent":["import { PlatformPressable } from '@react-navigation/elements'\nimport React from 'react'\nimport { Platform, StyleSheet, View } from 'react-native'\nimport { Icon, Image, Text } from '../../../components'\nimport { useTheme } from '../../../hooks'\nimport { platformFontWeightMedium } from '../../../utils'\n\ninterface RecipientLinkRowProps {\n onPress: () => void\n accessibilityLabel: string\n accessibilityHint: string\n imageUri?: string\n title: string\n subtitle: string\n}\n\nexport const RecipientLinkRow = ({\n onPress,\n accessibilityLabel,\n accessibilityHint,\n imageUri,\n title,\n subtitle,\n}: RecipientLinkRowProps) => {\n const styles = useStyles()\n\n return (\n <PlatformPressable\n style={styles.row}\n onPress={onPress}\n accessibilityRole=\"link\"\n accessibilityLabel={accessibilityLabel}\n accessibilityHint={accessibilityHint}\n >\n <View style={styles.innerContainer}>\n {imageUri && (\n <Image source={{ uri: imageUri }} resizeMode=\"cover\" style={styles.image} alt=\"\" />\n )}\n <View style={styles.content}>\n <Text style={styles.title} numberOfLines={2}>\n {title}\n </Text>\n <Text variant=\"tertiary\" numberOfLines={1}>\n {subtitle}\n </Text>\n </View>\n {Platform.OS === 'ios' && (\n <Icon name=\"general.rightChevron\" size={16} style={styles.icon} />\n )}\n </View>\n </PlatformPressable>\n )\n}\n\nconst ASPECT_RATIO = 16 / 9\nconst THUMBNAIL_WIDTH = 80\nconst THUMBNAIL_HEIGHT = THUMBNAIL_WIDTH / ASPECT_RATIO\n\nconst useStyles = () => {\n const theme = useTheme()\n\n return StyleSheet.create({\n row: {\n paddingLeft: 16,\n },\n innerContainer: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 12,\n paddingVertical: 16,\n paddingRight: 16,\n borderBottomWidth: 1,\n borderColor: theme.colors.fillColorNeutral050Base,\n },\n image: {\n width: THUMBNAIL_WIDTH,\n height: THUMBNAIL_HEIGHT,\n borderRadius: 4,\n },\n title: {\n fontWeight: platformFontWeightMedium,\n flexShrink: 1,\n },\n content: {\n flexShrink: 1,\n gap: 2,\n },\n icon: {\n marginLeft: 'auto',\n color: theme.colors.iconColorDefaultDisabled,\n },\n })\n}\n"]}
1
+ {"version":3,"file":"recipient_link_row.js","sourceRoot":"","sources":["../../../../src/screens/conversation_select_recipients/components/recipient_link_row.tsx"],"names":[],"mappings":"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","sourcesContent":["import { PlatformPressable } from '@react-navigation/elements'\nimport React from 'react'\nimport { Platform, StyleSheet, View } from 'react-native'\nimport { Icon, Image, Text } from '../../../components'\nimport { useTheme } from '../../../hooks'\nimport { platformFontWeightMedium } from '../../../utils'\n\ninterface RecipientLinkRowProps {\n onPress: () => void\n accessibilityLabel: string\n accessibilityHint: string\n imageUri?: string\n title: string\n subtitle?: string\n}\n\nexport const RecipientLinkRow = ({\n onPress,\n accessibilityLabel,\n accessibilityHint,\n imageUri,\n title,\n subtitle,\n}: RecipientLinkRowProps) => {\n const styles = useStyles()\n\n return (\n <PlatformPressable\n style={styles.row}\n onPress={onPress}\n accessibilityRole=\"link\"\n accessibilityLabel={accessibilityLabel}\n accessibilityHint={accessibilityHint}\n >\n <View style={styles.innerContainer}>\n {imageUri && (\n <Image source={{ uri: imageUri }} resizeMode=\"cover\" style={styles.image} alt=\"\" />\n )}\n <View style={styles.content}>\n <Text style={styles.title} numberOfLines={2}>\n {title}\n </Text>\n {subtitle && (\n <Text variant=\"tertiary\" numberOfLines={1}>\n {subtitle}\n </Text>\n )}\n </View>\n {Platform.OS === 'ios' && (\n <Icon name=\"general.rightChevron\" size={16} style={styles.icon} />\n )}\n </View>\n </PlatformPressable>\n )\n}\n\nconst ASPECT_RATIO = 16 / 9\nconst THUMBNAIL_WIDTH = 80\nconst THUMBNAIL_HEIGHT = THUMBNAIL_WIDTH / ASPECT_RATIO\n\nconst useStyles = () => {\n const theme = useTheme()\n\n return StyleSheet.create({\n row: {\n paddingLeft: 16,\n },\n innerContainer: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 12,\n paddingVertical: 16,\n paddingRight: 16,\n borderBottomWidth: 1,\n borderColor: theme.colors.fillColorNeutral050Base,\n },\n image: {\n width: THUMBNAIL_WIDTH,\n height: THUMBNAIL_HEIGHT,\n borderRadius: 4,\n },\n title: {\n fontWeight: platformFontWeightMedium,\n flexShrink: 1,\n },\n content: {\n flexShrink: 1,\n gap: 2,\n },\n icon: {\n marginLeft: 'auto',\n color: theme.colors.iconColorDefaultDisabled,\n },\n })\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"team_recipient_row.d.ts","sourceRoot":"","sources":["../../../../src/screens/conversation_select_recipients/components/team_recipient_row.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAA;AAGjF,UAAU,qBAAqB;IAC7B,WAAW,EAAE,oBAAoB,CAAA;IACjC,OAAO,EAAE,CAAC,KAAK,EAAE,oBAAoB,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;CACxD;AAED,eAAO,MAAM,gBAAgB,GAAI,0BAA0B,qBAAqB,gCAa/E,CAAA"}
1
+ {"version":3,"file":"team_recipient_row.d.ts","sourceRoot":"","sources":["../../../../src/screens/conversation_select_recipients/components/team_recipient_row.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAA;AAGjF,UAAU,qBAAqB;IAC7B,WAAW,EAAE,oBAAoB,CAAA;IACjC,OAAO,EAAE,CAAC,KAAK,EAAE,oBAAoB,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;CACxD;AAED,eAAO,MAAM,gBAAgB,GAAI,0BAA0B,qBAAqB,gCAc/E,CAAA"}
@@ -2,7 +2,7 @@ import { pluralize } from '../../../utils';
2
2
  import { RecipientLinkRow } from './recipient_link_row';
3
3
  export const TeamRecipientRow = ({ serviceType, onPress }) => {
4
4
  const serviceTypeAccessibilityLabel = `Select ${pluralize(serviceType.teams.length, 'team')} for ${serviceType.name}`;
5
- const teamNames = serviceType.teams.map(team => team.name).join(', ');
5
+ const teamNames = serviceType.id > 0 ? serviceType.teams.map(team => team.name).join(', ') : undefined;
6
6
  return (<RecipientLinkRow title={serviceType.name} subtitle={teamNames} onPress={() => onPress(serviceType.teams)} accessibilityLabel={serviceTypeAccessibilityLabel} accessibilityHint={`Selects Service Type's teams as recipients and navigates to the final screen to finish creating the conversation`}/>);
7
7
  };
8
8
  //# sourceMappingURL=team_recipient_row.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"team_recipient_row.js","sourceRoot":"","sources":["../../../../src/screens/conversation_select_recipients/components/team_recipient_row.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAOvD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,EAAE,WAAW,EAAE,OAAO,EAAyB,EAAE,EAAE;IAClF,MAAM,6BAA6B,GAAG,UAAU,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,WAAW,CAAC,IAAI,EAAE,CAAA;IACrH,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAErE,OAAO,CACL,CAAC,gBAAgB,CACf,KAAK,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CACxB,QAAQ,CAAC,CAAC,SAAS,CAAC,CACpB,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAC1C,kBAAkB,CAAC,CAAC,6BAA6B,CAAC,CAClD,iBAAiB,CAAC,CAAC,kHAAkH,CAAC,EACtI,CACH,CAAA;AACH,CAAC,CAAA","sourcesContent":["import { pluralize } from '../../../utils'\nimport { ServiceTypeWithTeams } from '../../conversation_filter_recipients/types'\nimport { RecipientLinkRow } from './recipient_link_row'\n\ninterface TeamRecipientRowProps {\n serviceType: ServiceTypeWithTeams\n onPress: (teams: ServiceTypeWithTeams['teams']) => void\n}\n\nexport const TeamRecipientRow = ({ serviceType, onPress }: TeamRecipientRowProps) => {\n const serviceTypeAccessibilityLabel = `Select ${pluralize(serviceType.teams.length, 'team')} for ${serviceType.name}`\n const teamNames = serviceType.teams.map(team => team.name).join(', ')\n\n return (\n <RecipientLinkRow\n title={serviceType.name}\n subtitle={teamNames}\n onPress={() => onPress(serviceType.teams)}\n accessibilityLabel={serviceTypeAccessibilityLabel}\n accessibilityHint={`Selects Service Type's teams as recipients and navigates to the final screen to finish creating the conversation`}\n />\n )\n}\n"]}
1
+ {"version":3,"file":"team_recipient_row.js","sourceRoot":"","sources":["../../../../src/screens/conversation_select_recipients/components/team_recipient_row.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAOvD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,EAAE,WAAW,EAAE,OAAO,EAAyB,EAAE,EAAE;IAClF,MAAM,6BAA6B,GAAG,UAAU,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,WAAW,CAAC,IAAI,EAAE,CAAA;IACrH,MAAM,SAAS,GACb,WAAW,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAEtF,OAAO,CACL,CAAC,gBAAgB,CACf,KAAK,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CACxB,QAAQ,CAAC,CAAC,SAAS,CAAC,CACpB,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAC1C,kBAAkB,CAAC,CAAC,6BAA6B,CAAC,CAClD,iBAAiB,CAAC,CAAC,kHAAkH,CAAC,EACtI,CACH,CAAA;AACH,CAAC,CAAA","sourcesContent":["import { pluralize } from '../../../utils'\nimport { ServiceTypeWithTeams } from '../../conversation_filter_recipients/types'\nimport { RecipientLinkRow } from './recipient_link_row'\n\ninterface TeamRecipientRowProps {\n serviceType: ServiceTypeWithTeams\n onPress: (teams: ServiceTypeWithTeams['teams']) => void\n}\n\nexport const TeamRecipientRow = ({ serviceType, onPress }: TeamRecipientRowProps) => {\n const serviceTypeAccessibilityLabel = `Select ${pluralize(serviceType.teams.length, 'team')} for ${serviceType.name}`\n const teamNames =\n serviceType.id > 0 ? serviceType.teams.map(team => team.name).join(', ') : undefined\n\n return (\n <RecipientLinkRow\n title={serviceType.name}\n subtitle={teamNames}\n onPress={() => onPress(serviceType.teams)}\n accessibilityLabel={serviceTypeAccessibilityLabel}\n accessibilityHint={`Selects Service Type's teams as recipients and navigates to the final screen to finish creating the conversation`}\n />\n )\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"team_conversation_screen.d.ts","sourceRoot":"","sources":["../../src/screens/team_conversation_screen.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgB,iBAAiB,EAAiB,MAAM,0BAA0B,CAAA;AAOzF,MAAM,MAAM,0BAA0B,GAAG;IACvC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG,iBAAiB,CAAC,0BAA0B,CAAC,CAAA;AAEvF,eAAO,MAAM,sBAAsB,GAAI,WAAW,2BAA2B,gCA8B5E,CAAA"}
1
+ {"version":3,"file":"team_conversation_screen.d.ts","sourceRoot":"","sources":["../../src/screens/team_conversation_screen.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgB,iBAAiB,EAAiB,MAAM,0BAA0B,CAAA;AASzF,MAAM,MAAM,0BAA0B,GAAG;IACvC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG,iBAAiB,CAAC,0BAA0B,CAAC,CAAA;AAEvF,eAAO,MAAM,sBAAsB,GAAI,WAAW,2BAA2B,gCAqC5E,CAAA"}
@@ -2,19 +2,22 @@ import { StackActions, useNavigation } from '@react-navigation/native';
2
2
  import { useQuery, useQueryClient } from '@tanstack/react-query';
3
3
  import { useEffect } from 'react';
4
4
  import { DefaultLoading } from '../components/page/loading';
5
+ import BlankState from '../components/primitive/blank_state_primitive';
5
6
  import { useApiClient } from '../hooks';
6
7
  import { findOrCreateServicesConversation } from '../hooks/services/use_find_or_create_services_conversation';
8
+ import { ResponseError } from '../utils/response_error';
7
9
  export const TeamConversationScreen = ({ route }) => {
8
10
  const apiClient = useApiClient();
9
11
  const queryClient = useQueryClient();
10
12
  const navigation = useNavigation();
11
- const { data: conversation } = useQuery({
13
+ const { data: conversation, isError, error, } = useQuery({
12
14
  queryKey: ['team-conversation', route.params.team_ids, route.params.plan_id],
13
15
  queryFn: () => findOrCreateServicesConversation({
14
16
  apiClient,
15
17
  teamIds: route.params.team_ids ?? [],
16
18
  planId: route.params.plan_id,
17
19
  }).then(r => r.conversation),
20
+ retry: (failureCount, err) => !(err instanceof ResponseError) && failureCount < 3,
18
21
  });
19
22
  useEffect(() => {
20
23
  if (!conversation?.id)
@@ -27,6 +30,26 @@ export const TeamConversationScreen = ({ route }) => {
27
30
  queryClient.removeQueries({ queryKey: ['team-conversation'] });
28
31
  };
29
32
  }, [conversation?.id, conversation?.title, navigation, queryClient]);
33
+ if (isError)
34
+ return <TeamConversationError error={error} onGoBack={navigation.goBack}/>;
30
35
  return <DefaultLoading />;
31
36
  };
37
+ function TeamConversationError({ error, onGoBack }) {
38
+ const detail = error instanceof ResponseError
39
+ ? error.errors
40
+ .map(e => e.detail)
41
+ .filter(Boolean)
42
+ .join('\n')
43
+ : '';
44
+ return (<BlankState.Root>
45
+ <BlankState.Imagery name="people.noTextMessage"/>
46
+ <BlankState.Content>
47
+ <BlankState.Heading>Can't start this conversation</BlankState.Heading>
48
+ <BlankState.Text>
49
+ {detail || 'Something went wrong while starting this conversation.'}
50
+ </BlankState.Text>
51
+ </BlankState.Content>
52
+ <BlankState.Button title="Go back" onPress={onGoBack} size="md" accessibilityRole="link"/>
53
+ </BlankState.Root>);
54
+ }
32
55
  //# sourceMappingURL=team_conversation_screen.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"team_conversation_screen.js","sourceRoot":"","sources":["../../src/screens/team_conversation_screen.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAqB,aAAa,EAAE,MAAM,0BAA0B,CAAA;AACzF,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,EAAE,gCAAgC,EAAE,MAAM,4DAA4D,CAAA;AAS7G,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,EAAE,KAAK,EAA+B,EAAE,EAAE;IAC/E,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,WAAW,GAAG,cAAc,EAAE,CAAA;IACpC,MAAM,UAAU,GAAG,aAAa,EAAE,CAAA;IAClC,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,QAAQ,CAAC;QACtC,QAAQ,EAAE,CAAC,mBAAmB,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC;QAC5E,OAAO,EAAE,GAAG,EAAE,CACZ,gCAAgC,CAAC;YAC/B,SAAS;YACT,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE;YACpC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO;SAC7B,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC;KAC/B,CAAC,CAAA;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,YAAY,EAAE,EAAE;YAAE,OAAM;QAE7B,UAAU,CAAC,QAAQ,CACjB,YAAY,CAAC,OAAO,CAAC,cAAc,EAAE;YACnC,eAAe,EAAE,YAAY,CAAC,EAAE;YAChC,KAAK,EAAE,YAAY,CAAC,KAAK;SAC1B,CAAC,CACH,CAAA;QAED,OAAO,GAAG,EAAE;YACV,WAAW,CAAC,aAAa,CAAC,EAAE,QAAQ,EAAE,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAA;QAChE,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,YAAY,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC,CAAA;IAEpE,OAAO,CAAC,cAAc,CAAC,AAAD,EAAG,CAAA;AAC3B,CAAC,CAAA","sourcesContent":["import { StackActions, StaticScreenProps, useNavigation } from '@react-navigation/native'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useEffect } from 'react'\nimport { DefaultLoading } from '../components/page/loading'\nimport { useApiClient } from '../hooks'\nimport { findOrCreateServicesConversation } from '../hooks/services/use_find_or_create_services_conversation'\n\nexport type TeamConversationRouteProps = {\n plan_id?: number\n team_ids?: number[]\n}\n\nexport type TeamConversationScreenProps = StaticScreenProps<TeamConversationRouteProps>\n\nexport const TeamConversationScreen = ({ route }: TeamConversationScreenProps) => {\n const apiClient = useApiClient()\n const queryClient = useQueryClient()\n const navigation = useNavigation()\n const { data: conversation } = useQuery({\n queryKey: ['team-conversation', route.params.team_ids, route.params.plan_id],\n queryFn: () =>\n findOrCreateServicesConversation({\n apiClient,\n teamIds: route.params.team_ids ?? [],\n planId: route.params.plan_id,\n }).then(r => r.conversation),\n })\n\n useEffect(() => {\n if (!conversation?.id) return\n\n navigation.dispatch(\n StackActions.replace('Conversation', {\n conversation_id: conversation.id,\n title: conversation.title,\n })\n )\n\n return () => {\n queryClient.removeQueries({ queryKey: ['team-conversation'] })\n }\n }, [conversation?.id, conversation?.title, navigation, queryClient])\n\n return <DefaultLoading />\n}\n"]}
1
+ {"version":3,"file":"team_conversation_screen.js","sourceRoot":"","sources":["../../src/screens/team_conversation_screen.tsx"],"names":[],"mappings":"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","sourcesContent":["import { StackActions, StaticScreenProps, useNavigation } from '@react-navigation/native'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useEffect } from 'react'\nimport { DefaultLoading } from '../components/page/loading'\nimport BlankState from '../components/primitive/blank_state_primitive'\nimport { useApiClient } from '../hooks'\nimport { findOrCreateServicesConversation } from '../hooks/services/use_find_or_create_services_conversation'\nimport { ResponseError } from '../utils/response_error'\n\nexport type TeamConversationRouteProps = {\n plan_id?: number\n team_ids?: number[]\n}\n\nexport type TeamConversationScreenProps = StaticScreenProps<TeamConversationRouteProps>\n\nexport const TeamConversationScreen = ({ route }: TeamConversationScreenProps) => {\n const apiClient = useApiClient()\n const queryClient = useQueryClient()\n const navigation = useNavigation()\n const {\n data: conversation,\n isError,\n error,\n } = useQuery({\n queryKey: ['team-conversation', route.params.team_ids, route.params.plan_id],\n queryFn: () =>\n findOrCreateServicesConversation({\n apiClient,\n teamIds: route.params.team_ids ?? [],\n planId: route.params.plan_id,\n }).then(r => r.conversation),\n retry: (failureCount, err) => !(err instanceof ResponseError) && failureCount < 3,\n })\n\n useEffect(() => {\n if (!conversation?.id) return\n\n navigation.dispatch(\n StackActions.replace('Conversation', {\n conversation_id: conversation.id,\n title: conversation.title,\n })\n )\n\n return () => {\n queryClient.removeQueries({ queryKey: ['team-conversation'] })\n }\n }, [conversation?.id, conversation?.title, navigation, queryClient])\n\n if (isError) return <TeamConversationError error={error} onGoBack={navigation.goBack} />\n\n return <DefaultLoading />\n}\n\nfunction TeamConversationError({ error, onGoBack }: { error: Error; onGoBack: () => void }) {\n const detail =\n error instanceof ResponseError\n ? error.errors\n .map(e => e.detail)\n .filter(Boolean)\n .join('\\n')\n : ''\n\n return (\n <BlankState.Root>\n <BlankState.Imagery name=\"people.noTextMessage\" />\n <BlankState.Content>\n <BlankState.Heading>Can't start this conversation</BlankState.Heading>\n <BlankState.Text>\n {detail || 'Something went wrong while starting this conversation.'}\n </BlankState.Text>\n </BlankState.Content>\n <BlankState.Button title=\"Go back\" onPress={onGoBack} size=\"md\" accessibilityRole=\"link\" />\n </BlankState.Root>\n )\n}\n"]}
@@ -17,7 +17,7 @@ export declare class Client {
17
17
  patch<T extends ApiCollection | ApiResource>(args: PatchRequest): Promise<T>;
18
18
  post<T extends ApiCollection | ApiResource>(args: PostRequest): Promise<T>;
19
19
  delete(args: DeleteRequest): Promise<any>;
20
- handleNotOk: (response: Response) => Promise<never>;
20
+ handleNotOk: (response: Response | Error) => Promise<never>;
21
21
  parseErrorResponse: (response: Response) => Promise<Partial<FailedResponse>>;
22
22
  appUrl(url: string): string;
23
23
  get headers(): {
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/utils/client/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AASxE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAe,MAAM,SAAS,CAAA;AAE3F,MAAM,MAAM,sBAAsB,GAAG,CAAC,SAAS,EAAE,cAAc,KAAK,IAAI,CAAA;AAExE,KAAK,UAAU,GAAG;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvC,sBAAsB,CAAC,EAAE,sBAAsB,CAAA;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,qBAAa,MAAM;IACjB,OAAO,EAAE,MAAM,CAAK;IACpB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAK;IAC3C,sBAAsB,CAAC,EAAE,sBAAsB,CAAA;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAA;gBAED,EAAE,OAAO,EAAE,cAAmB,EAAE,sBAAsB,EAAE,IAAI,EAAE,EAAE,UAAU;IAOhF,GAAG,CAAC,CAAC,SAAS,aAAa,GAAG,WAAW,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC;IA2CxE,KAAK,CAAC,CAAC,SAAS,aAAa,GAAG,WAAW,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,CAAC,CAAC;IAW5E,IAAI,CAAC,CAAC,SAAS,aAAa,GAAG,WAAW,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;IAW1E,MAAM,CAAC,IAAI,EAAE,aAAa;IAShC,WAAW,GAAU,UAAU,QAAQ,oBAUtC;IAED,kBAAkB,GAAU,UAAU,QAAQ,KAAG,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAMhF;IAED,MAAM,CAAC,GAAG,EAAE,MAAM;IAIlB,IAAI,OAAO;;;;MAOV;CACF;AAED,eAAe,MAAM,CAAA"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/utils/client/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AASxE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAe,MAAM,SAAS,CAAA;AAE3F,MAAM,MAAM,sBAAsB,GAAG,CAAC,SAAS,EAAE,cAAc,KAAK,IAAI,CAAA;AAExE,KAAK,UAAU,GAAG;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvC,sBAAsB,CAAC,EAAE,sBAAsB,CAAA;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,qBAAa,MAAM;IACjB,OAAO,EAAE,MAAM,CAAK;IACpB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAK;IAC3C,sBAAsB,CAAC,EAAE,sBAAsB,CAAA;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAA;gBAED,EAAE,OAAO,EAAE,cAAmB,EAAE,sBAAsB,EAAE,IAAI,EAAE,EAAE,UAAU;IAOhF,GAAG,CAAC,CAAC,SAAS,aAAa,GAAG,WAAW,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC;IA2CxE,KAAK,CAAC,CAAC,SAAS,aAAa,GAAG,WAAW,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,CAAC,CAAC;IAW5E,IAAI,CAAC,CAAC,SAAS,aAAa,GAAG,WAAW,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;IAW1E,MAAM,CAAC,IAAI,EAAE,aAAa;IAShC,WAAW,GAAU,UAAU,QAAQ,GAAG,KAAK,oBAY9C;IAED,kBAAkB,GAAU,UAAU,QAAQ,KAAG,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAMhF;IAED,MAAM,CAAC,GAAG,EAAE,MAAM;IAIlB,IAAI,OAAO;;;;MAOV;CACF;AAED,eAAe,MAAM,CAAA"}
@@ -65,14 +65,15 @@ export class Client {
65
65
  return makeRequest(requestArgs).catch(this.handleNotOk);
66
66
  }
67
67
  handleNotOk = async (response) => {
68
+ if (!(response instanceof Response))
69
+ return Promise.reject(response);
70
+ const errorData = await this.parseErrorResponse(response);
71
+ const notOkResponse = response.clone();
72
+ notOkResponse.errors = errorData.errors || [];
68
73
  if (response.status === 401) {
69
- const errorData = await this.parseErrorResponse(response);
70
- this.onUnauthorizedResponse?.({
71
- ...response,
72
- errors: errorData.errors || [],
73
- });
74
+ this.onUnauthorizedResponse?.(notOkResponse);
74
75
  }
75
- return Promise.reject(response);
76
+ return Promise.reject(notOkResponse);
76
77
  };
77
78
  parseErrorResponse = async (response) => {
78
79
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/utils/client/client.ts"],"names":[],"mappings":"AACA,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,WAAW,EAEX,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,mBAAmB,CAAA;AAY1B,MAAM,OAAO,MAAM;IACjB,OAAO,GAAW,EAAE,CAAA;IACpB,cAAc,GAA2B,EAAE,CAAA;IAC3C,sBAAsB,CAAyB;IAC/C,IAAI,CAAS;IAEb,YAAY,EAAE,OAAO,EAAE,cAAc,GAAG,EAAE,EAAE,sBAAsB,EAAE,IAAI,EAAc;QACpF,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,cAAc,GAAG,cAAc,CAAA;QACpC,IAAI,CAAC,sBAAsB,GAAG,sBAAsB,CAAA;QACpD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;IAED,KAAK,CAAC,GAAG,CAAwC,IAAgB;QAC/D,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,CAAA;QACnC,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;QAC/B,MAAM,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAA;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAEjC,MAAM,uBAAuB,CAAC,GAAG,CAAC,CAAA;QAElC,MAAM,WAAW,GAAoB,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;QAE1E,MAAM,WAAW,GAAG,CAAC,EACnB,GAAG,EAAE,UAAU,EACf,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,EACxB,GAAG,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EACrD,GAAG,OAAO,EACE,EAAc,EAAE;YAC5B,OAAO,WAAW,CAAC;gBACjB,MAAM,EAAE,KAAK;gBACb,IAAI,EAAE,CAAC;gBACP,GAAG,EAAE,UAAU;gBACf,GAAG,OAAO;gBACV,OAAO;aACR,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE;gBAC7B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;gBAE1E,iFAAiF;gBACjF,IAAI,KAAK,EAAE,IAAI,EAAE,CAAC;oBAChB,OAAO,WAAW,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAA;gBAC5E,CAAC;qBAAM,CAAC;oBACN,OAAO,OAAO,CAAA;gBAChB,CAAC;YACH,CAAC,CAAC,CAAA;QACJ,CAAC,CAAA;QAED,MAAM,OAAO,GAAG,SAAS;YACvB,CAAC,CAAC,CAAC,CAAgC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAgB,CAAC;YACrE,CAAC,CAAC,CAAC,CAAgC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAoB,CAAC,CAAA;QAE3E,OAAO,yBAAyB,CAAC,OAAO,EAAE,WAAW,CAAC;aACnD,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAa,CAAC;aAC/B,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IAC5B,CAAC;IAED,KAAK,CAAC,KAAK,CAAwC,IAAkB;QACnE,MAAM,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAA;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAEjC,MAAM,WAAW,GAAoB,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;QAEvF,OAAO,wBAAwB,CAAC,WAAW,EAAE,WAAW,CAAC;aACtD,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAa,CAAC;aAC/B,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IAC5B,CAAC;IAED,KAAK,CAAC,IAAI,CAAwC,IAAiB;QACjE,MAAM,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAA;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAEjC,MAAM,WAAW,GAAoB,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAA;QAE/F,OAAO,wBAAwB,CAAC,WAAW,EAAE,WAAW,CAAC;aACtD,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAa,CAAC;aAC/B,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IAC5B,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,IAAmB;QAC9B,MAAM,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAA;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAEjC,MAAM,WAAW,GAAoB,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAA;QAEvE,OAAO,WAAW,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IACzD,CAAC;IAED,WAAW,GAAG,KAAK,EAAE,QAAkB,EAAE,EAAE;QACzC,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAA;YACzD,IAAI,CAAC,sBAAsB,EAAE,CAAC;gBAC5B,GAAG,QAAQ;gBACX,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,EAAE;aACb,CAAC,CAAA;QACtB,CAAC;QAED,OAAO,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IACjC,CAAC,CAAA;IAED,kBAAkB,GAAG,KAAK,EAAE,QAAkB,EAAoC,EAAE;QAClF,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAmB,CAAA;QAC1D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAA;QACX,CAAC;IACH,CAAC,CAAA;IAED,MAAM,CAAC,GAAW;QAChB,OAAO,GAAG,IAAI,CAAC,IAAI,GAAG,GAAG,EAAE,CAAA;IAC7B,CAAC;IAED,IAAI,OAAO;QACT,OAAO;YACL,MAAM,EAAE,0BAA0B;YAClC,cAAc,EAAE,kBAAkB;YAClC,mBAAmB,EAAE,IAAI,CAAC,OAAO;YACjC,GAAG,IAAI,CAAC,cAAc;SACvB,CAAA;IACH,CAAC;CACF;AAED,eAAe,MAAM,CAAA","sourcesContent":["import { ApiCollection, ApiResource, FailedResponse } from '../../types'\nimport {\n concatRecords,\n ensureNoQueryParamsInDev,\n makeRequest,\n MakeRequestArgs,\n throwErrorIfFieldsMissing,\n throwErrorIfQueryParams,\n} from './request_helpers'\nimport { DeleteRequest, GetRequest, PatchRequest, PostRequest, WalkRequest } from './types'\n\nexport type OnUnauthorizedResponse = (_response: FailedResponse) => void\n\ntype ClientArgs = {\n version: string\n defaultHeaders?: Record<string, string>\n onUnauthorizedResponse?: OnUnauthorizedResponse\n root?: string\n}\n\nexport class Client {\n version: string = ''\n defaultHeaders: Record<string, string> = {}\n onUnauthorizedResponse?: OnUnauthorizedResponse\n root?: string\n\n constructor({ version, defaultHeaders = {}, onUnauthorizedResponse, root }: ClientArgs) {\n this.version = version\n this.defaultHeaders = defaultHeaders\n this.onUnauthorizedResponse = onUnauthorizedResponse\n this.root = root\n }\n\n async get<T extends ApiCollection | ApiResource>(args: GetRequest): Promise<T> {\n const { walk, ...data } = args.data\n const isWalking = Boolean(walk)\n const headers = { ...this.headers, ...args.headers }\n const url = this.appUrl(args.url)\n\n await throwErrorIfQueryParams(url)\n\n const requestArgs: MakeRequestArgs = { data, url, action: 'GET', headers }\n\n const walkRequest = ({\n url: requestUrl,\n data: d = { fields: {} },\n acc = { data: [], included: [], meta: {}, links: {} },\n ...options\n }: WalkRequest): Promise<T> => {\n return makeRequest({\n action: 'GET',\n data: d,\n url: requestUrl,\n ...options,\n headers,\n }).then(({ links, ...rest }) => {\n const records = Array.isArray(rest.data) ? concatRecords(acc, rest) : rest\n\n // `next` will have our params in the link so we do not want to pass them back in\n if (links?.next) {\n return walkRequest({ ...options, data: d, url: links.next, acc: records })\n } else {\n return records\n }\n })\n }\n\n const handler = isWalking\n ? (a: MakeRequestArgs | WalkRequest) => walkRequest(a as WalkRequest)\n : (a: MakeRequestArgs | WalkRequest) => makeRequest(a as MakeRequestArgs)\n\n return throwErrorIfFieldsMissing(handler, requestArgs)\n .then(response => response as T)\n .catch(this.handleNotOk)\n }\n\n async patch<T extends ApiCollection | ApiResource>(args: PatchRequest): Promise<T> {\n const headers = { ...this.headers, ...args.headers }\n const url = this.appUrl(args.url)\n\n const requestArgs: MakeRequestArgs = { data: args.data, url, action: 'PATCH', headers }\n\n return ensureNoQueryParamsInDev(makeRequest, requestArgs)\n .then(response => response as T)\n .catch(this.handleNotOk)\n }\n\n async post<T extends ApiCollection | ApiResource>(args: PostRequest): Promise<T> {\n const headers = { ...this.headers, ...args.headers }\n const url = this.appUrl(args.url)\n\n const requestArgs: MakeRequestArgs = { ...args, data: args.data, url, action: 'POST', headers }\n\n return ensureNoQueryParamsInDev(makeRequest, requestArgs)\n .then(response => response as T)\n .catch(this.handleNotOk)\n }\n\n async delete(args: DeleteRequest) {\n const headers = { ...this.headers, ...args.headers }\n const url = this.appUrl(args.url)\n\n const requestArgs: MakeRequestArgs = { url, action: 'DELETE', headers }\n\n return makeRequest(requestArgs).catch(this.handleNotOk)\n }\n\n handleNotOk = async (response: Response) => {\n if (response.status === 401) {\n const errorData = await this.parseErrorResponse(response)\n this.onUnauthorizedResponse?.({\n ...response,\n errors: errorData.errors || [],\n } as FailedResponse)\n }\n\n return Promise.reject(response)\n }\n\n parseErrorResponse = async (response: Response): Promise<Partial<FailedResponse>> => {\n try {\n return (await response.clone().json()) as FailedResponse\n } catch {\n return {}\n }\n }\n\n appUrl(url: string) {\n return `${this.root}${url}`\n }\n\n get headers() {\n return {\n Accept: 'application/vnd.api+json',\n 'Content-Type': 'application/json',\n 'X-PCO-API-Version': this.version,\n ...this.defaultHeaders,\n }\n }\n}\n\nexport default Client\n"]}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/utils/client/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiCollection, ApiResource, FailedResponse } from '../../types'\nimport {\n concatRecords,\n ensureNoQueryParamsInDev,\n makeRequest,\n MakeRequestArgs,\n throwErrorIfFieldsMissing,\n throwErrorIfQueryParams,\n} from './request_helpers'\nimport { DeleteRequest, GetRequest, PatchRequest, PostRequest, WalkRequest } from './types'\n\nexport type OnUnauthorizedResponse = (_response: FailedResponse) => void\n\ntype ClientArgs = {\n version: string\n defaultHeaders?: Record<string, string>\n onUnauthorizedResponse?: OnUnauthorizedResponse\n root?: string\n}\n\nexport class Client {\n version: string = ''\n defaultHeaders: Record<string, string> = {}\n onUnauthorizedResponse?: OnUnauthorizedResponse\n root?: string\n\n constructor({ version, defaultHeaders = {}, onUnauthorizedResponse, root }: ClientArgs) {\n this.version = version\n this.defaultHeaders = defaultHeaders\n this.onUnauthorizedResponse = onUnauthorizedResponse\n this.root = root\n }\n\n async get<T extends ApiCollection | ApiResource>(args: GetRequest): Promise<T> {\n const { walk, ...data } = args.data\n const isWalking = Boolean(walk)\n const headers = { ...this.headers, ...args.headers }\n const url = this.appUrl(args.url)\n\n await throwErrorIfQueryParams(url)\n\n const requestArgs: MakeRequestArgs = { data, url, action: 'GET', headers }\n\n const walkRequest = ({\n url: requestUrl,\n data: d = { fields: {} },\n acc = { data: [], included: [], meta: {}, links: {} },\n ...options\n }: WalkRequest): Promise<T> => {\n return makeRequest({\n action: 'GET',\n data: d,\n url: requestUrl,\n ...options,\n headers,\n }).then(({ links, ...rest }) => {\n const records = Array.isArray(rest.data) ? concatRecords(acc, rest) : rest\n\n // `next` will have our params in the link so we do not want to pass them back in\n if (links?.next) {\n return walkRequest({ ...options, data: d, url: links.next, acc: records })\n } else {\n return records\n }\n })\n }\n\n const handler = isWalking\n ? (a: MakeRequestArgs | WalkRequest) => walkRequest(a as WalkRequest)\n : (a: MakeRequestArgs | WalkRequest) => makeRequest(a as MakeRequestArgs)\n\n return throwErrorIfFieldsMissing(handler, requestArgs)\n .then(response => response as T)\n .catch(this.handleNotOk)\n }\n\n async patch<T extends ApiCollection | ApiResource>(args: PatchRequest): Promise<T> {\n const headers = { ...this.headers, ...args.headers }\n const url = this.appUrl(args.url)\n\n const requestArgs: MakeRequestArgs = { data: args.data, url, action: 'PATCH', headers }\n\n return ensureNoQueryParamsInDev(makeRequest, requestArgs)\n .then(response => response as T)\n .catch(this.handleNotOk)\n }\n\n async post<T extends ApiCollection | ApiResource>(args: PostRequest): Promise<T> {\n const headers = { ...this.headers, ...args.headers }\n const url = this.appUrl(args.url)\n\n const requestArgs: MakeRequestArgs = { ...args, data: args.data, url, action: 'POST', headers }\n\n return ensureNoQueryParamsInDev(makeRequest, requestArgs)\n .then(response => response as T)\n .catch(this.handleNotOk)\n }\n\n async delete(args: DeleteRequest) {\n const headers = { ...this.headers, ...args.headers }\n const url = this.appUrl(args.url)\n\n const requestArgs: MakeRequestArgs = { url, action: 'DELETE', headers }\n\n return makeRequest(requestArgs).catch(this.handleNotOk)\n }\n\n handleNotOk = async (response: Response | Error) => {\n if (!(response instanceof Response)) return Promise.reject(response)\n\n const errorData = await this.parseErrorResponse(response)\n const notOkResponse = response.clone() as FailedResponse\n notOkResponse.errors = errorData.errors || []\n\n if (response.status === 401) {\n this.onUnauthorizedResponse?.(notOkResponse)\n }\n\n return Promise.reject(notOkResponse)\n }\n\n parseErrorResponse = async (response: Response): Promise<Partial<FailedResponse>> => {\n try {\n return (await response.clone().json()) as FailedResponse\n } catch {\n return {}\n }\n }\n\n appUrl(url: string) {\n return `${this.root}${url}`\n }\n\n get headers() {\n return {\n Accept: 'application/vnd.api+json',\n 'Content-Type': 'application/json',\n 'X-PCO-API-Version': this.version,\n ...this.defaultHeaders,\n }\n }\n}\n\nexport default Client\n"]}
@@ -13,7 +13,7 @@ export async function instrumentedFetch(url, init) {
13
13
  return response;
14
14
  }
15
15
  catch (networkError) {
16
- reportNetworkError(networkError, method, url);
16
+ warnNetworkError(networkError, method, url);
17
17
  throw networkError;
18
18
  }
19
19
  }
@@ -34,11 +34,9 @@ function reportHttpError(response, method, url) {
34
34
  },
35
35
  });
36
36
  }
37
- function reportNetworkError(networkError, method, url) {
37
+ function warnNetworkError(networkError, method, url) {
38
38
  const path = templatePath(url);
39
- const error = new Error(`Network failure ${method} ${path}: ${networkError.message}`);
40
- error.name = 'NetworkError';
41
- Log.reportError(error, {
39
+ console.warn(`Network failure ${method} ${path}: ${networkError.message}`, {
42
40
  scope: 'http',
43
41
  tags: {
44
42
  ...SHARED_TAGS,
@@ -1 +1 @@
1
- {"version":3,"file":"instrumented_fetch.js","sourceRoot":"","sources":["../../../src/utils/client/instrumented_fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kCAAkC,CAAA;AAEtD,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,mBAAmB;CACpB,CAAA;AAEV,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;AAExC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAW,EAAE,IAAiB;IACpE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,KAAK,CAAA;IACnC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QACvC,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,CAAA;QACxD,OAAO,QAAQ,CAAA;IACjB,CAAC;IAAC,OAAO,YAAY,EAAE,CAAC;QACtB,kBAAkB,CAAC,YAAqB,EAAE,MAAM,EAAE,GAAG,CAAC,CAAA;QACtD,MAAM,YAAY,CAAA;IACpB,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,QAAkB,EAAE,MAAc,EAAE,GAAW;IACtE,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAA;IAC3B,IAAI,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAM;IAE7C,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAC9B,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC,CAAA;IAC3D,KAAK,CAAC,IAAI,GAAG,YAAY,MAAM,EAAE,CAAA;IAEjC,GAAG,CAAC,WAAW,CAAC,KAAK,EAAE;QACrB,KAAK,EAAE,MAAM;QACb,IAAI,EAAE;YACJ,GAAG,WAAW;YACd,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC;YAC7B,aAAa,EAAE,MAAM;YACrB,WAAW,EAAE,IAAI;SAClB;KACF,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,YAAmB,EAAE,MAAc,EAAE,GAAW;IAC1E,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAC9B,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,mBAAmB,MAAM,IAAI,IAAI,KAAK,YAAY,CAAC,OAAO,EAAE,CAAC,CAAA;IACrF,KAAK,CAAC,IAAI,GAAG,cAAc,CAAA;IAE3B,GAAG,CAAC,WAAW,CAAC,KAAK,EAAE;QACrB,KAAK,EAAE,MAAM;QACb,IAAI,EAAE;YACJ,GAAG,WAAW;YACd,aAAa,EAAE,MAAM;YACrB,WAAW,EAAE,IAAI;YACjB,YAAY,EAAE,SAAS;SACxB;KACF,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,GAAW;IAC/B,IAAI,IAAY,CAAA;IAChB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAA;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,GAAG,CAAA;IACZ,CAAC;IAED,OAAO,IAAI;SACR,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;SACzD,IAAI,CAAC,GAAG,CAAC,CAAA;AACd,CAAC","sourcesContent":["import { Log } from '../native_adapters/configuration'\n\nconst SHARED_TAGS = {\n team: 'chat',\n package: 'chat-react-native',\n} as const\n\nconst SKIPPED_STATUSES = [401, 403, 404]\n\nexport async function instrumentedFetch(url: string, init: RequestInit): Promise<Response> {\n const method = init.method ?? 'GET'\n try {\n const response = await fetch(url, init)\n if (!response.ok) reportHttpError(response, method, url)\n return response\n } catch (networkError) {\n reportNetworkError(networkError as Error, method, url)\n throw networkError\n }\n}\n\nfunction reportHttpError(response: Response, method: string, url: string) {\n const { status } = response\n if (SKIPPED_STATUSES.includes(status)) return\n\n const path = templatePath(url)\n const error = new Error(`HTTP ${status} ${method} ${path}`)\n error.name = `HTTPError${status}`\n\n Log.reportError(error, {\n scope: 'http',\n tags: {\n ...SHARED_TAGS,\n 'http.status': String(status),\n 'http.method': method,\n 'http.path': path,\n },\n })\n}\n\nfunction reportNetworkError(networkError: Error, method: string, url: string) {\n const path = templatePath(url)\n const error = new Error(`Network failure ${method} ${path}: ${networkError.message}`)\n error.name = 'NetworkError'\n\n Log.reportError(error, {\n scope: 'http',\n tags: {\n ...SHARED_TAGS,\n 'http.method': method,\n 'http.path': path,\n 'http.error': 'network',\n },\n })\n}\n\nfunction templatePath(url: string): string {\n let path: string\n try {\n path = new URL(url).pathname\n } catch {\n path = url\n }\n\n return path\n .split('/')\n .map(segment => (/^\\d+$/.test(segment) ? ':id' : segment))\n .join('/')\n}\n"]}
1
+ {"version":3,"file":"instrumented_fetch.js","sourceRoot":"","sources":["../../../src/utils/client/instrumented_fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kCAAkC,CAAA;AAEtD,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,mBAAmB;CACpB,CAAA;AAEV,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;AAExC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAW,EAAE,IAAiB;IACpE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,KAAK,CAAA;IACnC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QACvC,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,CAAA;QACxD,OAAO,QAAQ,CAAA;IACjB,CAAC;IAAC,OAAO,YAAY,EAAE,CAAC;QACtB,gBAAgB,CAAC,YAAqB,EAAE,MAAM,EAAE,GAAG,CAAC,CAAA;QACpD,MAAM,YAAY,CAAA;IACpB,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,QAAkB,EAAE,MAAc,EAAE,GAAW;IACtE,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAA;IAC3B,IAAI,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAM;IAE7C,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAC9B,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC,CAAA;IAC3D,KAAK,CAAC,IAAI,GAAG,YAAY,MAAM,EAAE,CAAA;IAEjC,GAAG,CAAC,WAAW,CAAC,KAAK,EAAE;QACrB,KAAK,EAAE,MAAM;QACb,IAAI,EAAE;YACJ,GAAG,WAAW;YACd,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC;YAC7B,aAAa,EAAE,MAAM;YACrB,WAAW,EAAE,IAAI;SAClB;KACF,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,YAAmB,EAAE,MAAc,EAAE,GAAW;IACxE,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAC9B,OAAO,CAAC,IAAI,CAAC,mBAAmB,MAAM,IAAI,IAAI,KAAK,YAAY,CAAC,OAAO,EAAE,EAAE;QACzE,KAAK,EAAE,MAAM;QACb,IAAI,EAAE;YACJ,GAAG,WAAW;YACd,aAAa,EAAE,MAAM;YACrB,WAAW,EAAE,IAAI;YACjB,YAAY,EAAE,SAAS;SACxB;KACF,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,GAAW;IAC/B,IAAI,IAAY,CAAA;IAChB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAA;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,GAAG,CAAA;IACZ,CAAC;IAED,OAAO,IAAI;SACR,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;SACzD,IAAI,CAAC,GAAG,CAAC,CAAA;AACd,CAAC","sourcesContent":["import { Log } from '../native_adapters/configuration'\n\nconst SHARED_TAGS = {\n team: 'chat',\n package: 'chat-react-native',\n} as const\n\nconst SKIPPED_STATUSES = [401, 403, 404]\n\nexport async function instrumentedFetch(url: string, init: RequestInit): Promise<Response> {\n const method = init.method ?? 'GET'\n try {\n const response = await fetch(url, init)\n if (!response.ok) reportHttpError(response, method, url)\n return response\n } catch (networkError) {\n warnNetworkError(networkError as Error, method, url)\n throw networkError\n }\n}\n\nfunction reportHttpError(response: Response, method: string, url: string) {\n const { status } = response\n if (SKIPPED_STATUSES.includes(status)) return\n\n const path = templatePath(url)\n const error = new Error(`HTTP ${status} ${method} ${path}`)\n error.name = `HTTPError${status}`\n\n Log.reportError(error, {\n scope: 'http',\n tags: {\n ...SHARED_TAGS,\n 'http.status': String(status),\n 'http.method': method,\n 'http.path': path,\n },\n })\n}\n\nfunction warnNetworkError(networkError: Error, method: string, url: string) {\n const path = templatePath(url)\n console.warn(`Network failure ${method} ${path}: ${networkError.message}`, {\n scope: 'http',\n tags: {\n ...SHARED_TAGS,\n 'http.method': method,\n 'http.path': path,\n 'http.error': 'network',\n },\n })\n}\n\nfunction templatePath(url: string): string {\n let path: string\n try {\n path = new URL(url).pathname\n } catch {\n path = url\n }\n\n return path\n .split('/')\n .map(segment => (/^\\d+$/.test(segment) ? ':id' : segment))\n .join('/')\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/chat-react-native",
3
- "version": "3.38.0-rc.0",
3
+ "version": "3.38.0-rc.10",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "react-native": "./src/index.tsx",
@@ -26,9 +26,9 @@
26
26
  "dependencies": {
27
27
  "@fortawesome/fontawesome-svg-core": "^7.2.0",
28
28
  "@fortawesome/react-native-fontawesome": "^0.3.2",
29
+ "@planningcenter/emoji-keyboard": "3.38.0-rc.10",
29
30
  "lodash-inflection": "^1.5.0",
30
- "react-compiler-runtime": "^1.0.0",
31
- "rn-emoji-keyboard": "1.7.0"
31
+ "react-compiler-runtime": "^1.0.0"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@planningcenter/datetime-fmt": ">=2.0.0",
@@ -72,5 +72,5 @@
72
72
  "react-native-url-polyfill": "^2.0.0",
73
73
  "typescript": "~5.9.2"
74
74
  },
75
- "gitHead": "e88a39dc1b59889e7757955b536ae8fc9efe98c6"
75
+ "gitHead": "2d9a404aadc8137412ca18ad9d5c7949097009ac"
76
76
  }
@@ -0,0 +1,50 @@
1
+ import { QueryClientProvider } from '@tanstack/react-query'
2
+ import { renderHook, waitFor } from '@testing-library/react-native'
3
+ import React from 'react'
4
+ import { buildTestQueryClient } from '../../__utils__/query_client'
5
+ import { useGroupChatConversationPayload } from '../../hooks/groups/use_group_chat_conversation_payload'
6
+ import * as useApiClientModule from '../../hooks/use_api_client'
7
+
8
+ const mockGroupsPost = (impl: jest.Mock) => {
9
+ jest.spyOn(useApiClientModule, 'useApiClient').mockReturnValue({
10
+ groups: { post: impl },
11
+ } as unknown as ReturnType<typeof useApiClientModule.useApiClient>)
12
+ return impl
13
+ }
14
+
15
+ const resolveWith = (value: string) =>
16
+ jest.fn().mockResolvedValue({
17
+ data: { type: 'GroupChatConversationPayload', id: '1', value },
18
+ links: {},
19
+ meta: {},
20
+ })
21
+
22
+ describe('useGroupChatConversationPayload', () => {
23
+ afterEach(() => {
24
+ jest.restoreAllMocks()
25
+ })
26
+
27
+ it('refetches on mount even when a fresh payload is already cached', async () => {
28
+ // Prime the cache as if a payload was fetched moments ago. The hook's 25-min staleTime
29
+ // means the default refetchOnMount would treat this as fresh and skip the request, so a
30
+ // group composition change on web would stay invisible until the cache is dropped (app
31
+ // restart). refetchOnMount: 'always' must override that and refetch on every mount.
32
+ const queryClient = buildTestQueryClient()
33
+ queryClient.setQueryData(['groups', '/me/groups/1/chat_conversation_payload'], {
34
+ data: { type: 'GroupChatConversationPayload', id: '1', value: 'stale' },
35
+ links: {},
36
+ meta: {},
37
+ })
38
+ const post = mockGroupsPost(resolveWith('fresh'))
39
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
40
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
41
+ )
42
+
43
+ const { result } = renderHook(() => useGroupChatConversationPayload({ groupId: 1 }), {
44
+ wrapper,
45
+ })
46
+
47
+ await waitFor(() => expect(post).toHaveBeenCalledTimes(1))
48
+ await waitFor(() => expect(result.current.payload).toBe('fresh'))
49
+ })
50
+ })
@@ -4,8 +4,8 @@ describe('jestTransformPackages', () => {
4
4
  it('exports an array of package patterns', () => {
5
5
  expect(jestTransformPackages).toEqual([
6
6
  '@planningcenter/chat-react-native',
7
+ '@planningcenter/emoji-keyboard',
7
8
  '@fortawesome',
8
- 'rn-emoji-keyboard',
9
9
  ])
10
10
  })
11
11
  })
@@ -369,4 +369,36 @@ describe('error handling', () => {
369
369
  ).rejects.toHaveProperty('status', 401)
370
370
  }
371
371
  })
372
+
373
+ describe('non-401 errors', () => {
374
+ it('attaches the parsed body so the message survives to the UI', async () => {
375
+ const url = '/records'
376
+
377
+ MockServer.post(
378
+ url,
379
+ {
380
+ errors: [
381
+ {
382
+ status: '422',
383
+ title: 'Unprocessable Entity',
384
+ detail: 'Conversations including a minor must have at least two confirmed adults.',
385
+ },
386
+ ],
387
+ },
388
+ 422,
389
+ { once: true }
390
+ )
391
+
392
+ const rejection = (await client
393
+ .post({ url, data: { data: { type: 'Record', attributes: {} } } })
394
+ .catch(e => e)) as FailedResponse
395
+
396
+ expect(rejection).toHaveProperty('status', 422)
397
+ expect(rejection.errors?.[0]?.detail).toEqual(
398
+ 'Conversations including a minor must have at least two confirmed adults.'
399
+ )
400
+ // A 422 must not be mistaken for an auth failure
401
+ expect(onUnauthorizedResponse).not.toHaveBeenCalled()
402
+ })
403
+ })
372
404
  })
@@ -0,0 +1,14 @@
1
+ import { render } from '@testing-library/react-native'
2
+ import { createRef } from 'react'
3
+ import { FlatList } from 'react-native-gesture-handler'
4
+ import { MessageList } from '../message_list'
5
+
6
+ describe('MessageList', () => {
7
+ it('renders with the gesture-handler FlatList so overscroll cannot drag an enclosing sheet', () => {
8
+ const listRef = createRef<FlatList>()
9
+
10
+ const screen = render(<MessageList listRef={listRef} data={[]} renderItem={() => null} />)
11
+
12
+ expect(screen.UNSAFE_getByType(FlatList)).toBeTruthy()
13
+ })
14
+ })
@@ -0,0 +1,42 @@
1
+ import { RefObject } from 'react'
2
+ import { StyleSheet, type FlatListProps } from 'react-native'
3
+ import { FlatList } from 'react-native-gesture-handler'
4
+ import type { EnrichedMessage } from '../../utils/group_messages'
5
+
6
+ const extractItemKey = (item: EnrichedMessage) => String(item.id)
7
+ const maintainVisibleContentPosition = { minIndexForVisible: 0 }
8
+
9
+ type MessageListProps = Pick<
10
+ FlatListProps<EnrichedMessage>,
11
+ | 'data'
12
+ | 'renderItem'
13
+ | 'onScroll'
14
+ | 'onScrollBeginDrag'
15
+ | 'viewabilityConfigCallbackPairs'
16
+ | 'onContentSizeChange'
17
+ | 'onScrollToIndexFailed'
18
+ | 'onEndReached'
19
+ | 'ListHeaderComponent'
20
+ > & {
21
+ listRef: RefObject<FlatList | null>
22
+ }
23
+
24
+ export function MessageList({ listRef, ...props }: MessageListProps) {
25
+ return (
26
+ <FlatList
27
+ inverted
28
+ ref={listRef}
29
+ contentContainerStyle={styles.listContainer}
30
+ maintainVisibleContentPosition={maintainVisibleContentPosition}
31
+ keyExtractor={extractItemKey}
32
+ scrollEventThrottle={64}
33
+ {...props}
34
+ />
35
+ )
36
+ }
37
+
38
+ const styles = StyleSheet.create({
39
+ listContainer: {
40
+ paddingVertical: 12,
41
+ },
42
+ })
@@ -3,11 +3,12 @@ import React, { useMemo } from 'react'
3
3
  import { FlatList, StyleSheet, View } from 'react-native'
4
4
  import { useConversationsContext } from '../../contexts/conversations_context'
5
5
  import { useTheme } from '../../hooks'
6
+ import { useCanCreateConversations } from '../../hooks/use_chat_permissions'
6
7
  import { useConversationsJoltEvents } from '../../hooks/use_conversations_jolt_events'
7
8
  import { ConversationResource } from '../../types'
8
9
  import { throwResponseError } from '../../utils/response_error'
9
- import BlankState from '../primitive/blank_state_primitive'
10
10
  import { ConversationPreview, ConversationPreviewSkeleton } from './conversation_preview'
11
+ import { ConversationsBlankState } from './conversations_blank_state'
11
12
 
12
13
  interface ConversationsProps {
13
14
  ListHeaderComponent?:
@@ -28,9 +29,11 @@ export const Conversations = ({ ListHeaderComponent }: ConversationsProps) => {
28
29
  isFetched,
29
30
  isError,
30
31
  error,
31
- args: { chat_group_graph_id },
32
+ args: { chat_group_graph_id, group_source_app_name },
32
33
  } = useConversationsContext()
33
34
  const navigation = useNavigation()
35
+ const canCreateConversations = useCanCreateConversations()
36
+ const isFilterApplied = !!chat_group_graph_id || !!group_source_app_name
34
37
 
35
38
  const showBadges = !chat_group_graph_id
36
39
 
@@ -61,14 +64,10 @@ export const Conversations = ({ ListHeaderComponent }: ConversationsProps) => {
61
64
  refreshing={!isFetched && isRefetching}
62
65
  ListHeaderComponent={ListHeaderComponent}
63
66
  ListEmptyComponent={
64
- <View style={styles.listEmpty}>
65
- <BlankState.Root>
66
- <BlankState.Imagery name="general.outlinedTextMessage" />
67
- <BlankState.Content>
68
- <BlankState.Heading>No conversations</BlankState.Heading>
69
- </BlankState.Content>
70
- </BlankState.Root>
71
- </View>
67
+ <ConversationsBlankState
68
+ isFilterApplied={isFilterApplied}
69
+ canCreateConversations={canCreateConversations}
70
+ />
72
71
  }
73
72
  renderItem={({ item }) => {
74
73
  if (item.type === 'loading') {
@@ -104,12 +103,6 @@ const useStyles = () => {
104
103
  container: { flex: 1 },
105
104
  contentContainer: { paddingVertical: 16 },
106
105
  listItem: { color: colors.fillColorNeutral020 },
107
- listEmpty: {
108
- flex: 1,
109
- justifyContent: 'center',
110
- alignItems: 'center',
111
- paddingVertical: 32,
112
- },
113
106
  })
114
107
  }
115
108
 
@@ -0,0 +1,42 @@
1
+ import React from 'react'
2
+ import { StyleSheet, View } from 'react-native'
3
+ import BlankState from '../primitive/blank_state_primitive'
4
+
5
+ interface ConversationsBlankStateProps {
6
+ isFilterApplied: boolean
7
+ canCreateConversations: boolean
8
+ }
9
+
10
+ export function ConversationsBlankState({
11
+ isFilterApplied,
12
+ canCreateConversations,
13
+ }: ConversationsBlankStateProps) {
14
+ return (
15
+ <View style={styles.container}>
16
+ <BlankState.Root>
17
+ <BlankState.Imagery name="general.outlinedTextMessage" />
18
+ <BlankState.Content>
19
+ <BlankState.Heading>No conversations yet.</BlankState.Heading>
20
+ {isFilterApplied ? (
21
+ <BlankState.Text>Adjust your filters to find conversations.</BlankState.Text>
22
+ ) : canCreateConversations ? (
23
+ <BlankState.Text>Tap the compose button to get started.</BlankState.Text>
24
+ ) : (
25
+ <BlankState.Text>
26
+ When your groups or teams start chatting, you&rsquo;ll find them here.
27
+ </BlankState.Text>
28
+ )}
29
+ </BlankState.Content>
30
+ </BlankState.Root>
31
+ </View>
32
+ )
33
+ }
34
+
35
+ const styles = StyleSheet.create({
36
+ container: {
37
+ flex: 1,
38
+ justifyContent: 'center',
39
+ alignItems: 'center',
40
+ paddingVertical: 32,
41
+ },
42
+ })
@@ -52,6 +52,7 @@ interface ConversationAvatarProps {
52
52
  showFallback?: boolean
53
53
  fallbackIconName?: IconString
54
54
  style?: ViewStyle
55
+ maxFontSizeMultiplier?: AvatarRootProps['maxFontSizeMultiplier']
55
56
  }
56
57
 
57
58
  export function ConversationAvatar({
@@ -60,30 +61,31 @@ export function ConversationAvatar({
60
61
  showFallback = false,
61
62
  fallbackIconName = 'general.person',
62
63
  style,
64
+ maxFontSizeMultiplier,
63
65
  }: ConversationAvatarProps) {
64
66
  const avatar = resolveAvatar(conversation)
67
+ const sizeProps = { size, maxFontSizeMultiplier, style }
65
68
 
66
69
  switch (avatar.kind) {
67
70
  case 'image':
68
71
  return (
69
- <AvatarPrimitive.Root size={size} style={style}>
72
+ <AvatarPrimitive.Root {...sizeProps}>
70
73
  <AvatarPrimitive.Mask>
71
74
  <AvatarPrimitive.Image sourceUri={avatar.url} />
72
75
  </AvatarPrimitive.Mask>
73
76
  </AvatarPrimitive.Root>
74
77
  )
75
78
  case 'icon':
76
- return <IconAvatar iconKey={avatar.iconKey} color={avatar.color} size={size} />
79
+ return <IconAvatar iconKey={avatar.iconKey} color={avatar.color} {...sizeProps} />
77
80
  case 'emoji':
78
- return <EmojiAvatar emoji={avatar.emoji} color={avatar.color} size={size} />
81
+ return <EmojiAvatar emoji={avatar.emoji} color={avatar.color} {...sizeProps} />
79
82
  case 'group':
80
83
  return (
81
84
  <AvatarGroup
82
85
  sourceUris={avatar.sources || []}
83
- size={size}
84
86
  showFallback={showFallback || !avatar.sources || avatar.sources.length === 0}
85
87
  fallbackIconName={fallbackIconName}
86
- style={style}
88
+ {...sizeProps}
87
89
  />
88
90
  )
89
91
  }
@@ -20,13 +20,21 @@ interface EmojiAvatarProps {
20
20
  emoji: string
21
21
  color?: string | null
22
22
  size?: AvatarRootProps['size']
23
+ maxFontSizeMultiplier?: AvatarRootProps['maxFontSizeMultiplier']
24
+ style?: AvatarRootProps['style']
23
25
  }
24
26
 
25
- export function EmojiAvatar({ emoji, color, size = 'lg' }: EmojiAvatarProps) {
27
+ export function EmojiAvatar({
28
+ emoji,
29
+ color,
30
+ size = 'lg',
31
+ maxFontSizeMultiplier,
32
+ style,
33
+ }: EmojiAvatarProps) {
26
34
  const gradientProps = getAvatarGradientProps(color)
27
35
 
28
36
  return (
29
- <AvatarPrimitive.Root size={size}>
37
+ <AvatarPrimitive.Root size={size} maxFontSizeMultiplier={maxFontSizeMultiplier} style={style}>
30
38
  <AvatarPrimitive.Mask>
31
39
  <LinearGradient {...gradientProps} style={styles.gradientFill}>
32
40
  <View style={styles.contentContainer}>