@sansavision/create-pulse 0.4.4 → 0.4.5
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/package.json +2 -2
- package/templates/aurora-auth-node-demo/README.md +43 -0
- package/templates/aurora-auth-node-demo/aurora.config.ts +15 -0
- package/templates/aurora-auth-node-demo/bun.lock +679 -0
- package/templates/aurora-auth-node-demo/drizzle.config.ts +9 -0
- package/templates/aurora-auth-node-demo/package.json +39 -0
- package/templates/aurora-auth-node-demo/postcss.config.mjs +7 -0
- package/templates/aurora-auth-node-demo/server.mjs +46 -0
- package/templates/aurora-auth-node-demo/src/actions/createMessage.action.server.ts +31 -0
- package/templates/aurora-auth-node-demo/src/aurora.auth.ts +65 -0
- package/templates/aurora-auth-node-demo/src/lib/auth-client.ts +30 -0
- package/templates/aurora-auth-node-demo/src/lib/auth.server.ts +11 -0
- package/templates/aurora-auth-node-demo/src/lib/auth.ts +30 -0
- package/templates/aurora-auth-node-demo/src/lib/db.ts +6 -0
- package/templates/aurora-auth-node-demo/src/lib/pulse.ts +45 -0
- package/templates/aurora-auth-node-demo/src/lib/schema.ts +107 -0
- package/templates/aurora-auth-node-demo/src/queries/listMessages.server.ts +25 -0
- package/templates/aurora-auth-node-demo/src/routes/api/auth/[...slug]/handler.ts +14 -0
- package/templates/aurora-auth-node-demo/src/routes/api/pulse/verify/handler.ts +55 -0
- package/templates/aurora-auth-node-demo/src/routes/auth/sign-in/page.client.tsx +132 -0
- package/templates/aurora-auth-node-demo/src/routes/auth/sign-in/page.tsx +5 -0
- package/templates/aurora-auth-node-demo/src/routes/auth/sign-up/page.client.tsx +154 -0
- package/templates/aurora-auth-node-demo/src/routes/auth/sign-up/page.tsx +5 -0
- package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/arena-game/page.client.tsx +640 -0
- package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/arena-game/page.tsx +5 -0
- package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/chat/page.client.tsx +349 -0
- package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/chat/page.tsx +5 -0
- package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/encrypted-chat/page.client.tsx +472 -0
- package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/encrypted-chat/page.tsx +5 -0
- package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/game-sync/page.client.tsx +375 -0
- package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/game-sync/page.tsx +5 -0
- package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/queues/page.client.tsx +423 -0
- package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/queues/page.tsx +5 -0
- package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/video-call/page.client.tsx +840 -0
- package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/video-call/page.tsx +5 -0
- package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/watch-together/page.client.tsx +722 -0
- package/templates/aurora-auth-node-demo/src/routes/dashboard/demos/watch-together/page.tsx +5 -0
- package/templates/aurora-auth-node-demo/src/routes/dashboard/layout.client.tsx +113 -0
- package/templates/aurora-auth-node-demo/src/routes/dashboard/layout.tsx +5 -0
- package/templates/aurora-auth-node-demo/src/routes/dashboard/page.client.tsx +195 -0
- package/templates/aurora-auth-node-demo/src/routes/dashboard/page.tsx +5 -0
- package/templates/aurora-auth-node-demo/src/routes/favicon.ico +0 -0
- package/templates/aurora-auth-node-demo/src/routes/layout.tsx +18 -0
- package/templates/aurora-auth-node-demo/src/routes/page.client.tsx +263 -0
- package/templates/aurora-auth-node-demo/src/routes/page.tsx +5 -0
- package/templates/aurora-auth-node-demo/src/styles/app.css +96 -0
- package/templates/aurora-auth-node-demo/tsconfig.json +27 -0
- package/templates/aurora-auth-node-demo/tsconfig.tsbuildinfo +1 -0
- package/templates/nextjs-auth-demo/next-env.d.ts +1 -1
- package/templates/nextjs-auth-demo/package.json +8 -7
- package/templates/nextjs-auth-demo/src/app/dashboard/demos/arena-game/page.tsx +20 -3
- package/templates/nextjs-auth-demo/src/app/dashboard/demos/chat/page.tsx +108 -23
- package/templates/nextjs-auth-demo/src/app/dashboard/demos/encrypted-chat/page.tsx +278 -217
- package/templates/nextjs-auth-demo/src/app/dashboard/demos/game-sync/page.tsx +66 -35
- package/templates/nextjs-auth-demo/src/app/dashboard/demos/queues/page.tsx +213 -87
- package/templates/nextjs-auth-demo/src/app/dashboard/demos/video-call/page.tsx +106 -6
- package/templates/nextjs-auth-demo/src/app/dashboard/demos/watch-together/page.tsx +415 -262
- package/templates/nextjs-auth-node-demo/.env.example +10 -0
- package/templates/nextjs-auth-node-demo/Dockerfile +19 -0
- package/templates/nextjs-auth-node-demo/README.md +159 -0
- package/templates/nextjs-auth-node-demo/_gitignore +33 -0
- package/templates/nextjs-auth-node-demo/drizzle.config.ts +10 -0
- package/templates/nextjs-auth-node-demo/eslint.config.mjs +18 -0
- package/templates/nextjs-auth-node-demo/next-env.d.ts +6 -0
- package/templates/nextjs-auth-node-demo/next.config.ts +7 -0
- package/templates/nextjs-auth-node-demo/package.json +38 -0
- package/templates/nextjs-auth-node-demo/postcss.config.mjs +7 -0
- package/templates/nextjs-auth-node-demo/public/file.svg +1 -0
- package/templates/nextjs-auth-node-demo/public/globe.svg +1 -0
- package/templates/nextjs-auth-node-demo/public/next.svg +1 -0
- package/templates/nextjs-auth-node-demo/public/vercel.svg +1 -0
- package/templates/nextjs-auth-node-demo/public/window.svg +1 -0
- package/templates/nextjs-auth-node-demo/server.mjs +45 -0
- package/templates/nextjs-auth-node-demo/src/app/api/auth/[...all]/route.ts +4 -0
- package/templates/nextjs-auth-node-demo/src/app/api/pulse/verify/route.ts +54 -0
- package/templates/nextjs-auth-node-demo/src/app/auth/sign-in/page.tsx +131 -0
- package/templates/nextjs-auth-node-demo/src/app/auth/sign-up/page.tsx +153 -0
- package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/arena-game/page.tsx +640 -0
- package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/chat/page.tsx +349 -0
- package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/encrypted-chat/page.tsx +472 -0
- package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/game-sync/page.tsx +375 -0
- package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/queues/page.tsx +423 -0
- package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/video-call/page.tsx +840 -0
- package/templates/nextjs-auth-node-demo/src/app/dashboard/demos/watch-together/page.tsx +724 -0
- package/templates/nextjs-auth-node-demo/src/app/dashboard/layout.tsx +113 -0
- package/templates/nextjs-auth-node-demo/src/app/dashboard/page.tsx +195 -0
- package/templates/nextjs-auth-node-demo/src/app/favicon.ico +0 -0
- package/templates/nextjs-auth-node-demo/src/app/globals.css +96 -0
- package/templates/nextjs-auth-node-demo/src/app/layout.tsx +27 -0
- package/templates/nextjs-auth-node-demo/src/app/page.tsx +254 -0
- package/templates/nextjs-auth-node-demo/src/lib/auth-client.ts +15 -0
- package/templates/nextjs-auth-node-demo/src/lib/auth.ts +14 -0
- package/templates/nextjs-auth-node-demo/src/lib/db.ts +6 -0
- package/templates/nextjs-auth-node-demo/src/lib/pulse.ts +45 -0
- package/templates/nextjs-auth-node-demo/src/lib/schema.ts +107 -0
- package/templates/nextjs-auth-node-demo/tsconfig.json +34 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="next" />
|
|
2
2
|
/// <reference types="next/image-types/global" />
|
|
3
|
-
import "./.next/types/routes.d.ts";
|
|
3
|
+
import "./.next/dev/types/routes.d.ts";
|
|
4
4
|
|
|
5
5
|
// NOTE: This file should not be edited
|
|
6
6
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
|
@@ -12,25 +12,26 @@
|
|
|
12
12
|
"db:studio": "npx drizzle-kit studio"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"
|
|
16
|
-
"react": "19.2.3",
|
|
17
|
-
"react-dom": "19.2.3",
|
|
15
|
+
"@sansavision/pulse-sdk": "^0.4.2",
|
|
18
16
|
"better-auth": "^1.2.0",
|
|
19
17
|
"better-sqlite3": "^12.0.0",
|
|
20
18
|
"drizzle-orm": "^0.41.0",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
19
|
+
"lucide-react": "^0.412.0",
|
|
20
|
+
"next": "16.1.6",
|
|
21
|
+
"react": "19.2.3",
|
|
22
|
+
"react-dom": "19.2.3",
|
|
23
|
+
"react-player": "^2.16.0"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
25
26
|
"@tailwindcss/postcss": "^4",
|
|
27
|
+
"@types/better-sqlite3": "^7.6.0",
|
|
26
28
|
"@types/node": "^20",
|
|
27
29
|
"@types/react": "^19",
|
|
28
30
|
"@types/react-dom": "^19",
|
|
29
|
-
"@types/better-sqlite3": "^7.6.0",
|
|
30
31
|
"drizzle-kit": "^0.31.0",
|
|
31
32
|
"eslint": "^9",
|
|
32
33
|
"eslint-config-next": "16.1.6",
|
|
33
34
|
"tailwindcss": "^4",
|
|
34
35
|
"typescript": "^5"
|
|
35
36
|
}
|
|
36
|
-
}
|
|
37
|
+
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
Trophy,
|
|
17
17
|
Zap,
|
|
18
18
|
Activity,
|
|
19
|
+
AlertCircle,
|
|
19
20
|
} from "lucide-react";
|
|
20
21
|
import type { PulseConnection } from "@sansavision/pulse-sdk";
|
|
21
22
|
|
|
@@ -47,6 +48,7 @@ export default function ArenaGamePage() {
|
|
|
47
48
|
// Room management
|
|
48
49
|
const [roomId, setRoomId] = useState("");
|
|
49
50
|
const [inputRoomId, setInputRoomId] = useState("");
|
|
51
|
+
const [error, setError] = useState("");
|
|
50
52
|
const [inGame, setInGame] = useState(false);
|
|
51
53
|
const [copied, setCopied] = useState(false);
|
|
52
54
|
const [playerCount, setPlayerCount] = useState(1);
|
|
@@ -401,8 +403,16 @@ export default function ArenaGamePage() {
|
|
|
401
403
|
// Join existing room
|
|
402
404
|
function handleJoinRoom(e: React.FormEvent) {
|
|
403
405
|
e.preventDefault();
|
|
404
|
-
|
|
405
|
-
|
|
406
|
+
const cleaned = inputRoomId.trim();
|
|
407
|
+
if (!cleaned) return;
|
|
408
|
+
|
|
409
|
+
if (!/^[a-zA-Z0-9-]{6,12}$/.test(cleaned)) {
|
|
410
|
+
setError("Invalid room format. Must be 6-12 characters (letters, numbers, hyphens).");
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
setError("");
|
|
415
|
+
joinGame(cleaned);
|
|
406
416
|
}
|
|
407
417
|
|
|
408
418
|
// Copy room ID
|
|
@@ -493,11 +503,18 @@ export default function ArenaGamePage() {
|
|
|
493
503
|
</div>
|
|
494
504
|
</div>
|
|
495
505
|
|
|
506
|
+
{error && (
|
|
507
|
+
<div className="mb-4 p-3 rounded-lg bg-red-500/10 border border-red-500/20 flex items-center gap-2 text-red-400 text-sm text-left">
|
|
508
|
+
<AlertCircle className="w-4 h-4 shrink-0" />
|
|
509
|
+
<p>{error}</p>
|
|
510
|
+
</div>
|
|
511
|
+
)}
|
|
512
|
+
|
|
496
513
|
<form onSubmit={handleJoinRoom} className="flex gap-3">
|
|
497
514
|
<input
|
|
498
515
|
type="text"
|
|
499
516
|
value={inputRoomId}
|
|
500
|
-
onChange={(e) => setInputRoomId(e.target.value)}
|
|
517
|
+
onChange={(e) => { setInputRoomId(e.target.value); setError(""); }}
|
|
501
518
|
placeholder="Enter room code..."
|
|
502
519
|
className="flex-1 px-4 py-2.5 rounded-xl bg-slate-800/50 border border-slate-700 focus:border-orange-500 focus:ring-1 focus:ring-orange-500 outline-none text-sm transition-colors placeholder:text-slate-600"
|
|
503
520
|
/>
|
|
@@ -25,7 +25,15 @@ interface ChatMessage {
|
|
|
25
25
|
export default function ChatDemoPage() {
|
|
26
26
|
const { data: session } = useSession();
|
|
27
27
|
const [connected, setConnected] = useState(false);
|
|
28
|
-
const [messages, setMessages] = useState<ChatMessage[]>(
|
|
28
|
+
const [messages, setMessages] = useState<ChatMessage[]>(() => {
|
|
29
|
+
if (typeof window !== "undefined") {
|
|
30
|
+
try {
|
|
31
|
+
const stored = localStorage.getItem("pulse-chat-messages");
|
|
32
|
+
if (stored) return JSON.parse(stored);
|
|
33
|
+
} catch { /* ignore */ }
|
|
34
|
+
}
|
|
35
|
+
return [];
|
|
36
|
+
});
|
|
29
37
|
const [input, setInput] = useState("");
|
|
30
38
|
const [authUser, setAuthUser] = useState<{
|
|
31
39
|
id: string;
|
|
@@ -35,11 +43,24 @@ export default function ChatDemoPage() {
|
|
|
35
43
|
const [connecting, setConnecting] = useState(true);
|
|
36
44
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
37
45
|
const connRef = useRef<PulseConnection | null>(null);
|
|
46
|
+
const streamRef = useRef<ReturnType<PulseConnection["stream"]> | null>(null);
|
|
47
|
+
const sessionRef = useRef(session);
|
|
48
|
+
const seenIdsRef = useRef(new Set<string>());
|
|
49
|
+
|
|
50
|
+
// Keep session ref in sync
|
|
51
|
+
useEffect(() => { sessionRef.current = session; }, [session]);
|
|
38
52
|
|
|
39
53
|
const scrollToBottom = useCallback(() => {
|
|
40
54
|
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
41
55
|
}, []);
|
|
42
56
|
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (typeof window !== "undefined") {
|
|
59
|
+
localStorage.setItem("pulse-chat-messages", JSON.stringify(messages.slice(-100)));
|
|
60
|
+
}
|
|
61
|
+
}, [messages]);
|
|
62
|
+
|
|
63
|
+
// Connect to relay — run ONCE when session is available
|
|
43
64
|
useEffect(() => {
|
|
44
65
|
if (!session) return;
|
|
45
66
|
let cancelled = false;
|
|
@@ -53,39 +74,50 @@ export default function ChatDemoPage() {
|
|
|
53
74
|
setConnected(true);
|
|
54
75
|
setConnecting(false);
|
|
55
76
|
|
|
56
|
-
// Check auth user from the ACCEPT payload
|
|
57
77
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
78
|
const user = (connection as any).user;
|
|
59
79
|
if (user) setAuthUser(user);
|
|
60
80
|
|
|
61
|
-
// Open
|
|
81
|
+
// Open the chat stream ONCE and store in ref
|
|
62
82
|
const stream = connection.stream("chat-room");
|
|
83
|
+
streamRef.current = stream;
|
|
63
84
|
|
|
64
85
|
// Announce join
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
86
|
+
const sess = sessionRef.current;
|
|
87
|
+
if (sess) {
|
|
88
|
+
stream.send(
|
|
89
|
+
JSON.stringify({
|
|
90
|
+
type: "join",
|
|
91
|
+
user: sess.user.name,
|
|
92
|
+
userId: sess.user.id,
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
}
|
|
72
96
|
|
|
73
|
-
// Listen for messages
|
|
97
|
+
// Listen for messages — uses sessionRef to avoid stale closures
|
|
74
98
|
stream.on("data", (data: Uint8Array) => {
|
|
75
99
|
try {
|
|
76
100
|
const msg = JSON.parse(new TextDecoder().decode(data));
|
|
77
|
-
|
|
101
|
+
const currentSess = sessionRef.current;
|
|
102
|
+
if (!currentSess) return;
|
|
103
|
+
const selfId = currentSess.user.id;
|
|
104
|
+
|
|
105
|
+
// Deduplication via message ID
|
|
106
|
+
if (msg.msgId && seenIdsRef.current.has(msg.msgId)) return;
|
|
107
|
+
if (msg.msgId) seenIdsRef.current.add(msg.msgId);
|
|
108
|
+
|
|
109
|
+
if (msg.type === "chat" && msg.userId !== selfId) {
|
|
78
110
|
setMessages((prev) => [
|
|
79
111
|
...prev,
|
|
80
112
|
{
|
|
81
|
-
id: crypto.randomUUID(),
|
|
113
|
+
id: msg.msgId || crypto.randomUUID(),
|
|
82
114
|
user: msg.user,
|
|
83
115
|
userId: msg.userId,
|
|
84
116
|
text: msg.text,
|
|
85
117
|
timestamp: Date.now(),
|
|
86
118
|
},
|
|
87
119
|
]);
|
|
88
|
-
} else if (msg.type === "join") {
|
|
120
|
+
} else if (msg.type === "join" && msg.userId !== selfId) {
|
|
89
121
|
setOnlineUsers((prev) =>
|
|
90
122
|
prev.includes(msg.user) ? prev : [...prev, msg.user]
|
|
91
123
|
);
|
|
@@ -99,6 +131,32 @@ export default function ChatDemoPage() {
|
|
|
99
131
|
timestamp: Date.now(),
|
|
100
132
|
},
|
|
101
133
|
]);
|
|
134
|
+
|
|
135
|
+
// Announce our presence back
|
|
136
|
+
const s = sessionRef.current;
|
|
137
|
+
if (s) {
|
|
138
|
+
stream.send(JSON.stringify({
|
|
139
|
+
type: "presence",
|
|
140
|
+
user: s.user.name,
|
|
141
|
+
userId: s.user.id,
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
} else if (msg.type === "presence" && msg.userId !== selfId) {
|
|
145
|
+
setOnlineUsers((prev) =>
|
|
146
|
+
prev.includes(msg.user) ? prev : [...prev, msg.user]
|
|
147
|
+
);
|
|
148
|
+
} else if (msg.type === "leave" && msg.userId !== selfId) {
|
|
149
|
+
setOnlineUsers((prev) => prev.filter(u => u !== msg.user));
|
|
150
|
+
setMessages((prev) => [
|
|
151
|
+
...prev,
|
|
152
|
+
{
|
|
153
|
+
id: crypto.randomUUID(),
|
|
154
|
+
user: "System",
|
|
155
|
+
userId: "system",
|
|
156
|
+
text: `${msg.user} left the chat`,
|
|
157
|
+
timestamp: Date.now(),
|
|
158
|
+
},
|
|
159
|
+
]);
|
|
102
160
|
}
|
|
103
161
|
} catch {
|
|
104
162
|
// ignore parse errors
|
|
@@ -106,7 +164,18 @@ export default function ChatDemoPage() {
|
|
|
106
164
|
});
|
|
107
165
|
|
|
108
166
|
connection.on("disconnect", () => setConnected(false));
|
|
109
|
-
connection.on("reconnected", () =>
|
|
167
|
+
connection.on("reconnected", () => {
|
|
168
|
+
setConnected(true);
|
|
169
|
+
// Re-announce on reconnect
|
|
170
|
+
const s = sessionRef.current;
|
|
171
|
+
if (s && streamRef.current) {
|
|
172
|
+
streamRef.current.send(JSON.stringify({
|
|
173
|
+
type: "join",
|
|
174
|
+
user: s.user.name,
|
|
175
|
+
userId: s.user.id,
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
});
|
|
110
179
|
} catch (err) {
|
|
111
180
|
console.error("Failed to connect:", err);
|
|
112
181
|
setConnecting(false);
|
|
@@ -117,6 +186,17 @@ export default function ChatDemoPage() {
|
|
|
117
186
|
|
|
118
187
|
return () => {
|
|
119
188
|
cancelled = true;
|
|
189
|
+
const sess = sessionRef.current;
|
|
190
|
+
if (streamRef.current && sess) {
|
|
191
|
+
try {
|
|
192
|
+
streamRef.current.send(JSON.stringify({
|
|
193
|
+
type: "leave",
|
|
194
|
+
user: sess.user.name,
|
|
195
|
+
userId: sess.user.id
|
|
196
|
+
}));
|
|
197
|
+
} catch { /* ignore */ }
|
|
198
|
+
}
|
|
199
|
+
streamRef.current = null;
|
|
120
200
|
connRef.current?.disconnect();
|
|
121
201
|
};
|
|
122
202
|
}, [session]);
|
|
@@ -125,27 +205,32 @@ export default function ChatDemoPage() {
|
|
|
125
205
|
|
|
126
206
|
function sendMessage(e: React.FormEvent) {
|
|
127
207
|
e.preventDefault();
|
|
128
|
-
if (!input.trim() || !
|
|
208
|
+
if (!input.trim() || !streamRef.current) return;
|
|
129
209
|
|
|
130
210
|
const text = input.trim();
|
|
131
|
-
const
|
|
132
|
-
const
|
|
211
|
+
const sess = sessionRef.current;
|
|
212
|
+
const user = sess?.user?.name || "Anonymous";
|
|
213
|
+
const usrId = sess?.user?.id || "unknown";
|
|
214
|
+
const msgId = crypto.randomUUID();
|
|
215
|
+
|
|
216
|
+
// Mark as seen so we don't add our own echo
|
|
217
|
+
seenIdsRef.current.add(msgId);
|
|
133
218
|
|
|
134
|
-
|
|
135
|
-
stream.send(
|
|
219
|
+
streamRef.current.send(
|
|
136
220
|
JSON.stringify({
|
|
137
221
|
type: "chat",
|
|
138
222
|
user,
|
|
139
223
|
userId: usrId,
|
|
140
224
|
text,
|
|
225
|
+
msgId,
|
|
141
226
|
})
|
|
142
227
|
);
|
|
143
228
|
|
|
144
|
-
// Add to local state immediately
|
|
229
|
+
// Add to local state immediately
|
|
145
230
|
setMessages((prev) => [
|
|
146
231
|
...prev,
|
|
147
232
|
{
|
|
148
|
-
id:
|
|
233
|
+
id: msgId,
|
|
149
234
|
user,
|
|
150
235
|
userId: usrId,
|
|
151
236
|
text,
|
|
@@ -195,7 +280,7 @@ export default function ChatDemoPage() {
|
|
|
195
280
|
<div className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-slate-800">
|
|
196
281
|
<Users className="w-3.5 h-3.5 text-slate-400" />
|
|
197
282
|
<span className="text-xs text-slate-400">
|
|
198
|
-
{onlineUsers.length
|
|
283
|
+
{onlineUsers.length + 1} online
|
|
199
284
|
</span>
|
|
200
285
|
</div>
|
|
201
286
|
</div>
|