@mereb/app-messaging 0.0.7 → 0.0.8
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/dist/native.js +44 -8
- package/package.json +1 -1
package/dist/native.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useMutation, useQuery } from '@apollo/client/react';
|
|
3
3
|
import { useMemo, useState } from 'react';
|
|
4
|
-
import { ActivityIndicator, FlatList, Pressable, RefreshControl, StyleSheet, Text, TextInput, View } from 'react-native';
|
|
4
|
+
import { ActivityIndicator, FlatList, KeyboardAvoidingView, Platform, Pressable, RefreshControl, StyleSheet, Text, TextInput, View } from 'react-native';
|
|
5
5
|
import { tokens } from '@mereb/tokens/native';
|
|
6
6
|
import { buildPeopleDirectory, ConversationsDocument, deriveConversationTitle, deriveMessageSenderName, filterMessagingConversations, formatMessagingTimestamp, MessagingConversationParticipantsDocument, MessagingSearchUsersConnectionDocument, MessagingViewerDocument, MessagesDocument, SendMessageDocument, summarizeMessagingBody } from './headless';
|
|
7
7
|
function describeClientError(error, fallback) {
|
|
8
|
-
|
|
9
|
-
if (
|
|
8
|
+
let message = '';
|
|
9
|
+
if (error instanceof Error) {
|
|
10
|
+
message = error.message.trim();
|
|
11
|
+
}
|
|
12
|
+
else if (typeof error === 'string') {
|
|
13
|
+
message = error.trim();
|
|
14
|
+
}
|
|
15
|
+
const hasUsableMessage = message !== '' && message !== 'Subgraph errors redacted';
|
|
16
|
+
if (!hasUsableMessage) {
|
|
10
17
|
return fallback;
|
|
11
18
|
}
|
|
12
19
|
return __DEV__ ? `${fallback} (${message})` : fallback;
|
|
@@ -123,10 +130,10 @@ export function ConversationScreen({ auth, conversationId }) {
|
|
|
123
130
|
if (messagesQuery.error || conversationsQuery.error) {
|
|
124
131
|
return (_jsx(EmptyState, { title: "Conversation unavailable", body: describeClientError(messagesQuery.error ?? conversationsQuery.error, 'The message thread could not be loaded.') }));
|
|
125
132
|
}
|
|
126
|
-
return (_jsxs(
|
|
133
|
+
return (_jsxs(KeyboardAvoidingView, { behavior: Platform.OS === 'ios' ? 'padding' : undefined, style: styles.screen, children: [_jsx(Text, { style: styles.screenTitle, children: title }), _jsx(FlatList, { data: messages, keyExtractor: (item) => item.id, style: styles.flex, contentContainerStyle: styles.listContent, keyboardShouldPersistTaps: "handled", renderItem: ({ item }) => {
|
|
127
134
|
const isOwn = viewerId && item.senderId === viewerId;
|
|
128
135
|
return (_jsxs(View, { style: [styles.messageBubble, isOwn ? styles.ownBubble : styles.otherBubble], children: [_jsx(Text, { style: styles.messageSender, children: deriveMessageSenderName(item, viewerId, peopleById) }), _jsx(Text, { style: styles.messageBody, children: item.body }), _jsx(Text, { style: styles.messageMeta, children: formatMessagingTimestamp(item.sentAt) })] }));
|
|
129
|
-
}, ListEmptyComponent: messagesQuery.loading ? (_jsx(View, { style: styles.inlineState, children: _jsx(ActivityIndicator, {}) })) : (_jsx(EmptyState, { title: "No messages yet", body: "Send the first message in this conversation." })) }), _jsxs(View, { style: styles.composerRow, children: [_jsx(TextInput, { value: draft, onChangeText: setDraft, placeholder: "Write a message", style: styles.composerInput, multiline: true }), _jsx(Pressable, { accessibilityRole: "button", disabled: sendState.loading || !draft.trim(), onPress: () => void handleSend(), style: [styles.inlineButton,
|
|
136
|
+
}, ListEmptyComponent: messagesQuery.loading ? (_jsx(View, { style: styles.inlineState, children: _jsx(ActivityIndicator, {}) })) : (_jsx(EmptyState, { title: "No messages yet", body: "Send the first message in this conversation." })) }), _jsxs(View, { style: styles.composerRow, children: [_jsx(TextInput, { value: draft, onChangeText: setDraft, placeholder: "Write a message", style: styles.composerInput, multiline: true }), _jsx(Pressable, { accessibilityRole: "button", disabled: sendState.loading || !draft.trim(), onPress: () => void handleSend(), style: [styles.inlineButton, draft.trim() ? undefined : styles.disabledCard], children: _jsx(Text, { style: styles.inlineButtonText, children: sendState.loading ? 'Sending…' : 'Send' }) })] }), composerError ? _jsx(Text, { style: styles.errorText, children: composerError }) : null] }));
|
|
130
137
|
}
|
|
131
138
|
export function ComposeMessageScreen({ auth, onCreatedConversation, initialUser = null }) {
|
|
132
139
|
const [query, setQuery] = useState('');
|
|
@@ -143,9 +150,24 @@ export function ComposeMessageScreen({ auth, onCreatedConversation, initialUser
|
|
|
143
150
|
skip: !token || query.trim().length < 2
|
|
144
151
|
});
|
|
145
152
|
const viewerId = viewerQuery.data?.me?.id;
|
|
153
|
+
const hasSearchQuery = query.trim().length >= 2;
|
|
154
|
+
const canStartConversation = selectedUser !== null && draft.trim().length > 0 && !sendState.loading;
|
|
146
155
|
const results = useMemo(() => (searchQuery.data?.searchUsersConnection.edges ?? [])
|
|
147
156
|
.map((edge) => edge.node)
|
|
148
157
|
.filter((user) => user.id !== viewerId), [searchQuery.data?.searchUsersConnection.edges, viewerId]);
|
|
158
|
+
let searchEmptyState;
|
|
159
|
+
if (!hasSearchQuery) {
|
|
160
|
+
searchEmptyState = (_jsx(EmptyState, { title: "Search for someone", body: "Enter at least two characters to find a user." }));
|
|
161
|
+
}
|
|
162
|
+
else if (searchQuery.error) {
|
|
163
|
+
searchEmptyState = (_jsx(Text, { style: styles.errorText, children: describeClientError(searchQuery.error, 'We could not search for teammates right now.') }));
|
|
164
|
+
}
|
|
165
|
+
else if (searchQuery.loading) {
|
|
166
|
+
searchEmptyState = (_jsx(View, { style: styles.inlineState, children: _jsx(ActivityIndicator, {}) }));
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
searchEmptyState = (_jsx(EmptyState, { title: "No users found", body: "Try a different handle or display name." }));
|
|
170
|
+
}
|
|
149
171
|
const handleCreateConversation = async () => {
|
|
150
172
|
if (!token) {
|
|
151
173
|
await auth?.login?.();
|
|
@@ -175,20 +197,26 @@ export function ComposeMessageScreen({ auth, onCreatedConversation, initialUser
|
|
|
175
197
|
if (!token) {
|
|
176
198
|
return _jsx(EmptyState, { title: "Sign in to continue", body: "Log in to search for teammates and start a conversation." });
|
|
177
199
|
}
|
|
178
|
-
return (_jsxs(
|
|
200
|
+
return (_jsxs(KeyboardAvoidingView, { behavior: Platform.OS === 'ios' ? 'padding' : undefined, style: styles.screen, children: [_jsx(Text, { style: styles.screenTitle, children: "New conversation" }), _jsx(TextInput, { value: query, onChangeText: setQuery, placeholder: "Search for a teammate", style: styles.searchInput }), _jsx(FlatList, { data: results, keyExtractor: (item) => item.id, style: styles.composeResultsList, contentContainerStyle: [
|
|
201
|
+
styles.compactList,
|
|
202
|
+
results.length === 0 ? styles.compactListEmpty : undefined
|
|
203
|
+
], keyboardShouldPersistTaps: "handled", renderItem: ({ item }) => {
|
|
179
204
|
const selected = selectedUser?.id === item.id;
|
|
180
205
|
return (_jsxs(Pressable, { accessibilityRole: "button", style: [styles.card, selected ? styles.selectedCard : undefined], onPress: () => setSelectedUser({
|
|
181
206
|
id: item.id,
|
|
182
207
|
handle: item.handle,
|
|
183
208
|
displayName: item.displayName
|
|
184
209
|
}), children: [_jsx(Text, { style: styles.cardTitle, children: item.displayName }), _jsxs(Text, { style: styles.mutedText, children: ["@", item.handle] })] }));
|
|
185
|
-
}, ListEmptyComponent:
|
|
210
|
+
}, ListEmptyComponent: searchEmptyState }), selectedUser ? (_jsxs(View, { style: styles.selectionCard, children: [_jsxs(Text, { style: styles.cardTitle, children: ["To: ", selectedUser.displayName] }), _jsxs(Text, { style: styles.mutedText, children: ["@", selectedUser.handle] })] })) : null, _jsxs(View, { style: styles.composerColumn, children: [_jsx(TextInput, { value: draft, onChangeText: setDraft, placeholder: "Write the first message", style: styles.composerInput, multiline: true }), _jsx(Pressable, { accessibilityRole: "button", disabled: !canStartConversation, onPress: () => void handleCreateConversation(), style: [
|
|
186
211
|
styles.inlineButton,
|
|
187
|
-
|
|
212
|
+
canStartConversation ? undefined : styles.disabledCard
|
|
188
213
|
], children: _jsx(Text, { style: styles.inlineButtonText, children: sendState.loading ? 'Starting…' : 'Start conversation' }) })] }), composerError ? _jsx(Text, { style: styles.errorText, children: composerError }) : null] }));
|
|
189
214
|
}
|
|
190
215
|
const { color, radius, shadow, spacing } = tokens;
|
|
191
216
|
const styles = StyleSheet.create({
|
|
217
|
+
flex: {
|
|
218
|
+
flex: 1
|
|
219
|
+
},
|
|
192
220
|
screen: {
|
|
193
221
|
flex: 1,
|
|
194
222
|
backgroundColor: color.surfaceAlt,
|
|
@@ -231,6 +259,14 @@ const styles = StyleSheet.create({
|
|
|
231
259
|
gap: spacing.sm,
|
|
232
260
|
paddingBottom: spacing.md
|
|
233
261
|
},
|
|
262
|
+
compactListEmpty: {
|
|
263
|
+
paddingBottom: 0
|
|
264
|
+
},
|
|
265
|
+
composeResultsList: {
|
|
266
|
+
flexGrow: 0,
|
|
267
|
+
flexShrink: 1,
|
|
268
|
+
maxHeight: 280
|
|
269
|
+
},
|
|
234
270
|
card: {
|
|
235
271
|
borderRadius: radius.lg,
|
|
236
272
|
borderWidth: 1,
|