@planningcenter/chat-react-native 4.4.1-qa-staging.8 → 4.4.1-qa-staging.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/components/conversation/system_message.d.ts.map +1 -1
- package/build/components/conversation/system_message.js +85 -74
- package/build/components/conversation/system_message.js.map +1 -1
- package/build/components/conversations/conversation_preview.js +2 -0
- package/build/components/conversations/conversation_preview.js.map +1 -1
- package/build/hooks/use_features.d.ts +2 -0
- package/build/hooks/use_features.d.ts.map +1 -1
- package/build/hooks/use_features.js +2 -0
- package/build/hooks/use_features.js.map +1 -1
- package/build/hooks/use_filter_dropdown_enabled.d.ts +2 -0
- package/build/hooks/use_filter_dropdown_enabled.d.ts.map +1 -0
- package/build/hooks/use_filter_dropdown_enabled.js +18 -0
- package/build/hooks/use_filter_dropdown_enabled.js.map +1 -0
- package/build/types/jolt_events/message_events.d.ts +2 -0
- package/build/types/jolt_events/message_events.d.ts.map +1 -1
- package/build/types/jolt_events/message_events.js.map +1 -1
- package/build/types/resources/conversation.d.ts +1 -1
- package/build/types/resources/conversation.d.ts.map +1 -1
- package/build/types/resources/conversation.js.map +1 -1
- package/build/types/resources/message.d.ts +2 -0
- package/build/types/resources/message.d.ts.map +1 -1
- package/build/types/resources/message.js.map +1 -1
- package/build/utils/date.d.ts +1 -0
- package/build/utils/date.d.ts.map +1 -1
- package/build/utils/date.js +15 -1
- package/build/utils/date.js.map +1 -1
- package/build/utils/jolt/transform_message_event_data_to_message_resource.d.ts.map +1 -1
- package/build/utils/jolt/transform_message_event_data_to_message_resource.js +2 -1
- package/build/utils/jolt/transform_message_event_data_to_message_resource.js.map +1 -1
- package/build/utils/request/messages_data_options.d.ts.map +1 -1
- package/build/utils/request/messages_data_options.js +1 -1
- package/build/utils/request/messages_data_options.js.map +1 -1
- package/build/utils/system_messages.d.ts +7 -5
- package/build/utils/system_messages.d.ts.map +1 -1
- package/build/utils/system_messages.js +10 -2
- package/build/utils/system_messages.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/hooks/use_filter_dropdown_enabled.test.tsx +42 -0
- package/src/__tests__/utils/system_messages.ts +19 -32
- package/src/components/conversation/__tests__/system_message.test.tsx +104 -0
- package/src/components/conversation/system_message.tsx +20 -6
- package/src/components/conversations/__tests__/conversation_preview.test.tsx +70 -0
- package/src/components/conversations/conversation_preview.tsx +4 -0
- package/src/hooks/use_features.ts +2 -0
- package/src/hooks/use_filter_dropdown_enabled.ts +10 -0
- package/src/types/jolt_events/message_events.ts +2 -0
- package/src/types/resources/conversation.ts +1 -1
- package/src/types/resources/message.ts +3 -0
- package/src/utils/__tests__/date.test.ts +19 -0
- package/src/utils/__tests__/group_messages.test.ts +1 -0
- package/src/utils/date.ts +8 -1
- package/src/utils/jolt/__tests__/transform_message_event_data_to_message_resource.test.ts +35 -0
- package/src/utils/jolt/transform_message_event_data_to_message_resource.ts +1 -0
- package/src/utils/request/messages_data_options.ts +1 -0
- package/src/utils/system_messages.ts +19 -10
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { render } from '@testing-library/react-native'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import type { ConversationPreviewData, ConversationResource } from '../../../types'
|
|
4
|
+
import { formatDatePreview } from '../../../utils/date'
|
|
5
|
+
import { ConversationPreview } from '../conversation_preview'
|
|
6
|
+
|
|
7
|
+
jest.mock('../conversation_actions', () => ({
|
|
8
|
+
ConversationActions: ({
|
|
9
|
+
children,
|
|
10
|
+
accessibilityLabel,
|
|
11
|
+
}: {
|
|
12
|
+
children: React.ReactNode
|
|
13
|
+
accessibilityLabel?: string
|
|
14
|
+
}) => {
|
|
15
|
+
const { View: MockView } = jest.requireActual('react-native')
|
|
16
|
+
return <MockView accessibilityLabel={accessibilityLabel}>{children}</MockView>
|
|
17
|
+
},
|
|
18
|
+
}))
|
|
19
|
+
|
|
20
|
+
const createdAt = '2026-03-04T15:30:00Z'
|
|
21
|
+
|
|
22
|
+
const conversation = (conversationPreview: ConversationPreviewData): ConversationResource => ({
|
|
23
|
+
type: 'Conversation',
|
|
24
|
+
id: 7,
|
|
25
|
+
title: 'Worship Team',
|
|
26
|
+
conversationPreview,
|
|
27
|
+
createdAt,
|
|
28
|
+
lastMessageCreatedAt: createdAt,
|
|
29
|
+
muted: false,
|
|
30
|
+
repliesDisabled: false,
|
|
31
|
+
unreadCount: 0,
|
|
32
|
+
unreadReaction: false,
|
|
33
|
+
updatedAt: createdAt,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const renderPreview = (preview: ConversationPreviewData) =>
|
|
37
|
+
render(<ConversationPreview conversation={conversation(preview)} onPress={jest.fn()} />)
|
|
38
|
+
|
|
39
|
+
describe('ConversationPreview', () => {
|
|
40
|
+
it('prefixes message previews with the author', () => {
|
|
41
|
+
const view = renderPreview({
|
|
42
|
+
kind: 'message',
|
|
43
|
+
authorName: 'Ada Lovelace',
|
|
44
|
+
value: null,
|
|
45
|
+
text: 'See you Sunday',
|
|
46
|
+
createdAt,
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
expect(view.getByText('Ada Lovelace: See you Sunday')).toBeTruthy()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('shows system previews as their text alone', () => {
|
|
53
|
+
const text = 'This conversation was unarchived by Ada Lovelace'
|
|
54
|
+
const view = renderPreview({
|
|
55
|
+
kind: 'system',
|
|
56
|
+
authorName: 'System',
|
|
57
|
+
value: null,
|
|
58
|
+
text,
|
|
59
|
+
createdAt,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
expect(view.getByText(text)).toBeTruthy()
|
|
63
|
+
expect(view.queryByText('System', { exact: false })).toBeNull()
|
|
64
|
+
expect(
|
|
65
|
+
view.getByLabelText(
|
|
66
|
+
`Select Worship Team, Latest activity: ${text} at ${formatDatePreview(createdAt)}`
|
|
67
|
+
)
|
|
68
|
+
).toBeTruthy()
|
|
69
|
+
})
|
|
70
|
+
})
|
|
@@ -168,6 +168,8 @@ function reactionEmoji(value: ConversationPreviewData['value']): string | null {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
function formatConversationPreviewText(preview: ConversationPreviewData) {
|
|
171
|
+
if (preview.kind === 'system') return preview.text
|
|
172
|
+
|
|
171
173
|
if (preview.kind === 'reaction') {
|
|
172
174
|
const emoji = reactionEmoji(preview.value)
|
|
173
175
|
return `${preview.authorName}${emoji ? ` ${emoji}` : ' reacted'} to: "${preview.text}"`
|
|
@@ -179,6 +181,8 @@ function formatConversationPreviewText(preview: ConversationPreviewData) {
|
|
|
179
181
|
function formatConversationPreviewAccessibilityLabel(preview: ConversationPreviewData) {
|
|
180
182
|
const date = formatDatePreview(preview.createdAt)
|
|
181
183
|
|
|
184
|
+
if (preview.kind === 'system') return `Latest activity: ${preview.text} at ${date}`
|
|
185
|
+
|
|
182
186
|
if (preview.kind === 'reaction') {
|
|
183
187
|
return `${preview.authorName} reacted to your message sent ${date} and says ${preview.text}`
|
|
184
188
|
}
|
|
@@ -57,6 +57,8 @@ export const availableFeatures = {
|
|
|
57
57
|
dm_search_clear_fix: 'ROLLOUT_MOBILE_dm_search_clear_fix',
|
|
58
58
|
search: 'QA_MOBILE_search',
|
|
59
59
|
search_rollout: 'ROLLOUT_MOBILE_search',
|
|
60
|
+
filter_dropdown: 'QA_MOBILE_filter_dropdown',
|
|
61
|
+
filter_dropdown_rollout: 'ROLLOUT_MOBILE_filter_dropdown',
|
|
60
62
|
manual_archiving: 'QA_MOBILE_manual_archiving',
|
|
61
63
|
manual_archiving_rollout: 'ROLLOUT_MOBILE_manual_archiving',
|
|
62
64
|
} as const satisfies Record<string, `${'QA' | 'ROLLOUT'}_${string}`>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { availableFeatures, useFeatures } from './use_features'
|
|
2
|
+
|
|
3
|
+
export function useFilterDropdownEnabled() {
|
|
4
|
+
const { featureEnabled } = useFeatures()
|
|
5
|
+
|
|
6
|
+
return (
|
|
7
|
+
featureEnabled(availableFeatures.filter_dropdown) ||
|
|
8
|
+
featureEnabled(availableFeatures.filter_dropdown_rollout)
|
|
9
|
+
)
|
|
10
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CustomMessage } from '@planningcenter/jolt-client/dist/types/JoltConnection'
|
|
2
2
|
import { DenormalizedAttachmentResource } from '../resources/denormalized_attachment_resource'
|
|
3
|
+
import type { SystemTextFormat } from '../resources/message'
|
|
3
4
|
|
|
4
5
|
interface BaseMessageEventData extends Record<string, unknown> {
|
|
5
6
|
data: {
|
|
@@ -21,6 +22,7 @@ interface BaseMessageEventData extends Record<string, unknown> {
|
|
|
21
22
|
message_type: string
|
|
22
23
|
system_event_person_ids: number[] | null
|
|
23
24
|
system_text_parts: { names: string[]; overflow_count: number; action: string } | null
|
|
25
|
+
system_text_format: SystemTextFormat | null
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -6,7 +6,7 @@ import { MemberAbilityResource } from './member_ability'
|
|
|
6
6
|
import { ReactionCountResource } from './reaction'
|
|
7
7
|
|
|
8
8
|
export interface ConversationPreviewData {
|
|
9
|
-
kind: 'reaction' | 'message'
|
|
9
|
+
kind: 'reaction' | 'message' | 'system'
|
|
10
10
|
authorName: string
|
|
11
11
|
value: ReactionCountResource['value'] | null
|
|
12
12
|
text: string
|
|
@@ -3,6 +3,8 @@ import { DenormalizedAttachmentResourceForCreate } from './denormalized_attachme
|
|
|
3
3
|
import type { PersonResource } from './person'
|
|
4
4
|
import type { ReactionCountResource } from './reaction'
|
|
5
5
|
|
|
6
|
+
export type SystemTextFormat = 'people' | 'actor'
|
|
7
|
+
|
|
6
8
|
export interface MessageResource {
|
|
7
9
|
type: 'Message'
|
|
8
10
|
id: string
|
|
@@ -22,6 +24,7 @@ export interface MessageResource {
|
|
|
22
24
|
messageType: string
|
|
23
25
|
personIdsForSystemEvent?: number[] | null
|
|
24
26
|
systemTextParts?: { names: string[]; overflowCount: number; action: string } | null
|
|
27
|
+
systemTextFormat?: SystemTextFormat | null
|
|
25
28
|
|
|
26
29
|
// Custom Local Properties we set for rendering
|
|
27
30
|
renderAuthor?: boolean
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { formatEventDateTime } from '../date'
|
|
2
|
+
|
|
3
|
+
describe('formatEventDateTime', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
jest.useFakeTimers({ now: new Date(2026, 5, 15, 12, 0) })
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
jest.useRealTimers()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('leaves out the year for dates in the current year', () => {
|
|
13
|
+
expect(formatEventDateTime(new Date(2026, 1, 3, 14, 18))).toBe('Feb 3 at 2:18pm')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('includes the year for dates in other years', () => {
|
|
17
|
+
expect(formatEventDateTime(new Date(2025, 1, 3, 14, 18))).toBe('Feb 3, 2025 at 2:18pm')
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -206,6 +206,7 @@ describe('groupMessages — system messages', () => {
|
|
|
206
206
|
const messages = [
|
|
207
207
|
message('sys', {
|
|
208
208
|
messageType: 'user_joined',
|
|
209
|
+
systemTextFormat: 'people',
|
|
209
210
|
systemTextParts: { names: ['A'], overflowCount: 0, action: 'joined' },
|
|
210
211
|
personIdsForSystemEvent: [1],
|
|
211
212
|
}),
|
package/src/utils/date.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { date as formatDate } from '@planningcenter/datetime-fmt'
|
|
1
|
+
import { date as formatDate, time as formatTime } from '@planningcenter/datetime-fmt'
|
|
2
2
|
import dayjs from './dayjs'
|
|
3
3
|
|
|
4
4
|
export type DateProps = string | number | Date
|
|
@@ -61,3 +61,10 @@ export function relativeTime(date: DateProps): string {
|
|
|
61
61
|
return dayjs(date).format('h:mma')
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
export function formatEventDateTime(date: DateProps) {
|
|
66
|
+
const { isThisYear } = getRelativeDateStatus(date)
|
|
67
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
68
|
+
const day = formatDate(date, { style: 'abbreviated', year: !isThisYear, timeZone })
|
|
69
|
+
return `${day} at ${formatTime(date, { timeZone })}`
|
|
70
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { MessageCreatedEvent } from '../../../types/jolt_events/message_events'
|
|
2
|
+
import { isSystemMessage } from '../../system_messages'
|
|
3
|
+
import { transformMessageEventDataToMessageResource } from '../transform_message_event_data_to_message_resource'
|
|
4
|
+
|
|
5
|
+
const archivedEventData: MessageCreatedEvent['data']['data'] = {
|
|
6
|
+
author_avatar: '',
|
|
7
|
+
author_id: 1,
|
|
8
|
+
author_name: 'Wes',
|
|
9
|
+
conversation_id: 1,
|
|
10
|
+
created_at: '2026-01-01T00:00:00Z',
|
|
11
|
+
deleted_at: null,
|
|
12
|
+
organization_id: 1,
|
|
13
|
+
sort_key: '01J0000000000000000000001',
|
|
14
|
+
text: 'This conversation was archived by Wes',
|
|
15
|
+
text_edited_at: null,
|
|
16
|
+
html: '',
|
|
17
|
+
attachments: [],
|
|
18
|
+
reply_count: 0,
|
|
19
|
+
reply_root_id: null,
|
|
20
|
+
message_type: 'conversation_archived',
|
|
21
|
+
system_event_person_ids: [1],
|
|
22
|
+
system_text_parts: null,
|
|
23
|
+
system_text_format: 'actor',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('transformMessageEventDataToMessageResource', () => {
|
|
27
|
+
it('keeps the system text format so realtime archive events render as system messages', () => {
|
|
28
|
+
const message = transformMessageEventDataToMessageResource({
|
|
29
|
+
data: archivedEventData,
|
|
30
|
+
currentPersonId: 2,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
expect(isSystemMessage(message)).toBe(true)
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import type { MessageResource } from '../types'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
type SystemMessageType = (typeof SYSTEM_MESSAGE_TYPES)[number]
|
|
6
|
-
|
|
7
|
-
export interface SystemMessageResource extends MessageResource {
|
|
8
|
-
messageType: SystemMessageType
|
|
3
|
+
export interface PeopleSystemMessageResource extends MessageResource {
|
|
4
|
+
systemTextFormat: 'people'
|
|
9
5
|
systemTextParts: { names: string[]; overflowCount: number; action: string }
|
|
10
6
|
personIdsForSystemEvent: number[]
|
|
11
7
|
}
|
|
12
8
|
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
export interface ActorSystemMessageResource extends MessageResource {
|
|
10
|
+
systemTextFormat: 'actor'
|
|
11
|
+
text: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type SystemMessageResource = PeopleSystemMessageResource | ActorSystemMessageResource
|
|
15
|
+
|
|
16
|
+
export const isSystemMessage = (message: MessageResource): message is SystemMessageResource => {
|
|
17
|
+
switch (message.systemTextFormat) {
|
|
18
|
+
case 'people':
|
|
19
|
+
return message.systemTextParts != null && message.personIdsForSystemEvent != null
|
|
20
|
+
case 'actor':
|
|
21
|
+
return !!message.text
|
|
22
|
+
default:
|
|
23
|
+
return false
|
|
24
|
+
}
|
|
25
|
+
}
|