@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.
- package/CHANGELOG.md +45 -0
- package/dist/index.js +10 -5
- package/package.json +1 -1
- package/readme.md +2 -1
- package/templates/basic/package.json +19 -19
- package/templates/comments-board/package.json +2 -2
- package/templates/deal-room/package.json +2 -2
- package/templates/deal-room/src/App.tsx +17 -1
- package/templates/discord/package.json +20 -20
- package/templates/draw/package.json +25 -25
- package/templates/fleet-panel/package.json +2 -2
- package/templates/full/README.md +14 -4
- package/templates/full/fluxy.hosted.json +15 -0
- package/templates/full/package.json +4 -3
- package/templates/full/scripts/fluxy-deploy.mjs +53 -0
- package/templates/full/scripts/fluxy-setup.mjs +31 -7
- package/templates/full/src/App.tsx +14 -0
- package/templates/full/src/index.css +3 -3
- package/templates/full/src/session.ts +7 -2
- package/templates/full/src/vite-env.d.ts +2 -1
- package/templates/game-tick/package.json +2 -2
- package/templates/hr-feedback/package.json +1 -1
- package/templates/iot-panel/package.json +25 -25
- package/templates/javascript-live-cursors/package.json +1 -1
- package/templates/live-cursors/.env.example +8 -5
- package/templates/live-cursors/package.json +2 -2
- package/templates/live-cursors/src/App.tsx +19 -2
- package/templates/live-cursors/src/vite-env.d.ts +1 -0
- package/templates/live-cursors-chat/package.json +2 -2
- package/templates/minimal/package.json +3 -3
- package/templates/minimal/src/App.tsx +2 -0
- package/templates/polls/.env.example +13 -0
- package/templates/polls/README.md +13 -0
- package/templates/polls/index.html +12 -0
- package/templates/polls/package.json +24 -0
- package/templates/polls/src/App.tsx +135 -0
- package/templates/polls/src/index.css +85 -0
- package/templates/polls/src/main.tsx +10 -0
- package/templates/polls/src/session.ts +73 -0
- package/templates/polls/src/vite-env.d.ts +13 -0
- package/templates/polls/tsconfig.json +21 -0
- package/templates/polls/vite.config.ts +7 -0
- package/templates/react/.env.example +9 -5
- package/templates/react/README.md +7 -32
- package/templates/react/package.json +2 -2
- package/templates/react/src/App.tsx +31 -23
- package/templates/react/src/vite-env.d.ts +1 -0
- package/templates/slack/package.json +21 -21
- package/templates/telegram/package.json +20 -20
- package/templates/tiptap-room/package.json +29 -29
- package/templates/voice-stage/package.json +2 -2
- package/templates/war-room/package.json +2 -2
- 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
|
|
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.
|
|
13
|
-
"@fluxy-chat/sdk": "^0.6.
|
|
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"
|
|
@@ -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.
|
|
13
|
-
"@fluxy-chat/sdk": "^0.6.
|
|
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
|
+
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# FluxyChat worker URL (hosted or self-hosted)
|
|
2
|
-
VITE_FLUXYCHAT_WORKER_URL=https://
|
|
2
|
+
VITE_FLUXYCHAT_WORKER_URL=https://api.fluxychat.com
|
|
3
3
|
|
|
4
|
-
# Option A —
|
|
5
|
-
|
|
6
|
-
VITE_FLUXYCHAT_ROOM_ID=
|
|
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 —
|
|
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.
|
|
13
|
-
"@fluxy-chat/sdk": "^0.6.
|
|
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
|
|
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
|
);
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"preview": "vite preview"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@fluxy-chat/react": "^0.1.
|
|
13
|
-
"@fluxy-chat/sdk": "^0.6.
|
|
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.
|
|
12
|
+
"@fluxy-chat/ui-kit": "^0.1.5",
|
|
13
13
|
"@fluxy-chat/ui": "^0.1.4",
|
|
14
|
-
"@fluxy-chat/react": "^0.1.
|
|
15
|
-
"@fluxy-chat/sdk": "^0.6.
|
|
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,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
|
+
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# FluxyChat worker URL (hosted or self-hosted)
|
|
2
|
-
VITE_FLUXYCHAT_WORKER_URL=https://
|
|
2
|
+
VITE_FLUXYCHAT_WORKER_URL=https://api.fluxychat.com
|
|
3
3
|
|
|
4
|
-
# Option A —
|
|
5
|
-
|
|
6
|
-
VITE_FLUXYCHAT_ROOM_ID=
|
|
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 —
|
|
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
|