@cometchat/skills-cli 2.0.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.
Files changed (95) hide show
  1. package/README.md +70 -0
  2. package/bin/cometchat.mjs +7 -0
  3. package/dist/index.js +4395 -0
  4. package/dist/registry/schemas/template.schema.json +142 -0
  5. package/dist/registry/v6/experiences/1-conversation-list/astro.template.json +89 -0
  6. package/dist/registry/v6/experiences/1-conversation-list/nextjs-pages.template.json +83 -0
  7. package/dist/registry/v6/experiences/1-conversation-list/nextjs.template.json +83 -0
  8. package/dist/registry/v6/experiences/1-conversation-list/react-router.template.json +102 -0
  9. package/dist/registry/v6/experiences/1-conversation-list/reactjs.template.json +86 -0
  10. package/dist/registry/v6/experiences/1-conversation-list/templates/astro/ChatApp.css.tpl +39 -0
  11. package/dist/registry/v6/experiences/1-conversation-list/templates/astro/ChatApp.tsx.tpl +86 -0
  12. package/dist/registry/v6/experiences/1-conversation-list/templates/astro/CometChatSelector.tsx.tpl +40 -0
  13. package/dist/registry/v6/experiences/1-conversation-list/templates/astro/chat.astro.tpl +17 -0
  14. package/dist/registry/v6/experiences/1-conversation-list/templates/nextjs/CometChatNoSSR.css.tpl +39 -0
  15. package/dist/registry/v6/experiences/1-conversation-list/templates/nextjs/CometChatNoSSR.tsx.tpl +86 -0
  16. package/dist/registry/v6/experiences/1-conversation-list/templates/nextjs/CometChatSelector.tsx.tpl +41 -0
  17. package/dist/registry/v6/experiences/1-conversation-list/templates/nextjs/chat-page.tsx.tpl +11 -0
  18. package/dist/registry/v6/experiences/1-conversation-list/templates/nextjs-pages/CometChatNoSSR.css.tpl +39 -0
  19. package/dist/registry/v6/experiences/1-conversation-list/templates/nextjs-pages/CometChatNoSSR.tsx.tpl +88 -0
  20. package/dist/registry/v6/experiences/1-conversation-list/templates/nextjs-pages/CometChatSelector.tsx.tpl +41 -0
  21. package/dist/registry/v6/experiences/1-conversation-list/templates/nextjs-pages/chat-page.tsx.tpl +15 -0
  22. package/dist/registry/v6/experiences/1-conversation-list/templates/react-router/CometChat-route.tsx.tpl +22 -0
  23. package/dist/registry/v6/experiences/1-conversation-list/templates/react-router/CometChatNoSSR.css.tpl +39 -0
  24. package/dist/registry/v6/experiences/1-conversation-list/templates/react-router/CometChatNoSSR.tsx.tpl +87 -0
  25. package/dist/registry/v6/experiences/1-conversation-list/templates/react-router/CometChatSelector.tsx.tpl +40 -0
  26. package/dist/registry/v6/experiences/1-conversation-list/templates/reactjs/App.css.tpl +32 -0
  27. package/dist/registry/v6/experiences/1-conversation-list/templates/reactjs/App.tsx.tpl +55 -0
  28. package/dist/registry/v6/experiences/1-conversation-list/templates/reactjs/CometChatSelector.tsx.tpl +40 -0
  29. package/dist/registry/v6/experiences/1-conversation-list/templates/reactjs/main.tsx.tpl +46 -0
  30. package/dist/registry/v6/experiences/2-one-to-one/astro.template.json +81 -0
  31. package/dist/registry/v6/experiences/2-one-to-one/nextjs.template.json +77 -0
  32. package/dist/registry/v6/experiences/2-one-to-one/react-router.template.json +93 -0
  33. package/dist/registry/v6/experiences/2-one-to-one/reactjs.template.json +77 -0
  34. package/dist/registry/v6/experiences/2-one-to-one/templates/astro/ChatApp.css.tpl +21 -0
  35. package/dist/registry/v6/experiences/2-one-to-one/templates/astro/ChatApp.tsx.tpl +66 -0
  36. package/dist/registry/v6/experiences/2-one-to-one/templates/astro/chat.astro.tpl +17 -0
  37. package/dist/registry/v6/experiences/2-one-to-one/templates/nextjs/CometChatNoSSR.css.tpl +21 -0
  38. package/dist/registry/v6/experiences/2-one-to-one/templates/nextjs/CometChatNoSSR.tsx.tpl +66 -0
  39. package/dist/registry/v6/experiences/2-one-to-one/templates/nextjs/chat-page.tsx.tpl +11 -0
  40. package/dist/registry/v6/experiences/2-one-to-one/templates/react-router/CometChat-route.tsx.tpl +21 -0
  41. package/dist/registry/v6/experiences/2-one-to-one/templates/react-router/CometChatNoSSR.css.tpl +21 -0
  42. package/dist/registry/v6/experiences/2-one-to-one/templates/react-router/CometChatNoSSR.tsx.tpl +67 -0
  43. package/dist/registry/v6/experiences/2-one-to-one/templates/reactjs/App.css.tpl +4 -0
  44. package/dist/registry/v6/experiences/2-one-to-one/templates/reactjs/App.tsx.tpl +44 -0
  45. package/dist/registry/v6/experiences/2-one-to-one/templates/reactjs/main.tsx.tpl +46 -0
  46. package/dist/registry/v6/experiences/3-tab-based/astro.template.json +93 -0
  47. package/dist/registry/v6/experiences/3-tab-based/nextjs.template.json +90 -0
  48. package/dist/registry/v6/experiences/3-tab-based/react-router.template.json +105 -0
  49. package/dist/registry/v6/experiences/3-tab-based/reactjs.template.json +90 -0
  50. package/dist/registry/v6/experiences/3-tab-based/templates/astro/ChatApp.css.tpl +39 -0
  51. package/dist/registry/v6/experiences/3-tab-based/templates/astro/ChatApp.tsx.tpl +93 -0
  52. package/dist/registry/v6/experiences/3-tab-based/templates/astro/CometChatTabs.css.tpl +43 -0
  53. package/dist/registry/v6/experiences/3-tab-based/templates/astro/CometChatTabs.tsx.tpl +72 -0
  54. package/dist/registry/v6/experiences/3-tab-based/templates/astro/chat.astro.tpl +17 -0
  55. package/dist/registry/v6/experiences/3-tab-based/templates/nextjs/CometChatNoSSR.css.tpl +39 -0
  56. package/dist/registry/v6/experiences/3-tab-based/templates/nextjs/CometChatNoSSR.tsx.tpl +93 -0
  57. package/dist/registry/v6/experiences/3-tab-based/templates/nextjs/CometChatTabs.css.tpl +43 -0
  58. package/dist/registry/v6/experiences/3-tab-based/templates/nextjs/CometChatTabs.tsx.tpl +73 -0
  59. package/dist/registry/v6/experiences/3-tab-based/templates/nextjs/chat-page.tsx.tpl +11 -0
  60. package/dist/registry/v6/experiences/3-tab-based/templates/react-router/CometChat-route.tsx.tpl +21 -0
  61. package/dist/registry/v6/experiences/3-tab-based/templates/react-router/CometChatNoSSR.css.tpl +39 -0
  62. package/dist/registry/v6/experiences/3-tab-based/templates/react-router/CometChatNoSSR.tsx.tpl +94 -0
  63. package/dist/registry/v6/experiences/3-tab-based/templates/react-router/CometChatTabs.css.tpl +43 -0
  64. package/dist/registry/v6/experiences/3-tab-based/templates/react-router/CometChatTabs.tsx.tpl +72 -0
  65. package/dist/registry/v6/experiences/3-tab-based/templates/reactjs/App.css.tpl +7 -0
  66. package/dist/registry/v6/experiences/3-tab-based/templates/reactjs/App.tsx.tpl +62 -0
  67. package/dist/registry/v6/experiences/3-tab-based/templates/reactjs/CometChatTabs.css.tpl +43 -0
  68. package/dist/registry/v6/experiences/3-tab-based/templates/reactjs/CometChatTabs.tsx.tpl +72 -0
  69. package/dist/registry/v6/experiences/3-tab-based/templates/reactjs/main.tsx.tpl +46 -0
  70. package/dist/registry/v6/features/catalog.json +344 -0
  71. package/dist/registry/v6/production/astro/cometchat-token.ts.tpl +86 -0
  72. package/dist/registry/v6/production/astro.template.json +51 -0
  73. package/dist/registry/v6/production/nextjs/route.ts.tpl +79 -0
  74. package/dist/registry/v6/production/nextjs.template.json +50 -0
  75. package/dist/registry/v6/production/react-router/api.cometchat-token.ts.tpl +73 -0
  76. package/dist/registry/v6/production/react-router.template.json +58 -0
  77. package/dist/registry/v6/user-mgmt/astro/cometchat-user.ts.tpl +164 -0
  78. package/dist/registry/v6/user-mgmt/astro.template.json +33 -0
  79. package/dist/registry/v6/user-mgmt/nextjs/route.ts.tpl +212 -0
  80. package/dist/registry/v6/user-mgmt/nextjs.template.json +57 -0
  81. package/dist/registry/v6/user-mgmt/react-router/api.cometchat-user.ts.tpl +176 -0
  82. package/dist/registry/v6/user-mgmt/react-router.template.json +40 -0
  83. package/dist/registry/v6/widget/astro/CometChatWidget.css.tpl +105 -0
  84. package/dist/registry/v6/widget/astro/CometChatWidget.tsx.tpl +88 -0
  85. package/dist/registry/v6/widget/astro.template.json +38 -0
  86. package/dist/registry/v6/widget/nextjs/CometChatWidget.css.tpl +105 -0
  87. package/dist/registry/v6/widget/nextjs/CometChatWidget.tsx.tpl +88 -0
  88. package/dist/registry/v6/widget/nextjs.template.json +40 -0
  89. package/dist/registry/v6/widget/react-router/CometChatWidget.css.tpl +105 -0
  90. package/dist/registry/v6/widget/react-router/CometChatWidget.tsx.tpl +98 -0
  91. package/dist/registry/v6/widget/react-router.template.json +36 -0
  92. package/dist/registry/v6/widget/reactjs/CometChatWidget.css.tpl +105 -0
  93. package/dist/registry/v6/widget/reactjs/CometChatWidget.tsx.tpl +89 -0
  94. package/dist/registry/v6/widget/reactjs.template.json +36 -0
  95. package/package.json +53 -0
