@planningcenter/chat-react-native 3.16.0-rc.4 → 3.16.0-rc.6

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 (93) hide show
  1. package/build/components/conversation/message.js +1 -1
  2. package/build/components/conversation/message.js.map +1 -1
  3. package/build/contexts/chat_context.d.ts +3 -0
  4. package/build/contexts/chat_context.d.ts.map +1 -1
  5. package/build/contexts/chat_context.js +3 -1
  6. package/build/contexts/chat_context.js.map +1 -1
  7. package/build/hooks/index.d.ts +3 -0
  8. package/build/hooks/index.d.ts.map +1 -1
  9. package/build/hooks/index.js +3 -0
  10. package/build/hooks/index.js.map +1 -1
  11. package/build/hooks/use_current_person.d.ts.map +1 -1
  12. package/build/hooks/use_current_person.js +9 -1
  13. package/build/hooks/use_current_person.js.map +1 -1
  14. package/build/hooks/use_organization.d.ts +39 -0
  15. package/build/hooks/use_organization.d.ts.map +1 -0
  16. package/build/hooks/use_organization.js +14 -0
  17. package/build/hooks/use_organization.js.map +1 -0
  18. package/build/hooks/use_qualified_by_age.d.ts +2 -0
  19. package/build/hooks/use_qualified_by_age.d.ts.map +1 -0
  20. package/build/hooks/use_qualified_by_age.js +14 -0
  21. package/build/hooks/use_qualified_by_age.js.map +1 -0
  22. package/build/hooks/use_submit_age_check.d.ts +78 -0
  23. package/build/hooks/use_submit_age_check.d.ts.map +1 -0
  24. package/build/hooks/use_submit_age_check.js +37 -0
  25. package/build/hooks/use_submit_age_check.js.map +1 -0
  26. package/build/index.d.ts +1 -0
  27. package/build/index.d.ts.map +1 -1
  28. package/build/index.js +1 -0
  29. package/build/index.js.map +1 -1
  30. package/build/navigation/chat_access_gate.d.ts +3 -0
  31. package/build/navigation/chat_access_gate.d.ts.map +1 -0
  32. package/build/navigation/chat_access_gate.js +46 -0
  33. package/build/navigation/chat_access_gate.js.map +1 -0
  34. package/build/navigation/index.d.ts +6 -4
  35. package/build/navigation/index.d.ts.map +1 -1
  36. package/build/navigation/index.js +6 -3
  37. package/build/navigation/index.js.map +1 -1
  38. package/build/navigation/screenLayout.d.ts +3 -0
  39. package/build/navigation/screenLayout.d.ts.map +1 -1
  40. package/build/navigation/screenLayout.js +8 -0
  41. package/build/navigation/screenLayout.js.map +1 -1
  42. package/build/screens/age_check/age_check_required_screen.d.ts +3 -0
  43. package/build/screens/age_check/age_check_required_screen.d.ts.map +1 -0
  44. package/build/screens/age_check/age_check_required_screen.js +148 -0
  45. package/build/screens/age_check/age_check_required_screen.js.map +1 -0
  46. package/build/screens/age_check/age_check_underage_screen.d.ts +6 -0
  47. package/build/screens/age_check/age_check_underage_screen.d.ts.map +1 -0
  48. package/build/screens/age_check/age_check_underage_screen.js +71 -0
  49. package/build/screens/age_check/age_check_underage_screen.js.map +1 -0
  50. package/build/screens/age_check/components/age_check_select_birthdate_modal.d.ts +11 -0
  51. package/build/screens/age_check/components/age_check_select_birthdate_modal.d.ts.map +1 -0
  52. package/build/screens/age_check/components/age_check_select_birthdate_modal.js +91 -0
  53. package/build/screens/age_check/components/age_check_select_birthdate_modal.js.map +1 -0
  54. package/build/screens/age_check/index.d.ts +3 -0
  55. package/build/screens/age_check/index.d.ts.map +1 -0
  56. package/build/screens/age_check/index.js +3 -0
  57. package/build/screens/age_check/index.js.map +1 -0
  58. package/build/screens/age_check/screen_props.d.ts +5 -0
  59. package/build/screens/age_check/screen_props.d.ts.map +1 -0
  60. package/build/screens/age_check/screen_props.js +2 -0
  61. package/build/screens/age_check/screen_props.js.map +1 -0
  62. package/build/types/resources/index.d.ts +1 -0
  63. package/build/types/resources/index.d.ts.map +1 -1
  64. package/build/types/resources/index.js +1 -0
  65. package/build/types/resources/index.js.map +1 -1
  66. package/build/types/resources/organization.d.ts +6 -0
  67. package/build/types/resources/organization.d.ts.map +1 -0
  68. package/build/types/resources/organization.js +2 -0
  69. package/build/types/resources/organization.js.map +1 -0
  70. package/build/types/resources/person.d.ts +7 -0
  71. package/build/types/resources/person.d.ts.map +1 -1
  72. package/build/types/resources/person.js +5 -1
  73. package/build/types/resources/person.js.map +1 -1
  74. package/package.json +3 -2
  75. package/src/components/conversation/message.tsx +1 -1
  76. package/src/contexts/chat_context.tsx +14 -1
  77. package/src/hooks/index.ts +3 -0
  78. package/src/hooks/use_current_person.ts +9 -1
  79. package/src/hooks/use_organization.ts +17 -0
  80. package/src/hooks/use_qualified_by_age.ts +17 -0
  81. package/src/hooks/use_submit_age_check.ts +48 -0
  82. package/src/index.tsx +1 -0
  83. package/src/navigation/chat_access_gate.tsx +60 -0
  84. package/src/navigation/index.tsx +6 -3
  85. package/src/navigation/screenLayout.tsx +11 -0
  86. package/src/screens/age_check/age_check_required_screen.tsx +197 -0
  87. package/src/screens/age_check/age_check_underage_screen.tsx +96 -0
  88. package/src/screens/age_check/components/age_check_select_birthdate_modal.tsx +143 -0
  89. package/src/screens/age_check/index.ts +2 -0
  90. package/src/screens/age_check/screen_props.ts +3 -0
  91. package/src/types/resources/index.ts +1 -0
  92. package/src/types/resources/organization.ts +6 -0
  93. package/src/types/resources/person.ts +10 -0
