@fluxy-chat/create-fluxy-chat 0.5.14 → 0.5.21

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 (53) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/index.js +10 -5
  3. package/package.json +1 -1
  4. package/readme.md +2 -1
  5. package/templates/basic/package.json +19 -19
  6. package/templates/comments-board/package.json +2 -2
  7. package/templates/deal-room/package.json +2 -2
  8. package/templates/deal-room/src/App.tsx +17 -1
  9. package/templates/discord/package.json +20 -20
  10. package/templates/draw/package.json +25 -25
  11. package/templates/fleet-panel/package.json +2 -2
  12. package/templates/full/README.md +14 -4
  13. package/templates/full/fluxy.hosted.json +15 -0
  14. package/templates/full/package.json +4 -3
  15. package/templates/full/scripts/fluxy-deploy.mjs +53 -0
  16. package/templates/full/scripts/fluxy-setup.mjs +31 -7
  17. package/templates/full/src/App.tsx +14 -0
  18. package/templates/full/src/index.css +3 -3
  19. package/templates/full/src/session.ts +7 -2
  20. package/templates/full/src/vite-env.d.ts +2 -1
  21. package/templates/game-tick/package.json +2 -2
  22. package/templates/hr-feedback/package.json +1 -1
  23. package/templates/iot-panel/package.json +25 -25
  24. package/templates/javascript-live-cursors/package.json +1 -1
  25. package/templates/live-cursors/.env.example +8 -5
  26. package/templates/live-cursors/package.json +2 -2
  27. package/templates/live-cursors/src/App.tsx +19 -2
  28. package/templates/live-cursors/src/vite-env.d.ts +1 -0
  29. package/templates/live-cursors-chat/package.json +2 -2
  30. package/templates/minimal/package.json +3 -3
  31. package/templates/minimal/src/App.tsx +2 -0
  32. package/templates/polls/.env.example +13 -0
  33. package/templates/polls/README.md +13 -0
  34. package/templates/polls/index.html +12 -0
  35. package/templates/polls/package.json +24 -0
  36. package/templates/polls/src/App.tsx +135 -0
  37. package/templates/polls/src/index.css +85 -0
  38. package/templates/polls/src/main.tsx +10 -0
  39. package/templates/polls/src/session.ts +73 -0
  40. package/templates/polls/src/vite-env.d.ts +13 -0
  41. package/templates/polls/tsconfig.json +21 -0
  42. package/templates/polls/vite.config.ts +7 -0
  43. package/templates/react/.env.example +9 -5
  44. package/templates/react/README.md +7 -32
  45. package/templates/react/package.json +2 -2
  46. package/templates/react/src/App.tsx +31 -23
  47. package/templates/react/src/vite-env.d.ts +1 -0
  48. package/templates/slack/package.json +21 -21
  49. package/templates/telegram/package.json +20 -20
  50. package/templates/tiptap-room/package.json +29 -29
  51. package/templates/voice-stage/package.json +2 -2
  52. package/templates/war-room/package.json +2 -2
  53. package/templates/whiteboard/package.json +2 -2
@@ -2,7 +2,8 @@
2
2
 
3
3
  interface ImportMetaEnv {
4
4
  readonly VITE_FLUXYCHAT_WORKER_URL: string;
5
- readonly VITE_FLUXYCHAT_MEMBER_JWT: string;
5
+ readonly VITE_FLUXYCHAT_PUBLISHABLE_KEY?: string;
6
+ readonly VITE_FLUXYCHAT_MEMBER_JWT?: string;
6
7
  readonly VITE_FLUXYCHAT_ROOM_ID: string;
7
8
  readonly VITE_FLUXYCHAT_AGENT_ID: string;
8
9
  readonly VITE_FLUXYCHAT_AGENT_HANDLE: string;
@@ -9,8 +9,8 @@
9
9
  "preview": "vite preview"
10
10
  },
