@planningcenter/chat-react-native 3.2.0 → 3.4.0-rc.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.
Files changed (48) hide show
  1. package/build/hooks/services/use_find_or_create_services_conversation.d.ts +9 -0
  2. package/build/hooks/services/use_find_or_create_services_conversation.d.ts.map +1 -0
  3. package/build/hooks/services/use_find_or_create_services_conversation.js +72 -0
  4. package/build/hooks/services/use_find_or_create_services_conversation.js.map +1 -0
  5. package/build/hooks/services/use_team_members_for_new_conversation.d.ts +176 -0
  6. package/build/hooks/services/use_team_members_for_new_conversation.d.ts.map +1 -0
  7. package/build/hooks/services/use_team_members_for_new_conversation.js +29 -0
  8. package/build/hooks/services/use_team_members_for_new_conversation.js.map +1 -0
  9. package/build/hooks/services/use_team_plans.d.ts +8 -0
  10. package/build/hooks/services/use_team_plans.d.ts.map +1 -0
  11. package/build/hooks/services/use_team_plans.js +37 -0
  12. package/build/hooks/services/use_team_plans.js.map +1 -0
  13. package/build/hooks/services/use_teams_i_lead.d.ts +8 -0
  14. package/build/hooks/services/use_teams_i_lead.d.ts.map +1 -0
  15. package/build/hooks/services/use_teams_i_lead.js +34 -0
  16. package/build/hooks/services/use_teams_i_lead.js.map +1 -0
  17. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts +1 -1
  18. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts.map +1 -1
  19. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js +4 -28
  20. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js.map +1 -1
  21. package/build/screens/conversation_new/components/filter_by_plan_options.d.ts +8 -0
  22. package/build/screens/conversation_new/components/filter_by_plan_options.d.ts.map +1 -0
  23. package/build/screens/conversation_new/components/filter_by_plan_options.js +37 -0
  24. package/build/screens/conversation_new/components/filter_by_plan_options.js.map +1 -0
  25. package/build/screens/conversation_new/components/services_form.d.ts +8 -0
  26. package/build/screens/conversation_new/components/services_form.d.ts.map +1 -0
  27. package/build/screens/conversation_new/components/services_form.js +137 -0
  28. package/build/screens/conversation_new/components/services_form.js.map +1 -0
  29. package/build/screens/conversation_new/conversation_new_screen.js +2 -2
  30. package/build/screens/conversation_new/conversation_new_screen.js.map +1 -1
  31. package/build/types/resources/services/team_resource.d.ts +6 -3
  32. package/build/types/resources/services/team_resource.d.ts.map +1 -1
  33. package/build/types/resources/services/team_resource.js.map +1 -1
  34. package/package.json +2 -2
  35. package/src/hooks/services/use_find_or_create_services_conversation.ts +97 -0
  36. package/src/hooks/services/use_team_members_for_new_conversation.ts +40 -0
  37. package/src/hooks/services/use_team_plans.ts +42 -0
  38. package/src/hooks/services/use_teams_i_lead.ts +44 -0
  39. package/src/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.ts +5 -48
  40. package/src/screens/conversation_new/components/filter_by_plan_options.tsx +64 -0
  41. package/src/screens/conversation_new/components/services_form.tsx +229 -0
  42. package/src/screens/conversation_new/conversation_new_screen.tsx +2 -2
  43. package/src/types/resources/services/team_resource.ts +7 -3
  44. package/build/screens/conversation_new/components/team_form.d.ts +0 -8
  45. package/build/screens/conversation_new/components/team_form.d.ts.map +0 -1
  46. package/build/screens/conversation_new/components/team_form.js +0 -11
  47. package/build/screens/conversation_new/components/team_form.js.map +0 -1
  48. package/src/screens/conversation_new/components/team_form.tsx +0 -18