@@ -0,0 +1,197 @@
1
+ import React, { useState } from 'react'
2
+ import { date as formatDate } from '@planningcenter/datetime-fmt'
3
+ import { StyleSheet, View } from 'react-native'
4
+ import { Button } from '../../components/display/button'
5
+ import { Text } from '../../components/display/text'
6
+ import { Icon } from '../../components/display/icon'
7
+ import { Avatar } from '../../components/display/avatar'
8
+ import { Banner } from '../../components/display/banner'
9
+ import { useCurrentPerson, useTheme, useSubmitAgeCheck } from '../../hooks'
10
+ import { Heading } from '../../components'
11
+ import { platformFontWeightBold } from '../../utils/styles'
12
+ import { AgeCheckSelectBirthdateModal } from './components/age_check_select_birthdate_modal'
13
+
14
+ function calculateAge(birthdate: Date) {
15
+ const today = new Date()
16
+ const age = today.getFullYear() - birthdate.getFullYear()
17
+ const hasHadBirthdayThisYear =
18
+ today.getMonth() > birthdate.getMonth() ||
19
+ (today.getMonth() === birthdate.getMonth() && today.getDate() >= birthdate.getDate())
20
+ return hasHadBirthdayThisYear ? age : age - 1
21
+ }
22
+
23
+ export function AgeCheckRequiredScreen() {
24
+ const person = useCurrentPerson()
25
+
26
+ const [birthdate, setBirthdate] = useState<Date | null>(null)
27
+ const [draftBirthdate, setDraftBirthdate] = useState<Date | null>(null)
28
+ const [isBirthdateModalOpen, setBirthdateModalOpen] = useState(false)
29
+ const { submitAgeCheck, isPending, isSuccess } = useSubmitAgeCheck()
30
+ const styles = useStyles()
31
+
32
+ const birthdateStamp = birthdate
33
+ ? formatDate(birthdate, { style: 'standard', year: true })
34
+ : 'Missing'
35
+
36
+ const age = birthdate ? calculateAge(birthdate) : null
37
+
38
+ const confirmationText =
39
+ birthdate !== null
40
+ ? `Your birthdate is ${birthdateStamp} and you are ${age} years old. Is that correct?`
41
+ : ''
42
+
43
+ const openBirthdateModal = () => {
44
+ setDraftBirthdate(birthdate || new Date())
45
+ setBirthdateModalOpen(true)
46
+ }
47
+
48
+ const closeBirthdateModal = () => {
49
+ setBirthdateModalOpen(false)
50
+ }
51
+
52
+ const handleSubmitAgeConfirmation = () => {
53
+ if (!birthdate) return
54
+ submitAgeCheck(birthdate)
55
+ }
56
+
57
+ return (
58
+ <View style={styles.container}>
59
+ <View style={styles.header}>
60
+ <View style={styles.heading}>
61
+ <Icon name="general.exclamationTriangle" size={20} style={styles.warningIcon} />
62
+ <Heading variant="h2">Your age is required to use chat</Heading>
63
+ </View>
64
+ <Text>To help protect those who may be underage, we need to verify your birthdate.</Text>
65
+ </View>
66
+ <View style={styles.cards}>
67
+ <View style={styles.card}>
68
+ <Heading variant="h3">My profile</Heading>
69
+ <View style={styles.profileContent}>
70
+ <Avatar sourceUri={person.avatar || ''} size="lg" showFallback={!person.avatar} />
71
+ <View style={styles.personInfo}>
72
+ <Text style={styles.name} numberOfLines={1}>
73
+ {person.name}
74
+ </Text>
75
+ <View style={styles.birthdateInfo}>
76
+ <Text variant="tertiary" style={styles.birthdateLabel}>
77
+ Birthdate:
78
+ </Text>
79
+ <Text variant="tertiary">{birthdateStamp}</Text>
80
+ </View>
81
+ </View>
82
+ {birthdate && (
83
+ <Button
84
+ title="Edit"
85
+ accessibilityHint="Choose a new birthdate for your profile"
86
+ size="sm"
87
+ variant="outline"
88
+ style={styles.editButton}
89
+ onPress={openBirthdateModal}
90
+ />
91
+ )}
92
+ </View>
93
+ {!birthdate && (
94
+ <Button
95
+ title="Add birthdate"
96
+ accessibilityHint="Choose a birthdate for your profile"
97
+ size="lg"
98
+ style={styles.primaryButton}
99
+ onPress={openBirthdateModal}
100
+ />
101
+ )}
102
+ </View>
103
+ {birthdate && (
104
+ <View style={styles.card}>
105
+ <Heading variant="h3">Age confirmation</Heading>
106
+ {age !== null && age < 13 ? (
107
+ <Banner appearance="warning" description={confirmationText} />
108
+ ) : (
109
+ <Text>{confirmationText}</Text>
110
+ )}
111
+ <Button
112
+ title="Yes, update my profile"
113
+ size="lg"
114
+ style={styles.primaryButton}
115
+ onPress={handleSubmitAgeConfirmation}
116
+ disabled={isPending || isSuccess}
117
+ />
118
+ </View>
119
+ )}
120
+ </View>
121
+ <Text variant="tertiary">
122
+ Your birthdate will be added to your profile, but it is only visible to church
123
+ administrators. It will not be shared with other church members, unless you give permission.
124
+ </Text>
125
+
126
+ <AgeCheckSelectBirthdateModal
127
+ onChange={setDraftBirthdate}
128
+ onDismiss={closeBirthdateModal}
129
+ onRequestClose={closeBirthdateModal}
130
+ onSubmit={setBirthdate}
131
+ value={draftBirthdate || new Date()}
132
+ visible={isBirthdateModalOpen}
133
+ />
134
+ </View>
135
+ )
136
+ }
137
+
138
+ const useStyles = () => {
139
+ const { colors } = useTheme()
140
+ return StyleSheet.create({
141
+ container: {
142
+ flex: 1,
143
+ backgroundColor: colors.surfaceColor080,
144
+ padding: 16,
145
+ gap: 16,
146
+ },
147
+ header: {
148
+ gap: 8,
149
+ },
150
+ heading: {
151
+ flexDirection: 'row',
152
+ alignItems: 'center',
153
+ gap: 8,
154
+ },
155
+ warningIcon: {
156
+ color: colors.statusErrorIcon,
157
+ },
158
+ card: {
159
+ backgroundColor: colors.surfaceColor100,
160
+ borderRadius: 8,
161
+ padding: 16,
162
+ gap: 8,
163
+ },
164
+ cards: {
165
+ gap: 8,
166
+ },
167
+ profileContent: {
168
+ flexDirection: 'row',
169
+ alignItems: 'center',
170
+ gap: 8,
171
+ },
172
+ personInfo: {
173
+ flex: 1,
174
+ },
175
+ primaryButton: {
176
+ alignSelf: 'center',
177
+ marginTop: 8,
178
+ },
179
+ name: {
180
+ fontWeight: platformFontWeightBold,
181
+ },
182
+ birthdateLabel: {
183
+ fontWeight: platformFontWeightBold,
184
+ },
185
+ birthdateInfo: {
186
+ flexDirection: 'row',
187
+ alignItems: 'center',
188
+ gap: 4,
189
+ },
190
+ editButton: {
191
+ alignSelf: 'flex-end',
192
+ },
193
+ formSheetContent: {
194
+ flex: 1,
195
+ },
196
+ })
197
+ }
@@ -0,0 +1,96 @@
1
+ import React from 'react'
2
+ import { Linking, StyleSheet, View } from 'react-native'
3
+ import { Heading } from '../../components'
4
+ import { Icon, Text, TextInlineButton } from '../../components/display'
5
+ import { useTheme } from '../../hooks'
6
+ import { useRoute, RouteProp } from '@react-navigation/native'
7
+ import type { AgeCheckParams } from './screen_props'
8
+
9
+ export interface AgeCheckUnderageScreenProps {
10
+ contactEmail?: string
11
+ }
12
+
13
+ export function AgeCheckUnderageScreen({ contactEmail }: AgeCheckUnderageScreenProps = {}) {
14
+ const styles = useStyles()
15
+ const route = useRoute<RouteProp<AgeCheckParams, 'UnderagePerson'>>()
16
+
17
+ const email = contactEmail ?? route?.params?.contactEmail
18
+
19
+ const openChurchAdminHelp = () => {
20
+ if (!email) return
21
+ Linking.openURL(`mailto:${email}`)
22
+ }
23
+
24
+ const openTerms = () => {
25
+ Linking.openURL('https://www.planningcenter.com/terms')
26
+ }
27
+
28
+ return (
29
+ <View style={styles.container}>
30
+ <Icon name="general.outlinedTextMessage" size={32} style={styles.icon} />
31
+
32
+ <View style={styles.content}>
33
+ <Heading variant="h3" style={styles.baseText}>
34
+ Your age does not meet the minimum safety requirements to use chat.
35
+ </Heading>
36
+ <Text variant="tertiary" style={styles.baseText}>
37
+ If you submitted the wrong birthdate by accident,{` `}
38
+ {email ? (
39
+ <TextInlineButton
40
+ onPress={openChurchAdminHelp}
41
+ accessibilityRole="link"
42
+ accessibilityHint="Opens email client to contact your church administrator"
43
+ style={styles.linkText}
44
+ >
45
+ please contact your church administrator
46
+ </TextInlineButton>
47
+ ) : (
48
+ <>please contact your church administrator</>
49
+ )}
50
+ {` `}
51
+ to update your profile.
52
+ </Text>
53
+ <Text variant="tertiary" style={styles.baseText}>
54
+ For more information, view our{` `}
55
+ <TextInlineButton
56
+ onPress={openTerms}
57
+ accessibilityRole="link"
58
+ accessibilityHint="Opens terms of service in browser"
59
+ style={styles.linkText}
60
+ >
61
+ terms of service
62
+ </TextInlineButton>
63
+ .
64
+ </Text>
65
+ </View>
66
+ </View>
67
+ )
68
+ }
69
+
70
+ const useStyles = () => {
71
+ const { colors } = useTheme()
72
+ return StyleSheet.create({
73
+ container: {
74
+ flex: 1,
75
+ alignItems: 'center',
76
+ justifyContent: 'center',
77
+ gap: 16,
78
+ padding: 16,
79
+ },
80
+ icon: {
81
+ color: colors.iconColorDefaultDisabled,
82
+ },
83
+ content: {
84
+ alignItems: 'center',
85
+ gap: 8,
86
+ maxWidth: 250,
87
+ },
88
+ baseText: {
89
+ textAlign: 'center',
90
+ color: colors.textColorDefaultSecondary,
91
+ },
92
+ linkText: {
93
+ fontSize: 14,
94
+ },
95
+ })
96
+ }
@@ -0,0 +1,143 @@
1
+ import React from 'react'
2
+ import { Modal, Platform, StyleSheet, View } from 'react-native'
3
+ import DateTimePicker, { DateTimePickerEvent } from '@react-native-community/datetimepicker'
4
+ import FormSheet from '../../../components/primitive/form_sheet'
5
+ import { Text } from '../../../components/display/text'
6
+ import { useTheme } from '../../../hooks'
7
+
8
+ export type AgeCheckSelectBirthdateProps = {
9
+ onChange: (date: Date) => void
10
+ onDismiss?: () => void
11
+ onRequestClose: () => void
12
+ onSubmit: (date: Date) => void
13
+ value?: Date | null
14
+ visible?: boolean
15
+ }
16
+
17
+ export function AgeCheckSelectBirthdateModal(props: AgeCheckSelectBirthdateProps) {
18
+ if (Platform.OS === 'ios') return <IOSBirthdateModal {...props} />
19
+ if (Platform.OS === 'android') return <AndroidBirthdatePicker {...props} />
20
+
21
+ return <Text>Birthdate selection is not supported on this platform.</Text>
22
+ }
23
+
24
+ function IOSBirthdateModal({
25
+ onChange,
26
+ onDismiss,
27
+ onRequestClose,
28
+ onSubmit,
29
+ value,
30
+ visible,
31
+ }: AgeCheckSelectBirthdateProps) {
32
+ const styles = useStyles()
33
+ const today = React.useMemo(() => new Date(), [])
34
+ const selected = value || today
35
+
36
+ const handleChange = (_event: DateTimePickerEvent, date?: Date) => {
37
+ if (date) onChange(date)
38
+ }
39
+
40
+ const handleSave = () => {
41
+ onSubmit(selected)
42
+ onRequestClose()
43
+ }
44
+
45
+ return (
46
+ <Modal
47
+ visible={visible}
48
+ animationType="slide"
49
+ presentationStyle="pageSheet"
50
+ onRequestClose={onRequestClose}
51
+ onDismiss={onDismiss}
52
+ >
53
+ <FormSheet.Root style={styles.formSheet} contentStyle={styles.formSheetContent}>
54
+ <FormSheet.Header>
55
+ <FormSheet.HeaderTitle>Your birthdate</FormSheet.HeaderTitle>
56
+ <FormSheet.HeaderActions>
57
+ <FormSheet.HeaderTextButton onPress={onRequestClose}>Cancel</FormSheet.HeaderTextButton>
58
+ <FormSheet.HeaderButton title="Save" onPress={handleSave} />
59
+ </FormSheet.HeaderActions>
60
+ </FormSheet.Header>
61
+ <View style={styles.content}>
62
+ <View style={styles.card}>
63
+ <Text nativeID="birthdateLabel" variant="tertiary" style={styles.label}>
64
+ Birthdate
65
+ </Text>
66
+ <View style={styles.pickerContainer}>
67
+ <DateTimePicker
68
+ accessibilityLabelledBy="birthdateLabel"
69
+ testID="age-check-date-picker"
70
+ mode="date"
71
+ display="spinner"
72
+ value={selected}
73
+ maximumDate={today}
74
+ onChange={handleChange}
75
+ />
76
+ </View>
77
+ </View>
78
+ </View>
79
+ </FormSheet.Root>
80
+ </Modal>
81
+ )
82
+ }
83
+
84
+ function AndroidBirthdatePicker({
85
+ onRequestClose,
86
+ onSubmit,
87
+ value,
88
+ visible,
89
+ }: AgeCheckSelectBirthdateProps) {
90
+ if (!visible) return null
91
+
92
+ const today = new Date()
93
+ const selected = value || today
94
+
95
+ const handleAndroidDateChange = (event: DateTimePickerEvent, selectedDate?: Date) => {
96
+ if (event.type === 'set' && selectedDate) {
97
+ onSubmit(selectedDate)
98
+ }
99
+ onRequestClose()
100
+ }
101
+
102
+ return (
103
+ <DateTimePicker
104
+ testID="age-check-date-picker-android"
105
+ mode="date"
106
+ display="spinner"
107
+ value={selected}
108
+ maximumDate={today}
109
+ onChange={handleAndroidDateChange}
110
+ />
111
+ )
112
+ }
113
+
114
+ const useStyles = () => {
115
+ const { colors } = useTheme()
116
+ return StyleSheet.create({
117
+ formSheet: {
118
+ backgroundColor: colors.surfaceColor080,
119
+ },
120
+ formSheetContent: {
121
+ flex: 1,
122
+ },
123
+ content: {
124
+ flex: 1,
125
+ backgroundColor: colors.surfaceColor080,
126
+ padding: 16,
127
+ justifyContent: 'flex-start',
128
+ },
129
+ card: {
130
+ backgroundColor: colors.surfaceColor100,
131
+ borderRadius: 8,
132
+ padding: 16,
133
+ },
134
+ label: {
135
+ marginBottom: 8,
136
+ textTransform: 'uppercase',
137
+ },
138
+ pickerContainer: {
139
+ backgroundColor: colors.surfaceColor100,
140
+ borderRadius: 8,
141
+ },
142
+ })
143
+ }
@@ -0,0 +1,2 @@
1
+ export * from './age_check_required_screen'
2
+ export * from './age_check_underage_screen'
@@ -0,0 +1,3 @@
1
+ export type AgeCheckContactInfo = { contactEmail?: string }
2
+
3
+ export type AgeCheckParams = Record<string, AgeCheckContactInfo>
@@ -6,3 +6,4 @@ export * from './person'
6
6
  export * from './groups'
7
7
  export * from './app_grant'
8
8
  export * from './services'
9
+ export * from './organization'
@@ -0,0 +1,6 @@
1
+ import { ResourceObject } from '../api_primitives'
2
+
3
+ export interface OrganizationResource extends ResourceObject {
4
+ type: 'Organization'
5
+ contactEmail?: string
6
+ }
@@ -1,5 +1,14 @@
1
1
  import { ResourceObject } from '../api_primitives'
2
2
 
3
+ export const AgeQualificationStatus = {
4
+ QUALIFIED: 'qualified',
5
+ DISQUALIFIED: 'disqualified',
6
+ UNKNOWN: 'unknown',
7
+ } as const
8
+
9
+ export type AgeQualificationStatus =
10
+ (typeof AgeQualificationStatus)[keyof typeof AgeQualificationStatus]
11
+
3
12
  export interface PersonResource extends ResourceObject {
4
13
  id: number
5
14
  type: 'Person'
@@ -11,4 +20,5 @@ export interface CurrentPersonResource extends PersonResource {
11
20
  canChat: boolean
12
21
  unreadCount: number
13
22
  pcoChatEnabled: boolean
23
+ ageQualificationStatus?: AgeQualificationStatus
14
24
  }