11
11
  "dependencies": {
12
- "@fluxy-chat/react": "^0.1.4",
13
- "@fluxy-chat/sdk": "^0.6.5",
12
+ "@fluxy-chat/react": "^0.1.7",
13
+ "@fluxy-chat/sdk": "^0.6.10",
14
14
  "react": "^19.0.0",
15
15
  "react-dom": "^19.0.0",
16
16
  "zustand": "^5.0.0"
@@ -9,7 +9,7 @@
9
9
  "type-check": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@fluxy-chat/sdk": "latest"
12
+ "@fluxy-chat/sdk": "^0.6.10"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@cloudflare/workers-types": "^4.0.0",
@@ -1,25 +1,25 @@
1
- {
2
- "name": "fluxy-iot-panel",
3
- "private": true,
4
- "version": "0.1.0",
5
- "type": "module",
6
- "scripts": {
7
- "dev": "vite",
8
- "build": "tsc -b && vite build",
9
- "preview": "vite preview"
10
- },
11
- "dependencies": {
12
- "@fluxy-chat/react": "^0.1.4",
13
- "@fluxy-chat/sdk": "^0.6.5",
14
- "react": "^19.0.0",
15
- "react-dom": "^19.0.0",
16
- "zustand": "^5.0.0"
17
- },
18
- "devDependencies": {
19
- "@types/react": "^19.0.0",
20
- "@types/react-dom": "^19.0.0",
21
- "@vitejs/plugin-react": "^4.3.0",
22
- "typescript": "^5.6.0",
23
- "vite": "^6.0.0"
24
- }
25
- }
1
+ {
2
+ "name": "fluxy-iot-panel",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "@fluxy-chat/react": "^0.1.7",
13
+ "@fluxy-chat/sdk": "^0.6.10",
14
+ "react": "^19.0.0",
15
+ "react-dom": "^19.0.0",
16
+ "zustand": "^5.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/react": "^19.0.0",
20
+ "@types/react-dom": "^19.0.0",
21
+ "@vitejs/plugin-react": "^4.3.0",
22
+ "typescript": "^5.6.0",
23
+ "vite": "^6.0.0"
24
+ }
25
+ }
@@ -9,7 +9,7 @@
9
9
  "preview": "vite preview"
10
10
  },
11
11
  "dependencies": {
12
- "@fluxy-chat/sdk": "^0.6.5"
12
+ "@fluxy-chat/sdk": "^0.6.10"
13
13
  },
14
14
  "devDependencies": {
15
15
  "typescript": "^5.6.0",
@@ -1,9 +1,12 @@
1
1
  # FluxyChat worker URL (hosted or self-hosted)
2
- VITE_FLUXYCHAT_WORKER_URL=https://your-worker.example.workers.dev
2
+ VITE_FLUXYCHAT_WORKER_URL=https://api.fluxychat.com
3
3
 
4
- # Option A — member JWT (~2 min via onboarding)
5
- VITE_FLUXYCHAT_MEMBER_JWT=eyJ...
6
- VITE_FLUXYCHAT_ROOM_ID=demo
4
+ # Option A — public room (pin sdk 0.6.7+)
5
+ VITE_FLUXYCHAT_PUBLISHABLE_KEY=pk_...
6
+ VITE_FLUXYCHAT_ROOM_ID=general
7
7
 
8
- # Option B — public guest room (~60s, no signup)
8
+ # Option B — member JWT
9
+ # VITE_FLUXYCHAT_MEMBER_JWT=eyJ...
10
+
11
+ # Option C — guest session without pk_
9
12
  # VITE_FLUXYCHAT_PUBLIC_ROOM_ID=your-public-room-id
@@ -9,8 +9,8 @@
9
9
  "preview": "vite preview"
10
10
  },