@@ -0,0 +1,137 @@
1
+ import { StackActions, useNavigation } from '@react-navigation/native';
2
+ import React, { useCallback, useMemo, useState } from 'react';
3
+ import { StyleSheet, View } from 'react-native';
4
+ import { Badge, Banner, ChildNotice, Heading, Switch, Text } from '../../../components';
5
+ import { ActionButton } from '../../../components/display/action_button';
6
+ import { Divider, FormList } from './form_list';
7
+ import { pluralize } from '../../../utils';
8
+ import { uniq } from 'lodash';
9
+ import { useTeamsILead } from '../../../hooks/services/use_teams_i_lead';
10
+ import { FilterByPlanOptions } from './filter_by_plan_options';
11
+ import { useTeamMembersForNewConversation } from '../../../hooks/services/use_team_members_for_new_conversation';
12
+ import { useFindOrCreateServicesConversation } from '../../../hooks/services/use_find_or_create_services_conversation';
13
+ export const ServicesForm = ({ initialTeamIds, initialPlanId }) => {
14
+ const styles = useStyles();
15
+ const [selectedPlanId, setSelectedPlanId] = useState(initialPlanId);
16
+ const initialState = uniq(initialTeamIds) || []; // Uniq here because services can send duplicates in the teams_i_lead response.
17
+ const [selectedTeamIds, setSelectedTeamIds] = useState(initialState);
18
+ const removeSelection = useCallback((teamId) => {
19
+ setSelectedTeamIds(selectedTeamIds.filter(id => id !== teamId));
20
+ }, [selectedTeamIds]);
21
+ const [filerByPlan, setFilterByPlan] = useState(false);
22
+ const { members, isError: isMemberError } = useTeamMembersForNewConversation({
23
+ teamIds: selectedTeamIds,
24
+ planId: selectedPlanId,
25
+ });
26
+ const navigation = useNavigation();
27
+ const { mutate: createConversation } = useFindOrCreateServicesConversation({
28
+ teamIds: selectedTeamIds,
29
+ planId: filerByPlan ? selectedPlanId : undefined,
30
+ onSuccess: (conversation) => {
31
+ // exit from the create stack
32
+ navigation.getParent()?.goBack();
33
+ // navigate to the conversation screen
34
+ navigation.dispatch(StackActions.push('Conversation', {
35
+ conversation_id: conversation.id,
36
+ }));
37
+ },
38
+ });
39
+ return (<View style={styles.formContainer}>
40
+ <FormList memberData={members} FormContent={<FormContent selectedTeamIds={selectedTeamIds} removeSelection={removeSelection} selectedPlanId={selectedPlanId} setSelectedPlanId={setSelectedPlanId} filterByPlan={filerByPlan} setFilterByPlan={setFilterByPlan} members={members} isMemberError={isMemberError}/>}/>
41
+ <ActionButton disabled={!selectedTeamIds.length} title="Start Conversation" onPress={createConversation} infoText="Conversation will be automatically updated if any members are added or removed from included teams."/>
42
+ </View>);
43
+ };
44
+ function FormContent({ selectedTeamIds, removeSelection, selectedPlanId, setSelectedPlanId, filterByPlan, setFilterByPlan, members, isMemberError, }) {
45
+ const { teamsILead } = useTeamsILead();
46
+ const selectedTeamsILead = useMemo(() => {
47
+ return teamsILead.filter(team => selectedTeamIds.includes(team.value.teamId));
48
+ }, [selectedTeamIds, teamsILead]);
49
+ const styles = useStyles();
50
+ const teamCountHeader = pluralize(selectedTeamIds.length, 'team');
51
+ const memberCount = members.length;
52
+ const childMembers = members.filter(member => member.child);
53
+ const hasChildren = childMembers.length > 0;
54
+ const multipleServiceTypes = uniq(selectedTeamsILead.map(team => team.value.serviceTypeId)).length > 1;
55
+ const firstTeamId = selectedTeamIds[0];
56
+ filterByPlan = filterByPlan && !!firstTeamId && !multipleServiceTypes;
57
+ return (<View style={styles.formContent}>
58
+ <View style={styles.toSection}>
59
+ <View style={styles.toSectionRow}>
60
+ <Heading variant="h3">To:</Heading>
61
+ <Text>{teamCountHeader}</Text>
62
+ </View>
63
+ <View style={styles.toSectionRow}>
64
+ {selectedTeamsILead.map(team => (<View key={team.value.teamId} style={styles.badgeRow}>
65
+ <Badge iconNameRight="general.x" label={team.name} onPress={() => removeSelection(team.value.teamId)}/>
66
+ </View>))}
67
+ </View>
68
+ </View>
69
+ <Divider />
70
+ <View style={styles.filterByPlanSection}>
71
+ <View style={styles.filterByPlanSectionLead}>
72
+ <Heading variant="h3">Filter by plan</Heading>
73
+ <Switch value={filterByPlan} onValueChange={setFilterByPlan} disabled={multipleServiceTypes}/>
74
+ </View>
75
+ {multipleServiceTypes ? (<Banner appearance="neutral" description="Plan filtering is not possible using teams from multiple service types. Try choosing teams above with only one service type."/>) : filterByPlan ? (<FilterByPlanOptions teamIds={selectedTeamIds} planId={selectedPlanId} onChange={setSelectedPlanId}/>) : null}
76
+ </View>
77
+ <Divider />
78
+ <View style={styles.memberSection}>
79
+ <Heading variant="h3">{pluralize(memberCount, 'member')} selected</Heading>
80
+ {hasChildren && <ChildNotice childMembers={childMembers} style={styles.banner}/>}
81
+ {isMemberError && (<Banner appearance="error" description="There was an issue loading team members, please refresh and try again." style={styles.banner}/>)}
82
+ </View>
83
+ </View>);
84
+ }
85
+ const useStyles = () => {
86
+ const sectionPadding = 16;
87
+ return StyleSheet.create({
88
+ formContainer: {
89
+ flex: 1,
90
+ },
91
+ formContent: {
92
+ paddingVertical: sectionPadding,
93
+ flex: 1,
94
+ },
95
+ toSection: {
96
+ padding: sectionPadding,
97
+ gap: 8,
98
+ },
99
+ toSectionRow: {
100
+ flexDirection: 'row',
101
+ gap: 8,
102
+ alignItems: 'baseline',
103
+ flexWrap: 'wrap',
104
+ },
105
+ badgeRow: {
106
+ flexDirection: 'row',
107
+ },
108
+ groupName: {
109
+ fontSize: 18,
110
+ },
111
+ filterByPlanSection: {
112
+ padding: sectionPadding,
113
+ gap: 8,
114
+ },
115
+ filterByPlanSectionLead: {
116
+ flexDirection: 'row',
117
+ gap: 8,
118
+ justifyContent: 'space-between',
119
+ alignItems: 'center',
120
+ },
121
+ filterByPlanSectionRow: {
122
+ flexDirection: 'row',
123
+ gap: 8,
124
+ },
125
+ titleInput: {
126
+ fontSize: 18,
127
+ },
128
+ memberSection: {
129
+ padding: sectionPadding,
130
+ paddingBottom: 0,
131
+ },
132
+ banner: {
133
+ marginTop: 16,
134
+ },
135
+ });
136
+ };
137
+ //# sourceMappingURL=services_form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"services_form.js","sourceRoot":"","sources":["../../../../src/screens/conversation_new/components/services_form.tsx"],"names":[],"mappings":"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","sourcesContent":["import { StackActions, useNavigation } from '@react-navigation/native'\nimport React, { useCallback, useMemo, useState } from 'react'\nimport { StyleSheet, View } from 'react-native'\nimport { Badge, Banner, ChildNotice, Heading, Switch, Text } from '../../../components'\nimport { ActionButton } from '../../../components/display/action_button'\nimport { Divider, FormList } from './form_list'\nimport { pluralize } from '../../../utils'\nimport { uniq } from 'lodash'\nimport { useTeamsILead } from '../../../hooks/services/use_teams_i_lead'\nimport { FilterByPlanOptions } from './filter_by_plan_options'\nimport { useTeamMembersForNewConversation } from '../../../hooks/services/use_team_members_for_new_conversation'\nimport { useFindOrCreateServicesConversation } from '../../../hooks/services/use_find_or_create_services_conversation'\nimport { ConversationResource, MemberResource } from '../../../types'\n\ntype ServicesFormProps = {\n initialTeamIds?: number[]\n initialPlanId?: number\n}\n\nexport const ServicesForm = ({ initialTeamIds, initialPlanId }: ServicesFormProps) => {\n const styles = useStyles()\n const [selectedPlanId, setSelectedPlanId] = useState<number | undefined>(initialPlanId)\n const initialState = uniq(initialTeamIds) || [] // Uniq here because services can send duplicates in the teams_i_lead response.\n const [selectedTeamIds, setSelectedTeamIds] = useState<number[]>(initialState)\n\n const removeSelection = useCallback(\n (teamId: number) => {\n setSelectedTeamIds(selectedTeamIds.filter(id => id !== teamId))\n },\n [selectedTeamIds]\n )\n\n const [filerByPlan, setFilterByPlan] = useState(false)\n\n const { members, isError: isMemberError } = useTeamMembersForNewConversation({\n teamIds: selectedTeamIds,\n planId: selectedPlanId,\n })\n\n const navigation = useNavigation()\n const { mutate: createConversation } = useFindOrCreateServicesConversation({\n teamIds: selectedTeamIds,\n planId: filerByPlan ? selectedPlanId : undefined,\n onSuccess: (conversation: ConversationResource) => {\n // exit from the create stack\n navigation.getParent()?.goBack()\n // navigate to the conversation screen\n navigation.dispatch(\n StackActions.push('Conversation', {\n conversation_id: conversation.id,\n })\n )\n },\n })\n\n return (\n <View style={styles.formContainer}>\n <FormList\n memberData={members}\n FormContent={\n <FormContent\n selectedTeamIds={selectedTeamIds}\n removeSelection={removeSelection}\n selectedPlanId={selectedPlanId}\n setSelectedPlanId={setSelectedPlanId}\n filterByPlan={filerByPlan}\n setFilterByPlan={setFilterByPlan}\n members={members}\n isMemberError={isMemberError}\n />\n }\n />\n <ActionButton\n disabled={!selectedTeamIds.length}\n title=\"Start Conversation\"\n onPress={createConversation}\n infoText=\"Conversation will be automatically updated if any members are added or removed from included teams.\"\n />\n </View>\n )\n}\n\ninterface FormContentProps {\n selectedTeamIds: number[]\n removeSelection: (teamId: number) => void\n selectedPlanId?: number\n setSelectedPlanId: (planId: number | undefined) => void\n filterByPlan: boolean\n setFilterByPlan: (value: boolean) => void\n members: MemberResource[]\n isMemberError: boolean\n}\n\nfunction FormContent({\n selectedTeamIds,\n removeSelection,\n selectedPlanId,\n setSelectedPlanId,\n filterByPlan,\n setFilterByPlan,\n members,\n isMemberError,\n}: FormContentProps) {\n const { teamsILead } = useTeamsILead()\n const selectedTeamsILead = useMemo(() => {\n return teamsILead.filter(team => selectedTeamIds.includes(team.value.teamId))\n }, [selectedTeamIds, teamsILead])\n\n const styles = useStyles()\n const teamCountHeader = pluralize(selectedTeamIds.length, 'team')\n const memberCount = members.length\n const childMembers = members.filter(member => member.child)\n const hasChildren = childMembers.length > 0\n const multipleServiceTypes =\n uniq(selectedTeamsILead.map(team => team.value.serviceTypeId)).length > 1\n const firstTeamId = selectedTeamIds[0]\n filterByPlan = filterByPlan && !!firstTeamId && !multipleServiceTypes\n\n return (\n <View style={styles.formContent}>\n <View style={styles.toSection}>\n <View style={styles.toSectionRow}>\n <Heading variant=\"h3\">To:</Heading>\n <Text>{teamCountHeader}</Text>\n </View>\n <View style={styles.toSectionRow}>\n {selectedTeamsILead.map(team => (\n <View key={team.value.teamId} style={styles.badgeRow}>\n <Badge\n iconNameRight=\"general.x\"\n label={team.name}\n onPress={() => removeSelection(team.value.teamId)}\n />\n </View>\n ))}\n </View>\n </View>\n <Divider />\n <View style={styles.filterByPlanSection}>\n <View style={styles.filterByPlanSectionLead}>\n <Heading variant=\"h3\">Filter by plan</Heading>\n <Switch\n value={filterByPlan}\n onValueChange={setFilterByPlan}\n disabled={multipleServiceTypes}\n />\n </View>\n {multipleServiceTypes ? (\n <Banner\n appearance=\"neutral\"\n description=\"Plan filtering is not possible using teams from multiple service types. Try choosing teams above with only one service type.\"\n />\n ) : filterByPlan ? (\n <FilterByPlanOptions\n teamIds={selectedTeamIds}\n planId={selectedPlanId}\n onChange={setSelectedPlanId}\n />\n ) : null}\n </View>\n <Divider />\n <View style={styles.memberSection}>\n <Heading variant=\"h3\">{pluralize(memberCount, 'member')} selected</Heading>\n {hasChildren && <ChildNotice childMembers={childMembers} style={styles.banner} />}\n {isMemberError && (\n <Banner\n appearance=\"error\"\n description=\"There was an issue loading team members, please refresh and try again.\"\n style={styles.banner}\n />\n )}\n </View>\n </View>\n )\n}\n\nconst useStyles = () => {\n const sectionPadding = 16\n\n return StyleSheet.create({\n formContainer: {\n flex: 1,\n },\n formContent: {\n paddingVertical: sectionPadding,\n flex: 1,\n },\n toSection: {\n padding: sectionPadding,\n gap: 8,\n },\n toSectionRow: {\n flexDirection: 'row',\n gap: 8,\n alignItems: 'baseline',\n flexWrap: 'wrap',\n },\n badgeRow: {\n flexDirection: 'row',\n },\n groupName: {\n fontSize: 18,\n },\n filterByPlanSection: {\n padding: sectionPadding,\n gap: 8,\n },\n filterByPlanSectionLead: {\n flexDirection: 'row',\n gap: 8,\n justifyContent: 'space-between',\n alignItems: 'center',\n },\n filterByPlanSectionRow: {\n flexDirection: 'row',\n gap: 8,\n },\n titleInput: {\n fontSize: 18,\n },\n memberSection: {\n padding: sectionPadding,\n paddingBottom: 0,\n },\n banner: {\n marginTop: 16,\n },\n })\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { GroupsForm } from './components/groups_form';
3
- import { TeamsForm } from './components/team_form';
3
+ import { ServicesForm } from './components/services_form';
4
4
  import { SourceAppErrorCard } from './components/source_app_error_card';
