@planningcenter/chat-react-native 3.36.1 → 3.36.2-qa-726.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/components/conversation/message.d.ts.map +1 -1
- package/build/components/conversation/message.js +3 -3
- package/build/components/conversation/message.js.map +1 -1
- package/build/components/display/action_button.d.ts +3 -1
- package/build/components/display/action_button.d.ts.map +1 -1
- package/build/components/display/action_button.js +8 -1
- package/build/components/display/action_button.js.map +1 -1
- package/build/components/display/index.d.ts +1 -0
- package/build/components/display/index.d.ts.map +1 -1
- package/build/components/display/index.js +1 -0
- package/build/components/display/index.js.map +1 -1
- package/build/components/index.d.ts +2 -0
- package/build/components/index.d.ts.map +1 -1
- package/build/components/index.js +2 -0
- package/build/components/index.js.map +1 -1
- package/build/components/page/component_error_boundary.d.ts +4 -0
- package/build/components/page/component_error_boundary.d.ts.map +1 -0
- package/build/components/page/component_error_boundary.js +8 -0
- package/build/components/page/component_error_boundary.js.map +1 -0
- package/build/components/page/error_boundary.d.ts +13 -10
- package/build/components/page/error_boundary.d.ts.map +1 -1
- package/build/components/page/error_boundary.js +20 -90
- package/build/components/page/error_boundary.js.map +1 -1
- package/build/components/page/page_error_boundary.d.ts +4 -0
- package/build/components/page/page_error_boundary.d.ts.map +1 -0
- package/build/components/page/page_error_boundary.js +80 -0
- package/build/components/page/page_error_boundary.js.map +1 -0
- package/build/hooks/groups/use_group_chat_conversation_payload.d.ts +168 -0
- package/build/hooks/groups/use_group_chat_conversation_payload.d.ts.map +1 -0
- package/build/hooks/groups/use_group_chat_conversation_payload.js +23 -0
- package/build/hooks/groups/use_group_chat_conversation_payload.js.map +1 -0
- package/build/hooks/groups/use_group_members_for_new_conversation.d.ts +0 -4
- package/build/hooks/groups/use_group_members_for_new_conversation.d.ts.map +1 -1
- package/build/hooks/groups/use_group_members_for_new_conversation.js +6 -18
- package/build/hooks/groups/use_group_members_for_new_conversation.js.map +1 -1
- package/build/hooks/groups/use_groups_conversation_create.js +1 -1
- package/build/hooks/groups/use_groups_conversation_create.js.map +1 -1
- package/build/hooks/index.d.ts +2 -1
- package/build/hooks/index.d.ts.map +1 -1
- package/build/hooks/index.js +2 -1
- package/build/hooks/index.js.map +1 -1
- package/build/hooks/services/use_find_or_create_services_conversation.d.ts +11 -3
- package/build/hooks/services/use_find_or_create_services_conversation.d.ts.map +1 -1
- package/build/hooks/services/use_find_or_create_services_conversation.js +10 -14
- package/build/hooks/services/use_find_or_create_services_conversation.js.map +1 -1
- package/build/hooks/services/use_services_chat_conversation_payload.d.ts +164 -0
- package/build/hooks/services/use_services_chat_conversation_payload.d.ts.map +1 -0
- package/build/hooks/services/use_services_chat_conversation_payload.js +16 -0
- package/build/hooks/services/use_services_chat_conversation_payload.js.map +1 -0
- package/build/hooks/services/use_team_members_for_new_conversation.d.ts.map +1 -1
- package/build/hooks/services/use_team_members_for_new_conversation.js +11 -4
- package/build/hooks/services/use_team_members_for_new_conversation.js.map +1 -1
- package/build/hooks/use_conversation_validate.d.ts +12 -0
- package/build/hooks/use_conversation_validate.d.ts.map +1 -0
- package/build/hooks/use_conversation_validate.js +28 -0
- package/build/hooks/use_conversation_validate.js.map +1 -0
- package/build/hooks/use_enrich_people.d.ts +13 -0
- package/build/hooks/use_enrich_people.d.ts.map +1 -0
- package/build/hooks/use_enrich_people.js +25 -0
- package/build/hooks/use_enrich_people.js.map +1 -0
- package/build/hooks/use_features.d.ts +9 -6
- package/build/hooks/use_features.d.ts.map +1 -1
- package/build/hooks/use_features.js +1 -0
- package/build/hooks/use_features.js.map +1 -1
- package/build/hooks/use_jolt.d.ts +2 -1
- package/build/hooks/use_jolt.d.ts.map +1 -1
- package/build/hooks/use_jolt.js.map +1 -1
- package/build/hooks/use_product_analytics.d.ts +7 -1
- package/build/hooks/use_product_analytics.d.ts.map +1 -1
- package/build/hooks/use_product_analytics.js +4 -0
- package/build/hooks/use_product_analytics.js.map +1 -1
- package/build/index.d.ts +3 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -1
- package/build/index.js.map +1 -1
- package/build/navigation/screenLayout.d.ts.map +1 -1
- package/build/navigation/screenLayout.js +5 -3
- package/build/navigation/screenLayout.js.map +1 -1
- package/build/screens/conversation_details_screen.js +1 -1
- package/build/screens/conversation_details_screen.js.map +1 -1
- package/build/screens/conversation_new/components/groups_form.d.ts.map +1 -1
- package/build/screens/conversation_new/components/groups_form.js +14 -1
- package/build/screens/conversation_new/components/groups_form.js.map +1 -1
- package/build/screens/conversation_new/components/services_form.d.ts.map +1 -1
- package/build/screens/conversation_new/components/services_form.js +20 -2
- package/build/screens/conversation_new/components/services_form.js.map +1 -1
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +2 -2
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/screens/conversations/conversations_screen.js +2 -2
- package/build/screens/conversations/conversations_screen.js.map +1 -1
- package/build/screens/team_conversation_screen.d.ts.map +1 -1
- package/build/screens/team_conversation_screen.js +6 -3
- package/build/screens/team_conversation_screen.js.map +1 -1
- package/build/types/jolt_events/index.d.ts +2 -0
- package/build/types/jolt_events/index.d.ts.map +1 -1
- package/build/types/jolt_events/index.js.map +1 -1
- package/build/types/resources/conversation_validate.d.ts +10 -0
- package/build/types/resources/conversation_validate.d.ts.map +1 -0
- package/build/types/resources/conversation_validate.js +2 -0
- package/build/types/resources/conversation_validate.js.map +1 -0
- package/build/types/resources/index.d.ts +1 -0
- package/build/types/resources/index.d.ts.map +1 -1
- package/build/types/resources/index.js +1 -0
- package/build/types/resources/index.js.map +1 -1
- package/build/utils/cache/messages_cache.d.ts +1 -1
- package/build/utils/cache/messages_cache.d.ts.map +1 -1
- package/build/utils/client/instrumented_fetch.d.ts +2 -0
- package/build/utils/client/instrumented_fetch.d.ts.map +1 -0
- package/build/utils/client/instrumented_fetch.js +64 -0
- package/build/utils/client/instrumented_fetch.js.map +1 -0
- package/build/utils/client/request_helpers.d.ts.map +1 -1
- package/build/utils/client/request_helpers.js +2 -1
- package/build/utils/client/request_helpers.js.map +1 -1
- package/build/utils/native_adapters/log.d.ts +11 -0
- package/build/utils/native_adapters/log.d.ts.map +1 -1
- package/build/utils/native_adapters/log.js +9 -0
- package/build/utils/native_adapters/log.js.map +1 -1
- package/build/utils/performance_tracking.d.ts +1 -1
- package/build/utils/performance_tracking.d.ts.map +1 -1
- package/build/utils/performance_tracking.js.map +1 -1
- package/build/utils/request/get_chat_configuration.d.ts +1 -1
- package/build/utils/request/get_chat_configuration.d.ts.map +1 -1
- package/build/utils/request/get_features.d.ts +1 -1
- package/build/utils/request/get_features.d.ts.map +1 -1
- package/build/utils/request/get_message.d.ts +1 -1
- package/build/utils/request/get_message.d.ts.map +1 -1
- package/build/utils/request/get_messages.d.ts +1 -1
- package/build/utils/request/get_messages.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/__tests__/hooks/use_conversation_validate.test.tsx +117 -0
- package/src/__tests__/hooks/use_enrich_people.test.tsx +95 -0
- package/src/components/conversation/message.tsx +6 -4
- package/src/components/display/action_button.tsx +18 -0
- package/src/components/display/index.ts +1 -0
- package/src/components/index.tsx +2 -0
- package/src/components/page/__tests__/component_error_boundary.test.tsx +46 -0
- package/src/components/page/__tests__/error_boundary.test.tsx +93 -0
- package/src/components/page/__tests__/page_error_boundary.test.tsx +77 -0
- package/src/components/page/component_error_boundary.tsx +13 -0
- package/src/components/page/error_boundary.tsx +34 -118
- package/src/components/page/page_error_boundary.tsx +112 -0
- package/src/hooks/groups/use_group_chat_conversation_payload.ts +38 -0
- package/src/hooks/groups/use_group_members_for_new_conversation.ts +9 -23
- package/src/hooks/groups/use_groups_conversation_create.ts +1 -1
- package/src/hooks/index.ts +2 -1
- package/src/hooks/services/use_find_or_create_services_conversation.ts +27 -24
- package/src/hooks/services/use_services_chat_conversation_payload.ts +26 -0
- package/src/hooks/services/use_team_members_for_new_conversation.ts +18 -7
- package/src/hooks/use_conversation_validate.ts +45 -0
- package/src/hooks/use_enrich_people.ts +35 -0
- package/src/hooks/use_features.ts +5 -2
- package/src/hooks/use_jolt.ts +2 -1
- package/src/hooks/use_product_analytics.ts +13 -3
- package/src/index.tsx +3 -2
- package/src/navigation/screenLayout.tsx +6 -3
- package/src/screens/conversation_details_screen.tsx +1 -1
- package/src/screens/conversation_new/components/groups_form.tsx +17 -1
- package/src/screens/conversation_new/components/services_form.tsx +26 -2
- package/src/screens/conversation_screen.tsx +2 -1
- package/src/screens/conversations/conversations_screen.tsx +2 -2
- package/src/screens/team_conversation_screen.tsx +6 -6
- package/src/types/jolt_events/index.ts +3 -0
- package/src/types/resources/conversation_validate.ts +11 -0
- package/src/types/resources/index.ts +1 -0
- package/src/utils/client/__tests__/instrumented_fetch.test.ts +84 -0
- package/src/utils/client/instrumented_fetch.ts +69 -0
- package/src/utils/client/request_helpers.ts +2 -1
- package/src/utils/native_adapters/__tests__/log.test.ts +62 -0
- package/src/utils/native_adapters/log.ts +22 -0
- package/src/utils/performance_tracking.ts +1 -1
|
@@ -1,137 +1,53 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import { onAuthRefresh } from '../../utils/auth_events'
|
|
1
|
+
import React, { PropsWithChildren } from 'react'
|
|
2
|
+
import { Log } from '../../utils/native_adapters/configuration'
|
|
3
|
+
import { ReportErrorScope } from '../../utils/native_adapters/log'
|
|
5
4
|
import { ResponseError } from '../../utils/response_error'
|
|
6
|
-
|
|
5
|
+
|
|
6
|
+
export type ErrorBoundaryFallback =
|
|
7
|
+
| React.ReactNode
|
|
8
|
+
| ((error: Error, reset: () => void) => React.ReactNode)
|
|
9
|
+
|
|
10
|
+
export type ErrorBoundaryProps = {
|
|
11
|
+
scope?: ReportErrorScope
|
|
12
|
+
screenName?: string
|
|
13
|
+
fallback?: ErrorBoundaryFallback
|
|
14
|
+
}
|
|
7
15
|
|
|
8
16
|
type ErrorBoundaryState = {
|
|
9
|
-
error:
|
|
10
|
-
unsubscriber: () => void
|
|
17
|
+
error: Error | null
|
|
11
18
|
}
|
|
12
19
|
|
|
13
|
-
class ErrorBoundary extends React.Component<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
20
|
+
export class ErrorBoundary extends React.Component<
|
|
21
|
+
PropsWithChildren<ErrorBoundaryProps>,
|
|
22
|
+
ErrorBoundaryState
|
|
23
|
+
> {
|
|
24
|
+
state: ErrorBoundaryState = { error: null }
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
27
|
+
return { error }
|
|
21
28
|
}
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
31
|
+
if (error instanceof ResponseError) return
|
|
32
|
+
|
|
33
|
+
Log.reportError(error, {
|
|
34
|
+
componentStack: errorInfo.componentStack ?? undefined,
|
|
35
|
+
scope: this.props.scope,
|
|
36
|
+
screenName: this.props.screenName,
|
|
37
|
+
tags: { team: 'chat', package: 'chat-react-native' },
|
|
38
|
+
})
|
|
25
39
|
}
|
|
26
40
|
|
|
27
41
|
handleReset = () => {
|
|
28
|
-
this.props.onReset?.()
|
|
29
42
|
this.setState({ error: null })
|
|
30
43
|
}
|
|
31
44
|
|
|
32
45
|
render() {
|
|
33
|
-
if (this.state.error)
|
|
34
|
-
return <ErrorView error={this.state.error} onReset={this.handleReset} />
|
|
35
|
-
} else {
|
|
36
|
-
return this.props.children
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
46
|
+
if (!this.state.error) return this.props.children
|
|
40
47
|
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
const { fallback } = this.props
|
|
49
|
+
if (fallback === undefined) return null
|
|
43
50
|
|
|
44
|
-
|
|
45
|
-
reset()
|
|
46
|
-
onReset()
|
|
47
|
-
}, [reset, onReset])
|
|
48
|
-
|
|
49
|
-
useEffect(() => handleReset, [handleReset])
|
|
50
|
-
|
|
51
|
-
if (error instanceof ResponseError) {
|
|
52
|
-
return <ResponseErrorView response={error.response} onReset={handleReset} />
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (isNetworkError(error)) {
|
|
56
|
-
return (
|
|
57
|
-
<ErrorContent
|
|
58
|
-
heading={'Problem connecting!'}
|
|
59
|
-
body={'Check your internet connection and try again.'}
|
|
60
|
-
/>
|
|
61
|
-
)
|
|
51
|
+
return typeof fallback === 'function' ? fallback(this.state.error, this.handleReset) : fallback
|
|
62
52
|
}
|
|
63
|
-
|
|
64
|
-
return <ErrorContent heading={'Oops!'} body={'Something unexpected happened.'} />
|
|
65
53
|
}
|
|
66
|
-
|
|
67
|
-
function isNetworkError(error: ResponseError | Error | TypeError | null) {
|
|
68
|
-
const isError = error instanceof Error
|
|
69
|
-
const networkFailedMessages = [
|
|
70
|
-
'Network request failed',
|
|
71
|
-
'Network request timed out',
|
|
72
|
-
'Failed to fetch',
|
|
73
|
-
]
|
|
74
|
-
|
|
75
|
-
if (!isError) return false
|
|
76
|
-
|
|
77
|
-
return new RegExp(networkFailedMessages.join('|'), 'i').test(error.message)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function ResponseErrorView({ response, onReset }: { response: Response; onReset: () => void }) {
|
|
81
|
-
const { status } = response
|
|
82
|
-
|
|
83
|
-
const heading = useMemo(() => {
|
|
84
|
-
switch (status) {
|
|
85
|
-
case 403:
|
|
86
|
-
return 'Permission required'
|
|
87
|
-
case 404:
|
|
88
|
-
return 'Content not found'
|
|
89
|
-
default:
|
|
90
|
-
return 'Oops!'
|
|
91
|
-
}
|
|
92
|
-
}, [status])
|
|
93
|
-
|
|
94
|
-
const body = useMemo(() => {
|
|
95
|
-
switch (status) {
|
|
96
|
-
case 403:
|
|
97
|
-
return 'Contact your administrator for access.'
|
|
98
|
-
case 404:
|
|
99
|
-
return 'If you believe something should be here, please reach out to your administrator.'
|
|
100
|
-
default:
|
|
101
|
-
return 'Something unexpected happened.'
|
|
102
|
-
}
|
|
103
|
-
}, [status])
|
|
104
|
-
|
|
105
|
-
useEffect(() => {
|
|
106
|
-
if (status !== 401) return
|
|
107
|
-
|
|
108
|
-
return onAuthRefresh(onReset)
|
|
109
|
-
}, [status, onReset])
|
|
110
|
-
|
|
111
|
-
return <ErrorContent heading={heading} body={body} />
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function ErrorContent({ heading, body }: { heading: string; body: string }) {
|
|
115
|
-
const navigation = useNavigation()
|
|
116
|
-
|
|
117
|
-
return (
|
|
118
|
-
<BlankState.Root>
|
|
119
|
-
<BlankState.Imagery name="people.noTextMessage" />
|
|
120
|
-
<BlankState.Content>
|
|
121
|
-
<BlankState.Heading>{heading}</BlankState.Heading>
|
|
122
|
-
<BlankState.Text>{body}</BlankState.Text>
|
|
123
|
-
</BlankState.Content>
|
|
124
|
-
<BlankState.Button
|
|
125
|
-
title="Go back"
|
|
126
|
-
onPress={navigation.goBack}
|
|
127
|
-
size="md"
|
|
128
|
-
accessibilityRole="link"
|
|
129
|
-
/>
|
|
130
|
-
<BlankState.TextButton onPress={() => navigation.navigate('BugReport')}>
|
|
131
|
-
Report a bug
|
|
132
|
-
</BlankState.TextButton>
|
|
133
|
-
</BlankState.Root>
|
|
134
|
-
)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export default ErrorBoundary
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { useNavigation } from '@react-navigation/native'
|
|
2
|
+
import { useQueryErrorResetBoundary } from '@tanstack/react-query'
|
|
3
|
+
import React, { PropsWithChildren, useCallback, useEffect, useMemo } from 'react'
|
|
4
|
+
import { onAuthRefresh } from '../../utils/auth_events'
|
|
5
|
+
import { ResponseError } from '../../utils/response_error'
|
|
6
|
+
import BlankState from '../primitive/blank_state_primitive'
|
|
7
|
+
import { ErrorBoundary, ErrorBoundaryFallback, ErrorBoundaryProps } from './error_boundary'
|
|
8
|
+
|
|
9
|
+
const renderPageFallback: ErrorBoundaryFallback = (error, reset) => (
|
|
10
|
+
<ErrorView error={error} reset={reset} />
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
export function PageErrorBoundary({ children, ...props }: PropsWithChildren<ErrorBoundaryProps>) {
|
|
14
|
+
return (
|
|
15
|
+
<ErrorBoundary scope="screen" fallback={renderPageFallback} {...props}>
|
|
16
|
+
{children}
|
|
17
|
+
</ErrorBoundary>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function ErrorView({ error, reset }: { error: Error; reset: () => void }) {
|
|
22
|
+
const { reset: resetQueries } = useQueryErrorResetBoundary()
|
|
23
|
+
|
|
24
|
+
const handleReset = useCallback(() => {
|
|
25
|
+
resetQueries()
|
|
26
|
+
reset()
|
|
27
|
+
}, [resetQueries, reset])
|
|
28
|
+
|
|
29
|
+
useEffect(() => handleReset, [handleReset])
|
|
30
|
+
|
|
31
|
+
if (error instanceof ResponseError) {
|
|
32
|
+
return <ResponseErrorView response={error.response} onReset={handleReset} />
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (isNetworkError(error)) {
|
|
36
|
+
return (
|
|
37
|
+
<ErrorContent
|
|
38
|
+
heading={'Problem connecting!'}
|
|
39
|
+
body={'Check your internet connection and try again.'}
|
|
40
|
+
/>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return <ErrorContent heading={'Oops!'} body={'Something unexpected happened.'} />
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isNetworkError(error: Error) {
|
|
48
|
+
const networkFailedMessages = [
|
|
49
|
+
'Network request failed',
|
|
50
|
+
'Network request timed out',
|
|
51
|
+
'Failed to fetch',
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
return new RegExp(networkFailedMessages.join('|'), 'i').test(error.message)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function ResponseErrorView({ response, onReset }: { response: Response; onReset: () => void }) {
|
|
58
|
+
const { status } = response
|
|
59
|
+
|
|
60
|
+
const heading = useMemo(() => {
|
|
61
|
+
switch (status) {
|
|
62
|
+
case 403:
|
|
63
|
+
return 'Permission required'
|
|
64
|
+
case 404:
|
|
65
|
+
return 'Content not found'
|
|
66
|
+
default:
|
|
67
|
+
return 'Oops!'
|
|
68
|
+
}
|
|
69
|
+
}, [status])
|
|
70
|
+
|
|
71
|
+
const body = useMemo(() => {
|
|
72
|
+
switch (status) {
|
|
73
|
+
case 403:
|
|
74
|
+
return 'Contact your administrator for access.'
|
|
75
|
+
case 404:
|
|
76
|
+
return 'If you believe something should be here, please reach out to your administrator.'
|
|
77
|
+
default:
|
|
78
|
+
return 'Something unexpected happened.'
|
|
79
|
+
}
|
|
80
|
+
}, [status])
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (status !== 401) return
|
|
84
|
+
|
|
85
|
+
return onAuthRefresh(onReset)
|
|
86
|
+
}, [status, onReset])
|
|
87
|
+
|
|
88
|
+
return <ErrorContent heading={heading} body={body} />
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function ErrorContent({ heading, body }: { heading: string; body: string }) {
|
|
92
|
+
const navigation = useNavigation()
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<BlankState.Root>
|
|
96
|
+
<BlankState.Imagery name="people.noTextMessage" />
|
|
97
|
+
<BlankState.Content>
|
|
98
|
+
<BlankState.Heading>{heading}</BlankState.Heading>
|
|
99
|
+
<BlankState.Text>{body}</BlankState.Text>
|
|
100
|
+
</BlankState.Content>
|
|
101
|
+
<BlankState.Button
|
|
102
|
+
title="Go back"
|
|
103
|
+
onPress={navigation.goBack}
|
|
104
|
+
size="md"
|
|
105
|
+
accessibilityRole="link"
|
|
106
|
+
/>
|
|
107
|
+
<BlankState.TextButton onPress={() => navigation.navigate('BugReport')}>
|
|
108
|
+
Report a bug
|
|
109
|
+
</BlankState.TextButton>
|
|
110
|
+
</BlankState.Root>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useQuery } from '@tanstack/react-query'
|
|
2
|
+
import { ApiResource, ResourceObject } from '../../types'
|
|
3
|
+
import { useApiClient } from '../use_api_client'
|
|
4
|
+
|
|
5
|
+
const STALE_TIME_MS = 25 * 60 * 1000 // under Groups' 30-min server-side expiry
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
groupId: number
|
|
9
|
+
enabled?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useGroupChatConversationPayload({ groupId, enabled = true }: Props) {
|
|
13
|
+
const apiClient = useApiClient()
|
|
14
|
+
const url = `/me/groups/${groupId}/chat_conversation_payload`
|
|
15
|
+
|
|
16
|
+
const { data, ...rest } = useQuery({
|
|
17
|
+
queryKey: ['groups', url],
|
|
18
|
+
queryFn: () =>
|
|
19
|
+
apiClient.groups.post<ApiResource<GroupChatConversationPayload>>({
|
|
20
|
+
url,
|
|
21
|
+
data: {
|
|
22
|
+
data: {
|
|
23
|
+
type: 'GroupChatConversationPayload',
|
|
24
|
+
attributes: { title: '' },
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
staleTime: STALE_TIME_MS,
|
|
29
|
+
enabled,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
return { payload: data?.data.value, ...rest }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface GroupChatConversationPayload extends ResourceObject {
|
|
36
|
+
type: 'GroupChatConversationPayload'
|
|
37
|
+
value: string
|
|
38
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import { MemberResource, ResourceObject } from '../../types'
|
|
2
3
|
import { GroupsGroupMemberResource } from '../../types/resources/groups/groups_member_resource_with_person'
|
|
3
|
-
import {
|
|
4
|
+
import { useEnrichPeople } from '../use_enrich_people'
|
|
4
5
|
import { useSuspensePaginator } from '../use_suspense_api'
|
|
5
6
|
|
|
6
7
|
type UseSuspensePaginatorResult<T extends ResourceObject> = ReturnType<
|
|
@@ -16,10 +17,6 @@ export type GroupMembersForNewConversationResult = Omit<
|
|
|
16
17
|
childMembers: MemberResource[]
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
/**
|
|
20
|
-
* This is specifically for the new conversation screen because we assign
|
|
21
|
-
* the "Conversation owner" badge to the current person.
|
|
22
|
-
*/
|
|
23
20
|
export function useGroupMembersForNewConversation({
|
|
24
21
|
id,
|
|
25
22
|
gender,
|
|
@@ -27,7 +24,6 @@ export function useGroupMembersForNewConversation({
|
|
|
27
24
|
id: number
|
|
28
25
|
gender: string | null
|
|
29
26
|
}) {
|
|
30
|
-
const currentPerson = useCurrentPerson()
|
|
31
27
|
const response = useSuspensePaginator<GroupsGroupMemberResource>({
|
|
32
28
|
url: `/me/groups/${id}/memberships`,
|
|
33
29
|
data: {
|
|
@@ -43,12 +39,16 @@ export function useGroupMembersForNewConversation({
|
|
|
43
39
|
})
|
|
44
40
|
|
|
45
41
|
const { data: memberships = [] } = response
|
|
42
|
+
const personIds = useMemo(() => memberships.map(m => +m.person.id), [memberships])
|
|
43
|
+
const enrichmentMap = useEnrichPeople({ personIds, groupId: id })
|
|
44
|
+
|
|
46
45
|
const members: MemberResource[] = memberships.map(membership => {
|
|
47
46
|
const { person } = membership
|
|
47
|
+
const enrichment = enrichmentMap.get(+person.id)
|
|
48
48
|
return {
|
|
49
49
|
type: 'Member',
|
|
50
50
|
avatar: person.avatarUrl,
|
|
51
|
-
badges:
|
|
51
|
+
badges: enrichment?.badges ?? [],
|
|
52
52
|
child: person.child,
|
|
53
53
|
gender: person.gender,
|
|
54
54
|
id: +person.id,
|
|
@@ -57,6 +57,7 @@ export function useGroupMembersForNewConversation({
|
|
|
57
57
|
role: membership.role,
|
|
58
58
|
}
|
|
59
59
|
})
|
|
60
|
+
|
|
60
61
|
const adultMembers = members.filter(member => !member.child)
|
|
61
62
|
const childMembers = members.filter(member => member.child)
|
|
62
63
|
|
|
@@ -67,18 +68,3 @@ export function useGroupMembersForNewConversation({
|
|
|
67
68
|
childMembers,
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
|
-
|
|
71
|
-
function buildBadges(
|
|
72
|
-
membership: GroupsGroupMemberResource,
|
|
73
|
-
currentPersonId: number
|
|
74
|
-
): MemberBadge[] {
|
|
75
|
-
const { person } = membership
|
|
76
|
-
const badges = []
|
|
77
|
-
if (membership.role === 'leader') {
|
|
78
|
-
badges.push({ title: 'Leader' })
|
|
79
|
-
}
|
|
80
|
-
if (person.id === currentPersonId) {
|
|
81
|
-
badges.push({ title: 'Conversation owner' })
|
|
82
|
-
}
|
|
83
|
-
return badges
|
|
84
|
-
}
|
package/src/hooks/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './use_animated_message_background_color'
|
|
2
|
-
export
|
|
2
|
+
export { useApiClient } from './use_api_client'
|
|
3
3
|
export * from './use_api'
|
|
4
|
+
export * from './use_app_state'
|
|
4
5
|
export * from './use_async_storage'
|
|
5
6
|
export * from './use_at_font_scale_breakpoint'
|
|
6
7
|
export * from './use_chat_permissions'
|
|
@@ -26,14 +26,7 @@ export const useFindOrCreateServicesConversation = ({ teamIds, planId, onSuccess
|
|
|
26
26
|
const apiClient = useApiClient()
|
|
27
27
|
|
|
28
28
|
const teamAndPlanParams: TeamAndPlanParams = useMemo(
|
|
29
|
-
() =>
|
|
30
|
-
omitBy(
|
|
31
|
-
{
|
|
32
|
-
team_id: teamIds?.join(',') || null,
|
|
33
|
-
plan_id: planId,
|
|
34
|
-
},
|
|
35
|
-
isNil
|
|
36
|
-
),
|
|
29
|
+
() => buildTeamAndPlanParams(teamIds, planId),
|
|
37
30
|
[teamIds, planId]
|
|
38
31
|
)
|
|
39
32
|
|
|
@@ -49,16 +42,23 @@ export const useFindOrCreateServicesConversation = ({ teamIds, planId, onSuccess
|
|
|
49
42
|
onSuccess: ({ conversation, created }) => {
|
|
50
43
|
onSuccess?.(conversation, { created })
|
|
51
44
|
},
|
|
52
|
-
mutationFn: async () =>
|
|
45
|
+
mutationFn: async () =>
|
|
46
|
+
findOrCreateServicesConversation({ apiClient, teamIds: teamIds ?? [], planId }),
|
|
53
47
|
})
|
|
54
48
|
|
|
55
49
|
return { ...mutation, selectionHasConversation, isLoadingConversationCheck }
|
|
56
50
|
}
|
|
57
51
|
|
|
58
|
-
export const findOrCreateServicesConversation = async (
|
|
59
|
-
apiClient
|
|
60
|
-
|
|
61
|
-
|
|
52
|
+
export const findOrCreateServicesConversation = async ({
|
|
53
|
+
apiClient,
|
|
54
|
+
teamIds,
|
|
55
|
+
planId,
|
|
56
|
+
}: {
|
|
57
|
+
apiClient: ApiClient
|
|
58
|
+
teamIds: number[]
|
|
59
|
+
planId?: number
|
|
60
|
+
}): Promise<{ conversation: ConversationResource; created: boolean }> => {
|
|
61
|
+
const teamAndPlanParams = buildTeamAndPlanParams(teamIds, planId)
|
|
62
62
|
const foundConversations = await getGroupIdsFromServices(apiClient, teamAndPlanParams)
|
|
63
63
|
.then(res => res.data.groupIdentifiers)
|
|
64
64
|
.then(groupIdentifiers => findConversationWithExactTeams(apiClient, groupIdentifiers))
|
|
@@ -69,24 +69,27 @@ export const findOrCreateServicesConversation = async (
|
|
|
69
69
|
return { conversation: foundConversation, created: false }
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
return
|
|
72
|
+
return getServicesChatConversationPayload({ apiClient, teamIds, planId })
|
|
73
73
|
.then(res => res.data.payload)
|
|
74
74
|
.then(payload => createConversation(apiClient, payload))
|
|
75
75
|
.then(res => ({ conversation: res.data, created: true }))
|
|
76
76
|
.catch(throwResponseError)
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
export const
|
|
80
|
-
apiClient
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
export const getServicesChatConversationPayload = ({
|
|
80
|
+
apiClient,
|
|
81
|
+
teamIds,
|
|
82
|
+
planId,
|
|
83
|
+
}: {
|
|
84
|
+
apiClient: ApiClient
|
|
85
|
+
teamIds: number[]
|
|
86
|
+
planId?: number
|
|
87
|
+
}) => {
|
|
83
88
|
return apiClient.services.get({
|
|
84
|
-
url:
|
|
89
|
+
url: '/chat',
|
|
85
90
|
data: {
|
|
86
|
-
...
|
|
87
|
-
fields: {
|
|
88
|
-
Chat: ['payload'],
|
|
89
|
-
},
|
|
91
|
+
...buildTeamAndPlanParams(teamIds, planId),
|
|
92
|
+
fields: { Chat: ['payload'] },
|
|
90
93
|
},
|
|
91
94
|
}) as Promise<ApiResource<ServicesChatPayloadResource>>
|
|
92
95
|
}
|
|
@@ -157,7 +160,7 @@ export const checkIfConversationWithGroupExists = (
|
|
|
157
160
|
export const buildTeamAndPlanParams = (teamIds: number[] = [], planId?: number) => {
|
|
158
161
|
return omitBy(
|
|
159
162
|
{
|
|
160
|
-
team_id: teamIds.join(','),
|
|
163
|
+
team_id: teamIds.length ? teamIds.join(',') : null,
|
|
161
164
|
plan_id: planId,
|
|
162
165
|
},
|
|
163
166
|
isNil
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useQuery } from '@tanstack/react-query'
|
|
2
|
+
import { useApiClient } from '../use_api_client'
|
|
3
|
+
import { getServicesChatConversationPayload } from './use_find_or_create_services_conversation'
|
|
4
|
+
|
|
5
|
+
const STALE_TIME_MS = 55 * 60 * 1000 // under Services' 1-hour server-side expiry
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
teamIds: number[]
|
|
9
|
+
planId?: number
|
|
10
|
+
enabled?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useServicesChatConversationPayload({ teamIds, planId, enabled = true }: Props) {
|
|
14
|
+
const apiClient = useApiClient()
|
|
15
|
+
const sortedTeamIds = [...teamIds].sort((a, b) => a - b)
|
|
16
|
+
|
|
17
|
+
const { data, ...rest } = useQuery({
|
|
18
|
+
queryKey: ['services', '/chat', { teamIds: sortedTeamIds, planId }],
|
|
19
|
+
queryFn: () =>
|
|
20
|
+
getServicesChatConversationPayload({ apiClient, teamIds: sortedTeamIds, planId }),
|
|
21
|
+
staleTime: STALE_TIME_MS,
|
|
22
|
+
enabled: enabled && sortedTeamIds.length > 0,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return { payload: data?.data.payload, ...rest }
|
|
26
|
+
}
|
|
@@ -2,6 +2,7 @@ import { isNil, omitBy } from 'lodash'
|
|
|
2
2
|
import { useMemo } from 'react'
|
|
3
3
|
import { MemberResource, TeamPeopleResource, TeamPersonResponseItem } from '../../types'
|
|
4
4
|
import { useApiGet } from '../use_api'
|
|
5
|
+
import { useEnrichPeople } from '../use_enrich_people'
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
7
8
|
teamIds: number[]
|
|
@@ -23,14 +24,24 @@ export function useTeamMembersForNewConversation({ teamIds, planId }: Props) {
|
|
|
23
24
|
})
|
|
24
25
|
|
|
25
26
|
const people = data?.people || stableEmptyPersonArray
|
|
27
|
+
const personIds = useMemo(() => people.map(p => p.id), [people])
|
|
28
|
+
const enrichmentMap = useEnrichPeople({ personIds })
|
|
26
29
|
|
|
27
|
-
const members: MemberResource[] = useMemo(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
const members: MemberResource[] = useMemo(
|
|
31
|
+
() =>
|
|
32
|
+
people.map(person => {
|
|
33
|
+
const enrichedBadges = enrichmentMap.get(person.id)?.badges ?? []
|
|
34
|
+
const existingTitles = new Set(person.badges.map(b => b.title))
|
|
35
|
+
const newBadges = enrichedBadges.filter(b => !existingTitles.has(b.title))
|
|
36
|
+
return {
|
|
37
|
+
...person,
|
|
38
|
+
type: 'Member',
|
|
39
|
+
gender: null,
|
|
40
|
+
badges: [...person.badges, ...newBadges],
|
|
41
|
+
}
|
|
42
|
+
}),
|
|
43
|
+
[people, enrichmentMap]
|
|
44
|
+
)
|
|
34
45
|
|
|
35
46
|
return {
|
|
36
47
|
members,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useQuery } from '@tanstack/react-query'
|
|
2
|
+
import { ApiResource, ConversationValidateResource } from '../types'
|
|
3
|
+
import { useApiClient } from './use_api_client'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
payload: string | undefined
|
|
7
|
+
isLoadingPayload?: boolean
|
|
8
|
+
genderId?: string | null
|
|
9
|
+
enabled?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useConversationValidate({
|
|
13
|
+
payload,
|
|
14
|
+
isLoadingPayload = false,
|
|
15
|
+
genderId,
|
|
16
|
+
enabled = true,
|
|
17
|
+
}: Props) {
|
|
18
|
+
const apiClient = useApiClient()
|
|
19
|
+
|
|
20
|
+
const { data, isLoading } = useQuery({
|
|
21
|
+
queryKey: ['chat', '/me/conversation_validate', { payload, genderId }],
|
|
22
|
+
queryFn: () =>
|
|
23
|
+
apiClient.chat.post<ApiResource<ConversationValidateResource>>({
|
|
24
|
+
url: '/me/conversation_validate',
|
|
25
|
+
data: {
|
|
26
|
+
data: {
|
|
27
|
+
type: 'ConversationValidate',
|
|
28
|
+
attributes: {
|
|
29
|
+
payload: payload!,
|
|
30
|
+
...(genderId ? { gender_id: genderId } : {}),
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
enabled: enabled && payload != null,
|
|
36
|
+
retry: false,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const warnings = data?.data.warnings ?? []
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
warnings,
|
|
43
|
+
validationPending: enabled && (isLoadingPayload || isLoading),
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { keepPreviousData, useQuery } from '@tanstack/react-query'
|
|
2
|
+
import { useMemo } from 'react'
|
|
3
|
+
import { ApiCollection, ResourceObject } from '../types'
|
|
4
|
+
import { useApiClient } from './use_api_client'
|
|
5
|
+
|
|
6
|
+
export interface PersonEnrichmentResource extends ResourceObject {
|
|
7
|
+
type: 'PersonEnrichment'
|
|
8
|
+
id: string
|
|
9
|
+
badges: { title: string }[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useEnrichPeople({ personIds, groupId }: { personIds: number[]; groupId?: number }) {
|
|
13
|
+
const apiClient = useApiClient()
|
|
14
|
+
|
|
15
|
+
const { data } = useQuery<ApiCollection<PersonEnrichmentResource>>({
|
|
16
|
+
queryKey: ['enrich_people', [...personIds].sort((a, b) => a - b), groupId],
|
|
17
|
+
queryFn: () =>
|
|
18
|
+
apiClient.chat.post<ApiCollection<PersonEnrichmentResource>>({
|
|
19
|
+
url: '/enrich_people',
|
|
20
|
+
data: {
|
|
21
|
+
data: {
|
|
22
|
+
type: 'PersonEnrichment',
|
|
23
|
+
attributes: {
|
|
24
|
+
person_ids: personIds,
|
|
25
|
+
...(groupId !== undefined ? { group_id: groupId } : {}),
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
}),
|
|
30
|
+
enabled: personIds.length > 0,
|
|
31
|
+
placeholderData: keepPreviousData,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
return useMemo(() => new Map(data?.data.map(e => [+e.id, e]) ?? []), [data])
|
|
35
|
+
}
|
|
@@ -5,6 +5,8 @@ import type { FeatureResource } from '../types/resources/feature_resource'
|
|
|
5
5
|
import { getFeaturesRequestArgs, getFeaturesQueryKey } from '../utils/request/get_features'
|
|
6
6
|
import { useApiClient } from './use_api_client'
|
|
7
7
|
|
|
8
|
+
type FeatureName = (typeof availableFeatures)[keyof typeof availableFeatures]
|
|
9
|
+
|
|
8
10
|
export function useFeatures() {
|
|
9
11
|
const apiClient = useApiClient()
|
|
10
12
|
const requestArgs = getFeaturesRequestArgs()
|
|
@@ -22,7 +24,7 @@ export function useFeatures() {
|
|
|
22
24
|
const features = data.data
|
|
23
25
|
|
|
24
26
|
const featureEnabled = useCallback(
|
|
25
|
-
(featureName:
|
|
27
|
+
(featureName: FeatureName) =>
|
|
26
28
|
features.some(feature => feature.name === featureName && feature.enabled),
|
|
27
29
|
[features]
|
|
28
30
|
)
|
|
@@ -39,7 +41,8 @@ export const availableFeatures = {
|
|
|
39
41
|
granular_notifications_ui: 'ROLLOUT_granular_notification_preferences_ui',
|
|
40
42
|
custom_conversation_avatars: 'ROLLOUT_custom_conversation_avatars',
|
|
41
43
|
jump_to_unread: 'ROLLOUT_jump_to_unread',
|
|
42
|
-
|
|
44
|
+
conversation_safety_lock: 'ROLLOUT_conversation_safety_lock',
|
|
45
|
+
} as const satisfies Record<string, `ROLLOUT_${string}`>
|
|
43
46
|
|
|
44
47
|
const stableEmptyFeatures: ApiCollection<FeatureResource> = {
|
|
45
48
|
data: [],
|