realtimex-crm 0.1.3 → 0.2.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/README.md +48 -1
- package/bin/create-realtimex-crm.js +655 -0
- package/dist/assets/{DealList-DqDrFeDV.js → DealList-D2733gLg.js} +2 -2
- package/dist/assets/{DealList-DqDrFeDV.js.map → DealList-D2733gLg.js.map} +1 -1
- package/dist/assets/index-DY3zstf2.css +1 -0
- package/dist/assets/{index-CDIy4x-0.js → index-NL4AEbnn.js} +3 -3
- package/dist/assets/{index-CDIy4x-0.js.map → index-NL4AEbnn.js.map} +1 -1
- package/dist/index.html +1 -1
- package/dist/stats.html +1 -1
- package/package.json +5 -1
- package/src/components/atomic-crm/dashboard/Welcome.tsx +7 -7
- package/src/components/atomic-crm/login/SignupPage.tsx +1 -1
- package/src/components/atomic-crm/root/defaultConfiguration.ts +1 -1
- package/src/components/atomic-crm/setup/SupabaseSetupWizard.tsx +1 -1
- package/dist/assets/index-BiQoGq1P.css +0 -1
package/README.md
CHANGED
|
@@ -20,7 +20,29 @@ RealTimeX CRM is free and open-source.
|
|
|
20
20
|
- 🛠️ **Customize Everything**: Add custom fields, change the theme, and replace any component to fit your needs.
|
|
21
21
|
- 🗄️ **Bring Your Own Database**: Configure Supabase connection via UI or environment variables.
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### Option 1: Use the CLI (Recommended)
|
|
26
|
+
|
|
27
|
+
Create a new CRM project in seconds:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx create-realtimex-crm@latest
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The CLI will guide you through:
|
|
34
|
+
1. Choosing a template (standalone app, RealTimeX Local App, or component)
|
|
35
|
+
2. Configuring Supabase (optional)
|
|
36
|
+
3. Setting up your project structure
|
|
37
|
+
|
|
38
|
+
Then:
|
|
39
|
+
```bash
|
|
40
|
+
cd my-crm
|
|
41
|
+
npm install
|
|
42
|
+
npm run dev
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Option 2: Clone and Customize
|
|
24
46
|
|
|
25
47
|
To run this project locally, you will need the following tools installed on your computer:
|
|
26
48
|
|
|
@@ -118,6 +140,31 @@ RealTimeX CRM components are published as a Shadcn Registry file:
|
|
|
118
140
|
> [!WARNING]
|
|
119
141
|
> If the `registry.json` misses some changes you made, you MUST update the `scripts/generate-registry.mjs` to include those changes.
|
|
120
142
|
|
|
143
|
+
## CLI Templates
|
|
144
|
+
|
|
145
|
+
The `create-realtimex-crm` CLI provides three templates:
|
|
146
|
+
|
|
147
|
+
### 1. Standalone App
|
|
148
|
+
A complete CRM application ready to deploy:
|
|
149
|
+
- Full Vite + React setup
|
|
150
|
+
- TypeScript configuration
|
|
151
|
+
- Production build scripts
|
|
152
|
+
- Environment-based configuration
|
|
153
|
+
|
|
154
|
+
### 2. RealTimeX Local App
|
|
155
|
+
CRM integrated with RealTimeX.ai platform:
|
|
156
|
+
- `@realtimex/app-sdk` integration
|
|
157
|
+
- Auto-scoped data by user
|
|
158
|
+
- Parent-child user support
|
|
159
|
+
- Platform authentication
|
|
160
|
+
- See `LOCAL_APP.md` in generated project for details
|
|
161
|
+
|
|
162
|
+
### 3. Component Integration
|
|
163
|
+
Use RealTimeX CRM as a component in your existing app:
|
|
164
|
+
- Install via npm
|
|
165
|
+
- Import and configure
|
|
166
|
+
- Example integration code included
|
|
167
|
+
|
|
121
168
|
## NPM Package
|
|
122
169
|
|
|
123
170
|
RealTimeX CRM is available as an npm package:
|
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { mkdir, writeFile, copyFile } from "node:fs/promises";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import { execSync } from "node:child_process";
|
|
8
|
+
import { confirm, input, select } from "@inquirer/prompts";
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
|
|
13
|
+
const TEMPLATES = {
|
|
14
|
+
standalone: "Create a standalone CRM application",
|
|
15
|
+
localapp: "Create a RealTimeX Local App integration",
|
|
16
|
+
component: "Use as a React component in existing app",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
async function main() {
|
|
20
|
+
console.log(`
|
|
21
|
+
╔═══════════════════════════════════════╗
|
|
22
|
+
║ ║
|
|
23
|
+
║ RealTimeX CRM Setup ║
|
|
24
|
+
║ ║
|
|
25
|
+
╘═══════════════════════════════════════╝
|
|
26
|
+
`);
|
|
27
|
+
|
|
28
|
+
// Get project name
|
|
29
|
+
const projectName = await input({
|
|
30
|
+
message: "Project name:",
|
|
31
|
+
default: "my-crm",
|
|
32
|
+
validate: (value) => {
|
|
33
|
+
if (!value.trim()) return "Project name is required";
|
|
34
|
+
if (!/^[a-z0-9-_]+$/.test(value))
|
|
35
|
+
return "Project name must be lowercase alphanumeric with dashes/underscores only";
|
|
36
|
+
return true;
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const projectPath = join(process.cwd(), projectName);
|
|
41
|
+
|
|
42
|
+
// Check if directory exists
|
|
43
|
+
if (existsSync(projectPath)) {
|
|
44
|
+
const overwrite = await confirm({
|
|
45
|
+
message: `Directory "${projectName}" already exists. Overwrite?`,
|
|
46
|
+
default: false,
|
|
47
|
+
});
|
|
48
|
+
if (!overwrite) {
|
|
49
|
+
console.log("Setup cancelled.");
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Select template
|
|
55
|
+
const template = await select({
|
|
56
|
+
message: "Select a template:",
|
|
57
|
+
choices: Object.entries(TEMPLATES).map(([value, name]) => ({
|
|
58
|
+
value,
|
|
59
|
+
name,
|
|
60
|
+
})),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Get Supabase configuration
|
|
64
|
+
const configureSupabase = await confirm({
|
|
65
|
+
message: "Configure Supabase now?",
|
|
66
|
+
default: false,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
let supabaseUrl = "";
|
|
70
|
+
let supabaseAnonKey = "";
|
|
71
|
+
|
|
72
|
+
if (configureSupabase) {
|
|
73
|
+
supabaseUrl = await input({
|
|
74
|
+
message: "Supabase URL:",
|
|
75
|
+
validate: (value) => {
|
|
76
|
+
if (!value.trim()) return "Supabase URL is required";
|
|
77
|
+
if (!value.includes("supabase.co"))
|
|
78
|
+
return "URL should be a valid Supabase project URL";
|
|
79
|
+
return true;
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
supabaseAnonKey = await input({
|
|
84
|
+
message: "Supabase Anon Key:",
|
|
85
|
+
validate: (value) => {
|
|
86
|
+
if (!value.trim()) return "Supabase Anon Key is required";
|
|
87
|
+
return true;
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log("\n📦 Creating project...\n");
|
|
93
|
+
|
|
94
|
+
// Create project directory
|
|
95
|
+
await mkdir(projectPath, { recursive: true });
|
|
96
|
+
|
|
97
|
+
// Generate files based on template
|
|
98
|
+
if (template === "standalone") {
|
|
99
|
+
await createStandaloneApp(projectPath, projectName, {
|
|
100
|
+
supabaseUrl,
|
|
101
|
+
supabaseAnonKey,
|
|
102
|
+
});
|
|
103
|
+
} else if (template === "localapp") {
|
|
104
|
+
await createLocalApp(projectPath, projectName, {
|
|
105
|
+
supabaseUrl,
|
|
106
|
+
supabaseAnonKey,
|
|
107
|
+
});
|
|
108
|
+
} else if (template === "component") {
|
|
109
|
+
await createComponentSetup(projectPath, projectName, {
|
|
110
|
+
supabaseUrl,
|
|
111
|
+
supabaseAnonKey,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.log("\n✅ Project created successfully!\n");
|
|
116
|
+
console.log("Next steps:\n");
|
|
117
|
+
console.log(` cd ${projectName}`);
|
|
118
|
+
console.log(` npm install`);
|
|
119
|
+
|
|
120
|
+
if (!configureSupabase) {
|
|
121
|
+
console.log(
|
|
122
|
+
` # Configure Supabase in .env or via Settings → Database in the app`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (template === "standalone") {
|
|
127
|
+
console.log(` npm run dev # Start development server`);
|
|
128
|
+
console.log(` npm run build # Build for production`);
|
|
129
|
+
} else if (template === "localapp") {
|
|
130
|
+
console.log(
|
|
131
|
+
` npm run dev # Start as RealTimeX Local App (localhost:5173)`,
|
|
132
|
+
);
|
|
133
|
+
console.log(` npm run build # Build for production`);
|
|
134
|
+
console.log(
|
|
135
|
+
`\n📖 See LOCAL_APP.md for RealTimeX.ai integration instructions`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log("");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function createStandaloneApp(projectPath, projectName, config) {
|
|
143
|
+
const packageJson = {
|
|
144
|
+
name: projectName,
|
|
145
|
+
version: "0.1.0",
|
|
146
|
+
private: true,
|
|
147
|
+
type: "module",
|
|
148
|
+
scripts: {
|
|
149
|
+
dev: "vite --force",
|
|
150
|
+
build: "tsc && vite build",
|
|
151
|
+
preview: "vite preview",
|
|
152
|
+
typecheck: "tsc --noEmit",
|
|
153
|
+
lint: "eslint **/*.{mjs,ts,tsx}",
|
|
154
|
+
},
|
|
155
|
+
dependencies: {
|
|
156
|
+
"realtimex-crm": "latest",
|
|
157
|
+
react: "^19.1.0",
|
|
158
|
+
"react-dom": "^19.1.0",
|
|
159
|
+
},
|
|
160
|
+
devDependencies: {
|
|
161
|
+
"@types/react": "^19.1.8",
|
|
162
|
+
"@types/react-dom": "^19.1.6",
|
|
163
|
+
"@vitejs/plugin-react": "^4.6.0",
|
|
164
|
+
typescript: "~5.8.3",
|
|
165
|
+
vite: "^7.0.4",
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
await writeFile(
|
|
170
|
+
join(projectPath, "package.json"),
|
|
171
|
+
JSON.stringify(packageJson, null, 2),
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Create src/App.tsx
|
|
175
|
+
const appTsx = `import { CRM } from "realtimex-crm";
|
|
176
|
+
import "realtimex-crm/dist/style.css";
|
|
177
|
+
|
|
178
|
+
function App() {
|
|
179
|
+
return (
|
|
180
|
+
<CRM
|
|
181
|
+
title="My CRM"
|
|
182
|
+
// Customize your CRM here
|
|
183
|
+
// darkModeLogo="./logos/dark.svg"
|
|
184
|
+
// lightModeLogo="./logos/light.svg"
|
|
185
|
+
// contactGender={[...]}
|
|
186
|
+
// dealStages={[...]}
|
|
187
|
+
/>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export default App;
|
|
192
|
+
`;
|
|
193
|
+
|
|
194
|
+
await mkdir(join(projectPath, "src"), { recursive: true });
|
|
195
|
+
await writeFile(join(projectPath, "src/App.tsx"), appTsx);
|
|
196
|
+
|
|
197
|
+
// Create src/main.tsx
|
|
198
|
+
const mainTsx = `import React from "react";
|
|
199
|
+
import ReactDOM from "react-dom/client";
|
|
200
|
+
import App from "./App";
|
|
201
|
+
|
|
202
|
+
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
203
|
+
<React.StrictMode>
|
|
204
|
+
<App />
|
|
205
|
+
</React.StrictMode>
|
|
206
|
+
);
|
|
207
|
+
`;
|
|
208
|
+
|
|
209
|
+
await writeFile(join(projectPath, "src/main.tsx"), mainTsx);
|
|
210
|
+
|
|
211
|
+
// Create index.html
|
|
212
|
+
const indexHtml = `<!DOCTYPE html>
|
|
213
|
+
<html lang="en">
|
|
214
|
+
<head>
|
|
215
|
+
<meta charset="UTF-8" />
|
|
216
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
217
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
218
|
+
<title>CRM</title>
|
|
219
|
+
</head>
|
|
220
|
+
<body>
|
|
221
|
+
<div id="root"></div>
|
|
222
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
223
|
+
</body>
|
|
224
|
+
</html>
|
|
225
|
+
`;
|
|
226
|
+
|
|
227
|
+
await writeFile(join(projectPath, "index.html"), indexHtml);
|
|
228
|
+
|
|
229
|
+
// Create vite.config.ts
|
|
230
|
+
const viteConfig = `import { defineConfig } from "vite";
|
|
231
|
+
import react from "@vitejs/plugin-react";
|
|
232
|
+
|
|
233
|
+
export default defineConfig({
|
|
234
|
+
plugins: [react()],
|
|
235
|
+
server: {
|
|
236
|
+
port: 5173,
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
`;
|
|
240
|
+
|
|
241
|
+
await writeFile(join(projectPath, "vite.config.ts"), viteConfig);
|
|
242
|
+
|
|
243
|
+
// Create tsconfig.json
|
|
244
|
+
const tsConfig = {
|
|
245
|
+
compilerOptions: {
|
|
246
|
+
target: "ES2020",
|
|
247
|
+
useDefineForClassFields: true,
|
|
248
|
+
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
249
|
+
module: "ESNext",
|
|
250
|
+
skipLibCheck: true,
|
|
251
|
+
moduleResolution: "bundler",
|
|
252
|
+
allowImportingTsExtensions: true,
|
|
253
|
+
resolveJsonModule: true,
|
|
254
|
+
isolatedModules: true,
|
|
255
|
+
noEmit: true,
|
|
256
|
+
jsx: "react-jsx",
|
|
257
|
+
strict: true,
|
|
258
|
+
noUnusedLocals: true,
|
|
259
|
+
noUnusedParameters: true,
|
|
260
|
+
noFallthroughCasesInSwitch: true,
|
|
261
|
+
},
|
|
262
|
+
include: ["src"],
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
await writeFile(
|
|
266
|
+
join(projectPath, "tsconfig.json"),
|
|
267
|
+
JSON.stringify(tsConfig, null, 2),
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
// Create .env file if config provided
|
|
271
|
+
if (config.supabaseUrl && config.supabaseAnonKey) {
|
|
272
|
+
const envContent = `VITE_SUPABASE_URL=${config.supabaseUrl}
|
|
273
|
+
VITE_SUPABASE_ANON_KEY=${config.supabaseAnonKey}
|
|
274
|
+
`;
|
|
275
|
+
await writeFile(join(projectPath, ".env"), envContent);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Create .gitignore
|
|
279
|
+
const gitignore = `# Dependencies
|
|
280
|
+
node_modules
|
|
281
|
+
|
|
282
|
+
# Build output
|
|
283
|
+
dist
|
|
284
|
+
dist-ssr
|
|
285
|
+
*.local
|
|
286
|
+
|
|
287
|
+
# Environment
|
|
288
|
+
.env
|
|
289
|
+
.env.local
|
|
290
|
+
.env.production.local
|
|
291
|
+
|
|
292
|
+
# Editor
|
|
293
|
+
.vscode/*
|
|
294
|
+
!.vscode/extensions.json
|
|
295
|
+
.idea
|
|
296
|
+
.DS_Store
|
|
297
|
+
*.suo
|
|
298
|
+
*.ntvs*
|
|
299
|
+
*.njsproj
|
|
300
|
+
*.sln
|
|
301
|
+
*.sw?
|
|
302
|
+
`;
|
|
303
|
+
|
|
304
|
+
await writeFile(join(projectPath, ".gitignore"), gitignore);
|
|
305
|
+
|
|
306
|
+
// Create README.md
|
|
307
|
+
const readme = `# ${projectName}
|
|
308
|
+
|
|
309
|
+
A CRM application built with RealTimeX CRM.
|
|
310
|
+
|
|
311
|
+
## Getting Started
|
|
312
|
+
|
|
313
|
+
1. Install dependencies:
|
|
314
|
+
\`\`\`bash
|
|
315
|
+
npm install
|
|
316
|
+
\`\`\`
|
|
317
|
+
|
|
318
|
+
2. Configure Supabase:
|
|
319
|
+
- Create a \`.env\` file with your Supabase credentials:
|
|
320
|
+
\`\`\`
|
|
321
|
+
VITE_SUPABASE_URL=https://xxxxx.supabase.co
|
|
322
|
+
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
323
|
+
\`\`\`
|
|
324
|
+
- Or configure via the UI after starting the app
|
|
325
|
+
|
|
326
|
+
3. Start development server:
|
|
327
|
+
\`\`\`bash
|
|
328
|
+
npm run dev
|
|
329
|
+
\`\`\`
|
|
330
|
+
|
|
331
|
+
4. Open [http://localhost:5173](http://localhost:5173)
|
|
332
|
+
|
|
333
|
+
## Customization
|
|
334
|
+
|
|
335
|
+
Edit \`src/App.tsx\` to customize your CRM:
|
|
336
|
+
|
|
337
|
+
- \`title\`: App title
|
|
338
|
+
- \`darkModeLogo\` / \`lightModeLogo\`: Custom logos
|
|
339
|
+
- \`contactGender\`: Gender options
|
|
340
|
+
- \`companySectors\`: Industry sectors
|
|
341
|
+
- \`dealStages\`: Deal pipeline stages
|
|
342
|
+
- \`dealCategories\`: Deal categories
|
|
343
|
+
- \`noteStatuses\`: Note status options
|
|
344
|
+
- \`taskTypes\`: Task type options
|
|
345
|
+
|
|
346
|
+
See [RealTimeX CRM Documentation](https://github.com/therealtimex/realtimex-crm) for more options.
|
|
347
|
+
|
|
348
|
+
## Build for Production
|
|
349
|
+
|
|
350
|
+
\`\`\`bash
|
|
351
|
+
npm run build
|
|
352
|
+
\`\`\`
|
|
353
|
+
|
|
354
|
+
The built files will be in the \`dist\` directory.
|
|
355
|
+
`;
|
|
356
|
+
|
|
357
|
+
await writeFile(join(projectPath, "README.md"), readme);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async function createLocalApp(projectPath, projectName, config) {
|
|
361
|
+
// Start with standalone setup
|
|
362
|
+
await createStandaloneApp(projectPath, projectName, config);
|
|
363
|
+
|
|
364
|
+
// Update package.json with RealTimeX App SDK
|
|
365
|
+
const packageJsonPath = join(projectPath, "package.json");
|
|
366
|
+
const packageJson = JSON.parse(
|
|
367
|
+
await import("fs").then((fs) => fs.promises.readFile(packageJsonPath, "utf8")),
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
packageJson.dependencies["@realtimex/app-sdk"] = "latest";
|
|
371
|
+
|
|
372
|
+
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
373
|
+
|
|
374
|
+
// Create updated App.tsx with RealTimeX integration
|
|
375
|
+
const appTsx = `import { RealTimeXApp } from "@realtimex/app-sdk";
|
|
376
|
+
import { SupabaseProvider } from "@realtimex/app-sdk/providers/supabase";
|
|
377
|
+
import { CRM } from "realtimex-crm";
|
|
378
|
+
import "realtimex-crm/dist/style.css";
|
|
379
|
+
|
|
380
|
+
function App() {
|
|
381
|
+
return (
|
|
382
|
+
<RealTimeXApp
|
|
383
|
+
appId="${projectName}"
|
|
384
|
+
appName="CRM"
|
|
385
|
+
version="1.0.0"
|
|
386
|
+
description="Full-featured CRM for RealTimeX"
|
|
387
|
+
>
|
|
388
|
+
<SupabaseProvider
|
|
389
|
+
url={import.meta.env.VITE_SUPABASE_URL}
|
|
390
|
+
anonKey={import.meta.env.VITE_SUPABASE_ANON_KEY}
|
|
391
|
+
autoScope={{
|
|
392
|
+
enabled: true,
|
|
393
|
+
userIdField: "realtimex_user_id",
|
|
394
|
+
}}
|
|
395
|
+
>
|
|
396
|
+
<CRM
|
|
397
|
+
title="CRM"
|
|
398
|
+
// Customize your CRM here
|
|
399
|
+
/>
|
|
400
|
+
</SupabaseProvider>
|
|
401
|
+
</RealTimeXApp>
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export default App;
|
|
406
|
+
`;
|
|
407
|
+
|
|
408
|
+
await writeFile(join(projectPath, "src/App.tsx"), appTsx);
|
|
409
|
+
|
|
410
|
+
// Create LOCAL_APP.md documentation
|
|
411
|
+
const localAppDoc = `# RealTimeX Local App Integration
|
|
412
|
+
|
|
413
|
+
This CRM is configured as a RealTimeX Local App, which allows it to integrate with the RealTimeX.ai platform.
|
|
414
|
+
|
|
415
|
+
## What is a RealTimeX Local App?
|
|
416
|
+
|
|
417
|
+
A Local App runs in the user's browser and communicates with RealTimeX.ai via the App SDK. It receives:
|
|
418
|
+
- User authentication context
|
|
419
|
+
- Parent-child user relationships
|
|
420
|
+
- Global state management
|
|
421
|
+
- Platform-level permissions
|
|
422
|
+
|
|
423
|
+
## Architecture
|
|
424
|
+
|
|
425
|
+
\`\`\`
|
|
426
|
+
RealTimeX Platform (realtimex.ai)
|
|
427
|
+
↓ (postMessage API)
|
|
428
|
+
Local App (localhost:5173 or embedded)
|
|
429
|
+
↓ (HTTP + RLS)
|
|
430
|
+
Supabase Database
|
|
431
|
+
\`\`\`
|
|
432
|
+
|
|
433
|
+
## Key Features
|
|
434
|
+
|
|
435
|
+
1. **Authentication via Platform**: Users authenticate with RealTimeX, not Supabase directly
|
|
436
|
+
2. **Auto-scoping**: Data is automatically filtered by \`realtimex_user_id\`
|
|
437
|
+
3. **Parent-child support**: Parent users can see their children's data
|
|
438
|
+
4. **No JWT tokens**: Uses platform-provided user headers instead
|
|
439
|
+
|
|
440
|
+
## Database Schema Changes
|
|
441
|
+
|
|
442
|
+
To use this as a Local App, your Supabase schema needs \`realtimex_user_id\` instead of \`sales_id\`:
|
|
443
|
+
|
|
444
|
+
\`\`\`sql
|
|
445
|
+
-- Add RealTimeX user ID to all tables
|
|
446
|
+
ALTER TABLE contacts ADD COLUMN realtimex_user_id INTEGER;
|
|
447
|
+
ALTER TABLE companies ADD COLUMN realtimex_user_id INTEGER;
|
|
448
|
+
ALTER TABLE deals ADD COLUMN realtimex_user_id INTEGER;
|
|
449
|
+
ALTER TABLE tasks ADD COLUMN realtimex_user_id INTEGER;
|
|
450
|
+
-- etc.
|
|
451
|
+
|
|
452
|
+
-- Create indexes
|
|
453
|
+
CREATE INDEX idx_contacts_rtx_user ON contacts(realtimex_user_id);
|
|
454
|
+
CREATE INDEX idx_companies_rtx_user ON companies(realtimex_user_id);
|
|
455
|
+
-- etc.
|
|
456
|
+
|
|
457
|
+
-- Update RLS policies to use realtimex_user_id
|
|
458
|
+
CREATE POLICY "Users see own contacts" ON contacts
|
|
459
|
+
FOR SELECT USING (
|
|
460
|
+
realtimex_user_id = current_setting('request.headers')::json->>'x-realtimex-user-id'::INTEGER
|
|
461
|
+
);
|
|
462
|
+
\`\`\`
|
|
463
|
+
|
|
464
|
+
## Running as Local App
|
|
465
|
+
|
|
466
|
+
### Development Mode
|
|
467
|
+
|
|
468
|
+
\`\`\`bash
|
|
469
|
+
npm run dev
|
|
470
|
+
\`\`\`
|
|
471
|
+
|
|
472
|
+
Then register in RealTimeX.ai:
|
|
473
|
+
1. Go to RealTimeX.ai → Settings → Local Apps
|
|
474
|
+
2. Add new app:
|
|
475
|
+
- Name: CRM
|
|
476
|
+
- URL: http://localhost:5173
|
|
477
|
+
- App ID: ${projectName}
|
|
478
|
+
|
|
479
|
+
### Production Mode
|
|
480
|
+
|
|
481
|
+
Build and deploy to any static host:
|
|
482
|
+
|
|
483
|
+
\`\`\`bash
|
|
484
|
+
npm run build
|
|
485
|
+
\`\`\`
|
|
486
|
+
|
|
487
|
+
Deploy \`dist/\` to:
|
|
488
|
+
- Vercel
|
|
489
|
+
- Netlify
|
|
490
|
+
- GitHub Pages
|
|
491
|
+
- Your own CDN
|
|
492
|
+
|
|
493
|
+
Then update the Local App URL in RealTimeX.ai settings.
|
|
494
|
+
|
|
495
|
+
## Configuration
|
|
496
|
+
|
|
497
|
+
Environment variables:
|
|
498
|
+
\`\`\`
|
|
499
|
+
VITE_SUPABASE_URL=https://xxxxx.supabase.co
|
|
500
|
+
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
501
|
+
\`\`\`
|
|
502
|
+
|
|
503
|
+
## SDK Usage
|
|
504
|
+
|
|
505
|
+
The \`@realtimex/app-sdk\` provides:
|
|
506
|
+
|
|
507
|
+
### Get Current User
|
|
508
|
+
\`\`\`typescript
|
|
509
|
+
import { useRealTimeXUser } from "@realtimex/app-sdk";
|
|
510
|
+
|
|
511
|
+
function MyComponent() {
|
|
512
|
+
const user = useRealTimeXUser();
|
|
513
|
+
// { id: 123, email: "user@example.com", role: "user" }
|
|
514
|
+
}
|
|
515
|
+
\`\`\`
|
|
516
|
+
|
|
517
|
+
### Navigate to Platform
|
|
518
|
+
\`\`\`typescript
|
|
519
|
+
import { useRealTimeXNavigate } from "@realtimex/app-sdk";
|
|
520
|
+
|
|
521
|
+
function MyComponent() {
|
|
522
|
+
const navigate = useRealTimeXNavigate();
|
|
523
|
+
navigate("/contacts/123"); // Navigate within RealTimeX
|
|
524
|
+
}
|
|
525
|
+
\`\`\`
|
|
526
|
+
|
|
527
|
+
### Access Global State
|
|
528
|
+
\`\`\`typescript
|
|
529
|
+
import { useRealTimeXState } from "@realtimex/app-sdk";
|
|
530
|
+
|
|
531
|
+
function MyComponent() {
|
|
532
|
+
const [value, setValue] = useRealTimeXState("my-key");
|
|
533
|
+
}
|
|
534
|
+
\`\`\`
|
|
535
|
+
|
|
536
|
+
## Resources
|
|
537
|
+
|
|
538
|
+
- [RealTimeX App SDK Docs](https://realtimex.ai/docs/app-sdk)
|
|
539
|
+
- [RealTimeX CRM Docs](https://github.com/therealtimex/realtimex-crm)
|
|
540
|
+
- [Supabase RLS Guide](https://supabase.com/docs/guides/auth/row-level-security)
|
|
541
|
+
`;
|
|
542
|
+
|
|
543
|
+
await writeFile(join(projectPath, "LOCAL_APP.md"), localAppDoc);
|
|
544
|
+
|
|
545
|
+
// Create realtimex.config.json
|
|
546
|
+
const rtxConfig = {
|
|
547
|
+
appId: projectName,
|
|
548
|
+
name: "CRM",
|
|
549
|
+
version: "1.0.0",
|
|
550
|
+
description: "Full-featured CRM for RealTimeX",
|
|
551
|
+
permissions: ["database.read", "database.write", "user.read"],
|
|
552
|
+
settings: {
|
|
553
|
+
supabase: {
|
|
554
|
+
required: true,
|
|
555
|
+
fields: ["url", "anonKey"],
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
await writeFile(
|
|
561
|
+
join(projectPath, "realtimex.config.json"),
|
|
562
|
+
JSON.stringify(rtxConfig, null, 2),
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
async function createComponentSetup(projectPath, projectName, config) {
|
|
567
|
+
const packageJson = {
|
|
568
|
+
name: projectName,
|
|
569
|
+
version: "0.1.0",
|
|
570
|
+
private: true,
|
|
571
|
+
type: "module",
|
|
572
|
+
dependencies: {
|
|
573
|
+
"realtimex-crm": "latest",
|
|
574
|
+
},
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
await writeFile(
|
|
578
|
+
join(projectPath, "package.json"),
|
|
579
|
+
JSON.stringify(packageJson, null, 2),
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
// Create example integration files
|
|
583
|
+
await mkdir(join(projectPath, "examples"), { recursive: true });
|
|
584
|
+
|
|
585
|
+
const exampleReact = `import { CRM } from "realtimex-crm";
|
|
586
|
+
import "realtimex-crm/dist/style.css";
|
|
587
|
+
|
|
588
|
+
export function MyCRMPage() {
|
|
589
|
+
return (
|
|
590
|
+
<div className="h-screen">
|
|
591
|
+
<CRM
|
|
592
|
+
title="My CRM"
|
|
593
|
+
// Customize as needed
|
|
594
|
+
/>
|
|
595
|
+
</div>
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
`;
|
|
599
|
+
|
|
600
|
+
await writeFile(join(projectPath, "examples/react-integration.tsx"), exampleReact);
|
|
601
|
+
|
|
602
|
+
const readme = `# ${projectName}
|
|
603
|
+
|
|
604
|
+
RealTimeX CRM component integration.
|
|
605
|
+
|
|
606
|
+
## Installation
|
|
607
|
+
|
|
608
|
+
\`\`\`bash
|
|
609
|
+
npm install realtimex-crm
|
|
610
|
+
\`\`\`
|
|
611
|
+
|
|
612
|
+
## Usage
|
|
613
|
+
|
|
614
|
+
\`\`\`tsx
|
|
615
|
+
import { CRM } from "realtimex-crm";
|
|
616
|
+
import "realtimex-crm/dist/style.css";
|
|
617
|
+
|
|
618
|
+
function App() {
|
|
619
|
+
return <CRM title="My CRM" />;
|
|
620
|
+
}
|
|
621
|
+
\`\`\`
|
|
622
|
+
|
|
623
|
+
## Configuration
|
|
624
|
+
|
|
625
|
+
Configure Supabase via environment variables or UI:
|
|
626
|
+
|
|
627
|
+
\`\`\`
|
|
628
|
+
VITE_SUPABASE_URL=https://xxxxx.supabase.co
|
|
629
|
+
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
630
|
+
\`\`\`
|
|
631
|
+
|
|
632
|
+
## Customization
|
|
633
|
+
|
|
634
|
+
See \`examples/react-integration.tsx\` for a complete example.
|
|
635
|
+
|
|
636
|
+
Available props:
|
|
637
|
+
- \`title\`: string
|
|
638
|
+
- \`darkModeLogo\` / \`lightModeLogo\`: string
|
|
639
|
+
- \`contactGender\`: ContactGender[]
|
|
640
|
+
- \`companySectors\`: string[]
|
|
641
|
+
- \`dealStages\`: DealStage[]
|
|
642
|
+
- \`dealCategories\`: string[]
|
|
643
|
+
- \`noteStatuses\`: NoteStatus[]
|
|
644
|
+
- \`taskTypes\`: string[]
|
|
645
|
+
|
|
646
|
+
See [documentation](https://github.com/therealtimex/realtimex-crm) for details.
|
|
647
|
+
`;
|
|
648
|
+
|
|
649
|
+
await writeFile(join(projectPath, "README.md"), readme);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
main().catch((error) => {
|
|
653
|
+
console.error("\n❌ Error:", error.message);
|
|
654
|
+
process.exit(1);
|
|
655
|
+
});
|