@realtimexsco/live-chat 1.0.0 → 1.2.0

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/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # @realtimexsco/live-chat
2
2
 
3
- > Drop-in real-time chat UI for React — direct messages, group chats, reactions, replies, forwarding, pinned/starred messages, and more. Built on Socket.IO, Zustand, Tailwind CSS v4, and Radix UI.
3
+ > Drop-in real-time chat UI for React and Next.js — direct messages, group chats, reactions, replies, forwarding, pinned/starred messages, and more. Built on Socket.IO, Zustand, Tailwind CSS v4, and Radix UI.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@realtimexsco/live-chat.svg)](https://www.npmjs.com/package/@realtimexsco/live-chat)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/@realtimexsco/live-chat.svg)](https://www.npmjs.com/package/@realtimexsco/live-chat)
7
7
  [![license](https://img.shields.io/npm/l/@realtimexsco/live-chat.svg)](https://www.npmjs.com/package/@realtimexsco/live-chat)
8
8
 
9
- Use the all-in-one `<ChatMain />` for a complete chat app in minutes, or compose
10
- the individual pieces (`<Chat>`, `<ChannelList>`, `<MessageList>`, …) for full
11
- control over layout and styling.
9
+ Use the all-in-one `<ChatMain />` for a complete chat app in minutes, drop in
10
+ `<ChatLayout />` for a composable shell with section toggles, or build your own
11
+ layout from the individual pieces (`<Chat>`, `<ChannelList>`, `<MessageList>`, …).
12
12
 
13
13
  ---
14
14
 
@@ -18,13 +18,19 @@ control over layout and styling.
18
18
  - [Requirements](#requirements)
19
19
  - [Installation](#installation)
20
20
  - [Tailwind CSS v4 setup (required)](#tailwind-css-v4-setup-required)
21
- - [Quick start — full UI](#quick-start--full-ui)
22
- - [Composable usage (recommended)](#composable-usage-recommended)
21
+ - [Configuration](#configuration)
22
+ - [Quick start — full UI (`ChatMain`)](#quick-start--full-ui-chatmain)
23
+ - [Composable shell (`ChatLayout`)](#composable-shell-chatlayout)
24
+ - [Custom layout (advanced)](#custom-layout-advanced)
25
+ - [React (Vite) — complete setup](#react-vite--complete-setup)
26
+ - [Next.js (App Router) — complete setup](#nextjs-app-router--complete-setup)
23
27
  - [How it works](#how-it-works)
24
28
  - [`Chat` provider props](#chat-provider-props)
29
+ - [Layout customization](#layout-customization)
25
30
  - [Exported components & hooks](#exported-components--hooks)
26
31
  - [Theming](#theming)
27
32
  - [Troubleshooting](#troubleshooting)
33
+ - [Checklist](#checklist)
28
34
  - [License](#license)
29
35
 
30
36
  ---
@@ -36,29 +42,32 @@ control over layout and styling.
36
42
  - 😀 Reactions, replies, forwarding, pinned, starred & saved messages
37
43
  - 🔎 Conversation + message search, attachments, member/admin management
38
44
  - 🎨 **Themeable** via a single `themeColor` prop and CSS variables (light/dark)
39
- - 🧩 **Two integration modes** — all-in-one component or composable building blocks
45
+ - 🧩 **Three integration modes** — all-in-one, composable shell, or fully custom
40
46
  - 🦾 Ships TypeScript types and ESM + CJS builds
47
+ - 📦 Published on the **public npm registry** — no private registry or auth token needed
41
48
 
42
49
  ---
43
50
 
44
51
  ## Requirements
45
52
 
46
- This package relies on the following **peer dependencies** in your app:
47
-
48
- | Peer dependency | Version |
49
- | --------------- | ------- |
50
- | `react` | ≥ 17 (19 recommended) |
51
- | `react-dom` | ≥ 17 |
52
- | `zustand` | 4 |
53
- | `tailwindcss` | **v4** (required) |
53
+ | Requirement | Version |
54
+ | ----------- | ------- |
55
+ | Node.js | 18+ |
56
+ | React | 17 (19 recommended) |
57
+ | `react-dom` | ≥ 17 |
58
+ | `zustand` | ≥ 4 |
59
+ | `tailwindcss` | **v4** (required) |
60
+ | Next.js | 13 App Router (optional — for Next.js projects) |
54
61
 
55
62
  You also need, at runtime:
56
63
 
57
- - **Client ID** your RealtimeX client/app id
58
- - **Access token** JWT for the logged-in user
59
- - **Logged user details** `{ _id, name, email }`
60
- - **API URL** e.g. `https://your-server.com/api/v1`
61
- - **Socket URL** e.g. `https://your-server.com`
64
+ | Field | Description |
65
+ | ----- | ----------- |
66
+ | **Client ID** | Your RealtimeX client / app id |
67
+ | **Access token** | JWT for the logged-in user |
68
+ | **Logged user details** | `{ _id, name, email }` |
69
+ | **API URL** | REST base ending with `/api/v1`, e.g. `https://your-server.com/api/v1` |
70
+ | **Socket URL** | Base host only (no `/api/v1`), e.g. `https://your-server.com` |
62
71
 
63
72
  ---
64
73
 
@@ -70,200 +79,860 @@ npm install @realtimexsco/live-chat
70
79
  npm install react react-dom zustand tailwindcss @tailwindcss/vite
71
80
  ```
72
81
 
73
- > Published on the public npm registry — no `.npmrc` or auth token needed.
82
+ Or with pnpm:
83
+
84
+ ```bash
85
+ pnpm add @realtimexsco/live-chat react react-dom zustand tailwindcss @tailwindcss/vite
86
+ ```
87
+
88
+ > Published on the public npm registry — **no `.npmrc` or auth token needed**.
89
+
90
+ Verify the installed version:
91
+
92
+ ```bash
93
+ cat node_modules/@realtimexsco/live-chat/package.json | grep version
94
+ ```
74
95
 
75
96
  ---
76
97
 
77
98
  ## Tailwind CSS v4 setup (required)
78
99
 
79
- The package ships Tailwind utility classes, so your app must (1) import the
80
- package stylesheet and (2) scan the built package output so Tailwind generates
81
- the classes it uses.
100
+ The package ships Tailwind utility classes, so your app must:
82
101
 
83
- In your global stylesheet (e.g. `src/index.css`):
102
+ 1. Import the package stylesheet
103
+ 2. Scan the built package output so Tailwind generates the classes it uses
104
+ 3. Define `--chat-theme*` CSS variables
105
+
106
+ ### Vite — `src/index.css`
107
+
108
+ > From `src/index.css`, `@source` uses `../node_modules`.
84
109
 
85
110
  ```css
86
111
  @import "tailwindcss";
87
112
  @import "@realtimexsco/live-chat/styles.css";
88
113
 
89
- /* Scan the package dist so Tailwind emits the classes it uses */
90
114
  @source "../node_modules/@realtimexsco/live-chat/dist/index.mjs";
91
115
  @source "../node_modules/@realtimexsco/live-chat/dist/index.cjs";
116
+ @source "../**/*.{js,jsx,ts,tsx}";
117
+
118
+ .chat-page h1,
119
+ .chat-container-root h1 {
120
+ @apply text-[15px] font-semibold leading-snug text-gray-900;
121
+ }
92
122
 
93
123
  :root {
94
124
  --radius: 0.625rem;
125
+ --background: oklch(1 0 0);
126
+ --foreground: oklch(0.145 0 0);
127
+ --primary: oklch(0.205 0 0);
128
+ --primary-foreground: oklch(0.985 0 0);
129
+ --muted: oklch(0.97 0 0);
130
+ --muted-foreground: oklch(0.556 0 0);
131
+ --border: oklch(0.922 0 0);
132
+ --ring: oklch(0.708 0 0);
133
+ --destructive: oklch(0.577 0.245 27.325);
134
+ --card: oklch(1 0 0);
135
+ --card-foreground: oklch(0.145 0 0);
136
+ --popover: oklch(1 0 0);
137
+ --popover-foreground: oklch(0.145 0 0);
138
+ --accent: oklch(0.97 0 0);
139
+ --accent-foreground: oklch(0.205 0 0);
140
+ --secondary: oklch(0.97 0 0);
141
+ --secondary-foreground: oklch(0.205 0 0);
142
+ --input: oklch(0.922 0 0);
95
143
 
96
144
  /* Chat theme — overridden at runtime by the Chat `themeColor` prop */
97
- --chat-theme: #3b82f6;
98
- --chat-theme-5: #3b82f60d;
99
- --chat-theme-10: #3b82f61a;
100
- --chat-theme-20: #3b82f633;
101
- --chat-theme-40: #3b82f666;
102
- --chat-theme-60: #3b82f699;
145
+ --chat-theme: #6366f1;
146
+ --chat-theme-5: #6366f10D;
147
+ --chat-theme-10: #6366f11A;
148
+ --chat-theme-20: #6366f133;
149
+ --chat-theme-40: #6366f166;
150
+ --chat-theme-60: #6366f199;
151
+ }
152
+
153
+ @theme inline {
154
+ --color-background: var(--background);
155
+ --color-foreground: var(--foreground);
156
+ --color-primary: var(--primary);
157
+ --color-primary-foreground: var(--primary-foreground);
158
+ --color-muted: var(--muted);
159
+ --color-muted-foreground: var(--muted-foreground);
160
+ --color-border: var(--border);
161
+ --color-ring: var(--ring);
162
+ --color-destructive: var(--destructive);
163
+ --color-card: var(--card);
164
+ --color-card-foreground: var(--card-foreground);
165
+ --color-popover: var(--popover);
166
+ --color-popover-foreground: var(--popover-foreground);
167
+ --color-accent: var(--accent);
168
+ --color-accent-foreground: var(--accent-foreground);
169
+ --color-secondary: var(--secondary);
170
+ --color-secondary-foreground: var(--secondary-foreground);
171
+ --color-input: var(--input);
172
+ --color-chat-theme: var(--chat-theme);
173
+ --color-chat-theme-5: var(--chat-theme-5);
174
+ --color-chat-theme-10: var(--chat-theme-10);
175
+ --color-chat-theme-20: var(--chat-theme-20);
176
+ --color-chat-theme-40: var(--chat-theme-40);
177
+ --color-chat-theme-60: var(--chat-theme-60);
178
+ }
179
+
180
+ @layer base {
181
+ html,
182
+ body,
183
+ #root {
184
+ height: 100%;
185
+ }
186
+
187
+ body {
188
+ @apply bg-background text-foreground;
189
+ }
103
190
  }
104
191
  ```
105
192
 
106
- If you use **Vite**, dedupe React/Zustand and pre-bundle the package:
193
+ ### Next.js `src/app/globals.css`
194
+
195
+ > **Critical:** `globals.css` is in `src/app/`, so `@source` must use `../../node_modules` (NOT `../node_modules`).
196
+
197
+ ```css
198
+ @import "tailwindcss";
199
+ @import "@realtimexsco/live-chat/styles.css";
200
+
201
+ @source "../../node_modules/@realtimexsco/live-chat/dist/index.mjs";
202
+ @source "../../node_modules/@realtimexsco/live-chat/dist/index.cjs";
203
+
204
+ /* …same :root, @theme inline, and @layer base blocks as above… */
205
+ ```
206
+
207
+ ### `@source` path reference
208
+
209
+ | CSS file location | Correct `@source` |
210
+ | ----------------- | ----------------- |
211
+ | `src/index.css` | `../node_modules/...` |
212
+ | `src/app/globals.css` | `../../node_modules/...` |
213
+
214
+ Wrong path → Tailwind skips package classes → **old / broken UI**.
215
+
216
+ ### Vite config
107
217
 
108
218
  ```ts
109
219
  // vite.config.ts
220
+ import path from "path";
221
+ import { fileURLToPath } from "url";
110
222
  import { defineConfig } from "vite";
111
223
  import react from "@vitejs/plugin-react";
112
224
  import tailwindcss from "@tailwindcss/vite";
113
225
 
226
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
227
+
114
228
  export default defineConfig({
115
229
  plugins: [react(), tailwindcss()],
116
- resolve: { dedupe: ["react", "react-dom", "zustand"] },
117
- optimizeDeps: { include: ["@realtimexsco/live-chat", "zustand"] },
230
+ resolve: {
231
+ alias: { "@": path.resolve(__dirname, "src") },
232
+ dedupe: ["react", "react-dom", "zustand", "socket.io-client", "@realtimexsco/live-chat"],
233
+ },
234
+ optimizeDeps: {
235
+ // Required: stale .vite/deps cache shows OLD UI after package upgrade
236
+ exclude: ["@realtimexsco/live-chat"],
237
+ include: ["zustand", "socket.io-client"],
238
+ },
239
+ server: {
240
+ proxy: {
241
+ "/chat-api": {
242
+ target: "http://YOUR_API_HOST",
243
+ changeOrigin: true,
244
+ rewrite: (p) => p.replace(/^\/chat-api/, "/api/v1"),
245
+ },
246
+ },
247
+ },
118
248
  });
119
249
  ```
120
250
 
251
+ ### Next.js config
252
+
253
+ ```ts
254
+ // next.config.ts
255
+ import type { NextConfig } from "next";
256
+
257
+ const nextConfig: NextConfig = {
258
+ transpilePackages: ["@realtimexsco/live-chat"],
259
+ async rewrites() {
260
+ return [
261
+ {
262
+ source: "/chat-api/:path*",
263
+ destination: "http://YOUR_API_HOST/api/v1/:path*",
264
+ },
265
+ ];
266
+ },
267
+ };
268
+
269
+ export default nextConfig;
270
+ ```
271
+
272
+ `transpilePackages` is required so Next.js compiles the chat package correctly.
273
+
274
+ ### PostCSS (Next.js)
275
+
276
+ ```js
277
+ // postcss.config.mjs
278
+ const config = {
279
+ plugins: { "@tailwindcss/postcss": {} },
280
+ };
281
+ export default config;
282
+ ```
283
+
284
+ ---
285
+
286
+ ## Configuration
287
+
288
+ Create a shared config file and replace `YOUR_*` placeholders with real credentials.
289
+
290
+ **TypeScript** — `src/configs/chat.config.ts`:
291
+
292
+ ```ts
293
+ export const chatConfig = {
294
+ clientId: "YOUR_CLIENT_ID",
295
+ accessToken: "YOUR_JWT_ACCESS_TOKEN",
296
+ loggedUserDetails: {
297
+ _id: "YOUR_USER_ID",
298
+ name: "Your Name",
299
+ email: "you@example.com",
300
+ },
301
+ apiUrl: "http://YOUR_API_HOST/api/v1",
302
+ socketUrl: "http://YOUR_API_HOST",
303
+ themeColor: "#6366f1",
304
+ } as const;
305
+ ```
306
+
307
+ **JavaScript** — `src/configs/chat.config.js`:
308
+
309
+ ```js
310
+ export const chatConfig = {
311
+ clientId: "YOUR_CLIENT_ID",
312
+ accessToken: "YOUR_JWT_ACCESS_TOKEN",
313
+ loggedUserDetails: {
314
+ _id: "YOUR_USER_ID",
315
+ name: "Your Name",
316
+ email: "you@example.com",
317
+ },
318
+ apiUrl: "http://YOUR_API_HOST/api/v1",
319
+ socketUrl: "http://YOUR_API_HOST",
320
+ themeColor: "#6366f1",
321
+ };
322
+ ```
323
+
324
+ | Field | Description |
325
+ | ----- | ----------- |
326
+ | `clientId` | Chat client ID from backend |
327
+ | `accessToken` | JWT token for authenticated user |
328
+ | `loggedUserDetails._id` | Current user MongoDB / API id |
329
+ | `apiUrl` | REST API base URL ending with `/api/v1` |
330
+ | `socketUrl` | Socket server base URL (no `/api/v1`) |
331
+ | `themeColor` | Primary chat color (hex) |
332
+
121
333
  ---
122
334
 
123
- ## Quick start — full UI
335
+ ## Quick start — full UI (`ChatMain`)
124
336
 
125
- The fastest path. `ChatMain` renders the sidebar, chat area, and modals in one
126
- component.
337
+ The fastest path. `ChatMain` renders the sidebar, chat area, and modals in one component.
127
338
 
128
339
  ```tsx
129
340
  import ChatMain from "@realtimexsco/live-chat";
341
+ import { chatConfig } from "@/configs/chat.config";
342
+
343
+ export default function App() {
344
+ return (
345
+ <div className="chat-page h-dvh min-h-0 overflow-hidden">
346
+ <ChatMain
347
+ clientId={chatConfig.clientId}
348
+ accessToken={chatConfig.accessToken}
349
+ loggedUserDetails={chatConfig.loggedUserDetails}
350
+ apiUrl={chatConfig.apiUrl}
351
+ socketUrl={chatConfig.socketUrl}
352
+ themeColor={chatConfig.themeColor}
353
+ />
354
+ </div>
355
+ );
356
+ }
357
+ ```
358
+
359
+ Hide sections via the `layout` prop:
360
+
361
+ ```tsx
362
+ <ChatMain
363
+ {...chatConfig}
364
+ clientId={chatConfig.clientId}
365
+ layout={{ input: false }} // read-only chat
366
+ />
367
+ ```
368
+
369
+ ---
370
+
371
+ ## Composable shell (`ChatLayout`)
372
+
373
+ Recommended when you want control over the page wrapper but not every internal
374
+ detail. `ChatLayout` is shipped inside the package — no need to copy a large
375
+ layout file.
376
+
377
+ ```tsx
378
+ import { Chat, ChatLayout } from "@realtimexsco/live-chat";
379
+ import { chatConfig } from "@/configs/chat.config";
380
+ import { chatLayoutConfig } from "@/configs/chat.layout.config";
130
381
 
131
382
  export default function App() {
132
383
  return (
133
- <ChatMain
134
- clientId="YOUR_CLIENT_ID"
135
- accessToken="YOUR_JWT_TOKEN"
136
- loggedUserDetails={{
137
- _id: "USER_ID",
138
- name: "John Doe",
139
- email: "john@example.com",
140
- }}
141
- apiUrl="https://your-server.com/api/v1"
142
- socketUrl="https://your-server.com"
143
- themeColor="#3b82f6"
144
- />
384
+ <div className="chat-page h-dvh min-h-0 overflow-hidden">
385
+ <Chat
386
+ client={{ id: chatConfig.clientId }}
387
+ accessToken={chatConfig.accessToken}
388
+ loggedUserDetails={chatConfig.loggedUserDetails}
389
+ apiUrl={chatConfig.apiUrl}
390
+ socketUrl={chatConfig.socketUrl}
391
+ themeColor={chatConfig.themeColor}
392
+ >
393
+ <ChatLayout layout={chatLayoutConfig} themeColor={chatConfig.themeColor} />
394
+ </Chat>
395
+ </div>
145
396
  );
146
397
  }
147
398
  ```
148
399
 
400
+ **Layout config** — `src/configs/chat.layout.config.ts`:
401
+
402
+ ```ts
403
+ import type { ChatLayoutConfig } from "@realtimexsco/live-chat";
404
+
405
+ export const chatLayoutConfig: ChatLayoutConfig = {
406
+ sidebar: true,
407
+ loggedUserDetails: true,
408
+ channelList: true,
409
+ header: true,
410
+ messageList: true,
411
+ input: true,
412
+ forwardModal: true,
413
+ };
414
+ ```
415
+
416
+ Or re-export the built-in helpers:
417
+
418
+ ```ts
419
+ import {
420
+ defaultChatLayoutConfig,
421
+ mergeChatLayoutConfig,
422
+ type ChatLayoutConfig,
423
+ } from "@realtimexsco/live-chat";
424
+
425
+ export const chatLayoutConfig = defaultChatLayoutConfig;
426
+
427
+ export function mergeLayout(layout: ChatLayoutConfig) {
428
+ return mergeChatLayoutConfig(layout);
429
+ }
430
+ ```
431
+
149
432
  ---
150
433
 
151
- ## Composable usage (recommended)
434
+ ## Custom layout (advanced)
435
+
436
+ For full control, compose the individual pieces. Wrap the active chat panel in
437
+ `<Channel>` — it resolves the conversation id, fetches messages, joins the socket
438
+ room, and marks messages read. **Do not** manually emit `group_join` /
439
+ `group_message_read` yourself.
440
+
441
+ Use `useChatStore` selectors and pass explicit props to each component (the
442
+ package does not auto-wire props when used outside `ChatLayout`).
152
443
 
153
- Build your own layout from the exported pieces for full control over the
154
- sidebar, routing, and styling.
444
+ **TypeScript** `src/components/chat/ModularChatLayout.tsx`:
155
445
 
156
446
  ```tsx
157
- import { useState, useCallback } from "react";
447
+ "use client"; // required in Next.js
448
+
449
+ import { useCallback, useEffect, useState } from "react";
158
450
  import {
159
- Chat,
160
451
  Channel,
161
- ChannelList,
162
452
  ChannelHeader,
163
- MessageList,
164
- MessageInput,
165
- LoggedUserDetails,
453
+ ChannelList,
166
454
  ForwardModal,
167
- useChatContext,
455
+ LoggedUserDetails,
456
+ MessageInput,
457
+ MessageList,
168
458
  useChatStore,
459
+ mergeChatLayoutConfig,
460
+ type ChatLayoutConfig,
169
461
  } from "@realtimexsco/live-chat";
462
+ import classNames from "classnames";
463
+
464
+ type ModularChatLayoutProps = {
465
+ layout?: ChatLayoutConfig;
466
+ };
467
+
468
+ const ModularChatLayout = ({ layout }: ModularChatLayoutProps) => {
469
+ const sections = mergeChatLayoutConfig(layout);
470
+ const showSidebar =
471
+ sections.sidebar && (sections.loggedUserDetails || sections.channelList);
472
+
473
+ const activeChannel = useChatStore((s) => s.activeChannel);
474
+ const setActiveChannel = useChatStore((s) => s.setActiveChannel);
475
+ const conversationId = useChatStore((s) => s.activeConversationId);
476
+ const rateLimitResetAt = useChatStore((s) => s.rateLimitResetAt);
477
+ const isFetchingMessages = useChatStore((s) => s.isFetchingMessages);
478
+ const loggedUserDetails = useChatStore((s) => s.loggedUserDetails);
479
+ const conversationInfos = useChatStore((s) => s.conversationInfos);
480
+ const messagesByConversation = useChatStore((s) => s.messagesByConversation);
481
+ const emitDmSend = useChatStore((s) => s.emitDmSend);
482
+ const emitGroupMessageSend = useChatStore((s) => s.emitGroupMessageSend);
483
+ const fetchAllUsers = useChatStore((s) => s.fetchAllUsers);
484
+ const isSessionReady = useChatStore((s) => s.isSessionReady);
485
+ const accessToken = useChatStore((s) => s.accessToken);
486
+
487
+ const conversationMessages = conversationId
488
+ ? messagesByConversation[conversationId] || []
489
+ : [];
490
+
491
+ const [searchQuery, setSearchQuery] = useState("");
492
+ const [debouncedSearch, setDebouncedSearch] = useState("");
493
+ const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
494
+ const [replyTo, setReplyTo] = useState<Parameters<typeof MessageInput>[0]["replyTo"]>(null);
495
+ const [forwardMessage, setForwardMessage] = useState<Parameters<typeof ForwardModal>[0]["message"]>(null);
496
+ const [isForwardModalOpen, setIsForwardModalOpen] = useState(false);
497
+
498
+ useEffect(() => {
499
+ const timer = setTimeout(() => setDebouncedSearch(searchQuery), 400);
500
+ return () => clearTimeout(timer);
501
+ }, [searchQuery]);
502
+
503
+ useEffect(() => {
504
+ if (isSessionReady && accessToken) fetchAllUsers(true);
505
+ }, [isSessionReady, accessToken, fetchAllUsers]);
506
+
507
+ useEffect(() => {
508
+ if (isCreateModalOpen && isSessionReady && accessToken) fetchAllUsers(true);
509
+ }, [isCreateModalOpen, isSessionReady, accessToken, fetchAllUsers]);
510
+
511
+ const handleChannelSelect = useCallback(
512
+ (channel: Parameters<typeof setActiveChannel>[0]) => {
513
+ setActiveChannel(channel);
514
+ setReplyTo(null);
515
+ },
516
+ [setActiveChannel],
517
+ );
170
518
 
171
- function MainChatArea() {
172
- const { activeChannel } = useChatContext();
173
- const { setActiveChannel, error, status } = useChatStore();
174
-
175
- const [search, setSearch] = useState("");
176
- const [isMenuOpen, setIsMenuOpen] = useState(false);
177
- const [replyToMessage, setReplyToMessage] = useState<any>(null);
178
- const [forwardMessage, setForwardMessage] = useState<any>(null);
179
-
180
- // ⚠️ Declare all hooks BEFORE any conditional return (see Troubleshooting)
181
- const handleUserSelect = useCallback(
182
- (channel: any) => {
519
+ const handleForwardComplete = useCallback(
520
+ (channel: Parameters<typeof setActiveChannel>[0]) => {
183
521
  setActiveChannel(channel);
184
- setIsMenuOpen(false);
185
- setReplyToMessage(null);
522
+ setIsForwardModalOpen(false);
186
523
  setForwardMessage(null);
187
524
  },
188
- [setActiveChannel]
525
+ [setActiveChannel],
189
526
  );
190
527
 
191
- if (error) return <div>Connection error: {error}</div>;
192
- if (status === "connecting" || status === "idle") return <div>Loading chat…</div>;
528
+ const handleSendMessage = useCallback(
529
+ (
530
+ content: string,
531
+ attachments: Parameters<NonNullable<Parameters<typeof MessageInput>[0]["onSendMessage"]>>[1],
532
+ replyToMessage?: Parameters<typeof MessageInput>[0]["replyTo"],
533
+ ) => {
534
+ if (!activeChannel || !loggedUserDetails) return;
535
+
536
+ const isGroup = !!activeChannel.isGroup;
537
+ const dmReceiverId = isGroup ? undefined : String(activeChannel.id);
538
+
539
+ const payload = {
540
+ tempId: `temp-${Date.now()}`,
541
+ sender: loggedUserDetails._id,
542
+ receiver: dmReceiverId,
543
+ content,
544
+ type: "text",
545
+ conversationId,
546
+ replyTo: replyToMessage
547
+ ? {
548
+ _id: replyToMessage._id || replyToMessage.id,
549
+ content: replyToMessage.content,
550
+ attachments: replyToMessage.attachments || [],
551
+ sender: {
552
+ _id: (replyToMessage.sender as { _id?: string })?._id || replyToMessage.sender,
553
+ name: (replyToMessage.sender as { name?: string })?.name || "",
554
+ },
555
+ }
556
+ : undefined,
557
+ };
558
+
559
+ if (attachments.length === 0) {
560
+ if (isGroup) emitGroupMessageSend(payload);
561
+ else emitDmSend(payload);
562
+ }
563
+
564
+ setReplyTo(null);
565
+ },
566
+ [activeChannel, conversationId, emitDmSend, emitGroupMessageSend, loggedUserDetails],
567
+ );
193
568
 
194
569
  return (
195
- <div className="flex h-screen w-screen overflow-hidden">
196
- <aside className="w-[400px] border-r flex flex-col">
197
- <LoggedUserDetails
198
- searchQuery={search}
199
- setSearchQuery={setSearch}
200
- isCreateModalOpen={isMenuOpen}
201
- setIsCreateModalOpen={setIsMenuOpen}
202
- onUserSelect={handleUserSelect}
203
- />
204
- <ChannelList debouncedSearch={search} onNewChatClick={() => setIsMenuOpen(true)} />
205
- </aside>
570
+ <div className="chat-shell chat-container-root flex h-full w-full overflow-hidden bg-[#f4f5f7] p-3 md:p-4">
571
+ <div className="relative flex flex-1 overflow-hidden rounded-2xl border border-black/[0.06] bg-white shadow-[0_8px_30px_rgba(15,23,42,0.06)]">
572
+ {showSidebar && (
573
+ <aside
574
+ className={classNames(
575
+ "flex h-full shrink-0 flex-col border-r border-black/[0.06] bg-white transition-all duration-300 ease-in-out",
576
+ "w-full md:w-[340px] lg:w-[360px] xl:w-[380px]",
577
+ activeChannel ? "hidden md:flex" : "flex",
578
+ )}
579
+ >
580
+ {sections.loggedUserDetails && (
581
+ <LoggedUserDetails
582
+ onUserSelect={handleChannelSelect}
583
+ searchQuery={searchQuery}
584
+ setSearchQuery={setSearchQuery}
585
+ isCreateModalOpen={isCreateModalOpen}
586
+ setIsCreateModalOpen={setIsCreateModalOpen}
587
+ />
588
+ )}
589
+ {sections.channelList && (
590
+ <ChannelList
591
+ activeChannel={activeChannel}
592
+ onChannelSelect={handleChannelSelect}
593
+ onNewChatClick={() => setIsCreateModalOpen(true)}
594
+ debouncedSearch={debouncedSearch}
595
+ />
596
+ )}
597
+ </aside>
598
+ )}
206
599
 
207
- <main className="flex flex-col flex-1 min-w-0">
208
- {/* Channel resolves the conversation, fetches messages, and joins the socket room */}
209
600
  <Channel>
210
- {activeChannel ? (
211
- <>
212
- <ChannelHeader />
213
- <div className="flex-1 min-h-0">
214
- <MessageList
215
- onReply={(msg) => setReplyToMessage(msg)}
216
- onForward={(msg) => setForwardMessage(msg)}
217
- />
218
- </div>
601
+ {rateLimitResetAt > Date.now() && (
602
+ <div className="shrink-0 border-b border-amber-200/80 bg-amber-50 px-4 py-2 text-center text-xs font-medium text-amber-800">
603
+ Too many requests — please wait before trying again.
604
+ </div>
605
+ )}
606
+
607
+ {sections.header && (
608
+ <div className="shrink-0">
609
+ <ChannelHeader
610
+ activeChannel={activeChannel}
611
+ conversationId={conversationId}
612
+ onBack={() => setActiveChannel(null)}
613
+ />
614
+ </div>
615
+ )}
616
+
617
+ {sections.messageList && (
618
+ <div className="relative min-h-0 flex-1 overflow-hidden">
619
+ <MessageList
620
+ activeChannel={activeChannel}
621
+ loggedUserId={loggedUserDetails?._id}
622
+ conversationMessages={conversationMessages}
623
+ isLoading={isFetchingMessages}
624
+ conversationId={conversationId}
625
+ conversationInfo={
626
+ conversationId ? conversationInfos[conversationId] || null : null
627
+ }
628
+ onReply={setReplyTo}
629
+ onForward={(msg) => {
630
+ setForwardMessage(msg);
631
+ setIsForwardModalOpen(true);
632
+ }}
633
+ />
634
+ </div>
635
+ )}
636
+
637
+ {sections.input && (
638
+ <div className="shrink-0 border-t border-black/[0.06] bg-white">
219
639
  <MessageInput
220
- replyTo={replyToMessage}
221
- onCancelReply={() => setReplyToMessage(null)}
222
- onMessageSent={() => setReplyToMessage(null)}
640
+ activeChannel={activeChannel}
641
+ conversationId={conversationId}
642
+ onSendMessage={handleSendMessage}
643
+ replyTo={replyTo}
644
+ onCancelReply={() => setReplyTo(null)}
223
645
  />
224
- </>
225
- ) : (
226
- <div className="flex h-full items-center justify-center text-gray-400">
227
- Select a conversation
228
646
  </div>
229
647
  )}
230
648
  </Channel>
231
- </main>
649
+ </div>
232
650
 
233
- {forwardMessage && (
651
+ {sections.forwardModal && (
234
652
  <ForwardModal
235
- isOpen
236
- onClose={() => setForwardMessage(null)}
653
+ isOpen={isForwardModalOpen}
654
+ onClose={() => {
655
+ setIsForwardModalOpen(false);
656
+ setForwardMessage(null);
657
+ }}
237
658
  message={forwardMessage}
238
- onForwardComplete={(channel) => setActiveChannel(channel)}
659
+ onForwardComplete={handleForwardComplete}
239
660
  />
240
661
  )}
241
662
  </div>
242
663
  );
243
- }
664
+ };
665
+
666
+ export default ModularChatLayout;
667
+ ```
668
+
669
+ Wrap with `<Chat>`:
670
+
671
+ ```tsx
672
+ import { Chat } from "@realtimexsco/live-chat";
673
+ import ModularChatLayout from "@/components/chat/ModularChatLayout";
674
+ import { chatConfig } from "@/configs/chat.config";
675
+ import { chatLayoutConfig } from "@/configs/chat.layout.config";
244
676
 
245
677
  export default function App() {
246
678
  return (
247
- <Chat
248
- client={{ id: "YOUR_CLIENT_ID" }}
249
- accessToken="YOUR_JWT_TOKEN"
250
- loggedUserDetails={{ _id: "USER_ID", name: "John Doe", email: "john@example.com" }}
251
- apiUrl="https://your-server.com/api/v1"
252
- socketUrl="https://your-server.com"
253
- themeColor="#3b82f6"
254
- onMessageReceived={(msg) => console.log("Background message:", msg)}
255
- onSocketConnected={(id) => console.log("Socket connected:", id)}
256
- onSocketError={(err) => console.error("Socket error:", err)}
257
- >
258
- <MainChatArea />
259
- </Chat>
679
+ <div className="chat-page h-dvh min-h-0 overflow-hidden">
680
+ <Chat
681
+ client={{ id: chatConfig.clientId }}
682
+ accessToken={chatConfig.accessToken}
683
+ loggedUserDetails={chatConfig.loggedUserDetails}
684
+ apiUrl={chatConfig.apiUrl}
685
+ socketUrl={chatConfig.socketUrl}
686
+ themeColor={chatConfig.themeColor}
687
+ onMessageReceived={(msg) => console.log("Background message:", msg)}
688
+ onSocketConnected={(id) => console.log("Socket connected:", id)}
689
+ onSocketError={(err) => console.error("Socket error:", err)}
690
+ >
691
+ <ModularChatLayout layout={chatLayoutConfig} />
692
+ </Chat>
693
+ </div>
694
+ );
695
+ }
696
+ ```
697
+
698
+ > **Important:** Declare all hooks (`useState`, `useCallback`, `useEffect`, …) at
699
+ > the **top** of the component, before any conditional `return`.
700
+
701
+ ---
702
+
703
+ ## React (Vite) — complete setup
704
+
705
+ ### Project structure
706
+
707
+ ```
708
+ my-app/
709
+ ├── package.json
710
+ ├── vite.config.ts
711
+ ├── jsconfig.json # or tsconfig.json
712
+ ├── index.html
713
+ └── src/
714
+ ├── main.jsx
715
+ ├── App.jsx
716
+ ├── index.css
717
+ ├── configs/
718
+ │ ├── chat.config.js
719
+ │ └── chat.layout.config.js
720
+ └── components/
721
+ └── chat/
722
+ └── ModularChatLayout.jsx # optional — or use ChatLayout
723
+ ```
724
+
725
+ ### Create app
726
+
727
+ ```bash
728
+ pnpm create vite my-app --template react
729
+ cd my-app
730
+ ```
731
+
732
+ ### `package.json` dependencies
733
+
734
+ ```json
735
+ {
736
+ "dependencies": {
737
+ "@realtimexsco/live-chat": "^1.0.0",
738
+ "classnames": "^2.5.1",
739
+ "react": "^19.2.7",
740
+ "react-dom": "^19.2.7",
741
+ "zustand": "^5.0.14"
742
+ },
743
+ "devDependencies": {
744
+ "@tailwindcss/vite": "^4.3.1",
745
+ "@vitejs/plugin-react": "^4.7.0",
746
+ "tailwindcss": "^4.3.1",
747
+ "vite": "^6.3.5"
748
+ }
749
+ }
750
+ ```
751
+
752
+ ### `jsconfig.json`
753
+
754
+ ```json
755
+ {
756
+ "compilerOptions": {
757
+ "baseUrl": ".",
758
+ "paths": { "@/*": ["src/*"] }
759
+ },
760
+ "include": ["src"]
761
+ }
762
+ ```
763
+
764
+ ### `src/main.jsx`
765
+
766
+ ```jsx
767
+ import { createRoot } from "react-dom/client";
768
+ import "./index.css";
769
+ import App from "./App.jsx";
770
+
771
+ createRoot(document.getElementById("root")).render(<App />);
772
+ ```
773
+
774
+ ### Run
775
+
776
+ ```bash
777
+ pnpm install
778
+ pnpm dev
779
+ ```
780
+
781
+ Open **http://localhost:3000**
782
+
783
+ Production:
784
+
785
+ ```bash
786
+ pnpm build
787
+ pnpm preview
788
+ ```
789
+
790
+ ### Update package
791
+
792
+ ```bash
793
+ pnpm add @realtimexsco/live-chat@latest
794
+ rm -rf node_modules/.vite dist
795
+ pnpm dev
796
+ ```
797
+
798
+ Hard refresh: **Ctrl + Shift + R**
799
+
800
+ ---
801
+
802
+ ## Next.js (App Router) — complete setup
803
+
804
+ ### Project structure
805
+
806
+ ```
807
+ my-chat-app/
808
+ ├── package.json
809
+ ├── next.config.ts
810
+ ├── postcss.config.mjs
811
+ ├── tsconfig.json
812
+ └── src/
813
+ ├── app/
814
+ │ ├── globals.css
815
+ │ ├── layout.tsx
816
+ │ ├── page.tsx
817
+ │ └── chat/
818
+ │ └── page.tsx
819
+ ├── configs/
820
+ │ ├── chat.config.ts
821
+ │ └── chat.layout.config.ts
822
+ └── components/
823
+ └── chat/
824
+ └── ModularChatLayout.tsx # optional — or use ChatLayout
825
+ ```
826
+
827
+ ### Create app
828
+
829
+ ```bash
830
+ pnpm create next-app@latest my-chat-app --typescript --tailwind --app --src-dir
831
+ cd my-chat-app
832
+ ```
833
+
834
+ ### `package.json` dependencies
835
+
836
+ ```json
837
+ {
838
+ "dependencies": {
839
+ "@realtimexsco/live-chat": "^1.0.0",
840
+ "classnames": "^2.5.1",
841
+ "next": "16.2.9",
842
+ "react": "19.2.4",
843
+ "react-dom": "19.2.4",
844
+ "zustand": "^5.0.14"
845
+ },
846
+ "devDependencies": {
847
+ "@tailwindcss/postcss": "^4",
848
+ "tailwindcss": "^4",
849
+ "typescript": "^5"
850
+ }
851
+ }
852
+ ```
853
+
854
+ ### `tsconfig.json` path alias
855
+
856
+ ```json
857
+ {
858
+ "compilerOptions": {
859
+ "paths": { "@/*": ["./src/*"] }
860
+ }
861
+ }
862
+ ```
863
+
864
+ ### `src/app/layout.tsx`
865
+
866
+ ```tsx
867
+ import type { Metadata } from "next";
868
+ import "./globals.css";
869
+
870
+ export const metadata: Metadata = {
871
+ title: "RealtimeX Chat",
872
+ description: "RealtimeX chat integration with Next.js",
873
+ };
874
+
875
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
876
+ return (
877
+ <html lang="en" className="h-full antialiased">
878
+ <body className="min-h-full flex flex-col">{children}</body>
879
+ </html>
880
+ );
881
+ }
882
+ ```
883
+
884
+ ### `src/app/chat/page.tsx`
885
+
886
+ ```tsx
887
+ "use client";
888
+
889
+ import { Chat, ChatLayout } from "@realtimexsco/live-chat";
890
+ import { chatConfig } from "@/configs/chat.config";
891
+ import { chatLayoutConfig } from "@/configs/chat.layout.config";
892
+
893
+ export default function ChatPage() {
894
+ return (
895
+ <div className="chat-page h-dvh min-h-0 overflow-hidden">
896
+ <Chat
897
+ client={{ id: chatConfig.clientId }}
898
+ accessToken={chatConfig.accessToken}
899
+ loggedUserDetails={chatConfig.loggedUserDetails}
900
+ apiUrl={chatConfig.apiUrl}
901
+ socketUrl={chatConfig.socketUrl}
902
+ themeColor={chatConfig.themeColor}
903
+ >
904
+ <ChatLayout layout={chatLayoutConfig} themeColor={chatConfig.themeColor} />
905
+ </Chat>
906
+ </div>
260
907
  );
261
908
  }
262
909
  ```
263
910
 
264
- > **Important:** Wrap the active chat panel in `<Channel>`. It resolves the
265
- > conversation id, fetches messages, and emits the correct group/DM socket
266
- > events. Don't manually emit `group_join` / `group_message_read` yourself.
911
+ > `'use client'` is required — the chat package uses React hooks and Zustand.
912
+
913
+ ### Run
914
+
915
+ ```bash
916
+ pnpm install
917
+ pnpm dev
918
+ ```
919
+
920
+ Open **http://localhost:3000/chat**
921
+
922
+ Clear cache after CSS or package changes:
923
+
924
+ ```bash
925
+ rm -rf .next
926
+ pnpm dev
927
+ ```
928
+
929
+ ### Update package
930
+
931
+ ```bash
932
+ pnpm add @realtimexsco/live-chat@latest
933
+ rm -rf .next
934
+ pnpm dev
935
+ ```
267
936
 
268
937
  ---
269
938
 
@@ -335,64 +1004,182 @@ export default function App() {
335
1004
 
336
1005
  ---
337
1006
 
1007
+ ## Layout customization
1008
+
1009
+ Edit your layout config or pass overrides inline:
1010
+
1011
+ ```ts
1012
+ export const chatLayoutConfig = {
1013
+ sidebar: true, // left panel wrapper
1014
+ loggedUserDetails: true, // user profile + new chat button
1015
+ channelList: true, // conversation list ("Messages")
1016
+ header: true, // channel header (name, calls, menu)
1017
+ messageList: true, // message bubbles area
1018
+ input: true, // message composer
1019
+ forwardModal: true, // forward message dialog
1020
+ };
1021
+ ```
1022
+
1023
+ Examples:
1024
+
1025
+ ```ts
1026
+ { input: false } // read-only chat (no composer)
1027
+ { sidebar: false } // full-width messages (hide sidebar)
1028
+ { forwardModal: false } // hide forward modal
1029
+ ```
1030
+
1031
+ Pass to `ChatMain`, `ChatLayout`, or `ModularChatLayout`:
1032
+
1033
+ ```tsx
1034
+ <ChatLayout layout={{ sidebar: true, input: false }} />
1035
+ ```
1036
+
1037
+ ---
1038
+
338
1039
  ## Exported components & hooks
339
1040
 
340
1041
  | Export | Description |
341
1042
  | ------ | ----------- |
342
- | `ChatMain` | All-in-one chat UI (default export) |
1043
+ | `ChatMain` (default) | All-in-one chat UI with optional `layout` prop |
343
1044
  | `Chat` | Provider — initializes auth, API, socket, store |
1045
+ | `ChatLayout` | Composable shell — toggle sections via `layout` prop |
344
1046
  | `Channel` | Active conversation lifecycle (fetch, join, mark-read) |
345
1047
  | `ChannelList` | Conversation sidebar list |
346
1048
  | `ChannelHeader` | Chat header with search, details, actions |
347
- | `MessageList` | Message thread |
348
- | `MessageInput` | Compose box |
1049
+ | `MessageList` | Message thread (`ActiveChannelMessages`) |
1050
+ | `MessageInput` | Compose box (`ChannelMessageBox`) |
349
1051
  | `LoggedUserDetails` | User profile + new chat / create group |
350
1052
  | `ForwardModal` | Forward message modal |
351
1053
  | `useChatContext` | `{ activeChannel, setActiveChannel, themeColor, uiConfig }` |
352
1054
  | `useChatStore` | Full Zustand store (messages, socket actions, status, error) |
1055
+ | `useChatController` | State + handlers used internally by `ChatLayout` |
1056
+ | `ChatLayoutConfig` | Type for layout section toggles |
1057
+ | `defaultChatLayoutConfig` | Default layout (all sections enabled) |
1058
+ | `mergeChatLayoutConfig` | Merge partial layout with defaults |
353
1059
  | `socketService` | Low-level socket service |
1060
+ | `getSocketConfig` | Socket configuration helper |
354
1061
  | `setChatClientId`, `setChatAccessToken`, `setChatBaseURL` | Manual API config |
1062
+ | `IChannel`, `IMessage`, `IAttachment` | Core TypeScript types |
1063
+ | `ISocketConfig` | Socket config type |
1064
+
1065
+ ### Import example
1066
+
1067
+ ```ts
1068
+ import {
1069
+ Chat,
1070
+ ChatMain,
1071
+ ChatLayout,
1072
+ Channel,
1073
+ ChannelList,
1074
+ ChannelHeader,
1075
+ MessageList,
1076
+ MessageInput,
1077
+ LoggedUserDetails,
1078
+ ForwardModal,
1079
+ useChatStore,
1080
+ useChatController,
1081
+ getSocketConfig,
1082
+ type ChatLayoutConfig,
1083
+ } from "@realtimexsco/live-chat";
1084
+ ```
355
1085
 
356
1086
  ---
357
1087
 
358
1088
  ## Theming
359
1089
 
360
1090
  Set a single accent color via the `themeColor` prop; it drives the
361
- `--chat-theme*` CSS variables. Override any of the variables shown in the
362
- [Tailwind setup](#tailwind-css-v4-setup-required) to customize further, and pass
363
- `theme="dark"` for dark mode.
1091
+ `--chat-theme*` CSS variables at runtime. Override any of the variables in your
1092
+ global CSS to customize further, and pass `theme="dark"` for dark mode.
364
1093
 
365
1094
  ```tsx
366
- <Chat themeColor="#7c3aed" theme="dark" /* … */>{/* … */}</Chat>
1095
+ <Chat themeColor="#7c3aed" theme="dark">{/* … */}</Chat>
367
1096
  ```
368
1097
 
369
1098
  ---
370
1099
 
371
1100
  ## Troubleshooting
372
1101
 
373
- **`Rendered more hooks than during the previous render`**
374
- Declare all hooks (`useState`, `useCallback`, …) at the **top** of the component,
375
- before any conditional `return`. Handle loading/error states after the hooks.
1102
+ ### Old or broken UI
1103
+
1104
+ | Cause | Fix |
1105
+ | ----- | --- |
1106
+ | Wrong `@source` path in CSS | Use `../node_modules` from `src/index.css`, or `../../node_modules` from `src/app/globals.css` |
1107
+ | Missing `styles.css` import | `@import "@realtimexsco/live-chat/styles.css"` |
1108
+ | Missing `--chat-theme` CSS vars | Copy full CSS block from [Tailwind setup](#tailwind-css-v4-setup-required) |
1109
+ | Stale Vite cache | `rm -rf node_modules/.vite && pnpm dev` |
1110
+ | Stale Next.js cache | `rm -rf .next && pnpm dev` |
1111
+ | Package in `optimizeDeps.include` (Vite) | Use `exclude: ["@realtimexsco/live-chat"]` |
1112
+ | Missing `transpilePackages` (Next.js) | Add `transpilePackages: ["@realtimexsco/live-chat"]` in `next.config.ts` |
1113
+
1114
+ ### `Rendered more hooks than during the previous render`
1115
+
1116
+ Declare all hooks at the **top** of the component, before any conditional
1117
+ `return`. Handle loading / error states after the hooks.
376
1118
 
377
- **Styles missing / unstyled components**
378
- 1. Confirm the `@source` paths in your CSS point to the package `dist/` files.
379
- 2. Import `@realtimexsco/live-chat/styles.css`.
380
- 3. Restart your dev server after installing or updating the package.
1119
+ ### Chat loads but messages don't appear
381
1120
 
382
- **Chat loads but messages don't appear**
383
1121
  Ensure `<Channel>` wraps `MessageList` and `MessageInput`. The package keys
384
1122
  messages by `conversationId` internally — don't key DM messages by user id.
385
1123
 
386
- **Group events firing in DM (or vice versa)**
1124
+ ### Group events firing in DM (or vice versa)
1125
+
387
1126
  Always use the composable pattern with `<Channel>`, and don't manually emit
388
1127
  `group_join` / `group_message_read` outside the package.
389
1128
 
390
- **Duplicate React / invalid hook call**
391
- Add `resolve: { dedupe: ["react", "react-dom", "zustand"] }` to your
392
- `vite.config.ts`.
1129
+ ### Duplicate React / invalid hook call
1130
+
1131
+ Add `resolve: { dedupe: ["react", "react-dom", "zustand"] }` to `vite.config.ts`.
1132
+
1133
+ ### `use client` error (Next.js)
1134
+
1135
+ Any file importing `Chat`, `useChatStore`, or chat hooks must start with:
1136
+
1137
+ ```tsx
1138
+ "use client";
1139
+ ```
1140
+
1141
+ ### Socket / API errors
1142
+
1143
+ - `apiUrl` must end with `/api/v1`
1144
+ - `socketUrl` = base host only (no `/api/v1`)
1145
+ - Use a dev proxy (`/chat-api`) or Next.js rewrite if CORS blocks requests
1146
+
1147
+ ### Hydration warnings (Next.js)
1148
+
1149
+ Keep chat inside a client component (`chat/page.tsx`). Do not render
1150
+ socket-dependent UI in server components.
1151
+
1152
+ ---
1153
+
1154
+ ## Checklist
1155
+
1156
+ ### All projects
1157
+
1158
+ - [ ] `@realtimexsco/live-chat` installed from npm
1159
+ - [ ] Peer deps: `react`, `react-dom`, `zustand`, `tailwindcss` v4
1160
+ - [ ] Global CSS imports `tailwindcss` + `@realtimexsco/live-chat/styles.css`
1161
+ - [ ] `@source` paths point to package `dist/index.mjs` and `dist/index.cjs`
1162
+ - [ ] `--chat-theme*` CSS variables defined in `:root`
1163
+ - [ ] `src/configs/chat.config.ts` (or `.js`) — your credentials
1164
+ - [ ] Chat wrapped in `h-dvh min-h-0 overflow-hidden` container
1165
+
1166
+ ### React (Vite)
1167
+
1168
+ - [ ] `vite.config.ts` — `@` alias, dedupe, **exclude** package from `optimizeDeps`
1169
+ - [ ] `jsconfig.json` / `tsconfig.json` — `"@/*": ["src/*"]`
1170
+ - [ ] `src/main.jsx` imports `index.css`
1171
+ - [ ] Clear `node_modules/.vite` after every package upgrade
1172
+
1173
+ ### Next.js
1174
+
1175
+ - [ ] `next.config.ts` — `transpilePackages: ["@realtimexsco/live-chat"]`
1176
+ - [ ] `postcss.config.mjs` — `@tailwindcss/postcss`
1177
+ - [ ] `globals.css` uses **`../../node_modules`** in `@source`
1178
+ - [ ] `src/app/chat/page.tsx` — `'use client'` + `<Chat>` + `<ChatLayout>`
1179
+ - [ ] Clear `.next` after every package upgrade
393
1180
 
394
1181
  ---
395
1182
 
396
1183
  ## License
397
1184
 
398
- [MIT](https://www.npmjs.com/package/@realtimexsco/live-chat) © RealtimeX
1185
+ [MIT](LICENSE) © RealtimeX