From a724805eaf927b0776a592e3f8dcd1d3465abb30 Mon Sep 17 00:00:00 2001 From: Swapnil Godambe Date: Tue, 28 Apr 2026 20:45:54 +0530 Subject: [PATCH] feat(solutions): add CometChat Chat Starter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A blank Next.js (App Router) example with CometChat AI-coding skills pre-installed at .agents/skills/. Users deploy or clone, open the project in any Agent-Skills-compatible IDE (Cursor / Cline / Codex / Replit Agent / Copilot / Windsurf), and ask "add chat to my app" — the CometChat dispatcher skill walks them through signup, app provisioning, and writes a working chat integration into app/. What's included: - Standard Next.js 16 + React 19 scaffold (app/, package.json, tsconfig) - .agents/skills/ pre-bundled with the 13-skill CometChat web set (dispatcher + foundation + framework patterns + Phase B) - .env.example with NEXT_PUBLIC_COMETCHAT_{APP_ID,REGION,AUTH_KEY} - .eslintrc.json (next/core-web-vitals) - Front matter for vercel.com/templates listing (useCase: SaaS + Realtime Apps, css: CSS, related: slackbot) Demo: https://cometchat-vercel-template.vercel.app Skills source: github.com/cometchat/cometchat-skills Co-Authored-By: Claude Opus 4.7 (1M context) --- .../skills/cometchat-astro-patterns/SKILL.md | 687 +++++++++ .../skills/cometchat-components/SKILL.md | 1002 +++++++++++++ .../.agents/skills/cometchat-core/SKILL.md | 642 ++++++++ .../skills/cometchat-customization/SKILL.md | 555 +++++++ .../references/component-catalog.md | 236 +++ .../skills/cometchat-features/SKILL.md | 514 +++++++ .../skills/cometchat-nextjs-patterns/SKILL.md | 857 +++++++++++ .../skills/cometchat-placement/SKILL.md | 1293 +++++++++++++++++ .../skills/cometchat-production/SKILL.md | 1020 +++++++++++++ .../skills/cometchat-react-patterns/SKILL.md | 527 +++++++ .../cometchat-react-router-patterns/SKILL.md | 717 +++++++++ .../.agents/skills/cometchat-theming/SKILL.md | 335 +++++ .../skills/cometchat-troubleshooting/SKILL.md | 274 ++++ .../.agents/skills/cometchat/SKILL.md | 862 +++++++++++ solutions/cometchat-chat/.env.example | 11 + solutions/cometchat-chat/.eslintrc.json | 4 + solutions/cometchat-chat/.gitignore | 39 + solutions/cometchat-chat/README.md | 103 ++ solutions/cometchat-chat/app/globals.css | 15 + solutions/cometchat-chat/app/layout.tsx | 18 + solutions/cometchat-chat/app/page.tsx | 90 ++ solutions/cometchat-chat/next.config.ts | 7 + solutions/cometchat-chat/package.json | 25 + solutions/cometchat-chat/tsconfig.json | 21 + 24 files changed, 9854 insertions(+) create mode 100644 solutions/cometchat-chat/.agents/skills/cometchat-astro-patterns/SKILL.md create mode 100644 solutions/cometchat-chat/.agents/skills/cometchat-components/SKILL.md create mode 100644 solutions/cometchat-chat/.agents/skills/cometchat-core/SKILL.md create mode 100644 solutions/cometchat-chat/.agents/skills/cometchat-customization/SKILL.md create mode 100644 solutions/cometchat-chat/.agents/skills/cometchat-customization/references/component-catalog.md create mode 100644 solutions/cometchat-chat/.agents/skills/cometchat-features/SKILL.md create mode 100644 solutions/cometchat-chat/.agents/skills/cometchat-nextjs-patterns/SKILL.md create mode 100644 solutions/cometchat-chat/.agents/skills/cometchat-placement/SKILL.md create mode 100644 solutions/cometchat-chat/.agents/skills/cometchat-production/SKILL.md create mode 100644 solutions/cometchat-chat/.agents/skills/cometchat-react-patterns/SKILL.md create mode 100644 solutions/cometchat-chat/.agents/skills/cometchat-react-router-patterns/SKILL.md create mode 100644 solutions/cometchat-chat/.agents/skills/cometchat-theming/SKILL.md create mode 100644 solutions/cometchat-chat/.agents/skills/cometchat-troubleshooting/SKILL.md create mode 100644 solutions/cometchat-chat/.agents/skills/cometchat/SKILL.md create mode 100644 solutions/cometchat-chat/.env.example create mode 100644 solutions/cometchat-chat/.eslintrc.json create mode 100644 solutions/cometchat-chat/.gitignore create mode 100644 solutions/cometchat-chat/README.md create mode 100644 solutions/cometchat-chat/app/globals.css create mode 100644 solutions/cometchat-chat/app/layout.tsx create mode 100644 solutions/cometchat-chat/app/page.tsx create mode 100644 solutions/cometchat-chat/next.config.ts create mode 100644 solutions/cometchat-chat/package.json create mode 100644 solutions/cometchat-chat/tsconfig.json diff --git a/solutions/cometchat-chat/.agents/skills/cometchat-astro-patterns/SKILL.md b/solutions/cometchat-chat/.agents/skills/cometchat-astro-patterns/SKILL.md new file mode 100644 index 0000000000..499b0f3230 --- /dev/null +++ b/solutions/cometchat-chat/.agents/skills/cometchat-astro-patterns/SKILL.md @@ -0,0 +1,687 @@ +--- +name: cometchat-astro-patterns +description: "Framework-specific patterns for integrating CometChat React UI Kit v6 into Astro projects using React islands. Covers client:only rendering, island communication, CSS handling, and common pitfalls." +license: "MIT" +compatibility: "Node.js >=18; React >=18; Astro >=3; @astrojs/react; @cometchat/chat-uikit-react ^6; @cometchat/chat-sdk-javascript ^4" +allowed-tools: "executeBash, readFile, fileSearch, listDirectory" +metadata: + author: "CometChat" + version: "3.0.0" + tags: "chat cometchat astro react islands client-only patterns" +--- + +## Purpose + +This skill teaches Claude how to integrate CometChat into an Astro project using React islands. Astro is a static-first framework -- most of the page is HTML rendered at build time. Interactive React components run as isolated "islands" in the browser. CometChat components are React islands that must use `client:only="react"` to bypass server rendering entirely. + +**Read these companion skills first:** +- `cometchat-core` -- initialization, login, CSS, provider pattern, anti-patterns +- `cometchat-components` -- component catalog and composition patterns +- `cometchat-placement` -- WHERE to put chat (route, modal, drawer, embedded) + +--- + +## 1. Project detection + +A project uses Astro when `package.json` has `astro` as a dependency. CometChat integration also requires the React integration: + +```bash +# Check for Astro + React +grep -E '"astro"|"@astrojs/react"' package.json +``` + +**If `@astrojs/react` is missing**, it must be installed first: + +```bash +npx astro add react +``` + +This adds `@astrojs/react` to `package.json` and configures it in `astro.config.mjs`. + +Verify the React integration is configured: + +```bash +# astro.config.mjs should have react() in the integrations array +grep -A 5 "integrations" astro.config.mjs 2>/dev/null || grep -A 5 "integrations" astro.config.ts 2>/dev/null +``` + +--- + +## 2. Critical: client:only="react" + +Every Astro component that renders CometChat MUST use `client:only="react"`. This is the single most important rule for Astro + CometChat. + +### Why client:only and not client:load or client:visible + +Astro's client directives control when and how interactive components are hydrated: + +| Directive | Server renders? | When hydrates? | Works with CometChat? | +|---|---|---|---| +| `client:load` | Yes | On page load | **NO** -- server render crashes | +| `client:visible` | Yes | When visible in viewport | **NO** -- server render crashes | +| `client:idle` | Yes | When browser is idle | **NO** -- server render crashes | +| `client:only="react"` | No | On page load (client-only) | **YES** | + +`client:load`, `client:visible`, and `client:idle` all attempt to render the component on the server first, then hydrate in the browser. CometChat components access `window` and `document` at import time, so server rendering crashes with `ReferenceError: window is not defined`. + +`client:only="react"` skips server rendering entirely. The component only runs in the browser. This is the ONLY valid directive for CometChat. + +### Usage in .astro files + +```astro +--- +import ChatView from "../components/ChatView"; +--- + + + + + + + + + + + + +``` + +--- + +## 3. CometChatProvider for Astro + +The provider pattern is the same React component as in other frameworks, but in Astro it runs entirely inside a React island. The provider is not mounted at the Astro layout level -- it is mounted inside the React island. + +### Full implementation + +```tsx +// src/components/CometChatProvider.tsx +import React, { useEffect, useState, createContext, useContext } from "react"; +import { CometChatUIKit, UIKitSettingsBuilder } from "@cometchat/chat-uikit-react"; +import "@cometchat/chat-uikit-react/css-variables.css"; + +interface CometChatContextValue { + isReady: boolean; + error: string | null; +} + +const CometChatContext = createContext({ + isReady: false, + error: null, +}); + +export const useCometChat = () => useContext(CometChatContext); + +// Module-level state prevents both double-init AND double-login in React +// StrictMode. Without the loginInFlight guard, a second mount calls +// login() while the first is still pending and the SDK throws +// "Please wait until the previous login request ends." +let initialized = false; +let loginInFlight: Promise | null = null; + +async function ensureLoggedIn( + uid: string, + authToken?: string, +): Promise { + const existing = await CometChatUIKit.getLoggedinUser(); + if (existing) return; + if (loginInFlight) { + await loginInFlight; + return; + } + loginInFlight = authToken + ? CometChatUIKit.loginWithAuthToken(authToken) + : CometChatUIKit.login(uid); + try { + await loginInFlight; + } finally { + loginInFlight = null; + } +} + +interface CometChatProviderProps { + children: React.ReactNode; +} + +export function CometChatProvider({ children }: CometChatProviderProps) { + const [isReady, setIsReady] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + async function setup() { + try { + if (!initialized) { + initialized = true; + + const settings = new UIKitSettingsBuilder() + .setAppId(import.meta.env.PUBLIC_COMETCHAT_APP_ID) + .setRegion(import.meta.env.PUBLIC_COMETCHAT_REGION) + .setAuthKey(import.meta.env.PUBLIC_COMETCHAT_AUTH_KEY) + .subscribePresenceForAllUsers() + .build(); + + await CometChatUIKit.init(settings); + } + + await ensureLoggedIn("cometchat-uid-1"); // DEVELOPMENT ONLY — see cometchat-production skill + + setIsReady(true); + } catch (e) { + setError(String(e)); + } + } + + setup(); + }, []); + + if (error) { + return ( +
+ CometChat Error: {error} +
+ ); + } + + if (!isReady) return null; + + return ( + + {children} + + ); +} +``` + +### Key difference: CSS import is INSIDE the component + +Notice that `@cometchat/chat-uikit-react/css-variables.css` is imported inside the React component file, not in an Astro layout or global stylesheet. This is because `client:only` islands are completely isolated from Astro's CSS pipeline. Stylesheets imported in `.astro` files or global CSS do NOT reach `client:only` islands. + +--- + +## 4. Chat page with React island + +### Full page implementation + +```astro +--- +// src/pages/messages.astro +import Layout from "../layouts/Layout.astro"; +import ChatView from "../components/ChatView"; +--- + + +
+ +
+
+ + +``` + +### ChatView component + +```tsx +// src/components/ChatView.tsx +import { useState } from "react"; +import { CometChatProvider } from "./CometChatProvider"; +import { + CometChatConversations, + CometChatMessageHeader, + CometChatMessageList, + CometChatMessageComposer, +} from "@cometchat/chat-uikit-react"; +import { CometChat } from "@cometchat/chat-sdk-javascript"; + +export default function ChatView() { + return ( + + + + ); +} + +function ChatContent() { + const [selectedUser, setSelectedUser] = useState(); + const [selectedGroup, setSelectedGroup] = useState(); + + function handleConversationClick(conversation: CometChat.Conversation) { + const entity = conversation.getConversationWith(); + if (entity instanceof CometChat.User) { + setSelectedUser(entity); + setSelectedGroup(undefined); + } else if (entity instanceof CometChat.Group) { + setSelectedUser(undefined); + setSelectedGroup(entity); + } + } + + return ( +
+
+ +
+
+ {(selectedUser || selectedGroup) ? ( + <> + {selectedUser && } + {selectedGroup && } + {selectedUser && } + {selectedGroup && } + {selectedUser && } + {selectedGroup && } + + ) : ( +
+ Select a conversation to start chatting +
+ )} +
+
+ ); +} +``` + +**Important:** The `CometChatProvider` wraps the content INSIDE the React island, not at the Astro level. Each island is an independent React tree. The provider initializes CometChat when this specific island mounts. + +--- + +## 5. Drawer and modal patterns + +### Chat drawer as a React island + +```tsx +// src/components/ChatDrawerIsland.tsx +import { useState, useEffect } from "react"; +import { CometChatProvider } from "./CometChatProvider"; +import { + CometChatMessageHeader, + CometChatMessageList, + CometChatMessageComposer, +} from "@cometchat/chat-uikit-react"; +import { CometChat } from "@cometchat/chat-sdk-javascript"; + +interface ChatDrawerIslandProps { + targetUserId: string; +} + +export default function ChatDrawerIsland({ targetUserId }: ChatDrawerIslandProps) { + const [isOpen, setIsOpen] = useState(false); + + return ( + + + + {isOpen && ( + <> +
setIsOpen(false)} + style={{ position: "fixed", inset: 0, zIndex: 999, backgroundColor: "rgba(0,0,0,0.3)" }} + /> +
+
+ Chat + +
+ +
+ + )} + + ); +} + +function DrawerContent({ targetUserId }: { targetUserId: string }) { + const [user, setUser] = useState(); + + useEffect(() => { + CometChat.getUser(targetUserId).then(setUser); + }, [targetUserId]); + + if (!user) return
Loading...
; + + return ( + <> + +
+ +
+ + + ); +} +``` + +Usage in an Astro page: + +```astro +--- +import Layout from "../layouts/Layout.astro"; +import ChatDrawerIsland from "../components/ChatDrawerIsland"; +--- + + +