@@ -0,0 +1,176 @@
1
+ /**
2
+ * /api/cometchat-user — server-side CometChat user management.
3
+ *
4
+ * React Router v7 framework mode action+loader pattern.
5
+ * POST via action → create user
6
+ * PUT via action → update user
7
+ * DELETE via action → delete user
8
+ *
9
+ * The Auth Key NEVER leaves the server.
10
+ *
11
+ * Generated by `cometchat add-user-mgmt`.
12
+ *
13
+ * SECURITY: this template is unauthenticated by design — add your own
14
+ * session check before exposing it publicly.
15
+ */
16
+ import type { ActionFunctionArgs } from "react-router";
17
+
18
+ const COMETCHAT_APP_ID = process.env.VITE_COMETCHAT_APP_ID!;
19
+ const COMETCHAT_REGION = process.env.VITE_COMETCHAT_REGION!;
20
+ const COMETCHAT_AUTH_KEY = process.env.COMETCHAT_AUTH_KEY!;
21
+
22
+ const COMETCHAT_API_BASE = `https://${COMETCHAT_APP_ID}.api-${COMETCHAT_REGION}.cometchat.io/v3`;
23
+
24
+ const baseHeaders = {
25
+ "Content-Type": "application/json",
26
+ appid: COMETCHAT_APP_ID,
27
+ apikey: COMETCHAT_AUTH_KEY,
28
+ };
29
+
30
+ function jsonResponse(data: unknown, status = 200) {
31
+ return new Response(JSON.stringify(data), {
32
+ status,
33
+ headers: { "Content-Type": "application/json" },
34
+ });
35
+ }
36
+
37
+ function validateUid(uid: unknown): string | null {
38
+ if (typeof uid !== "string" || uid.length === 0) return "uid is required";
39
+ if (uid.length > 100) return "uid must be ≤100 characters";
40
+ if (!/^[a-zA-Z0-9_-]+$/.test(uid)) {
41
+ return "uid must contain only alphanumeric characters, hyphens, and underscores";
42
+ }
43
+ return null;
44
+ }
45
+
46
+ function validateConfig() {
47
+ if (!COMETCHAT_APP_ID || !COMETCHAT_REGION || !COMETCHAT_AUTH_KEY) {
48
+ return jsonResponse({ error: "CometChat credentials not configured on the server" }, 500);
49
+ }
50
+ return null;
51
+ }
52
+
53
+ export async function action({ request }: ActionFunctionArgs) {
54
+ const configError = validateConfig();
55
+ if (configError) return configError;
56
+
57
+ const method = request.method.toUpperCase();
58
+
59
+ if (method === "POST") {
60
+ let body;
61
+ try {
62
+ body = await request.json();
63
+ } catch {
64
+ return jsonResponse({ error: "Invalid JSON body" }, 400);
65
+ }
66
+ const uidError = validateUid(body.uid);
67
+ if (uidError) return jsonResponse({ error: uidError }, 400);
68
+ if (!body.name) return jsonResponse({ error: "name is required" }, 400);
69
+
70
+ try {
71
+ const res = await fetch(`${COMETCHAT_API_BASE}/users`, {
72
+ method: "POST",
73
+ headers: baseHeaders,
74
+ body: JSON.stringify({
75
+ uid: body.uid,
76
+ name: body.name,
77
+ ...(body.avatar && { avatar: body.avatar }),
78
+ ...(body.metadata && { metadata: body.metadata }),
79
+ ...(body.tags && { tags: body.tags }),
80
+ }),
81
+ });
82
+ if (!res.ok) {
83
+ const detail = await res.text();
84
+ return jsonResponse(
85
+ { error: `CometChat user create failed (${res.status})`, detail },
86
+ res.status === 409 ? 409 : 502,
87
+ );
88
+ }
89
+ const data = await res.json();
90
+ return jsonResponse({ user: data.data }, 201);
91
+ } catch (err) {
92
+ return jsonResponse(
93
+ { error: "Create failed", detail: err instanceof Error ? err.message : String(err) },
94
+ 500,
95
+ );
96
+ }
97
+ }
98
+
99
+ if (method === "PUT" || method === "PATCH") {
100
+ let body;
101
+ try {
102
+ body = await request.json();
103
+ } catch {
104
+ return jsonResponse({ error: "Invalid JSON body" }, 400);
105
+ }
106
+ const uidError = validateUid(body.uid);
107
+ if (uidError) return jsonResponse({ error: uidError }, 400);
108
+
109
+ const updates: Record<string, unknown> = {};
110
+ if (body.name !== undefined) updates.name = body.name;
111
+ if (body.avatar !== undefined) updates.avatar = body.avatar;
112
+ if (body.metadata !== undefined) updates.metadata = body.metadata;
113
+ if (body.tags !== undefined) updates.tags = body.tags;
114
+ if (Object.keys(updates).length === 0) {
115
+ return jsonResponse({ error: "No fields to update" }, 400);
116
+ }
117
+
118
+ try {
119
+ const res = await fetch(
120
+ `${COMETCHAT_API_BASE}/users/${encodeURIComponent(body.uid)}`,
121
+ {
122
+ method: "PUT",
123
+ headers: baseHeaders,
124
+ body: JSON.stringify(updates),
125
+ },
126
+ );
127
+ if (!res.ok) {
128
+ const detail = await res.text();
129
+ return jsonResponse(
130
+ { error: `CometChat user update failed (${res.status})`, detail },
131
+ 502,
132
+ );
133
+ }
134
+ const data = await res.json();
135
+ return jsonResponse({ user: data.data });
136
+ } catch (err) {
137
+ return jsonResponse(
138
+ { error: "Update failed", detail: err instanceof Error ? err.message : String(err) },
139
+ 500,
140
+ );
141
+ }
142
+ }
143
+
144
+ if (method === "DELETE") {
145
+ const url = new URL(request.url);
146
+ const uid = url.searchParams.get("uid");
147
+ const uidError = validateUid(uid);
148
+ if (uidError) return jsonResponse({ error: uidError }, 400);
149
+
150
+ try {
151
+ const res = await fetch(
152
+ `${COMETCHAT_API_BASE}/users/${encodeURIComponent(uid as string)}`,
153
+ {
154
+ method: "DELETE",
155
+ headers: baseHeaders,
156
+ body: JSON.stringify({ permanent: true }),
157
+ },
158
+ );
159
+ if (!res.ok) {
160
+ const detail = await res.text();
161
+ return jsonResponse(
162
+ { error: `CometChat user delete failed (${res.status})`, detail },
163
+ 502,
164
+ );
165
+ }
166
+ return jsonResponse({ deleted: uid });
167
+ } catch (err) {
168
+ return jsonResponse(
169
+ { error: "Delete failed", detail: err instanceof Error ? err.message : String(err) },
170
+ 500,
171
+ );
172
+ }
173
+ }
174
+
175
+ return jsonResponse({ error: `Method ${method} not allowed` }, 405);
176
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "$schema": "../../schemas/template.schema.json",
3
+ "version": "1.0.0",
4
+ "experience_id": 1,
5
+ "framework": "react-router",
6
+ "sdk_compatibility": ">=6.0.0 <7.0.0",
7
+ "default_placement": "dedicated-route",
8
+ "deps": [],
9
+ "env_vars": [
10
+ {
11
+ "name": "COMETCHAT_AUTH_KEY",
12
+ "required": true,
13
+ "secret": true,
14
+ "description": "Server-side only Auth Key (no VITE_ prefix). Used by the user management endpoints."
15
+ }
16
+ ],
17
+ "files": [
18
+ {
19
+ "path": "app/routes/api.cometchat-user.ts",
20
+ "action": "create",
21
+ "template_ref": "api.cometchat-user.ts.tpl",
22
+ "owned": true
23
+ },
24
+ {
25
+ "path": "app/routes.ts",
26
+ "action": "patch",
27
+ "patch_mode": "insert-before",
28
+ "anchor": "] satisfies RouteConfig",
29
+ "patch_content": " route(\"api/cometchat-user\", \"routes/api.cometchat-user.ts\"),\n",
30
+ "skip_if_exists": "routes/api.cometchat-user.ts"
31
+ }
32
+ ],
33
+ "verify_rules": [],
34
+ "next_steps": [
35
+ "1. Endpoint at /api/cometchat-user supports POST (create), PATCH (update), DELETE.",
36
+ "2. Add COMETCHAT_AUTH_KEY (no VITE_ prefix) to .env. The auth key stays server-only.",
37
+ "3. Wire it into your sign-up flow — when an app user is created, call POST /api/cometchat-user with { uid, name, ... }.",
38
+ "4. SECURITY: this template is unauthenticated. Add session checks before deploying."
39
+ ]
40
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * CometChatWidget styles — floating button + drawer.
3
+ *
4
+ * Edit the .cometchat-widget-button selector to change the button position
5
+ * (default: bottom-right). Edit the .cometchat-widget-drawer selector to
6
+ * change the drawer size + position.
7
+ */
8
+
9
+ .cometchat-widget-button {
10
+ position: fixed;
11
+ bottom: 24px;
12
+ right: 24px;
13
+ width: 56px;
14
+ height: 56px;
15
+ border-radius: 50%;
16
+ border: none;
17
+ background: var(--cometchat-primary-color, #6852D6);
18
+ color: white;
19
+ font-size: 24px;
20
+ cursor: pointer;
21
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
22
+ z-index: 9999;
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
27
+ }
28
+
29
+ .cometchat-widget-button:hover {
30
+ transform: scale(1.05);
31
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
32
+ }
33
+
34
+ .cometchat-widget-button:active {
35
+ transform: scale(0.98);
36
+ }
37
+
38
+ .cometchat-widget-drawer {
39
+ position: fixed;
40
+ bottom: 96px;
41
+ right: 24px;
42
+ width: 720px;
43
+ max-width: calc(100vw - 48px);
44
+ height: 600px;
45
+ max-height: calc(100vh - 120px);
46
+ background: white;
47
+ border-radius: 12px;
48
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.2);
49
+ z-index: 9998;
50
+ display: flex;
51
+ overflow: hidden;
52
+ border: 1px solid var(--cometchat-border-color-default, #e8e8e8);
53
+ }
54
+
55
+ .cometchat-widget-conversations {
56
+ width: 280px;
57
+ border-right: 1px solid var(--cometchat-border-color-default, #e8e8e8);
58
+ overflow: hidden;
59
+ display: flex;
60
+ flex-direction: column;
61
+ }
62
+
63
+ .cometchat-widget-conversations > .cometchat {
64
+ overflow: hidden;
65
+ }
66
+
67
+ .cometchat-widget-messages {
68
+ flex: 1;
69
+ display: flex;
70
+ flex-direction: column;
71
+ overflow: hidden;
72
+ }
73
+
74
+ .cometchat-widget-empty {
75
+ flex: 1;
76
+ display: flex;
77
+ align-items: center;
78
+ justify-content: center;
79
+ background: var(--cometchat-background-color-03, #f5f5f5);
80
+ color: var(--cometchat-text-color-secondary, #727272);
81
+ font: var(--cometchat-font-body-regular, 400 14px Roboto);
82
+ padding: 16px;
83
+ text-align: center;
84
+ }
85
+
86
+ @media (max-width: 600px) {
87
+ .cometchat-widget-drawer {
88
+ bottom: 16px;
89
+ right: 16px;
90
+ left: 16px;
91
+ width: auto;
92
+ height: calc(100vh - 100px);
93
+ flex-direction: column;
94
+ }
95
+ .cometchat-widget-conversations {
96
+ width: 100%;
97
+ height: 200px;
98
+ border-right: none;
99
+ border-bottom: 1px solid var(--cometchat-border-color-default, #e8e8e8);
100
+ }
101
+ .cometchat-widget-button {
102
+ bottom: 16px;
103
+ right: 16px;
104
+ }
105
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * CometChatWidget — floating chat overlay (Astro React island).
3
+ *
4
+ * Render this from any .astro file with `client:only="react"` so Astro
5
+ * skips SSR entirely:
6
+ * import CometChatWidget from "../cometchat/CometChatWidget";
7
+ * <CometChatWidget client:only="react" />
8
+ *
9
+ * Assumes CometChatUIKit.init() and CometChatUIKit.login() have already
10
+ * been called by your existing ChatApp island. The widget does NOT
11
+ * initialize CometChat — it just renders the existing logged-in user's
12
+ * chat. Mount the widget on a page that also mounts the ChatApp island,
13
+ * OR move init/login into a shared location.
14
+ */
15
+ import { useEffect, useState } from "react";
16
+ import {
17
+ CometChatMessageComposer,
18
+ CometChatMessageHeader,
19
+ CometChatMessageList,
20
+ } from "@cometchat/chat-uikit-react";
21
+ import { CometChat } from "@cometchat/chat-sdk-javascript";
22
+ import { CometChatSelector } from "./CometChatSelector";
23
+ import "./CometChatWidget.css";
24
+
25
+ export default function CometChatWidget() {
26
+ const [open, setOpen] = useState(false);
27
+ const [selectedUser, setSelectedUser] = useState<CometChat.User | undefined>();
28
+ const [selectedGroup, setSelectedGroup] = useState<CometChat.Group | undefined>();
29
+
30
+ // Close on Escape key
31
+ useEffect(() => {
32
+ if (!open) return;
33
+ const handler = (e: KeyboardEvent) => {
34
+ if (e.key === "Escape") setOpen(false);
35
+ };
36
+ window.addEventListener("keydown", handler);
37
+ return () => window.removeEventListener("keydown", handler);
38
+ }, [open]);
39
+
40
+ return (
41
+ <>
42
+ <button
43
+ type="button"
44
+ className="cometchat-widget-button"
45
+ onClick={() => setOpen((v) => !v)}
46
+ aria-label={open ? "Close chat" : "Open chat"}
47
+ >
48
+ {open ? "✕" : "💬"}
49
+ </button>
50
+
51
+ {open && (
52
+ <div className="cometchat-widget-drawer" role="dialog" aria-label="Chat">
53
+ <div className="cometchat-widget-conversations">
54
+ <CometChatSelector
55
+ onSelectorItemClicked={(activeItem) => {
56
+ const item =
57
+ activeItem instanceof CometChat.Conversation
58
+ ? activeItem.getConversationWith()
59
+ : activeItem;
60
+ if (item instanceof CometChat.User) {
61
+ setSelectedUser(item);
62
+ setSelectedGroup(undefined);
63
+ } else if (item instanceof CometChat.Group) {
64
+ setSelectedUser(undefined);
65
+ setSelectedGroup(item);
66
+ }
67
+ }}
68
+ />
69
+ </div>
70
+ {selectedUser || selectedGroup ? (
71
+ <div className="cometchat-widget-messages">
72
+ {selectedUser && <CometChatMessageHeader user={selectedUser} />}
73
+ {selectedGroup && <CometChatMessageHeader group={selectedGroup} />}
74
+ {selectedUser && <CometChatMessageList user={selectedUser} />}
75
+ {selectedGroup && <CometChatMessageList group={selectedGroup} />}
76
+ {selectedUser && <CometChatMessageComposer user={selectedUser} />}
77
+ {selectedGroup && <CometChatMessageComposer group={selectedGroup} />}
78
+ </div>
79
+ ) : (
80
+ <div className="cometchat-widget-empty">
81
+ Select a conversation to start chatting
82
+ </div>
83
+ )}
84
+ </div>
85
+ )}
86
+ </>
87
+ );
88
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "$schema": "../../schemas/template.schema.json",
3
+ "version": "1.0.0",
4
+ "experience_id": 1,
5
+ "framework": "astro",
6
+ "sdk_compatibility": ">=6.0.0 <7.0.0",
7
+ "default_placement": "spa-root",
8
+ "deps": [],
9
+ "env_vars": [],
10
+ "files": [
11
+ {
12
+ "path": "src/cometchat/CometChatWidget.tsx",
13
+ "action": "create",
14
+ "template_ref": "CometChatWidget.tsx.tpl",
15
+ "owned": true
16
+ },
17
+ {
18
+ "path": "src/cometchat/CometChatWidget.css",
19
+ "action": "create",
20
+ "template_ref": "CometChatWidget.css.tpl",
21
+ "owned": true
22
+ }
23
+ ],
24
+ "verify_rules": [],
25
+ "next_steps": [
26
+ "1. The widget component is now at src/cometchat/CometChatWidget.tsx (a React island).",
27
+ "2. Render it in any .astro layout or page using `client:only=\"react\"` so Astro skips SSR entirely:",
28
+ " ---",
29
+ " import CometChatWidget from \"../cometchat/CometChatWidget\";",
30
+ " ---",
31
+ " <CometChatWidget client:only=\"react\" />",
32
+ "3. The widget reuses your existing CometChat init/login flow from your existing ChatApp island.",
33
+ " You do NOT need to call init/login again inside the widget.",
34
+ "4. Important: the widget must live in the same page where init/login runs, OR the page must mount your existing ChatApp island first. Otherwise the SDK won't be initialized when the widget tries to render.",
35
+ "5. To customize position/size, edit the .cometchat-widget-button class in CometChatWidget.css.",
36
+ "6. The button appears in the bottom-right corner. Click it to open a drawer with the conversation list + message thread."
37
+ ]
38
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * CometChatWidget styles — floating button + drawer.
3
+ *
4
+ * Edit the .cometchat-widget-button selector to change the button position
5
+ * (default: bottom-right). Edit the .cometchat-widget-drawer selector to
6
+ * change the drawer size + position.
7
+ */
8
+
9
+ .cometchat-widget-button {
10
+ position: fixed;
11
+ bottom: 24px;
12
+ right: 24px;
13
+ width: 56px;
14
+ height: 56px;
15
+ border-radius: 50%;
16
+ border: none;
17
+ background: var(--cometchat-primary-color, #6852D6);
18
+ color: white;
19
+ font-size: 24px;
20
+ cursor: pointer;
21
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
22
+ z-index: 9999;
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
27
+ }
28
+
29
+ .cometchat-widget-button:hover {
30
+ transform: scale(1.05);
31
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
32
+ }
33
+
34
+ .cometchat-widget-button:active {
35
+ transform: scale(0.98);
36
+ }
37
+
38
+ .cometchat-widget-drawer {
39
+ position: fixed;
40
+ bottom: 96px;
41
+ right: 24px;
42
+ width: 720px;
43
+ max-width: calc(100vw - 48px);
44
+ height: 600px;
45
+ max-height: calc(100vh - 120px);
46
+ background: white;
47
+ border-radius: 12px;
48
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.2);
49
+ z-index: 9998;
50
+ display: flex;
51
+ overflow: hidden;
52
+ border: 1px solid var(--cometchat-border-color-default, #e8e8e8);
53
+ }
54
+
55
+ .cometchat-widget-conversations {
56
+ width: 280px;
57
+ border-right: 1px solid var(--cometchat-border-color-default, #e8e8e8);
58
+ overflow: hidden;
59
+ display: flex;
60
+ flex-direction: column;
61
+ }
62
+
63
+ .cometchat-widget-conversations > .cometchat {
64
+ overflow: hidden;
65
+ }
66
+
67
+ .cometchat-widget-messages {
68
+ flex: 1;
69
+ display: flex;
70
+ flex-direction: column;
71
+ overflow: hidden;
72
+ }
73
+
74
+ .cometchat-widget-empty {
75
+ flex: 1;
76
+ display: flex;
77
+ align-items: center;
78
+ justify-content: center;
79
+ background: var(--cometchat-background-color-03, #f5f5f5);
80
+ color: var(--cometchat-text-color-secondary, #727272);
81
+ font: var(--cometchat-font-body-regular, 400 14px Roboto);
82
+ padding: 16px;
83
+ text-align: center;
84
+ }
85
+
86
+ @media (max-width: 600px) {
87
+ .cometchat-widget-drawer {
88
+ bottom: 16px;
89
+ right: 16px;
90
+ left: 16px;
91
+ width: auto;
92
+ height: calc(100vh - 100px);
93
+ flex-direction: column;
94
+ }
95
+ .cometchat-widget-conversations {
96
+ width: 100%;
97
+ height: 200px;
98
+ border-right: none;
99
+ border-bottom: 1px solid var(--cometchat-border-color-default, #e8e8e8);
100
+ }
101
+ .cometchat-widget-button {
102
+ bottom: 16px;
103
+ right: 16px;
104
+ }
105
+ }
@@ -0,0 +1,88 @@
1
+ "use client";
2
+ /**
3
+ * CometChatWidget — floating chat overlay (Next.js App Router).
4
+ *
5
+ * Marked "use client" because it uses React state and the CometChat SDK
6
+ * (which talks to the browser). Render it from any client component or
7
+ * import it from a server component via:
8
+ * const CometChatWidget = dynamic(() => import("./CometChatWidget"), { ssr: false });
9
+ *
10
+ * Assumes CometChatUIKit.init() and CometChatUIKit.login() have already
11
+ * been called by the existing CometChatNoSSR.tsx integration. The widget
12
+ * does NOT initialize CometChat — it just renders the existing logged-in
13
+ * user's chat.
14
+ */
15
+ import { useEffect, useState } from "react";
16
+ import {
17
+ CometChatMessageComposer,
18
+ CometChatMessageHeader,
19
+ CometChatMessageList,
20
+ } from "@cometchat/chat-uikit-react";
21
+ import { CometChat } from "@cometchat/chat-sdk-javascript";
22
+ import { CometChatSelector } from "./CometChatSelector";
23
+ import "./CometChatWidget.css";
24
+
25
+ export default function CometChatWidget() {
26
+ const [open, setOpen] = useState(false);
27
+ const [selectedUser, setSelectedUser] = useState<CometChat.User | undefined>();
28
+ const [selectedGroup, setSelectedGroup] = useState<CometChat.Group | undefined>();
29
+
30
+ // Close on Escape key
31
+ useEffect(() => {
32
+ if (!open) return;
33
+ const handler = (e: KeyboardEvent) => {
34
+ if (e.key === "Escape") setOpen(false);
35
+ };
36
+ window.addEventListener("keydown", handler);
37
+ return () => window.removeEventListener("keydown", handler);
38
+ }, [open]);
39
+
40
+ return (
41
+ <>
42
+ <button
43
+ type="button"
44
+ className="cometchat-widget-button"
45
+ onClick={() => setOpen((v) => !v)}
46
+ aria-label={open ? "Close chat" : "Open chat"}
47
+ >
48
+ {open ? "✕" : "💬"}
49
+ </button>
50
+
51
+ {open && (
52
+ <div className="cometchat-widget-drawer" role="dialog" aria-label="Chat">
53
+ <div className="cometchat-widget-conversations">
54
+ <CometChatSelector
55
+ onSelectorItemClicked={(activeItem) => {
56
+ const item =
57
+ activeItem instanceof CometChat.Conversation
58
+ ? activeItem.getConversationWith()
59
+ : activeItem;
60
+ if (item instanceof CometChat.User) {
61
+ setSelectedUser(item);
62
+ setSelectedGroup(undefined);
63
+ } else if (item instanceof CometChat.Group) {
64
+ setSelectedUser(undefined);
65
+ setSelectedGroup(item);
66
+ }
67
+ }}
68
+ />
69
+ </div>
70
+ {selectedUser || selectedGroup ? (
71
+ <div className="cometchat-widget-messages">
72
+ {selectedUser && <CometChatMessageHeader user={selectedUser} />}
73
+ {selectedGroup && <CometChatMessageHeader group={selectedGroup} />}
74
+ {selectedUser && <CometChatMessageList user={selectedUser} />}
75
+ {selectedGroup && <CometChatMessageList group={selectedGroup} />}
76
+ {selectedUser && <CometChatMessageComposer user={selectedUser} />}
77
+ {selectedGroup && <CometChatMessageComposer group={selectedGroup} />}
78
+ </div>
79
+ ) : (
80
+ <div className="cometchat-widget-empty">
81
+ Select a conversation to start chatting
82
+ </div>
83
+ )}
84
+ </div>
85
+ )}
86
+ </>
87
+ );
88
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "$schema": "../../schemas/template.schema.json",
3
+ "version": "1.0.0",
4
+ "experience_id": 1,
5
+ "framework": "nextjs",
6
+ "sdk_compatibility": ">=6.0.0 <7.0.0",
7
+ "default_placement": "spa-root",
8
+ "deps": [],
9
+ "env_vars": [],
10
+ "files": [
11
+ {
12
+ "path": "src/app/cometchat/CometChatWidget.tsx",
13
+ "action": "create",
14
+ "template_ref": "CometChatWidget.tsx.tpl",
15
+ "owned": true
16
+ },
17
+ {
18
+ "path": "src/app/cometchat/CometChatWidget.css",
19
+ "action": "create",
20
+ "template_ref": "CometChatWidget.css.tpl",
21
+ "owned": true
22
+ }
23
+ ],
24
+ "verify_rules": [],
25
+ "next_steps": [
26
+ "1. The widget component is now at src/app/cometchat/CometChatWidget.tsx — it has a `\"use client\"` directive so it can use React state + the CometChat SDK in the browser.",
27
+ "2. Render it in any client component or layout. Two options:",
28
+ " A) Direct import in a client component:",
29
+ " import CometChatWidget from \"./cometchat/CometChatWidget\";",
30
+ " <CometChatWidget />",
31
+ " B) Render from a server component / layout via dynamic({ssr:false}):",
32
+ " import dynamic from \"next/dynamic\";",
33
+ " const CometChatWidget = dynamic(() => import(\"./cometchat/CometChatWidget\"), { ssr: false });",
34
+ " <CometChatWidget />",
35
+ "3. The widget reuses your existing CometChat init/login flow (CometChatNoSSR.tsx).",
36
+ " You do NOT need to call init/login again inside the widget.",
37
+ "4. To customize position/size, edit the .cometchat-widget-button class in CometChatWidget.css.",
38
+ "5. The fixed-position button appears in the bottom-right corner. Click it to open the conversation list + message thread drawer."
39
+ ]
40
+ }