@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.
Files changed (2) hide show
  1. package/dist/native.js +44 -8
  2. 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
- const message = error instanceof Error ? error.message.trim() : typeof error === 'string' ? error.trim() : '';
9
- if (!message || message === 'Subgraph errors redacted') {
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(View, { style: styles.screen, children: [_jsx(Text, { style: styles.screenTitle, children: title }), _jsx(FlatList, { data: messages, keyExtractor: (item) => item.id, contentContainerStyle: styles.listContent, renderItem: ({ item }) => {
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, !draft.trim() ? styles.disabledCard : undefined], children: _jsx(Text, { style: styles.inlineButtonText, children: sendState.loading ? 'Sending…' : 'Send' }) })] }), composerError ? _jsx(Text, { style: styles.errorText, children: composerError }) : null] }));
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(View, { 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, contentContainerStyle: styles.compactList, renderItem: ({ item }) => {
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: query.trim().length < 2 ? (_jsx(EmptyState, { title: "Search for someone", body: "Enter at least two characters to find a user." })) : searchQuery.error ? (_jsx(Text, { style: styles.errorText, children: describeClientError(searchQuery.error, 'We could not search for teammates right now.') })) : searchQuery.loading ? (_jsx(View, { style: styles.inlineState, children: _jsx(ActivityIndicator, {}) })) : (_jsx(EmptyState, { title: "No users found", body: "Try a different handle or display name." })) }), 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: sendState.loading || !selectedUser || !draft.trim(), onPress: () => void handleCreateConversation(), style: [
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
- !selectedUser || !draft.trim() ? styles.disabledCard : undefined
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mereb/app-messaging",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Messaging experience primitives for Mereb applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",