11
11
  "dependencies": {
12
- "@fluxy-chat/react": "^0.1.4",
13
- "@fluxy-chat/sdk": "^0.6.5",
12
+ "@fluxy-chat/react": "^0.1.7",
13
+ "@fluxy-chat/sdk": "^0.6.10",
14
14
  "react": "^19.0.0",
15
15
  "react-dom": "^19.0.0",
16
16
  "zustand": "^5.0.0"
@@ -3,6 +3,7 @@ import { FluxyChatClient } from "@fluxy-chat/sdk";
3
3
  import { FluxyRealtimeProvider, useChat } from "@fluxy-chat/react";
4
4
 
5
5
  const workerUrl = import.meta.env.VITE_FLUXYCHAT_WORKER_URL?.trim();
6
+ const publishableKey = import.meta.env.VITE_FLUXYCHAT_PUBLISHABLE_KEY?.trim();
6
7
  const memberJwt = import.meta.env.VITE_FLUXYCHAT_MEMBER_JWT?.trim();
7
8
  const publicRoomId = import.meta.env.VITE_FLUXYCHAT_PUBLIC_ROOM_ID?.trim();
8
9
  const configuredRoomId = import.meta.env.VITE_FLUXYCHAT_ROOM_ID?.trim() || "demo";
@@ -149,6 +150,23 @@ function hashCode(value: string): number {
149
150
  }
150
151
 
151
152
  export function App() {
153
+ const pkRoomId = publicRoomId || configuredRoomId;
154
+ if (workerUrl && publishableKey) {
155
+ return (
156
+ <main className="shell">
157
+ <h1>Live cursors</h1>
158
+ <p className="mode-badge">publishableKey · two tabs, same room</p>
159
+ <FluxyRealtimeProvider workerUrl={workerUrl} publishableKey={publishableKey}>
160
+ <CursorCanvas roomId={pkRoomId} selfUserId="guest" />
161
+ </FluxyRealtimeProvider>
162
+ </main>
163
+ );
164
+ }
165
+
166
+ return <MemberOrGuestCursors />;
167
+ }
168
+
169
+ function MemberOrGuestCursors() {
152
170
  const { session, loading, error } = useFluxySession();
153
171
  const [roomId, setRoomId] = useState(configuredRoomId);
154
172
 
@@ -166,8 +184,7 @@ export function App() {
166
184
  <code>VITE_FLUXYCHAT_WORKER_URL</code>.
167
185
  </p>
168
186
  <p>
169
- Then either <code>VITE_FLUXYCHAT_MEMBER_JWT</code> or{" "}
170
- <code>VITE_FLUXYCHAT_PUBLIC_ROOM_ID</code>.
187
+ Then <code>VITE_FLUXYCHAT_PUBLISHABLE_KEY</code>, a member JWT, or a public room id.
171
188
  </p>
172
189
  </main>
173
190
  );
@@ -2,6 +2,7 @@
2
2
 
3
3
  interface ImportMetaEnv {
4
4
  readonly VITE_FLUXYCHAT_WORKER_URL: string;
5
+ readonly VITE_FLUXYCHAT_PUBLISHABLE_KEY?: string;
5
6
  readonly VITE_FLUXYCHAT_MEMBER_JWT?: string;
6
7
  readonly VITE_FLUXYCHAT_ROOM_ID?: string;
7
8
  readonly VITE_FLUXYCHAT_PUBLIC_ROOM_ID?: string;
@@ -9,8 +9,8 @@
9
9
  "preview": "vite preview"
10
10
  },
11
11
  "dependencies": {
12
- "@fluxy-chat/react": "^0.1.4",
13
- "@fluxy-chat/sdk": "^0.6.5",
12
+ "@fluxy-chat/react": "^0.1.7",
13
+ "@fluxy-chat/sdk": "^0.6.10",
14
14
  "react": "^19.0.0",
15
15
  "react-dom": "^19.0.0",
16
16
  "zustand": "^5.0.0"
@@ -9,10 +9,10 @@
9
9
  "preview": "vite preview"
10
10
  },
11
11
  "dependencies": {
12
- "@fluxy-chat/ui-kit": "^0.1.3",
12
+ "@fluxy-chat/ui-kit": "^0.1.5",
13
13
  "@fluxy-chat/ui": "^0.1.4",
14
- "@fluxy-chat/react": "^0.1.4",
15
- "@fluxy-chat/sdk": "^0.6.5",
14
+ "@fluxy-chat/react": "^0.1.7",
15
+ "@fluxy-chat/sdk": "^0.6.10",
16
16
  "react": "^19.0.0",
17
17
  "react-dom": "^19.0.0"
18
18
  },
@@ -2,6 +2,7 @@ import { FluxyChatWidget } from "@fluxy-chat/ui-kit";
2
2
 
3
3
  const workerUrl = import.meta.env.VITE_FLUXYCHAT_WORKER_URL;
4
4
  const token = import.meta.env.VITE_FLUXYCHAT_MEMBER_JWT;
5
+ const publishableKey = import.meta.env.VITE_FLUXYCHAT_PUBLISHABLE_KEY;
5
6
  const roomId = import.meta.env.VITE_FLUXYCHAT_ROOM_ID || "general";
6
7
 
7
8
  export function App() {
@@ -13,6 +14,7 @@ export function App() {
13
14
  workerUrl={workerUrl}
14
15
  token={token}
15
16
  guest={!token}
17
+ publishableKey={publishableKey}
16
18
  theme="default"
17
19
  height={520}
18
20
  />
@@ -0,0 +1,13 @@
1
+ # FluxyChat worker URL (hosted or self-hosted)
2
+ VITE_FLUXYCHAT_WORKER_URL=https://your-worker.example.workers.dev
3
+
4
+ # Option A — public room, no member JWT (pin sdk 0.6.7+)
5
+ VITE_FLUXYCHAT_PUBLISHABLE_KEY=pk_...
6
+ VITE_FLUXYCHAT_ROOM_ID=your-public-room-id
7
+
8
+ # Option B — member JWT
9
+ # VITE_FLUXYCHAT_MEMBER_JWT=eyJ...
10
+ # VITE_FLUXYCHAT_ROOM_ID=demo
11
+
12
+ # Option C — public guest session without pk_
13
+ # VITE_FLUXYCHAT_PUBLIC_ROOM_ID=your-public-room-id
@@ -0,0 +1,13 @@
1
+ # FluxyChat polls
2
+
3
+ First-class room polls: `createPoll` / `votePoll` on the chat timeline.
4
+
5
+ Anonymous ballots (voter ids omitted from results) are a separate HTTP path: `POST /polls` with `isAnonymous: true`, then `POST /polls/:id/close`. That is not `createPoll`.
6
+
7
+ ```bash
8
+ npx @fluxy-chat/create-fluxy-chat@latest my-polls --example polls
9
+ cp .env.example .env
10
+ npm run dev
11
+ ```
12
+
13
+ Fastest path: `VITE_FLUXYCHAT_WORKER_URL` + `VITE_FLUXYCHAT_PUBLISHABLE_KEY` (`pk_`) + a public room id. Pin `@fluxy-chat/sdk@0.6.7` and `@fluxy-chat/react@0.1.6` once those versions are on npm.
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>FluxyChat polls</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "fluxy-polls",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "@fluxy-chat/react": "^0.1.7",
13
+ "@fluxy-chat/sdk": "^0.6.10",
14
+ "react": "^19.0.0",
15
+ "react-dom": "^19.0.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/react": "^19.0.0",
19
+ "@types/react-dom": "^19.0.0",
20
+ "@vitejs/plugin-react": "^4.3.0",
21
+ "typescript": "^5.6.0",
22
+ "vite": "^6.0.0"
23
+ }
24
+ }
@@ -0,0 +1,135 @@
1
+ import { useState } from "react";
2
+ import {
3
+ FluxyRealtimeProvider,
4
+ useChat,
5
+ useFluxyChat,
6
+ } from "@fluxy-chat/react";
7
+ import { useFluxySession, workerUrl } from "./session";
8
+
9
+ const publishableKey = import.meta.env.VITE_FLUXYCHAT_PUBLISHABLE_KEY?.trim();
10
+ const pkRoomId =
11
+ import.meta.env.VITE_FLUXYCHAT_ROOM_ID?.trim() ||
12
+ import.meta.env.VITE_FLUXYCHAT_PUBLIC_ROOM_ID?.trim() ||
13
+ "demo";
14
+
15
+ function PollRoom({ roomId }: { roomId: string }) {
16
+ const { client } = useFluxyChat();
17
+ const { messages } = useChat({ roomId });
18
+ const [question, setQuestion] = useState("Ship Friday?");
19
+ const [error, setError] = useState<string | null>(null);
20
+
21
+ async function createTimelinePoll() {
22
+ setError(null);
23
+ try {
24
+ await client.createPoll(roomId, {
25
+ question,
26
+ options: ["Yes", "No", "Abstain"],
27
+ });
28
+ } catch (err) {
29
+ setError(err instanceof Error ? err.message : "createPoll failed");
30
+ }
31
+ }
32
+
33
+ return (
34
+ <section className="panel">
35
+ <header className="chat-header">
36
+ <strong>Polls · {roomId}</strong>
37
+ <span className="status">createPoll / votePoll on the timeline</span>
38
+ </header>
39
+ <div className="composer">
40
+ <input
41
+ value={question}
42
+ onChange={(event) => setQuestion(event.target.value)}
43
+ placeholder="Question"
44
+ />
45
+ <button type="button" className="primary" onClick={() => void createTimelinePoll()}>
46
+ Create poll
47
+ </button>
48
+ </div>
49
+ {error ? <p className="error" style={{ padding: "0 1rem" }}>{error}</p> : null}
50
+ <ul className="log">
51
+ {messages.map((message) => (
52
+ <li key={message.id} className="poll-card">
53
+ {message.poll ? (
54
+ <>
55
+ <p>
56
+ <strong>{message.poll.question}</strong>
57
+ {message.poll.closed ? " · closed" : ""}
58
+ </p>
59
+ <div className="poll-options">
60
+ {message.poll.options.map((option) => (
61
+ <button
62
+ key={option.index}
63
+ type="button"
64
+ disabled={message.poll?.closed}
65
+ onClick={() => void client.votePoll(message.id, option.index)}
66
+ >
67
+ {option.text} · {option.votes}
68
+ </button>
69
+ ))}
70
+ </div>
71
+ <p className="hint" style={{ marginTop: 8 }}>
72
+ {message.poll.totalVoters} voters
73
+ {message.poll.userVote != null ? ` · you voted ${message.poll.userVote}` : ""}
74
+ </p>
75
+ </>
76
+ ) : (
77
+ <p>
78
+ <strong>{message.userId}</strong> {message.content}
79
+ </p>
80
+ )}
81
+ </li>
82
+ ))}
83
+ </ul>
84
+ </section>
85
+ );
86
+ }
87
+
88
+ export function App() {
89
+ if (!workerUrl) return <div className="shell">Set <code>VITE_FLUXYCHAT_WORKER_URL</code>.</div>;
90
+
91
+ if (publishableKey) {
92
+ return (
93
+ <div className="shell">
94
+ <p className="mode-badge">publishableKey · public room {pkRoomId}</p>
95
+ <p className="hint">
96
+ Timeline polls use createPoll. Anonymous ballots (no voter ids in results) are POST /polls with isAnonymous: true. See README.
97
+ </p>
98
+ <FluxyRealtimeProvider workerUrl={workerUrl} publishableKey={publishableKey}>
99
+ <PollRoom roomId={pkRoomId} />
100
+ </FluxyRealtimeProvider>
101
+ </div>
102
+ );
103
+ }
104
+
105
+ return <MemberOrGuestApp />;
106
+ }
107
+
108
+ function MemberOrGuestApp() {
109
+ const { session, loading, error } = useFluxySession();
110
+ if (loading) return <div className="shell">Starting…</div>;
111
+ if (error) return <div className="shell error">{error}</div>;
112
+ if (!session) {
113
+ return (
114
+ <div className="shell">
115
+ Set <code>VITE_FLUXYCHAT_PUBLISHABLE_KEY</code>, a member JWT, or a public room id.
116
+ </div>
117
+ );
118
+ }
119
+
120
+ return (
121
+ <div className="shell">
122
+ <p className="mode-badge">{session.mode} · {session.roomId}</p>
123
+ <p className="hint">
124
+ Timeline polls use createPoll. Anonymous ballots: POST /polls with isAnonymous: true. See README.
125
+ </p>
126
+ <FluxyRealtimeProvider
127
+ workerUrl={session.workerUrl}
128
+ authTokenProvider={session.token}
129
+ userId={session.userId}
130
+ >
131
+ <PollRoom roomId={session.roomId} />
132
+ </FluxyRealtimeProvider>
133
+ </div>
134
+ );
135
+ }
@@ -0,0 +1,85 @@
1
+ :root {
2
+ font-family: system-ui, -apple-system, sans-serif;
3
+ line-height: 1.5;
4
+ color: #0f172a;
5
+ background: #f8fafc;
6
+ }
7
+ * { box-sizing: border-box; }
8
+ body { margin: 0; }
9
+ .shell { max-width: 720px; margin: 0 auto; padding: 1.5rem; }
10
+ .panel {
11
+ border: 1px solid #e2e8f0;
12
+ border-radius: 12px;
13
+ overflow: hidden;
14
+ background: #fff;
15
+ }
16
+ .chat-header {
17
+ display: flex;
18
+ justify-content: space-between;
19
+ padding: 0.75rem 1rem;
20
+ background: #0f172a;
21
+ color: #fff;
22
+ }
23
+ .status { font-size: 0.75rem; opacity: 0.9; }
24
+ .roster {
25
+ margin: 0;
26
+ padding: 0.5rem 1rem;
27
+ list-style: none;
28
+ font-size: 0.8rem;
29
+ color: #64748b;
30
+ border-bottom: 1px solid #e2e8f0;
31
+ }
32
+ .log { min-height: 240px; max-height: 50vh; overflow: auto; padding: 1rem; }
33
+ .composer { display: flex; gap: 0.5rem; padding: 0.75rem; border-top: 1px solid #e2e8f0; }
34
+ .composer input { flex: 1; padding: 0.5rem 0.75rem; border: 1px solid #cbd5e1; border-radius: 8px; }
35
+ .composer button, button.primary {
36
+ padding: 0.5rem 0.75rem;
37
+ border: 0;
38
+ border-radius: 8px;
39
+ background: #2563eb;
40
+ color: #fff;
41
+ cursor: pointer;
42
+ }
43
+ .mode-badge { font-size: 0.75rem; color: #64748b; margin-bottom: 0.75rem; }
44
+ .error { color: #b91c1c; }
45
+ code { font-size: 0.85em; }
46
+ .hint { font-size: 0.8rem; color: #475569; white-space: pre-wrap; background: #f1f5f9; padding: 0.75rem; border-radius: 8px; }
47
+ .readings { margin: 0; padding: 0.75rem 1rem; list-style: none; font-size: 0.875rem; }
48
+ .canvas {
49
+ position: relative;
50
+ height: min(70vh, 480px);
51
+ background: #f8fafc;
52
+ touch-action: none;
53
+ }
54
+ .dot {
55
+ position: absolute;
56
+ width: 10px;
57
+ height: 10px;
58
+ border-radius: 999px;
59
+ transform: translate(-50%, -50%);
60
+ pointer-events: none;
61
+ }
62
+ .peer-cursor {
63
+ position: absolute;
64
+ pointer-events: none;
65
+ z-index: 2;
66
+ }
67
+ .peer-cursor span {
68
+ display: block;
69
+ margin-top: 2px;
70
+ padding: 1px 6px;
71
+ border-radius: 4px;
72
+ background: currentColor;
73
+ color: #fff;
74
+ font-size: 11px;
75
+ }
76
+ .poll-card { margin: 0 0 1rem; padding-bottom: 0.75rem; border-bottom: 1px solid #e2e8f0; }
77
+ .poll-options { display: flex; flex-wrap: wrap; gap: 0.5rem; }
78
+ .poll-options button {
79
+ padding: 0.4rem 0.7rem;
80
+ border: 1px solid #cbd5e1;
81
+ border-radius: 8px;
82
+ background: #f8fafc;
83
+ cursor: pointer;
84
+ }
85
+ .poll-options button:disabled { opacity: 0.6; cursor: default; }
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { App } from "./App";
4
+ import "./index.css";
5
+
6
+ createRoot(document.getElementById("root")!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ );
@@ -0,0 +1,73 @@
1
+ import { useEffect, useState } from "react";
2
+ import { FluxyChatClient } from "@fluxy-chat/sdk";
3
+
4
+ const workerUrl = import.meta.env.VITE_FLUXYCHAT_WORKER_URL?.trim();
5
+ const memberJwt = import.meta.env.VITE_FLUXYCHAT_MEMBER_JWT?.trim();
6
+ const publicRoomId = import.meta.env.VITE_FLUXYCHAT_PUBLIC_ROOM_ID?.trim();
7
+ const configuredRoomId = import.meta.env.VITE_FLUXYCHAT_ROOM_ID?.trim() || "demo";
8
+
9
+ export interface FluxySession {
10
+ workerUrl: string;
11
+ token: string;
12
+ userId: string;
13
+ roomId: string;
14
+ mode: "member" | "guest";
15
+ }
16
+
17
+ export function useFluxySession(): {
18
+ session: FluxySession | null;
19
+ loading: boolean;
20
+ error: string | null;
21
+ } {
22
+ const [session, setSession] = useState<FluxySession | null>(null);
23
+ const [loading, setLoading] = useState(Boolean(workerUrl));
24
+ const [error, setError] = useState<string | null>(null);
25
+
26
+ useEffect(() => {
27
+ if (!workerUrl) {
28
+ setLoading(false);
29
+ return;
30
+ }
31
+ if (memberJwt) {
32
+ setSession({
33
+ workerUrl,
34
+ token: memberJwt,
35
+ userId: "demo-user",
36
+ roomId: configuredRoomId,
37
+ mode: "member",
38
+ });
39
+ setLoading(false);
40
+ return;
41
+ }
42
+ if (publicRoomId) {
43
+ let cancelled = false;
44
+ void FluxyChatClient.joinPublicRoomAsGuest(workerUrl, publicRoomId, {
45
+ displayName: "Guest",
46
+ })
47
+ .then((guest) => {
48
+ if (cancelled) return;
49
+ setSession({
50
+ workerUrl,
51
+ token: guest.token,
52
+ userId: guest.userId,
53
+ roomId: guest.roomId,
54
+ mode: "guest",
55
+ });
56
+ })
57
+ .catch((err) => {
58
+ if (!cancelled) setError(err instanceof Error ? err.message : "Guest session failed");
59
+ })
60
+ .finally(() => {
61
+ if (!cancelled) setLoading(false);
62
+ });
63
+ return () => {
64
+ cancelled = true;
65
+ };
66
+ }
67
+ setLoading(false);
68
+ }, []);
69
+
70
+ return { session, loading, error };
71
+ }
72
+
73
+ export { workerUrl };
@@ -0,0 +1,13 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_FLUXYCHAT_WORKER_URL: string;
5
+ readonly VITE_FLUXYCHAT_PUBLISHABLE_KEY?: string;
6
+ readonly VITE_FLUXYCHAT_MEMBER_JWT?: string;
7
+ readonly VITE_FLUXYCHAT_ROOM_ID?: string;
8
+ readonly VITE_FLUXYCHAT_PUBLIC_ROOM_ID?: string;
9
+ }
10
+
11
+ interface ImportMeta {
12
+ readonly env: ImportMetaEnv;
13
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "isolatedModules": true,
11
+ "moduleDetection": "force",
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "noUncheckedSideEffectImports": true
19
+ },
20
+ "include": ["src"]
21
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: { port: 5173 },
7
+ });
@@ -1,9 +1,13 @@
1
1
  # FluxyChat worker URL (hosted or self-hosted)
2
- VITE_FLUXYCHAT_WORKER_URL=https://your-worker.example.workers.dev
2
+ VITE_FLUXYCHAT_WORKER_URL=https://api.fluxychat.com
3
3
 
4
- # Option A — member JWT (~2 min via onboarding)
5
- VITE_FLUXYCHAT_MEMBER_JWT=eyJ...
6
- VITE_FLUXYCHAT_ROOM_ID=demo
4
+ # Option A — public room, no JWT (pin @fluxy-chat/sdk@0.6.10+)
5
+ VITE_FLUXYCHAT_PUBLISHABLE_KEY=pk_...
6
+ VITE_FLUXYCHAT_ROOM_ID=general
7
7
 
8
- # Option B — public guest room (~60s, no signup)
8
+ # Option B — member JWT
9
+ # VITE_FLUXYCHAT_MEMBER_JWT=eyJ...
10
+ # VITE_FLUXYCHAT_ROOM_ID=demo
11
+
12
+ # Option C — guest session without pk_
9
13
  # VITE_FLUXYCHAT_PUBLIC_ROOM_ID=your-public-room-id