5
5
  export const ConversationNewScreen = ({ route }) => {
6
6
  const { group_id, team_ids, source_app_name, plan_id, chat_group_graph_id } = route.params;
@@ -8,7 +8,7 @@ export const ConversationNewScreen = ({ route }) => {
8
8
  case 'Groups':
9
9
  return group_id ? (<GroupsForm groupId={group_id} chat_group_graph_id={chat_group_graph_id}/>) : (<SourceAppErrorCard />);
10
10
  case 'Services':
11
- return <TeamsForm initialTeamIds={team_ids} initialPlanId={plan_id}/>;
11
+ return <ServicesForm initialTeamIds={team_ids} initialPlanId={plan_id}/>;
12
12
  default:
13
13
  return <SourceAppErrorCard />;
14
14
  }
@@ -1 +1 @@
1
- {"version":3,"file":"conversation_new_screen.js","sourceRoot":"","sources":["../../../src/screens/conversation_new/conversation_new_screen.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AAavE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,EAAE,KAAK,EAA8B,EAAE,EAAE;IAC7E,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,mBAAmB,EAAE,GAAG,KAAK,CAAC,MAAM,CAAA;IAE1F,QAAQ,eAAe,EAAE,CAAC;QACxB,KAAK,QAAQ;YACX,OAAO,QAAQ,CAAC,CAAC,CAAC,CAChB,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,mBAAmB,CAAC,CAAC,mBAAmB,CAAC,EAAG,CAC5E,CAAC,CAAC,CAAC,CACF,CAAC,kBAAkB,CAAC,AAAD,EAAG,CACvB,CAAA;QACH,KAAK,UAAU;YACb,OAAO,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC,QAAQ,CAAC,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,EAAG,CAAA;QACxE;YACE,OAAO,CAAC,kBAAkB,CAAC,AAAD,EAAG,CAAA;IACjC,CAAC;AACH,CAAC,CAAA","sourcesContent":["import { StaticScreenProps } from '@react-navigation/native'\nimport React from 'react'\nimport { GroupsForm } from './components/groups_form'\nimport { TeamsForm } from './components/team_form'\nimport { SourceAppErrorCard } from './components/source_app_error_card'\nimport { AppName } from '../../types/resources/app_name'\nimport { GraphId } from '../../types/resources/group_resource'\n\ntype ConversationNewScreenProps = StaticScreenProps<{\n group_id?: number\n team_ids?: number[]\n plan_id?: number\n source_app_name: AppName\n chat_group_graph_id?: GraphId\n group_source_app_name?: AppName\n}>\n\nexport const ConversationNewScreen = ({ route }: ConversationNewScreenProps) => {\n const { group_id, team_ids, source_app_name, plan_id, chat_group_graph_id } = route.params\n\n switch (source_app_name) {\n case 'Groups':\n return group_id ? (\n <GroupsForm groupId={group_id} chat_group_graph_id={chat_group_graph_id} />\n ) : (\n <SourceAppErrorCard />\n )\n case 'Services':\n return <TeamsForm initialTeamIds={team_ids} initialPlanId={plan_id} />\n default:\n return <SourceAppErrorCard />\n }\n}\n"]}
1
+ {"version":3,"file":"conversation_new_screen.js","sourceRoot":"","sources":["../../../src/screens/conversation_new/conversation_new_screen.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AAavE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,EAAE,KAAK,EAA8B,EAAE,EAAE;IAC7E,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,mBAAmB,EAAE,GAAG,KAAK,CAAC,MAAM,CAAA;IAE1F,QAAQ,eAAe,EAAE,CAAC;QACxB,KAAK,QAAQ;YACX,OAAO,QAAQ,CAAC,CAAC,CAAC,CAChB,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,mBAAmB,CAAC,CAAC,mBAAmB,CAAC,EAAG,CAC5E,CAAC,CAAC,CAAC,CACF,CAAC,kBAAkB,CAAC,AAAD,EAAG,CACvB,CAAA;QACH,KAAK,UAAU;YACb,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC,QAAQ,CAAC,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,EAAG,CAAA;QAC3E;YACE,OAAO,CAAC,kBAAkB,CAAC,AAAD,EAAG,CAAA;IACjC,CAAC;AACH,CAAC,CAAA","sourcesContent":["import { StaticScreenProps } from '@react-navigation/native'\nimport React from 'react'\nimport { GroupsForm } from './components/groups_form'\nimport { ServicesForm } from './components/services_form'\nimport { SourceAppErrorCard } from './components/source_app_error_card'\nimport { AppName } from '../../types/resources/app_name'\nimport { GraphId } from '../../types/resources/group_resource'\n\ntype ConversationNewScreenProps = StaticScreenProps<{\n group_id?: number\n team_ids?: number[]\n plan_id?: number\n source_app_name: AppName\n chat_group_graph_id?: GraphId\n group_source_app_name?: AppName\n}>\n\nexport const ConversationNewScreen = ({ route }: ConversationNewScreenProps) => {\n const { group_id, team_ids, source_app_name, plan_id, chat_group_graph_id } = route.params\n\n switch (source_app_name) {\n case 'Groups':\n return group_id ? (\n <GroupsForm groupId={group_id} chat_group_graph_id={chat_group_graph_id} />\n ) : (\n <SourceAppErrorCard />\n )\n case 'Services':\n return <ServicesForm initialTeamIds={team_ids} initialPlanId={plan_id} />\n default:\n return <SourceAppErrorCard />\n }\n}\n"]}
@@ -25,14 +25,14 @@ export type TeamOptionResponseItemGroupName = 'teams_i_lead' | 'other_teams';
25
25
  export interface PlansResource extends ServicesChatResource {
26
26
  plans: PlansResponseItem[];
27
27
  }
28
- interface PlansResponseItem {
28
+ export interface PlansResponseItem {
29
29
  value: number;
30
30
  name: string;
31
31
  }
32
32
  export interface TeamPeopleResource extends ServicesChatResource {
33
- people: PersonResponseItem[];
33
+ people: TeamPersonResponseItem[];
34
34
  }
35
- interface PersonResponseItem {
35
+ export interface TeamPersonResponseItem {
36
36
  id: number;
37
37
  name: string;
38
38
  avatar: string;
@@ -44,5 +44,8 @@ interface PersonResponseItem {
44
44
  export interface ServicesChatPayloadResource extends ServicesChatResource {
45
45
  payload: string;
46
46
  }
47
+ export interface ServicesChatGroupIdentifiersResource extends ServicesChatResource {
48
+ groupIdentifiers: string[];
49
+ }
47
50
  export {};
48
51
  //# sourceMappingURL=team_resource.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"team_resource.d.ts","sourceRoot":"","sources":["../../../../src/types/resources/services/team_resource.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAErD,UAAU,oBAAqB,SAAQ,cAAc;IACnD,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,YAAa,SAAQ,oBAAoB;IACxD,UAAU,EAAE,gBAAgB,EAAE,CAAA;CAC/B;AACD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE;QACL,MAAM,EAAE,MAAM,CAAA;QACd,aAAa,EAAE,MAAM,CAAA;KACtB,CAAA;IACD,eAAe,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED,MAAM,WAAW,kBAAmB,SAAQ,oBAAoB;IAC9D,KAAK,EAAE,sBAAsB,EAAE,CAAA;CAChC;AAED,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC9D,KAAK,EAAE,+BAA+B,CAAA;CACvC;AAED,MAAM,MAAM,+BAA+B,GAAG,cAAc,GAAG,aAAa,CAAA;AAE5E,MAAM,WAAW,aAAc,SAAQ,oBAAoB;IACzD,KAAK,EAAE,iBAAiB,EAAE,CAAA;CAC3B;AAED,UAAU,iBAAiB;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,kBAAmB,SAAQ,oBAAoB;IAC9D,MAAM,EAAE,kBAAkB,EAAE,CAAA;CAC7B;AAED,UAAU,kBAAkB;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC3B,KAAK,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,2BAA4B,SAAQ,oBAAoB;IACvE,OAAO,EAAE,MAAM,CAAA;CAChB"}
1
+ {"version":3,"file":"team_resource.d.ts","sourceRoot":"","sources":["../../../../src/types/resources/services/team_resource.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAErD,UAAU,oBAAqB,SAAQ,cAAc;IACnD,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,YAAa,SAAQ,oBAAoB;IACxD,UAAU,EAAE,gBAAgB,EAAE,CAAA;CAC/B;AACD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE;QACL,MAAM,EAAE,MAAM,CAAA;QACd,aAAa,EAAE,MAAM,CAAA;KACtB,CAAA;IACD,eAAe,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED,MAAM,WAAW,kBAAmB,SAAQ,oBAAoB;IAC9D,KAAK,EAAE,sBAAsB,EAAE,CAAA;CAChC;AAED,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC9D,KAAK,EAAE,+BAA+B,CAAA;CACvC;AAED,MAAM,MAAM,+BAA+B,GAAG,cAAc,GAAG,aAAa,CAAA;AAE5E,MAAM,WAAW,aAAc,SAAQ,oBAAoB;IACzD,KAAK,EAAE,iBAAiB,EAAE,CAAA;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,kBAAmB,SAAQ,oBAAoB;IAC9D,MAAM,EAAE,sBAAsB,EAAE,CAAA;CACjC;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC3B,KAAK,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,2BAA4B,SAAQ,oBAAoB;IACvE,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,oCAAqC,SAAQ,oBAAoB;IAChF,gBAAgB,EAAE,MAAM,EAAE,CAAA;CAC3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"team_resource.js","sourceRoot":"","sources":["../../../../src/types/resources/services/team_resource.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,kFAAkF;AAClF,6DAA6D;AAC7D,wGAAwG;AACxG,2FAA2F","sourcesContent":["// All of our calls out to services go out to /chat, which isn't a typical pco-api vertex.\n// They all have a shared type of 'Chat' which has the following array attributes:\n// group_identifiers, people, plans, teams and, teams_i_lead.\n// All of these have a type array, are only rendered on request, and are exclusively used for this form.\n// There is also a payload which is a string, and is used to create a conversation in chat.\n\nimport { ResourceObject } from '../../api_primitives'\n\ninterface ServicesChatResource extends ResourceObject {\n type: 'Chat'\n}\n\nexport interface TeamResource extends ServicesChatResource {\n teamsILead: TeamResponseItem[]\n}\nexport interface TeamResponseItem {\n name: string\n value: {\n teamId: number\n serviceTypeId: number\n }\n serviceTypeName: string\n teamName: string\n order: [number, string, string] // [serviceTypeId, serviceTypeName, teamName]\n}\n\nexport interface TeamOptionResource extends ServicesChatResource {\n teams: TeamOptionResponseItem[]\n}\n\nexport interface TeamOptionResponseItem extends TeamResponseItem {\n group: TeamOptionResponseItemGroupName\n}\n\nexport type TeamOptionResponseItemGroupName = 'teams_i_lead' | 'other_teams'\n\nexport interface PlansResource extends ServicesChatResource {\n plans: PlansResponseItem[]\n}\n\ninterface PlansResponseItem {\n value: number\n name: string\n}\n\nexport interface TeamPeopleResource extends ServicesChatResource {\n people: PersonResponseItem[]\n}\n\ninterface PersonResponseItem {\n id: number\n name: string\n avatar: string\n badges: { title: string }[]\n child: boolean\n}\n\nexport interface ServicesChatPayloadResource extends ServicesChatResource {\n payload: string\n}\n"]}
1
+ {"version":3,"file":"team_resource.js","sourceRoot":"","sources":["../../../../src/types/resources/services/team_resource.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,kFAAkF;AAClF,6DAA6D;AAC7D,wGAAwG;AACxG,2FAA2F","sourcesContent":["// All of our calls out to services go out to /chat, which isn't a typical pco-api vertex.\n// They all have a shared type of 'Chat' which has the following array attributes:\n// group_identifiers, people, plans, teams and, teams_i_lead.\n// All of these have a type array, are only rendered on request, and are exclusively used for this form.\n// There is also a payload which is a string, and is used to create a conversation in chat.\n\nimport { ResourceObject } from '../../api_primitives'\n\ninterface ServicesChatResource extends ResourceObject {\n type: 'Chat'\n}\n\nexport interface TeamResource extends ServicesChatResource {\n teamsILead: TeamResponseItem[]\n}\nexport interface TeamResponseItem {\n name: string\n value: {\n teamId: number\n serviceTypeId: number\n }\n serviceTypeName: string\n teamName: string\n order: [number, string, string] // [serviceTypeId, serviceTypeName, teamName]\n}\n\nexport interface TeamOptionResource extends ServicesChatResource {\n teams: TeamOptionResponseItem[]\n}\n\nexport interface TeamOptionResponseItem extends TeamResponseItem {\n group: TeamOptionResponseItemGroupName\n}\n\nexport type TeamOptionResponseItemGroupName = 'teams_i_lead' | 'other_teams'\n\nexport interface PlansResource extends ServicesChatResource {\n plans: PlansResponseItem[]\n}\n\nexport interface PlansResponseItem {\n value: number\n name: string\n}\n\nexport interface TeamPeopleResource extends ServicesChatResource {\n people: TeamPersonResponseItem[]\n}\n\nexport interface TeamPersonResponseItem {\n id: number\n name: string\n avatar: string\n badges: { title: string }[]\n child: boolean\n}\n\nexport interface ServicesChatPayloadResource extends ServicesChatResource {\n payload: string\n}\n\nexport interface ServicesChatGroupIdentifiersResource extends ServicesChatResource {\n groupIdentifiers: string[]\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/chat-react-native",
3
- "version": "3.2.0",
3
+ "version": "3.4.0-rc.0",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -55,5 +55,5 @@
55
55
  "prettier": "^3.4.2",
56
56
  "typescript": "<5.6.0"
57
57
  },
58
- "gitHead": "b8b81c84940cbe1eba3343808b7fd06e0ced6924"
58
+ "gitHead": "464031a14a1949e994a9720294029b82e2513705"
59
59
  }
@@ -0,0 +1,97 @@
1
+ import { useMutation } from '@tanstack/react-query'
2
+ import {
3
+ ApiCollection,
4
+ ApiResource,
5
+ ConversationResource,
6
+ ServicesChatGroupIdentifiersResource,
7
+ ServicesChatPayloadResource,
8
+ } from '../../types'
9
+ import { ApiClient, useApiClient } from '../use_api_client'
10
+
11
+ interface Props {
12
+ teamIds: number[]
13
+ planId?: number
14
+ onSuccess?: (conversation: ConversationResource) => void
15
+ }
16
+
17
+ export function useFindOrCreateServicesConversation({ teamIds, planId, onSuccess }: Props) {
18
+ const teamAndPlanParams: TeamAndPlanParams = {
19
+ team_id: teamIds.join(','),
20
+ ...(planId ? { plan_id: planId } : {}),
21
+ }
22
+
23
+ const apiClient = useApiClient()
24
+ return useMutation({
25
+ throwOnError: true,
26
+ onSuccess: result => {
27
+ onSuccess && onSuccess(result)
28
+ },
29
+ mutationFn: async () => {
30
+ const foundConversations = await getGroupIdsFromServices(apiClient, teamAndPlanParams)
31
+ .then(res => res.data.groupIdentifiers)
32
+ .then(groupIdentifiers => findConversationWithExactTeams(apiClient, groupIdentifiers))
33
+ .catch(() => null)
34
+ const foundConversation = foundConversations?.data[0]
35
+
36
+ if (foundConversation?.id) {
37
+ return foundConversation
38
+ }
39
+
40
+ return fetchServicesPayload(apiClient, teamAndPlanParams)
41
+ .then(res => res.data.payload)
42
+ .then(payload => createConversation(apiClient, payload))
43
+ .then(res => res.data)
44
+ },
45
+ })
46
+ }
47
+
48
+ interface TeamAndPlanParams {
49
+ plan_id?: number | undefined
50
+ team_id: string
51
+ }
52
+
53
+ function fetchServicesPayload(apiClient: ApiClient, teamAndPlanParams: TeamAndPlanParams) {
54
+ return apiClient.services.get({
55
+ url: `/chat`,
56
+ data: {
57
+ ...teamAndPlanParams,
58
+ fields: {
59
+ Chat: ['payload'],
60
+ },
61
+ },
62
+ }) as Promise<ApiResource<ServicesChatPayloadResource>>
63
+ }
64
+
65
+ function getGroupIdsFromServices(apiClient: ApiClient, teamAndPlanParams: TeamAndPlanParams) {
66
+ return apiClient.services.get({
67
+ url: '/chat',
68
+ data: {
69
+ ...teamAndPlanParams,
70
+ fields: {
71
+ Chat: ['group_identifiers'],
72
+ },
73
+ },
74
+ }) as Promise<ApiResource<ServicesChatGroupIdentifiersResource>>
75
+ }
76
+
77
+ function findConversationWithExactTeams(apiClient: ApiClient, groupIdentifiers: string[]) {
78
+ return apiClient.chat.get({
79
+ url: '/me/conversations',
80
+ data: {
81
+ fields: {
82
+ Conversation: ['stream_channel'],
83
+ },
84
+ filter: 'with_exact_groups',
85
+ gids: groupIdentifiers.join(','),
86
+ },
87
+ }) as Promise<ApiCollection<ConversationResource>>
88
+ }
89
+
90
+ function createConversation(apiClient: ApiClient, payload: string) {
91
+ return apiClient.chat.post({
92
+ url: '/me/conversations',
93
+ data: {
94
+ data: { type: 'Conversation', attributes: { payload } },
95
+ },
96
+ }) as Promise<ApiResource<ConversationResource>>
97
+ }
@@ -0,0 +1,40 @@
1
+ import { isNil, omitBy } from 'lodash'
2
+ import { useApiGet } from '../use_api'
3
+ import { MemberResource, TeamPeopleResource, TeamPersonResponseItem } from '../../types'
4
+ import { useMemo } from 'react'
5
+
6
+ interface Props {
7
+ teamIds: number[]
8
+ planId?: number
9
+ }
10
+
11
+ export function useTeamMembersForNewConversation({ teamIds, planId }: Props) {
12
+ const params = omitBy({ team_id: teamIds.join(','), plan_id: planId }, isNil)
13
+
14
+ const { data, ...rest } = useApiGet<TeamPeopleResource>({
15
+ url: '/chat',
16
+ data: {
17
+ ...params,
18
+ fields: {
19
+ Chat: ['people'],
20
+ },
21
+ },
22
+ app: 'services',
23
+ })
24
+
25
+ const people = data?.people || stableEmptyPersonArray
26
+
27
+ const members: MemberResource[] = useMemo(() => {
28
+ return people.map(person => ({
29
+ ...person,
30
+ type: 'Member',
31
+ }))
32
+ }, [people])
33
+
34
+ return {
35
+ members,
36
+ ...rest,
37
+ }
38
+ }
39
+
40
+ const stableEmptyPersonArray: TeamPersonResponseItem[] = []
@@ -0,0 +1,42 @@
1
+ import { ApiResource, PlansResource } from '../../types'
2
+ import { ApiClient, useApiClient } from '../use_api_client'
3
+ import { useMemo } from 'react'
4
+ import { useQueries } from '@tanstack/react-query'
5
+ import { uniqBy } from 'lodash'
6
+
7
+ export function useTeamPlans({ teamIds }: { teamIds: number[] }) {
8
+ const apiClient = useApiClient()
9
+ const planQueries = useQueries({
10
+ queries: teamIds.map(teamId => ({
11
+ queryKey: ['plansForTeam', teamId],
12
+ queryFn: () => fetchTeamPlans(apiClient, teamId),
13
+ initialData: NULL_RESPONSE,
14
+ retry: false,
15
+ })),
16
+ })
17
+
18
+ const uniqPlanOptions = useMemo(() => {
19
+ const planOptions = planQueries.flatMap(({ data }) => data?.data.plans).filter(p => !!p)
20
+ return uniqBy(planOptions, 'value')
21
+ }, [planQueries])
22
+
23
+ return {
24
+ planOptions: uniqPlanOptions,
25
+ isFetching: planQueries.some(({ isFetching }) => isFetching),
26
+ isError: planQueries.some(({ isError }) => isError),
27
+ }
28
+ }
29
+
30
+ const NULL_RESPONSE = { data: { id: 0, plans: [], type: 'Chat' }, links: {}, meta: {} }
31
+
32
+ function fetchTeamPlans(apiClient: ApiClient, teamId: number) {
33
+ return apiClient.services.get<ApiResource<PlansResource>>({
34
+ url: '/chat',
35
+ data: {
36
+ team_id: teamId,
37
+ fields: {
38
+ Chat: ['plans'],
39
+ },
40
+ },
41
+ })
42
+ }
@@ -0,0 +1,44 @@
1
+ import { useCallback } from 'react'
2
+ import { useApiGet } from '../../hooks/use_api'
3
+ import { ResourceObject, TeamResponseItem } from '../../types'
4
+
5
+ export function useTeamsILead() {
6
+ const response = useApiGet<ServicesChatResource>({
7
+ url: `/chat`,
8
+ data: {
9
+ fields: {
10
+ Chat: ['teams_i_lead'],
11
+ },
12
+ },
13
+ app: 'services',
14
+ })
15
+
16
+ const { data, isFetching, isError, error } = response
17
+
18
+ const teamsILead = data?.teamsILead || stableEmptyTeamResponseArray
19
+
20
+ const hasNoServicesAccess = useCallback(() => {
21
+ if (isError && error) {
22
+ const errorArray = error
23
+ const allErrors = errorArray.errors.map(e => e.detail)
24
+ if (allErrors.some(e => e.includes(NOT_IN_APPLICATION_CODE))) {
25
+ return true
26
+ }
27
+ }
28
+ return false
29
+ }, [isError, error])
30
+
31
+ return {
32
+ teamsILead,
33
+ isFetching,
34
+ isError,
35
+ noServicesAccess: hasNoServicesAccess(),
36
+ }
37
+ }
38
+
39
+ interface ServicesChatResource extends ResourceObject {
40
+ teamsILead: TeamResponseItem[]
41
+ }
42
+
43
+ const stableEmptyTeamResponseArray: TeamResponseItem[] = []
44
+ const NOT_IN_APPLICATION_CODE = 'TRASH_PANDA'
@@ -1,44 +1,19 @@
1
- import { useCallback, useMemo } from 'react'
2
- import { useApiGet } from '../../../hooks/use_api'
3
- import { ResourceObject } from '../../../types'
1
+ import { useMemo } from 'react'
2
+ import { TeamResponseItem } from '../../../types'
4
3
  import { ServiceTypeWithTeams } from '../types'
5
4
  import { uniqBy } from 'lodash'
5
+ import { useTeamsILead } from '../../../hooks/services/use_teams_i_lead'
6
6
 
7
7
  export function useServiceTypesWithTeams() {
8
- const response = useApiGet<ServicesChatResource>({
9
- url: `/chat`,
10
- data: {
11
- fields: {
12
- Chat: ['teams_i_lead'],
13
- },
14
- },
15
- app: 'services',
16
- })
17
-
18
- const { data, isFetching, isError, error } = response
19
-
20
- const teamsILead = data?.teamsILead || stableEmptyTeamResponseArray
8
+ const { teamsILead, ...data } = useTeamsILead()
21
9
 
22
10
  const decoratedResponse = useMemo(() => {
23
11
  return decorateTeamResponseItems(teamsILead)
24
12
  }, [teamsILead])
25
13
 
26
- const hasNoServicesAccess = useCallback(() => {
27
- if (isError && error) {
28
- const errorArray = error
29
- const allErrors = errorArray.errors.map(e => e.detail)
30
- if (allErrors.some(e => e.includes(NOT_IN_APPLICATION_CODE))) {
31
- return true
32
- }
33
- }
34
- return false
35
- }, [isError, error])
36
-
37
14
  return {
38
15
  serviceTypes: decoratedResponse,
39
- isFetching,
40
- isError,
41
- noServicesAccess: hasNoServicesAccess(),
16
+ ...data,
42
17
  }
43
18
  }
44
19
 
@@ -70,21 +45,3 @@ function decorateTeamResponseItems(teamResponseItems: TeamResponseItem[]) {
70
45
  return acc
71
46
  }, [])
72
47
  }
73
-
74
- interface TeamResponseItem {
75
- value: {
76
- teamId: number
77
- serviceTypeId: number
78
- }
79
- name: string
80
- teamName: string
81
- serviceTypeName: string
82
- order: string[]
83
- }
84
-
85
- interface ServicesChatResource extends ResourceObject {
86
- teamsILead: TeamResponseItem[]
87
- }
88
-
89
- const stableEmptyTeamResponseArray: TeamResponseItem[] = []
90
- const NOT_IN_APPLICATION_CODE = 'TRASH_PANDA'
@@ -0,0 +1,64 @@
1
+ import { Pressable, StyleSheet, View } from 'react-native'
2
+ import { Banner, Spinner, Text } from '../../../components'
3
+ import { useTeamPlans } from '../../../hooks/services/use_team_plans'
4
+
5
+ interface Props {
6
+ teamIds: number[]
7
+ planId?: number
8
+ onChange: (planId: number | undefined) => void
9
+ }
10
+
11
+ export function FilterByPlanOptions({ teamIds, planId, onChange }: Props) {
12
+ const styles = useStyles()
13
+ const { planOptions, isFetching, isError } = useTeamPlans({ teamIds })
14
+
15
+ if (isFetching) {
16
+ return (
17
+ <View style={styles.spinnerContainer}>
18
+ <Spinner size={20} />
19
+ </View>
20
+ )
21
+ }
22
+
23
+ if (isError) {
24
+ return (
25
+ <Banner
26
+ appearance="error"
27
+ description="An error occurred while fetching plans. Please try again."
28
+ />
29
+ )
30
+ }
31
+
32
+ if (!planOptions || planOptions.length === 0) {
33
+ return (
34
+ <Banner
35
+ appearance="neutral"
36
+ description="No plans available with the selected service type."
37
+ />
38
+ )
39
+ }
40
+
41
+ return (
42
+ <View style={styles.filterByPlanSectionRow}>
43
+ {planOptions.map(plan => (
44
+ <Pressable onPress={() => onChange(plan.value)} key={plan.value}>
45
+ <Text style={[planId === plan.value ? styles.selectedFilter : null]}>{plan.name}</Text>
46
+ </Pressable>
47
+ ))}
48
+ </View>
49
+ )
50
+ }
51
+
52
+ const useStyles = () => {
53
+ return StyleSheet.create({
54
+ spinnerContainer: {
55
+ marginVertical: 8,
56
+ },
57
+ filterByPlanSectionRow: {
58
+ gap: 8,
59
+ },
60
+ selectedFilter: {
61
+ fontWeight: '800',
62
+ },
63
+ })
64
+ }