@fluxy-chat/create-fluxy-chat 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -30,7 +30,7 @@ function isPackageManager(value) {
30
30
  return PACKAGE_MANAGERS.has(value);
31
31
  }
32
32
  function isAdapterType(value) {
33
- return ["basic", "slack", "telegram", "discord", "web"].includes(value);
33
+ return ["basic", "slack", "telegram", "discord", "web", "react"].includes(value);
34
34
  }
35
35
  function detectPackageManagerFromLockfiles(cwd) {
36
36
  if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) {
@@ -79,6 +79,25 @@ function writeJson(projectDir, filePath, value) {
79
79
  writeFile(projectDir, filePath, `${JSON.stringify(value, null, 2)}
80
80
  `);
81
81
  }
82
+ function copyDir(source, destination) {
83
+ fs.mkdirSync(destination, { recursive: true });
84
+ for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
85
+ const srcPath = path.join(source, entry.name);
86
+ const destPath = path.join(destination, entry.name);
87
+ if (entry.isDirectory()) {
88
+ copyDir(srcPath, destPath);
89
+ } else {
90
+ fs.copyFileSync(srcPath, destPath);
91
+ }
92
+ }
93
+ }
94
+ function templatesDir() {
95
+ return path.resolve(
96
+ path.dirname(new URL(import.meta.url).pathname.replace(/^\//, "")),
97
+ "..",
98
+ "templates"
99
+ );
100
+ }
82
101
 
83
102
  // src/prompts.ts
84
103
  var DEFAULT_PROJECT_NAME = "my-fluxy-bot";
@@ -101,15 +120,18 @@ async function runPrompts(inputs) {
101
120
  if (nameError) {
102
121
  throw new Error(nameError);
103
122
  }
123
+ const minimal = inputs.minimal ?? false;
104
124
  let adapter = inputs.adapter;
105
- if (!adapter) {
125
+ if (!minimal && !adapter) {
106
126
  if (inputs.yes) {
107
- adapter = "basic";
127
+ adapter = "react";
108
128
  } else {
109
129
  const result = await select({
110
130
  message: "Select an adapter:",
111
131
  options: [
112
- { label: "Basic (Cloudflare Workers)", value: "basic" },
132
+ { label: "Minimal chat widget (ui-kit, recommended)", value: "minimal" },
133
+ { label: "React chat app (Vite + useChat)", value: "react" },
134
+ { label: "Basic (Cloudflare Workers bot)", value: "basic" },
113
135
  { label: "Slack", value: "slack" },
114
136
  { label: "Telegram", value: "telegram" },
115
137
  { label: "Discord", value: "discord" },
@@ -117,6 +139,9 @@ async function runPrompts(inputs) {
117
139
  ]
118
140
  });
119
141
  if (isCancel(result)) return null;
142
+ if (result === "minimal") {
143
+ return runPrompts({ ...inputs, minimal: true, adapter: "react" });
144
+ }
120
145
  adapter = result;
121
146
  }
122
147
  }
@@ -167,11 +192,12 @@ async function runPrompts(inputs) {
167
192
  if (isCancel(shouldInitGit)) return null;
168
193
  return {
169
194
  name,
170
- adapter,
195
+ adapter: adapter ?? "react",
171
196
  packageManager,
172
197
  language,
173
198
  shouldInstall,
174
- shouldInitGit
199
+ shouldInitGit,
200
+ minimal: minimal || inputs.minimal === true
175
201
  };
176
202
  }
177
203
 
@@ -775,6 +801,7 @@ var execAsync = promisify(exec);
775
801
  function parseArgs(argv) {
776
802
  const args = {
777
803
  yes: false,
804
+ minimal: false,
778
805
  skipInstall: false,
779
806
  noGit: false,
780
807
  help: false
@@ -786,16 +813,28 @@ function parseArgs(argv) {
786
813
  args.help = true;
787
814
  } else if (arg === "-y" || arg === "--yes") {
788
815
  args.yes = true;
816
+ } else if (arg === "--minimal") {
817
+ args.minimal = true;
789
818
  } else if (arg === "--skip-install") {
790
819
  args.skipInstall = true;
791
820
  } else if (arg === "--no-git") {
792
821
  args.noGit = true;
822
+ } else if (arg === "--template" || arg === "-t") {
823
+ const value = argv[++i];
824
+ if (value && isAdapterType(value)) {
825
+ args.adapter = value;
826
+ } else if (value === "react") {
827
+ args.adapter = "react";
828
+ } else {
829
+ console.error(`Invalid template: ${value}. Choose: react, basic, slack, telegram, discord, web`);
830
+ process.exit(1);
831
+ }
793
832
  } else if (arg === "--adapter" || arg === "-a") {
794
833
  const value = argv[++i];
795
834
  if (value && isAdapterType(value)) {
796
835
  args.adapter = value;
797
836
  } else {
798
- console.error(`Invalid adapter: ${value}. Choose: basic, slack, telegram, discord, web`);
837
+ console.error(`Invalid adapter: ${value}. Choose: react, basic, slack, telegram, discord, web`);
799
838
  process.exit(1);
800
839
  }
801
840
  } else if (arg === "--pm" || arg === "--package-manager") {
@@ -833,16 +872,20 @@ ${pc.bold("Usage:")}
833
872
  npx create-fluxy-chat [project-name] [options]
834
873
 
835
874
  ${pc.bold("Options:")}
836
- -a, --adapter <type> Adapter: basic, slack, telegram, discord, web
875
+ -a, --adapter <type> Adapter: react, basic, slack, telegram, discord, web
876
+ -t, --template <type> Alias for --adapter (e.g. react)
837
877
  --pm <manager> Package manager: npm, pnpm, yarn
838
878
  -l, --language <lang> Language: typescript (default) or javascript
839
879
  -y, --yes Skip prompts and accept defaults
880
+ --minimal Chat-only widget (ui-kit) \u2014 no platform modules
840
881
  --skip-install Skip dependency installation
841
882
  --no-git Skip git repository initialization
842
883
  -h, --help Show this help
843
884
 
844
885
  ${pc.bold("Examples:")}
845
- ${pc.cyan("npx create-fluxy-chat my-bot")}
886
+ ${pc.cyan("npx create-fluxy-chat my-chat --minimal")}
887
+ ${pc.cyan("npx create-fluxy-chat my-chat --template react")}
888
+ ${pc.cyan("npx create-fluxy-chat my-bot --adapter basic")}
846
889
  ${pc.cyan("npx create-fluxy-chat my-bot --adapter slack")}
847
890
  ${pc.cyan("npx create-fluxy-chat my-bot --adapter telegram --pm pnpm")}
848
891
  ${pc.cyan("npx create-fluxy-chat my-bot -y --adapter discord")}
@@ -860,6 +903,7 @@ async function main() {
860
903
  packageManager: args.pm,
861
904
  language: args.language,
862
905
  yes: args.yes,
906
+ minimal: args.minimal,
863
907
  shouldInstall: args.skipInstall ? false : void 0,
864
908
  shouldInitGit: args.noGit ? false : void 0
865
909
  });
@@ -882,19 +926,37 @@ async function main() {
882
926
  s.start("Creating project files");
883
927
  try {
884
928
  fs2.mkdirSync(projectDir, { recursive: true });
885
- writeJson(projectDir, "package.json", generatePackageJson(config));
886
- if (config.language === "typescript") {
887
- writeJson(projectDir, "tsconfig.json", generateTsConfig());
929
+ if (config.minimal) {
930
+ const templateRoot = path2.join(templatesDir(), "minimal");
931
+ copyDir(templateRoot, projectDir);
932
+ const pkgPath = path2.join(projectDir, "package.json");
933
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
934
+ pkg.name = config.name;
935
+ writeJson(projectDir, "package.json", pkg);
936
+ s.stop("Minimal chat widget created.");
937
+ } else if (config.adapter === "react") {
938
+ const templateRoot = path2.join(templatesDir(), "react");
939
+ copyDir(templateRoot, projectDir);
940
+ const pkgPath = path2.join(projectDir, "package.json");
941
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
942
+ pkg.name = config.name;
943
+ writeJson(projectDir, "package.json", pkg);
944
+ s.stop("React chat app created.");
945
+ } else {
946
+ writeJson(projectDir, "package.json", generatePackageJson(config));
947
+ if (config.language === "typescript") {
948
+ writeJson(projectDir, "tsconfig.json", generateTsConfig());
949
+ }
950
+ writeFile(projectDir, "wrangler.toml", generateWranglerToml(config));
951
+ writeFile(projectDir, ".dev.vars", generateDevVars());
952
+ writeFile(projectDir, ".env.example", generateEnvExample(config));
953
+ writeFile(projectDir, ".gitignore", generateGitignore());
954
+ const ext = config.language === "typescript" ? "ts" : "js";
955
+ writeFile(projectDir, `src/index.${ext}`, generateWorkerIndex(config));
956
+ writeFile(projectDir, `src/bot.${ext}`, generateBotHandler(config));
957
+ writeFile(projectDir, "README.md", generateReadme(config));
958
+ s.stop("Project files created.");
888
959
  }
889
- writeFile(projectDir, "wrangler.toml", generateWranglerToml(config));
890
- writeFile(projectDir, ".dev.vars", generateDevVars());
891
- writeFile(projectDir, ".env.example", generateEnvExample(config));
892
- writeFile(projectDir, ".gitignore", generateGitignore());
893
- const ext = config.language === "typescript" ? "ts" : "js";
894
- writeFile(projectDir, `src/index.${ext}`, generateWorkerIndex(config));
895
- writeFile(projectDir, `src/bot.${ext}`, generateBotHandler(config));
896
- writeFile(projectDir, "README.md", generateReadme(config));
897
- s.stop("Project files created.");
898
960
  } catch (error) {
899
961
  s.stop("Failed to create project files.");
900
962
  throw error;
@@ -928,7 +990,11 @@ async function main() {
928
990
  }
929
991
  }
930
992
  note(
931
- [
993
+ config.adapter === "react" ? [
994
+ `cd ${config.name}`,
995
+ "cp .env.example .env",
996
+ `${config.packageManager === "npm" ? "npm run" : config.packageManager} dev`
997
+ ].join("\n") : [
932
998
  `cd ${config.name}`,
933
999
  "cp .env.example .dev.vars",
934
1000
  `${config.packageManager === "npm" ? "npm run" : config.packageManager} dev`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluxy-chat/create-fluxy-chat",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Scaffold a new FluxyChat bot project with a single command",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/readme.md CHANGED
@@ -5,14 +5,21 @@ Scaffold a new [FluxyChat](https://github.com/AlessandroFare/fluxychat) bot proj
5
5
  ## Quick start
6
6
 
7
7
  ```bash
8
- npx create-fluxy-chat my-bot
8
+ # Minimal chat widget (recommended — 3 lines in App.tsx)
9
+ npx create-fluxy-chat my-chat --minimal
10
+
11
+ # React + useChat (more control)
12
+ npx create-fluxy-chat my-chat --template react
9
13
  ```
10
14
 
11
- This launches an interactive prompt to configure your bot project.
15
+ Interactive mode defaults to the **React chat app** template.
12
16
 
13
17
  ## Non-interactive usage
14
18
 
15
19
  ```bash
20
+ # React + Vite + useChat (recommended)
21
+ npx create-fluxy-chat my-chat --template react -y
22
+
16
23
  # Create a Slack bot with pnpm
17
24
  npx create-fluxy-chat my-bot --adapter slack --pm pnpm
18
25
 
@@ -30,10 +37,12 @@ npx create-fluxy-chat my-bot --adapter basic
30
37
 
31
38
  | Flag | Short | Description |
32
39
  | --- | --- | --- |
33
- | `--adapter <type>` | `-a` | Adapter: `basic`, `slack`, `telegram`, `discord`, `web` |
40
+ | `--adapter <type>` | `-a` | Adapter: `react`, `basic`, `slack`, `telegram`, `discord`, `web` |
41
+ | `--template <type>` | `-t` | Alias for `--adapter (e.g. react)` |
34
42
  | `--pm <manager>` | | Package manager: `npm`, `pnpm`, `yarn` |
35
43
  | `--language <lang>` | `-l` | Language: `typescript` (default) or `javascript` |
36
44
  | `--yes` | `-y` | Skip prompts and accept defaults |
45
+ | `--minimal` | | Chat-only widget (`@fluxy-chat/ui-kit`) — no platform modules |
37
46
  | `--skip-install` | | Skip dependency installation |
38
47
  | `--no-git` | | Skip git repository initialization |
39
48
  | `--help` | `-h` | Show help |
@@ -42,6 +51,7 @@ npx create-fluxy-chat my-bot --adapter basic
42
51
 
43
52
  | Adapter | Description | Platform |
44
53
  | --- | --- | --- |
54
+ | `react` | Vite + React chat UI with `useChat` | Browser / SPA |
45
55
  | `basic` | Generic webhook bot | Cloudflare Workers |
46
56
  | `slack` | Slack Events API bot | Slack |
47
57
  | `telegram` | Telegram webhook bot | Telegram |
@@ -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.0"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@cloudflare/workers-types": "^4.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.0",
13
13
  "discord.js": "^14.0.0"
14
14
  },
15
15
  "devDependencies": {
@@ -0,0 +1,3 @@
1
+ VITE_FLUXYCHAT_WORKER_URL=https://your-worker.example.com
2
+ VITE_FLUXYCHAT_MEMBER_JWT=your-member-jwt
3
+ VITE_FLUXYCHAT_ROOM_ID=general
@@ -0,0 +1,14 @@
1
+ # FluxyChat minimal starter
2
+
3
+ Three-line chat widget — no platform modules in the generated project.
4
+
5
+ ```bash
6
+ cp .env.example .env
7
+ pnpm dev
8
+ ```
9
+
10
+ Uses `@fluxy-chat/ui-kit` (`FluxyChatWidget`).
11
+
12
+ ## Full platform
13
+
14
+ See [docs.fluxychat.com](https://docs.fluxychat.com/docs) for agents, stream, IoT, and self-host.
@@ -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</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,26 @@
1
+ {
2
+ "name": "fluxy-chat-minimal",
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/ui-kit": "^0.1.0",
13
+ "@fluxy-chat/ui": "^0.1.2",
14
+ "@fluxy-chat/react": "^0.1.1",
15
+ "@fluxy-chat/sdk": "^0.6.0",
16
+ "react": "^19.0.0",
17
+ "react-dom": "^19.0.0"
18
+ },
19
+ "devDependencies": {
20
+ "@types/react": "^19.0.0",
21
+ "@types/react-dom": "^19.0.0",
22
+ "@vitejs/plugin-react": "^4.3.0",
23
+ "typescript": "^5.6.0",
24
+ "vite": "^6.0.0"
25
+ }
26
+ }
@@ -0,0 +1,24 @@
1
+ import { FluxyChatWidget } from "@fluxy-chat/ui-kit";
2
+
3
+ const workerUrl = import.meta.env.VITE_FLUXYCHAT_WORKER_URL;
4
+ const token = import.meta.env.VITE_FLUXYCHAT_MEMBER_JWT;
5
+ const roomId = import.meta.env.VITE_FLUXYCHAT_ROOM_ID || "general";
6
+
7
+ export function App() {
8
+ return (
9
+ <main style={{ maxWidth: 720, margin: "2rem auto", padding: "0 1rem" }}>
10
+ <h1 style={{ fontSize: "1.25rem", marginBottom: "1rem" }}>FluxyChat</h1>
11
+ <FluxyChatWidget
12
+ roomId={roomId}
13
+ workerUrl={workerUrl}
14
+ token={token}
15
+ theme="default"
16
+ height={520}
17
+ />
18
+ <p style={{ marginTop: "1rem", fontSize: 13, color: "#71717a" }}>
19
+ Need stream, IoT, or agents? See{" "}
20
+ <a href="https://docs.fluxychat.com/docs">full platform docs</a>.
21
+ </p>
22
+ </main>
23
+ );
24
+ }
@@ -0,0 +1,13 @@
1
+ :root {
2
+ font-family: system-ui, sans-serif;
3
+ color: #18181b;
4
+ background: #fafafa;
5
+ }
6
+
7
+ body {
8
+ margin: 0;
9
+ }
10
+
11
+ a {
12
+ color: #c2410c;
13
+ }
@@ -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,7 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_FLUXYCHAT_WORKER_URL: string;
5
+ readonly VITE_FLUXYCHAT_MEMBER_JWT: string;
6
+ readonly VITE_FLUXYCHAT_ROOM_ID?: string;
7
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "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
+ },
16
+ "include": ["src"]
17
+ }
@@ -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
+ });
@@ -0,0 +1,8 @@
1
+ # FluxyChat worker URL (hosted or self-hosted)
2
+ VITE_FLUXYCHAT_WORKER_URL=https://your-worker.example.workers.dev
3
+
4
+ # Member JWT from POST /auth/token or onboarding wizard
5
+ VITE_FLUXYCHAT_MEMBER_JWT=eyJ...
6
+
7
+ # Demo room — create in console or onboarding
8
+ VITE_FLUXYCHAT_ROOM_ID=demo
@@ -0,0 +1,33 @@
1
+ # FluxyChat React starter
2
+
3
+ Minimal Vite + React app with `useChat` — first message in minutes.
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ cp .env.example .env
9
+ # Edit .env with your Worker URL + member JWT
10
+ npm install
11
+ npm run dev
12
+ ```
13
+
14
+ Open http://localhost:5173 and send a message.
15
+
16
+ ## Get credentials
17
+
18
+ 1. **Hosted:** [fluxychat.com/onboarding](https://fluxychat.com/onboarding) → copy Worker URL + JWT
19
+ 2. **Local monorepo:** `pnpm run first-message` from the FluxyChat repo
20
+
21
+ ## Scripts
22
+
23
+ | Command | Description |
24
+ |---------|-------------|
25
+ | `npm run dev` | Vite dev server |
26
+ | `npm run build` | Production build |
27
+ | `npm run preview` | Preview production build |
28
+
29
+ ## Next steps
30
+
31
+ - Add `@fluxy-chat/ui` for polished message bubbles
32
+ - Use `useInbox` for unified feed
33
+ - See [chat-only quickstart](https://docs.fluxychat.com/docs/getting-started/chat-only-quickstart)
@@ -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</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,25 @@
1
+ {
2
+ "name": "fluxy-chat-app",
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.1",
13
+ "@fluxy-chat/sdk": "^0.6.0",
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
+ }
@@ -0,0 +1,95 @@
1
+ import { useMemo, useState } from "react";
2
+ import { FluxyChatClient } from "@fluxy-chat/sdk";
3
+ import { FluxyRealtimeProvider, useChat } from "@fluxy-chat/react";
4
+
5
+ const workerUrl = import.meta.env.VITE_FLUXYCHAT_WORKER_URL;
6
+ const memberJwt = import.meta.env.VITE_FLUXYCHAT_MEMBER_JWT;
7
+ const defaultRoomId = import.meta.env.VITE_FLUXYCHAT_ROOM_ID || "demo";
8
+
9
+ function ChatPanel({ roomId }: { roomId: string }) {
10
+ const { messages, sendMessage, connectionState } = useChat({
11
+ roomId,
12
+ markReadLatest: true,
13
+ });
14
+ const [draft, setDraft] = useState("");
15
+
16
+ return (
17
+ <section className="chat-panel">
18
+ <header className="chat-header">
19
+ <strong>{roomId}</strong>
20
+ <span className="status">{connectionState.status}</span>
21
+ </header>
22
+ <ul className="messages">
23
+ {messages.map((m) => (
24
+ <li key={m.id} className="message">
25
+ <span className="author">{m.userId}</span>
26
+ <span>{m.content}</span>
27
+ </li>
28
+ ))}
29
+ </ul>
30
+ <form
31
+ className="composer"
32
+ onSubmit={(e) => {
33
+ e.preventDefault();
34
+ const text = draft.trim();
35
+ if (!text) return;
36
+ void sendMessage(text);
37
+ setDraft("");
38
+ }}
39
+ >
40
+ <input
41
+ value={draft}
42
+ onChange={(e) => setDraft(e.target.value)}
43
+ placeholder="Type a message…"
44
+ aria-label="Message"
45
+ />
46
+ <button type="submit">Send</button>
47
+ </form>
48
+ </section>
49
+ );
50
+ }
51
+
52
+ export function App() {
53
+ const [roomId, setRoomId] = useState(defaultRoomId);
54
+
55
+ const client = useMemo(() => {
56
+ if (!workerUrl?.trim() || !memberJwt?.trim()) return null;
57
+ return new FluxyChatClient({
58
+ baseUrl: workerUrl.trim(),
59
+ userId: "demo-user",
60
+ token: memberJwt.trim(),
61
+ });
62
+ }, []);
63
+
64
+ if (!client) {
65
+ return (
66
+ <main className="shell">
67
+ <h1>FluxyChat — chat-only starter</h1>
68
+ <p>
69
+ Copy <code>.env.example</code> to <code>.env</code> and set{" "}
70
+ <code>VITE_FLUXYCHAT_WORKER_URL</code> and <code>VITE_FLUXYCHAT_MEMBER_JWT</code>.
71
+ </p>
72
+ <p>
73
+ Get credentials from{" "}
74
+ <a href="https://fluxychat.com/onboarding" target="_blank" rel="noreferrer">
75
+ onboarding
76
+ </a>{" "}
77
+ or <code>pnpm run first-message</code> in the monorepo.
78
+ </p>
79
+ </main>
80
+ );
81
+ }
82
+
83
+ return (
84
+ <main className="shell">
85
+ <h1>FluxyChat</h1>
86
+ <label className="room-picker">
87
+ Room
88
+ <input value={roomId} onChange={(e) => setRoomId(e.target.value)} />
89
+ </label>
90
+ <FluxyRealtimeProvider client={client}>
91
+ <ChatPanel roomId={roomId.trim() || defaultRoomId} />
92
+ </FluxyRealtimeProvider>
93
+ </main>
94
+ );
95
+ }
@@ -0,0 +1,106 @@
1
+ :root {
2
+ font-family: system-ui, -apple-system, sans-serif;
3
+ line-height: 1.5;
4
+ color: #0f172a;
5
+ background: #f8fafc;
6
+ }
7
+
8
+ * {
9
+ box-sizing: border-box;
10
+ }
11
+
12
+ body {
13
+ margin: 0;
14
+ }
15
+
16
+ .shell {
17
+ max-width: 720px;
18
+ margin: 0 auto;
19
+ padding: 1.5rem;
20
+ }
21
+
22
+ .chat-panel {
23
+ display: flex;
24
+ flex-direction: column;
25
+ height: min(70vh, 560px);
26
+ border: 1px solid #e2e8f0;
27
+ border-radius: 12px;
28
+ background: #fff;
29
+ overflow: hidden;
30
+ }
31
+
32
+ .chat-header {
33
+ display: flex;
34
+ justify-content: space-between;
35
+ padding: 0.75rem 1rem;
36
+ border-bottom: 1px solid #e2e8f0;
37
+ background: #2563eb;
38
+ color: #fff;
39
+ }
40
+
41
+ .status {
42
+ font-size: 0.75rem;
43
+ opacity: 0.9;
44
+ }
45
+
46
+ .messages {
47
+ flex: 1;
48
+ overflow-y: auto;
49
+ list-style: none;
50
+ margin: 0;
51
+ padding: 1rem;
52
+ display: flex;
53
+ flex-direction: column;
54
+ gap: 0.5rem;
55
+ }
56
+
57
+ .message {
58
+ display: flex;
59
+ flex-direction: column;
60
+ gap: 0.15rem;
61
+ padding: 0.5rem 0.75rem;
62
+ border-radius: 8px;
63
+ background: #f1f5f9;
64
+ }
65
+
66
+ .author {
67
+ font-size: 0.7rem;
68
+ color: #64748b;
69
+ }
70
+
71
+ .composer {
72
+ display: flex;
73
+ gap: 0.5rem;
74
+ padding: 0.75rem;
75
+ border-top: 1px solid #e2e8f0;
76
+ }
77
+
78
+ .composer input {
79
+ flex: 1;
80
+ border: 1px solid #cbd5e1;
81
+ border-radius: 8px;
82
+ padding: 0.5rem 0.75rem;
83
+ }
84
+
85
+ .composer button {
86
+ border: none;
87
+ border-radius: 8px;
88
+ background: #2563eb;
89
+ color: #fff;
90
+ padding: 0.5rem 1rem;
91
+ cursor: pointer;
92
+ }
93
+
94
+ .room-picker {
95
+ display: flex;
96
+ flex-direction: column;
97
+ gap: 0.25rem;
98
+ margin-bottom: 1rem;
99
+ font-size: 0.875rem;
100
+ }
101
+
102
+ .room-picker input {
103
+ padding: 0.5rem 0.75rem;
104
+ border: 1px solid #cbd5e1;
105
+ border-radius: 8px;
106
+ }
@@ -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,11 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_FLUXYCHAT_WORKER_URL: string;
5
+ readonly VITE_FLUXYCHAT_MEMBER_JWT: string;
6
+ readonly VITE_FLUXYCHAT_ROOM_ID: string;
7
+ }
8
+
9
+ interface ImportMeta {
10
+ readonly env: ImportMetaEnv;
11
+ }
@@ -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
+ });
@@ -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.0",
13
13
  "@slack/bolt": "^4.0.0",
14
14
  "@slack/web-api": "^7.0.0"
15
15
  },
@@ -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.0",
13
13
  "node-telegram-bot-api": "^0.66.0"
14
14
  },
15
15
  "devDependencies": {