Product Details

+

Some product description...

+ +
+``` + +**Note:** The trigger button is INSIDE the React island. Astro's static HTML cannot trigger React state changes directly. The button must be part of the same React tree. + +--- + +## 6. Inter-island communication + +Astro's island architecture means different React islands are separate React trees. They cannot share React state, context, or refs. If you need communication between a navbar island and a chat island, use one of these approaches: + +### Option A: Custom DOM events + +```tsx +// NavbarIsland.tsx -- fires a custom event +function NavbarIsland() { + function openChat() { + window.dispatchEvent(new CustomEvent("open-chat", { detail: { userId: "uid-123" } })); + } + + return ; +} + +// ChatIsland.tsx -- listens for the event +function ChatIsland() { + const [isOpen, setIsOpen] = useState(false); + const [targetUserId, setTargetUserId] = useState(); + + useEffect(() => { + function handleOpenChat(e: CustomEvent) { + setTargetUserId(e.detail.userId); + setIsOpen(true); + } + + window.addEventListener("open-chat", handleOpenChat as EventListener); + return () => window.removeEventListener("open-chat", handleOpenChat as EventListener); + }, []); + + // ... render chat drawer when isOpen +} +``` + +### Option B: Nanostores (Astro's recommended approach) + +Install nanostores: `npm install nanostores @nanostores/react` + +```typescript +// src/stores/chatStore.ts +import { atom } from "nanostores"; + +export const $chatOpen = atom(false); +export const $chatTargetUserId = atom(undefined); +``` + +```tsx +// NavbarIsland.tsx +import { useStore } from "@nanostores/react"; +import { $chatOpen, $chatTargetUserId } from "../stores/chatStore"; + +function NavbarIsland() { + function openChat() { + $chatTargetUserId.set("uid-123"); + $chatOpen.set(true); + } + + return ; +} +``` + +```tsx +// ChatIsland.tsx +import { useStore } from "@nanostores/react"; +import { $chatOpen, $chatTargetUserId } from "../stores/chatStore"; + +function ChatIsland() { + const isOpen = useStore($chatOpen); + const targetUserId = useStore($chatTargetUserId); + + // ... render chat drawer when isOpen +} +``` + +Nanostores work across framework boundaries -- if the project also has Svelte or Vue islands, they can all share the same store. + +### Option C: URL-based state + +Navigate to a chat page with query parameters: + +```astro + +Message this user +``` + +```tsx +// ChatView.tsx -- reads user from URL +function ChatView() { + const params = new URLSearchParams(window.location.search); + const targetUserId = params.get("user"); + + // ... resolve and render chat for this user +} +``` + +--- + +## 7. Environment variables + +### Astro env var conventions + +Astro uses Vite under the hood. Client-side variables must have the `PUBLIC_` prefix: + +```env +PUBLIC_COMETCHAT_APP_ID=your_app_id +PUBLIC_COMETCHAT_REGION=us +PUBLIC_COMETCHAT_AUTH_KEY=your_auth_key +``` + +**Access in code:** `import.meta.env.PUBLIC_COMETCHAT_APP_ID` + +Variables without `PUBLIC_` prefix are server-only (available in Astro's frontmatter and API routes, but not in client islands). + +### .env file + +Create `.env` in the project root. Astro's `.env` is NOT gitignored by default -- add it: + +```bash +echo ".env" >> .gitignore +``` + +### Server-only variables (for production auth) + +```env +# Server-only (no PUBLIC_ prefix) -- for API endpoints +COMETCHAT_AUTH_TOKEN=your_server_secret +COMETCHAT_APP_ID=your_app_id +COMETCHAT_REGION=us +``` + +Access in Astro API routes or server-side code: + +```typescript +// src/pages/api/cometchat-token.ts +import type { APIRoute } from "astro"; + +export const POST: APIRoute = async ({ request }) => { + const { uid } = await request.json(); + const appId = import.meta.env.COMETCHAT_APP_ID; + const region = import.meta.env.COMETCHAT_REGION; + const authToken = import.meta.env.COMETCHAT_AUTH_TOKEN; + + const response = await fetch( + `https://${appId}.api-${region}.cometchat.io/v3/users/${uid}/auth_tokens`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + apiKey: authToken, + appId: appId, + }, + body: JSON.stringify({}), + } + ); + + const data = await response.json(); + return new Response(JSON.stringify({ token: data.data.authToken }), { + headers: { "Content-Type": "application/json" }, + }); +}; +``` + +**Note:** Astro API routes require on-demand rendering. In Astro 3: set `output: "server"` or `output: "hybrid"` in `astro.config.mjs`. In Astro 4+: the default is `output: "static"` with per-route opt-in — add `export const prerender = false;` at the top of the API route file. If the project is fully static, the auth endpoint must be hosted elsewhere. + +--- + +## 8. CSS handling + +### The island CSS isolation problem + +Unlike other frameworks, Astro's `client:only` islands are completely isolated from the Astro CSS pipeline. CSS imported in `.astro` files, global stylesheets linked in ``, and `