@mereb/app-messaging 0.0.2 → 0.0.3

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.
@@ -0,0 +1 @@
1
+ export * from './store';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from './store';
package/dist/native.js ADDED
@@ -0,0 +1,35 @@
1
+ import { useMemo, useState } from 'react';
2
+ import { Text, View } from 'react-native';
3
+ import { createInMemoryMessagingStore } from './store';
4
+ export function MessagesScreen() {
5
+ const store = useMemo(() => createInMemoryMessagingStore(), []);
6
+ const [version, setVersion] = useState(0);
7
+ const conversations = store.getConversations();
8
+ return (<View style={{ flex: 1, padding: 24, gap: 16 }}>
9
+ <Text style={{ fontWeight: '700', fontSize: 18, marginBottom: 8 }}>Messaging</Text>
10
+ {conversations.map((conversation) => (<View key={conversation.id} style={{
11
+ padding: 12,
12
+ borderRadius: 12,
13
+ borderWidth: 1,
14
+ borderColor: '#E2E8F0',
15
+ backgroundColor: '#FFFFFF',
16
+ gap: 4
17
+ }}>
18
+ <Text style={{ fontWeight: '600', fontSize: 16 }}>{conversation.title}</Text>
19
+ <Text style={{ color: '#475569' }}>{conversation.lastMessage?.body ?? 'No messages yet'}</Text>
20
+ <Text style={{ color: '#94A3B8', fontSize: 12 }}>Unread: {conversation.unreadCount}</Text>
21
+ </View>))}
22
+ <Text onPress={() => {
23
+ store.sendMessage(conversations[0]?.id ?? 'conv-1', 'Sent from mobile preview', {
24
+ id: 'mobile-user',
25
+ name: 'You'
26
+ });
27
+ setVersion((v) => v + 1);
28
+ }} style={{ color: '#2563EB', fontWeight: '600' }}>
29
+ Send a quick test message
30
+ </Text>
31
+ <Text style={{ color: '#94A3B8', fontSize: 12 }}>
32
+ Version {version} • This view uses the shared in-memory store so it stays in sync with the web shell preview.
33
+ </Text>
34
+ </View>);
35
+ }
@@ -0,0 +1,26 @@
1
+ export type MessagingMessage = {
2
+ id: string;
3
+ conversationId: string;
4
+ senderId: string;
5
+ senderName?: string;
6
+ body: string;
7
+ sentAt: string;
8
+ };
9
+ export type MessagingConversation = {
10
+ id: string;
11
+ title: string;
12
+ participantIds: string[];
13
+ lastMessage?: MessagingMessage;
14
+ unreadCount: number;
15
+ updatedAt: string;
16
+ };
17
+ export type MessagingStore = {
18
+ getConversations: () => MessagingConversation[];
19
+ getConversation: (id: string) => MessagingConversation | undefined;
20
+ getMessages: (conversationId: string) => MessagingMessage[];
21
+ sendMessage: (conversationId: string, body: string, sender?: {
22
+ id: string;
23
+ name?: string;
24
+ }) => MessagingMessage;
25
+ };
26
+ export declare function createInMemoryMessagingStore(seed?: MessagingConversation[]): MessagingStore;
package/dist/store.js ADDED
@@ -0,0 +1,77 @@
1
+ const seedConversations = [
2
+ {
3
+ id: 'conv-1',
4
+ title: 'Platform Ops',
5
+ participantIds: ['u-platform', 'u-release', 'u-ops'],
6
+ unreadCount: 2,
7
+ updatedAt: new Date().toISOString(),
8
+ lastMessage: {
9
+ id: 'msg-1',
10
+ conversationId: 'conv-1',
11
+ senderId: 'u-release',
12
+ senderName: 'Release Manager',
13
+ body: 'Copy that. Will queue prod promotion after smoke.',
14
+ sentAt: new Date().toISOString()
15
+ }
16
+ },
17
+ {
18
+ id: 'conv-2',
19
+ title: 'Support triage',
20
+ participantIds: ['u-support', 'u-oncall', 'u-platform'],
21
+ unreadCount: 0,
22
+ updatedAt: new Date().toISOString(),
23
+ lastMessage: {
24
+ id: 'msg-2',
25
+ conversationId: 'conv-2',
26
+ senderId: 'u-oncall',
27
+ senderName: 'On-call',
28
+ body: 'Acknowledged. Looking at the router dashboards now.',
29
+ sentAt: new Date().toISOString()
30
+ }
31
+ }
32
+ ];
33
+ export function createInMemoryMessagingStore(seed = seedConversations) {
34
+ let conversations = seed.map((conversation) => ({
35
+ ...conversation,
36
+ lastMessage: conversation.lastMessage
37
+ ? { ...conversation.lastMessage, sentAt: conversation.lastMessage.sentAt ?? new Date().toISOString() }
38
+ : undefined
39
+ }));
40
+ const messageLookup = {};
41
+ conversations.forEach((conversation) => {
42
+ messageLookup[conversation.id] = conversation.lastMessage ? [conversation.lastMessage] : [];
43
+ });
44
+ const refreshConversation = (conversationId, message) => {
45
+ const idx = conversations.findIndex((c) => c.id === conversationId);
46
+ if (idx >= 0) {
47
+ conversations[idx] = {
48
+ ...conversations[idx],
49
+ lastMessage: message,
50
+ updatedAt: message.sentAt,
51
+ unreadCount: Math.max(0, conversations[idx].unreadCount - 1)
52
+ };
53
+ }
54
+ };
55
+ return {
56
+ getConversations: () => [...conversations].sort((a, b) => (a.updatedAt > b.updatedAt ? -1 : 1)),
57
+ getConversation: (id) => conversations.find((conversation) => conversation.id === id),
58
+ getMessages: (conversationId) => [...(messageLookup[conversationId] ?? [])],
59
+ sendMessage: (conversationId, body, sender) => {
60
+ const conversationExists = conversations.some((conversation) => conversation.id === conversationId);
61
+ if (!conversationExists) {
62
+ throw new Error('Conversation not found');
63
+ }
64
+ const message = {
65
+ id: `msg-${Math.random().toString(36).slice(2, 8)}`,
66
+ conversationId,
67
+ senderId: sender?.id ?? 'anonymous',
68
+ senderName: sender?.name ?? 'You',
69
+ body,
70
+ sentAt: new Date().toISOString()
71
+ };
72
+ messageLookup[conversationId] = [...(messageLookup[conversationId] ?? []), message];
73
+ refreshConversation(conversationId, message);
74
+ return message;
75
+ }
76
+ };
77
+ }
package/package.json CHANGED
@@ -1,10 +1,27 @@
1
1
  {
2
2
  "name": "@mereb/app-messaging",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Messaging experience primitives for Mereb applications",
5
5
  "type": "module",
6
- "main": "src/index.tsx",
7
- "types": "src/index.tsx",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./store": {
15
+ "types": "./dist/store.d.ts",
16
+ "import": "./dist/store.js",
17
+ "default": "./dist/store.js"
18
+ },
19
+ "./native": {
20
+ "types": "./dist/native.d.ts",
21
+ "import": "./dist/native.js",
22
+ "default": "./dist/native.js"
23
+ }
24
+ },
8
25
  "files": [
9
26
  "src",
10
27
  "dist",
package/src/index.tsx CHANGED
@@ -1,9 +1 @@
1
- import { Text, View } from 'react-native';
2
-
3
- export function MessagesScreen() {
4
- return (
5
- <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
6
- <Text>Messages coming soon</Text>
7
- </View>
8
- );
9
- }
1
+ export * from './store'
package/src/native.tsx ADDED
@@ -0,0 +1,47 @@
1
+ import { useMemo, useState } from 'react'
2
+ import { Text, View } from 'react-native'
3
+ import { createInMemoryMessagingStore } from './store'
4
+
5
+ export function MessagesScreen() {
6
+ const store = useMemo(() => createInMemoryMessagingStore(), [])
7
+ const [version, setVersion] = useState(0)
8
+ const conversations = store.getConversations()
9
+
10
+ return (
11
+ <View style={{ flex: 1, padding: 24, gap: 16 }}>
12
+ <Text style={{ fontWeight: '700', fontSize: 18, marginBottom: 8 }}>Messaging</Text>
13
+ {conversations.map((conversation) => (
14
+ <View
15
+ key={conversation.id}
16
+ style={{
17
+ padding: 12,
18
+ borderRadius: 12,
19
+ borderWidth: 1,
20
+ borderColor: '#E2E8F0',
21
+ backgroundColor: '#FFFFFF',
22
+ gap: 4
23
+ }}
24
+ >
25
+ <Text style={{ fontWeight: '600', fontSize: 16 }}>{conversation.title}</Text>
26
+ <Text style={{ color: '#475569' }}>{conversation.lastMessage?.body ?? 'No messages yet'}</Text>
27
+ <Text style={{ color: '#94A3B8', fontSize: 12 }}>Unread: {conversation.unreadCount}</Text>
28
+ </View>
29
+ ))}
30
+ <Text
31
+ onPress={() => {
32
+ store.sendMessage(conversations[0]?.id ?? 'conv-1', 'Sent from mobile preview', {
33
+ id: 'mobile-user',
34
+ name: 'You'
35
+ })
36
+ setVersion((v) => v + 1)
37
+ }}
38
+ style={{ color: '#2563EB', fontWeight: '600' }}
39
+ >
40
+ Send a quick test message
41
+ </Text>
42
+ <Text style={{ color: '#94A3B8', fontSize: 12 }}>
43
+ Version {version} • This view uses the shared in-memory store so it stays in sync with the web shell preview.
44
+ </Text>
45
+ </View>
46
+ )
47
+ }
package/src/store.ts ADDED
@@ -0,0 +1,111 @@
1
+ export type MessagingMessage = {
2
+ id: string
3
+ conversationId: string
4
+ senderId: string
5
+ senderName?: string
6
+ body: string
7
+ sentAt: string
8
+ }
9
+
10
+ export type MessagingConversation = {
11
+ id: string
12
+ title: string
13
+ participantIds: string[]
14
+ lastMessage?: MessagingMessage
15
+ unreadCount: number
16
+ updatedAt: string
17
+ }
18
+
19
+ export type MessagingStore = {
20
+ getConversations: () => MessagingConversation[]
21
+ getConversation: (id: string) => MessagingConversation | undefined
22
+ getMessages: (conversationId: string) => MessagingMessage[]
23
+ sendMessage: (
24
+ conversationId: string,
25
+ body: string,
26
+ sender?: { id: string; name?: string }
27
+ ) => MessagingMessage
28
+ }
29
+
30
+ const seedConversations: MessagingConversation[] = [
31
+ {
32
+ id: 'conv-1',
33
+ title: 'Platform Ops',
34
+ participantIds: ['u-platform', 'u-release', 'u-ops'],
35
+ unreadCount: 2,
36
+ updatedAt: new Date().toISOString(),
37
+ lastMessage: {
38
+ id: 'msg-1',
39
+ conversationId: 'conv-1',
40
+ senderId: 'u-release',
41
+ senderName: 'Release Manager',
42
+ body: 'Copy that. Will queue prod promotion after smoke.',
43
+ sentAt: new Date().toISOString()
44
+ }
45
+ },
46
+ {
47
+ id: 'conv-2',
48
+ title: 'Support triage',
49
+ participantIds: ['u-support', 'u-oncall', 'u-platform'],
50
+ unreadCount: 0,
51
+ updatedAt: new Date().toISOString(),
52
+ lastMessage: {
53
+ id: 'msg-2',
54
+ conversationId: 'conv-2',
55
+ senderId: 'u-oncall',
56
+ senderName: 'On-call',
57
+ body: 'Acknowledged. Looking at the router dashboards now.',
58
+ sentAt: new Date().toISOString()
59
+ }
60
+ }
61
+ ]
62
+
63
+ export function createInMemoryMessagingStore(seed: MessagingConversation[] = seedConversations): MessagingStore {
64
+ let conversations: MessagingConversation[] = seed.map((conversation) => ({
65
+ ...conversation,
66
+ lastMessage: conversation.lastMessage
67
+ ? { ...conversation.lastMessage, sentAt: conversation.lastMessage.sentAt ?? new Date().toISOString() }
68
+ : undefined
69
+ }))
70
+ const messageLookup: Record<string, MessagingMessage[]> = {}
71
+
72
+ conversations.forEach((conversation) => {
73
+ messageLookup[conversation.id] = conversation.lastMessage ? [conversation.lastMessage] : []
74
+ })
75
+
76
+ const refreshConversation = (conversationId: string, message: MessagingMessage) => {
77
+ const idx = conversations.findIndex((c) => c.id === conversationId)
78
+ if (idx >= 0) {
79
+ conversations[idx] = {
80
+ ...conversations[idx],
81
+ lastMessage: message,
82
+ updatedAt: message.sentAt,
83
+ unreadCount: Math.max(0, conversations[idx].unreadCount - 1)
84
+ }
85
+ }
86
+ }
87
+
88
+ return {
89
+ getConversations: () => [...conversations].sort((a, b) => (a.updatedAt > b.updatedAt ? -1 : 1)),
90
+ getConversation: (id: string) => conversations.find((conversation) => conversation.id === id),
91
+ getMessages: (conversationId: string) => [...(messageLookup[conversationId] ?? [])],
92
+ sendMessage: (conversationId: string, body: string, sender?: { id: string; name?: string }) => {
93
+ const conversationExists = conversations.some((conversation) => conversation.id === conversationId)
94
+ if (!conversationExists) {
95
+ throw new Error('Conversation not found')
96
+ }
97
+ const message: MessagingMessage = {
98
+ id: `msg-${Math.random().toString(36).slice(2, 8)}`,
99
+ conversationId,
100
+ senderId: sender?.id ?? 'anonymous',
101
+ senderName: sender?.name ?? 'You',
102
+ body,
103
+ sentAt: new Date().toISOString()
104
+ }
105
+
106
+ messageLookup[conversationId] = [...(messageLookup[conversationId] ?? []), message]
107
+ refreshConversation(conversationId, message)
108
+ return message
109
+ }
110
+ }
111
+ }
File without changes