@clubz/cli 0.1.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/bin/clubz.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+
3
+ // This will require the built source code
4
+ require('../dist/index.js');
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.deployCommand = deployCommand;
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const child_process_1 = require("child_process");
11
+ const archiver_1 = __importDefault(require("archiver"));
12
+ async function deployCommand() {
13
+ console.log(chalk_1.default.blue('šŸš€ Deploying Widget to Clubz...'));
14
+ const cwd = process.cwd();
15
+ const configPath = path_1.default.join(cwd, 'clubz.json');
16
+ // 1. Validation
17
+ if (!fs_extra_1.default.existsSync(configPath)) {
18
+ console.error(chalk_1.default.red('āŒ clubz.json not found. Are you in a Clubz project?'));
19
+ process.exit(1);
20
+ }
21
+ const config = await fs_extra_1.default.readJson(configPath);
22
+ console.log(chalk_1.default.dim(` Project: ${config.name} v${config.version}`));
23
+ // 2. Build
24
+ console.log(chalk_1.default.yellow('\nšŸ“¦ Building project...'));
25
+ try {
26
+ (0, child_process_1.execSync)('npm run build', { stdio: 'inherit' });
27
+ }
28
+ catch (e) {
29
+ console.error(chalk_1.default.red('āŒ Build failed. Fix errors and try again.'));
30
+ process.exit(1);
31
+ }
32
+ // 3. Zip Dist Folder
33
+ console.log(chalk_1.default.yellow('\n🤐 Zipping assets...'));
34
+ const distDir = path_1.default.join(cwd, 'dist');
35
+ const zipPath = path_1.default.join(cwd, 'bundle.zip');
36
+ if (!fs_extra_1.default.existsSync(distDir)) {
37
+ console.error(chalk_1.default.red('āŒ dist/ directory not found after build.'));
38
+ process.exit(1);
39
+ }
40
+ await zipDirectory(distDir, zipPath);
41
+ console.log(chalk_1.default.green(` Bundle created: ${getSize(zipPath)}`));
42
+ // 4. Upload (Mock for now, replacing previous simple push)
43
+ console.log(chalk_1.default.yellow('\nā˜ļø Uploading to Clubz Registry...'));
44
+ try {
45
+ const apiKey = process.env.CLUBZ_API_KEY;
46
+ if (!apiKey) {
47
+ console.warn(chalk_1.default.yellow('āš ļø No CLUBZ_API_KEY found in env. simulating upload...'));
48
+ }
49
+ const isSubmission = process.argv.includes('--submit');
50
+ const status = isSubmission ? 'pending' : 'draft';
51
+ // Real implementation would look like this:
52
+ /*
53
+ const form = new FormData();
54
+ form.append('file', fs.createReadStream(zipPath));
55
+ form.append('manifest', JSON.stringify({ ...config, status }));
56
+
57
+ await axios.post('http://localhost:3000/api/registry/deploy', form, {
58
+ headers: {
59
+ ...form.getHeaders(),
60
+ 'Authorization': `Bearer ${apiKey}`
61
+ }
62
+ });
63
+ */
64
+ // Simulating network delay
65
+ await new Promise(r => setTimeout(r, 1500));
66
+ console.log(chalk_1.default.green('\nāœ… Deployment Successful!'));
67
+ console.log(chalk_1.default.cyan(` Widget ID: ${config.type}-${Date.now().toString().slice(-4)}`));
68
+ console.log(chalk_1.default.cyan(` Version: ${config.version}`));
69
+ if (config.tags && config.tags.length > 0) {
70
+ console.log(chalk_1.default.cyan(` Tags: ${config.tags.join(', ')}`));
71
+ }
72
+ if (isSubmission) {
73
+ console.log(chalk_1.default.magenta(' šŸš€ Submitted for validation (Status: Pending)'));
74
+ console.log(chalk_1.default.dim(' An admin will review your widget shortly.'));
75
+ }
76
+ else {
77
+ console.log(chalk_1.default.dim(' Saved as Draft. PRO TIP: Use --submit to send for validation.'));
78
+ }
79
+ console.log(chalk_1.default.dim(' Available in your "Submissions" tab.'));
80
+ // Cleanup
81
+ await fs_extra_1.default.remove(zipPath);
82
+ }
83
+ catch (error) {
84
+ console.error(chalk_1.default.red('āŒ Upload failed'), error.message);
85
+ process.exit(1);
86
+ }
87
+ }
88
+ function zipDirectory(source, out) {
89
+ const archive = (0, archiver_1.default)('zip', { zlib: { level: 9 } });
90
+ const stream = fs_extra_1.default.createWriteStream(out);
91
+ return new Promise((resolve, reject) => {
92
+ archive
93
+ .directory(source, false)
94
+ .on('error', err => reject(err))
95
+ .pipe(stream);
96
+ stream.on('close', () => resolve());
97
+ archive.finalize();
98
+ });
99
+ }
100
+ function getSize(filePath) {
101
+ const stats = fs_extra_1.default.statSync(filePath);
102
+ const bytes = stats.size;
103
+ if (bytes < 1024)
104
+ return bytes + ' B';
105
+ return (bytes / 1024).toFixed(2) + ' KB';
106
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.devCommand = devCommand;
7
+ const path_1 = __importDefault(require("path"));
8
+ const express_1 = __importDefault(require("express"));
9
+ const cross_spawn_1 = __importDefault(require("cross-spawn"));
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const fs_1 = __importDefault(require("fs"));
12
+ // In CommonJS (which we compile to), __dirname is available globally
13
+ // const __filename = fileURLToPath(import.meta.url); // Not needed
14
+ // const __dirname = path.dirname(__filename); // Not needed
15
+ async function devCommand() {
16
+ console.log(chalk_1.default.blue('šŸš€ Starting Clubz Development Environment...'));
17
+ // 1. Start User's Widget Server (Vite)
18
+ const widgetPort = 3001;
19
+ console.log(chalk_1.default.dim(` Starting widget server on port ${widgetPort}...`));
20
+ const widgetProcess = (0, cross_spawn_1.default)('npm', ['run', 'dev', '--', '--port', widgetPort.toString()], {
21
+ stdio: 'inherit',
22
+ env: { ...process.env, FORCE_COLOR: 'true' }
23
+ });
24
+ widgetProcess.on('error', (err) => {
25
+ console.error(chalk_1.default.red('āŒ Failed to start widget server'), err);
26
+ });
27
+ // 2. Start Simulator Server
28
+ const app = (0, express_1.default)();
29
+ const simulatorPort = 3000;
30
+ // Resolve path to built simulator client assets
31
+ // In dist structure: dist/src/commands/dev.js -> dist/simulator/client
32
+ // adjustable based on actual build output structure
33
+ let simulatorDist = path_1.default.join(__dirname, '../../simulator/client');
34
+ // Fallback for dev execution (ts-node)
35
+ if (!fs_1.default.existsSync(simulatorDist) || !fs_1.default.existsSync(path_1.default.join(simulatorDist, 'index.html'))) {
36
+ // If not built, maybe we can warn or try source (but source needs building)
37
+ // Check relative to src: src/commands/dev.ts -> src/simulator/client (source)
38
+ // For now, let's assume we need to build it.
39
+ // We can point to a mocked path or ensure build happens.
40
+ console.warn(chalk_1.default.yellow('āš ļø Simulator build not found. Ensure CLI is built.'));
41
+ // Try pointing to package root dist if structure is flattened
42
+ simulatorDist = path_1.default.join(__dirname, '../../../dist/simulator/client');
43
+ }
44
+ app.use(express_1.default.static(simulatorDist));
45
+ // Fallback to index.html for SPA routing
46
+ app.get('*', (req, res) => {
47
+ res.sendFile(path_1.default.join(simulatorDist, 'index.html'));
48
+ });
49
+ app.listen(simulatorPort, () => {
50
+ console.log(chalk_1.default.green(`\nšŸ“± Simulator running at http://localhost:${simulatorPort}`));
51
+ console.log(chalk_1.default.cyan(` Widget running at http://localhost:${widgetPort}`));
52
+ console.log(chalk_1.default.dim(' Press Ctrl+C to stop.'));
53
+ });
54
+ // Handle cleanup
55
+ process.on('SIGINT', () => {
56
+ widgetProcess.kill();
57
+ process.exit();
58
+ });
59
+ }
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.initCommand = initCommand;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const inquirer_1 = __importDefault(require("inquirer"));
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const child_process_1 = require("child_process");
12
+ // In CommonJS, __dirname is available globally
13
+ async function initCommand(name) {
14
+ const targetDir = path_1.default.resolve(process.cwd(), name);
15
+ if (fs_extra_1.default.existsSync(targetDir)) {
16
+ console.error(chalk_1.default.red(`āŒ Directory ${name} already exists.`));
17
+ process.exit(1);
18
+ }
19
+ console.log(chalk_1.default.blue(`šŸš€ Creating new Clubz widget project: ${name}`));
20
+ // 1. Copy Template
21
+ // In dev (ts-node/src), template is in src/templates
22
+ // In prod (dist), template is in dist/templates (need to ensure build copies it)
23
+ // For now we assume we are running from src or built dist where structure is preserved.
24
+ // Adjust path based on where we are executing.
25
+ // We need to resolve the template path relative to this file.
26
+ // If we are in dist/commands/init.js, template should be in ../templates/react-vite-ts relative to this file?
27
+ // No, standard is usually to keep templates as assets.
28
+ // Let's assume we are in src/commands/init.ts for dev flow or dist/commands/init.js
29
+ // Quick fix for path resolution in both envs: look for templates dir up the tree
30
+ const templateName = 'react-vite-ts';
31
+ let templateDir = path_1.default.join(__dirname, '../../templates', templateName); // from dist/commands or src/commands
32
+ // Fallback if structure is different (e.g. src vs dist) - explicit check
33
+ if (!fs_extra_1.default.existsSync(templateDir)) {
34
+ // Try source path if we are running ts-node directly?
35
+ templateDir = path_1.default.join(__dirname, '../../../src/templates', templateName);
36
+ }
37
+ if (!fs_extra_1.default.existsSync(templateDir)) {
38
+ console.error(chalk_1.default.red(`āŒ Template not found at ${templateDir}`));
39
+ process.exit(1);
40
+ }
41
+ try {
42
+ await fs_extra_1.default.copy(templateDir, targetDir);
43
+ // 2. Customize package.json
44
+ const pkgPath = path_1.default.join(targetDir, 'package.json');
45
+ const pkg = await fs_extra_1.default.readJson(pkgPath);
46
+ pkg.name = name;
47
+ await fs_extra_1.default.writeJson(pkgPath, pkg, { spaces: 2 });
48
+ // 3. Rename .gitignore if needed (npm often renames it to .npmignore on publish)
49
+ const gitignorePath = path_1.default.join(targetDir, '_gitignore');
50
+ if (fs_extra_1.default.existsSync(gitignorePath)) {
51
+ await fs_extra_1.default.move(gitignorePath, path_1.default.join(targetDir, '.gitignore'));
52
+ }
53
+ console.log(chalk_1.default.green(`āœ… Project created in ${targetDir}`));
54
+ // 4. Prompt for install
55
+ const { install } = await inquirer_1.default.prompt([{
56
+ type: 'confirm',
57
+ name: 'install',
58
+ message: 'Install dependencies now?',
59
+ default: true
60
+ }]);
61
+ if (install) {
62
+ console.log(chalk_1.default.yellow('šŸ“¦ Installing dependencies...'));
63
+ (0, child_process_1.execSync)('npm install', { cwd: targetDir, stdio: 'inherit' });
64
+ console.log(chalk_1.default.green('āœ… Dependencies installed'));
65
+ }
66
+ console.log('\nNext steps:');
67
+ console.log(chalk_1.default.cyan(` cd ${name}`));
68
+ console.log(chalk_1.default.cyan(' npm run dev'));
69
+ }
70
+ catch (e) {
71
+ console.error(chalk_1.default.red('āŒ Failed to create project'), e);
72
+ }
73
+ }
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const commander_1 = require("commander");
4
+ const init_1 = require("./commands/init");
5
+ const dev_1 = require("./commands/dev");
6
+ const deploy_1 = require("./commands/deploy");
7
+ const program = new commander_1.Command();
8
+ program
9
+ .name('clubz')
10
+ .description('Clubz Developer CLI')
11
+ .version('0.1.0');
12
+ program
13
+ .command('init')
14
+ .description('Initialize a new Clubz widget project')
15
+ .argument('<name>', 'Project name')
16
+ .action(init_1.initCommand);
17
+ program
18
+ .command('dev')
19
+ .description('Start development server with Simulator')
20
+ .action(dev_1.devCommand);
21
+ program
22
+ .command('deploy')
23
+ .description('Build and deploy widget to Clubz')
24
+ .option('--submit', 'Submit for validation immediately')
25
+ .action(deploy_1.deployCommand);
26
+ program.parse();
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.default = SimulatorApp;
37
+ const react_1 = __importStar(require("react"));
38
+ function SimulatorApp() {
39
+ // State
40
+ const [widgetUrl, setWidgetUrl] = (0, react_1.useState)('http://localhost:3001'); // Default widget port
41
+ const [logs, setLogs] = (0, react_1.useState)([]);
42
+ // Iframe ref to send responses back
43
+ const iframeRef = (0, react_1.useRef)(null);
44
+ // Initial load
45
+ (0, react_1.useEffect)(() => {
46
+ const handleMessage = (event) => {
47
+ const data = event.data;
48
+ // Filter only widget messages
49
+ if (data?.source !== 'clubz-widget')
50
+ return;
51
+ log('req', `${data.action} ${data.payload ? JSON.stringify(data.payload) : ''}`);
52
+ // Process Request
53
+ processBridgeRequest(data, event.source);
54
+ };
55
+ window.addEventListener('message', handleMessage);
56
+ return () => window.removeEventListener('message', handleMessage);
57
+ }, []);
58
+ const log = (type, msg) => {
59
+ setLogs(prev => [{
60
+ time: new Date().toLocaleTimeString().split(' ')[0],
61
+ type,
62
+ msg
63
+ }, ...prev]);
64
+ };
65
+ const processBridgeRequest = (req, sourceWindow) => {
66
+ let responseData = null;
67
+ let success = true;
68
+ switch (req.action) {
69
+ case 'GET_USER':
70
+ responseData = { id: 'sim-user-1', name: 'Simulator User', role: 'admin' };
71
+ break;
72
+ case 'VIBRATE':
73
+ // Visual feedback
74
+ if (navigator.vibrate)
75
+ navigator.vibrate(200);
76
+ alert('šŸ“³ VIBRATION TRIGGERED');
77
+ break;
78
+ case 'NAVIGATE':
79
+ alert(`🧭 Navigating to: ${req.payload?.route}`);
80
+ break;
81
+ default:
82
+ console.warn('Unknown action', req.action);
83
+ }
84
+ // Send Response
85
+ const response = {
86
+ id: req.id,
87
+ success,
88
+ data: responseData
89
+ };
90
+ sourceWindow.postMessage(response, '*');
91
+ log('res', `Sent data for ${req.id}`);
92
+ };
93
+ return (<div className="flex h-screen w-full">
94
+ {/* Left: Device Simulator */}
95
+ <div className="flex-1 flex flex-col items-center justify-center bg-slate-100 p-8">
96
+ <div className="relative border-gray-800 bg-gray-800 border-[14px] rounded-[2.5rem] h-[600px] w-[300px] shadow-xl">
97
+ <div className="w-[148px] h-[18px] bg-gray-800 top-0 rounded-b-[1rem] left-1/2 -translate-x-1/2 absolute"></div>
98
+ <div className="h-[32px] w-[3px] bg-gray-800 absolute -start-[17px] top-[72px] rounded-s-lg"></div>
99
+ <div className="h-[46px] w-[3px] bg-gray-800 absolute -start-[17px] top-[124px] rounded-s-lg"></div>
100
+ <div className="h-[46px] w-[3px] bg-gray-800 absolute -start-[17px] top-[178px] rounded-s-lg"></div>
101
+ <div className="h-[64px] w-[3px] bg-gray-800 absolute -end-[17px] top-[142px] rounded-e-lg"></div>
102
+ <div className="rounded-[2rem] overflow-hidden w-full h-full bg-white relative">
103
+ {/* Iframe Content */}
104
+ <iframe ref={iframeRef} src={widgetUrl} className="w-full h-full border-0" title="Widget Simulator"/>
105
+ </div>
106
+ </div>
107
+ <p className="mt-4 text-slate-400 text-sm">iPhone 14 Pro Simulator</p>
108
+ </div>
109
+
110
+ {/* Right: Debug Console */}
111
+ <div className="w-96 bg-white border-l border-slate-200 flex flex-col">
112
+ <div className="p-4 border-b border-slate-200 bg-slate-50">
113
+ <h2 className="font-bold text-slate-700">Bridge Debugger</h2>
114
+ </div>
115
+
116
+ <div className="p-4 border-b border-slate-200">
117
+ <label className="text-xs font-bold text-slate-500 uppercase">Widget URL</label>
118
+ <input type="text" value={widgetUrl} onChange={e => setWidgetUrl(e.target.value)} className="w-full mt-1 px-3 py-2 border rounded text-sm font-mono"/>
119
+ </div>
120
+
121
+ <div className="flex-1 overflow-y-auto p-4 space-y-2 bg-slate-900 font-mono text-xs">
122
+ {logs.length === 0 && <p className="text-slate-500 italic">Waiting for events...</p>}
123
+ {logs.map((l, i) => (<div key={i} className="flex gap-2">
124
+ <span className="text-slate-500">[{l.time}]</span>
125
+ <span className={l.type === 'req' ? 'text-blue-400' : 'text-green-400'}>
126
+ {l.type === 'req' ? '→' : '←'}
127
+ </span>
128
+ <span className="text-slate-300 break-all">{l.msg}</span>
129
+ </div>))}
130
+ </div>
131
+ </div>
132
+ </div>);
133
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const react_1 = __importDefault(require("react"));
7
+ const client_1 = __importDefault(require("react-dom/client"));
8
+ const SimulatorApp_1 = __importDefault(require("./SimulatorApp"));
9
+ client_1.default.createRoot(document.getElementById('root')).render(<react_1.default.StrictMode>
10
+ <SimulatorApp_1.default />
11
+ </react_1.default.StrictMode>);
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const vite_1 = require("vite");
7
+ const plugin_react_1 = __importDefault(require("@vitejs/plugin-react"));
8
+ exports.default = (0, vite_1.defineConfig)({
9
+ plugins: [(0, plugin_react_1.default)()],
10
+ root: __dirname, // Ensure root is set to this directory
11
+ build: {
12
+ outDir: '../../../dist/simulator/client', // Build into the CLI dist folder
13
+ emptyOutDir: true
14
+ }
15
+ });
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const react_1 = require("react");
4
+ const sdk_1 = require("@clubz/sdk");
5
+ function App() {
6
+ const [user, setUser] = (0, react_1.useState)(null);
7
+ (0, react_1.useEffect)(() => {
8
+ // Determine user context via Bridge
9
+ sdk_1.bridge.getUser().then((u) => setUser(u)).catch(() => console.log('Guest mode'));
10
+ }, []);
11
+ const handleVibrate = () => {
12
+ sdk_1.bridge.vibrate();
13
+ };
14
+ return (<div className="w-full h-full min-h-[200px] bg-white rounded-xl p-6 border border-slate-200 flex flex-col items-center justify-center">
15
+ <h1 className="text-xl font-bold text-slate-800 mb-2">
16
+ Hello {user ? user.name : 'Guest'}!
17
+ </h1>
18
+ <p className="text-slate-500 text-center mb-4">
19
+ Welcome to your new Clubz Widget.
20
+ </p>
21
+
22
+ <button onClick={handleVibrate} className="px-4 py-2 bg-blue-600 text-white rounded-lg active:bg-blue-700 transition">
23
+ Test Vibration
24
+ </button>
25
+ </div>);
26
+ }
27
+ exports.default = App;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const react_1 = __importDefault(require("react"));
7
+ const client_1 = __importDefault(require("react-dom/client"));
8
+ const App_tsx_1 = __importDefault(require("./App.tsx"));
9
+ client_1.default.createRoot(document.getElementById('root')).render(<react_1.default.StrictMode>
10
+ <App_tsx_1.default />
11
+ </react_1.default.StrictMode>);
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const vite_1 = require("vite");
7
+ const plugin_react_1 = __importDefault(require("@vitejs/plugin-react"));
8
+ // https://vitejs.dev/config/
9
+ exports.default = (0, vite_1.defineConfig)({
10
+ plugins: [(0, plugin_react_1.default)()],
11
+ build: {
12
+ rollupOptions: {
13
+ output: {
14
+ // Ensure we get a single recognizable entry point if possible,
15
+ // though standard vite build is fine for the platform zipper.
16
+ entryFileNames: 'assets/[name].js',
17
+ chunkFileNames: 'assets/[name].js',
18
+ assetFileNames: 'assets/[name].[ext]'
19
+ }
20
+ }
21
+ }
22
+ });
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@clubz/cli",
3
+ "version": "0.1.0",
4
+ "bin": {
5
+ "clubz": "bin/clubz.js"
6
+ },
7
+ "main": "dist/index.js",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "dev": "tsc -w"
11
+ },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "dependencies": {
16
+ "@types/archiver": "^7.0.0",
17
+ "@types/cross-spawn": "^6.0.6",
18
+ "@types/express": "^5.0.6",
19
+ "@types/fs-extra": "^11.0.4",
20
+ "archiver": "^7.0.1",
21
+ "axios": "^1.13.4",
22
+ "chalk": "^4.1.2",
23
+ "commander": "^11.0.0",
24
+ "cross-spawn": "^7.0.6",
25
+ "express": "^5.2.1",
26
+ "form-data": "^4.0.5",
27
+ "fs-extra": "^11.3.3",
28
+ "inquirer": "^8.0.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/inquirer": "^9.0.9",
32
+ "@types/node": "^20.0.0",
33
+ "typescript": "^5.0.0"
34
+ }
35
+ }
@@ -0,0 +1,119 @@
1
+ import path from 'path';
2
+ import fs from 'fs-extra';
3
+ import chalk from 'chalk';
4
+ import { execSync } from 'child_process';
5
+ import archiver from 'archiver';
6
+ import axios from 'axios';
7
+ import FormData from 'form-data';
8
+
9
+ export async function deployCommand() {
10
+ console.log(chalk.blue('šŸš€ Deploying Widget to Clubz...'));
11
+
12
+ const cwd = process.cwd();
13
+ const configPath = path.join(cwd, 'clubz.json');
14
+
15
+ // 1. Validation
16
+ if (!fs.existsSync(configPath)) {
17
+ console.error(chalk.red('āŒ clubz.json not found. Are you in a Clubz project?'));
18
+ process.exit(1);
19
+ }
20
+ const config = await fs.readJson(configPath);
21
+ console.log(chalk.dim(` Project: ${config.name} v${config.version}`));
22
+
23
+ // 2. Build
24
+ console.log(chalk.yellow('\nšŸ“¦ Building project...'));
25
+ try {
26
+ execSync('npm run build', { stdio: 'inherit' });
27
+ } catch (e) {
28
+ console.error(chalk.red('āŒ Build failed. Fix errors and try again.'));
29
+ process.exit(1);
30
+ }
31
+
32
+ // 3. Zip Dist Folder
33
+ console.log(chalk.yellow('\n🤐 Zipping assets...'));
34
+ const distDir = path.join(cwd, 'dist');
35
+ const zipPath = path.join(cwd, 'bundle.zip');
36
+
37
+ if (!fs.existsSync(distDir)) {
38
+ console.error(chalk.red('āŒ dist/ directory not found after build.'));
39
+ process.exit(1);
40
+ }
41
+
42
+ await zipDirectory(distDir, zipPath);
43
+ console.log(chalk.green(` Bundle created: ${getSize(zipPath)}`));
44
+
45
+ // 4. Upload (Mock for now, replacing previous simple push)
46
+ console.log(chalk.yellow('\nā˜ļø Uploading to Clubz Registry...'));
47
+
48
+ try {
49
+ const apiKey = process.env.CLUBZ_API_KEY;
50
+ if (!apiKey) {
51
+ console.warn(chalk.yellow('āš ļø No CLUBZ_API_KEY found in env. simulating upload...'));
52
+ }
53
+
54
+ const isSubmission = process.argv.includes('--submit');
55
+ const status = isSubmission ? 'pending' : 'draft';
56
+
57
+ // Real implementation would look like this:
58
+ /*
59
+ const form = new FormData();
60
+ form.append('file', fs.createReadStream(zipPath));
61
+ form.append('manifest', JSON.stringify({ ...config, status }));
62
+
63
+ await axios.post('http://localhost:3000/api/registry/deploy', form, {
64
+ headers: {
65
+ ...form.getHeaders(),
66
+ 'Authorization': `Bearer ${apiKey}`
67
+ }
68
+ });
69
+ */
70
+
71
+ // Simulating network delay
72
+ await new Promise(r => setTimeout(r, 1500));
73
+
74
+ console.log(chalk.green('\nāœ… Deployment Successful!'));
75
+ console.log(chalk.cyan(` Widget ID: ${config.type}-${Date.now().toString().slice(-4)}`));
76
+ console.log(chalk.cyan(` Version: ${config.version}`));
77
+ if (config.tags && config.tags.length > 0) {
78
+ console.log(chalk.cyan(` Tags: ${config.tags.join(', ')}`));
79
+ }
80
+
81
+ if (isSubmission) {
82
+ console.log(chalk.magenta(' šŸš€ Submitted for validation (Status: Pending)'));
83
+ console.log(chalk.dim(' An admin will review your widget shortly.'));
84
+ } else {
85
+ console.log(chalk.dim(' Saved as Draft. PRO TIP: Use --submit to send for validation.'));
86
+ }
87
+
88
+ console.log(chalk.dim(' Available in your "Submissions" tab.'));
89
+
90
+ // Cleanup
91
+ await fs.remove(zipPath);
92
+
93
+ } catch (error: any) {
94
+ console.error(chalk.red('āŒ Upload failed'), error.message);
95
+ process.exit(1);
96
+ }
97
+ }
98
+
99
+ function zipDirectory(source: string, out: string): Promise<void> {
100
+ const archive = archiver('zip', { zlib: { level: 9 } });
101
+ const stream = fs.createWriteStream(out);
102
+
103
+ return new Promise((resolve, reject) => {
104
+ archive
105
+ .directory(source, false)
106
+ .on('error', err => reject(err))
107
+ .pipe(stream);
108
+
109
+ stream.on('close', () => resolve());
110
+ archive.finalize();
111
+ });
112
+ }
113
+
114
+ function getSize(filePath: string) {
115
+ const stats = fs.statSync(filePath);
116
+ const bytes = stats.size;
117
+ if (bytes < 1024) return bytes + ' B';
118
+ return (bytes / 1024).toFixed(2) + ' KB';
119
+ }
@@ -0,0 +1,65 @@
1
+ import path from 'path';
2
+ import express from 'express';
3
+ import spawn from 'cross-spawn';
4
+ import chalk from 'chalk';
5
+ import fs from 'fs';
6
+
7
+ // In CommonJS (which we compile to), __dirname is available globally
8
+ // const __filename = fileURLToPath(import.meta.url); // Not needed
9
+ // const __dirname = path.dirname(__filename); // Not needed
10
+
11
+ export async function devCommand() {
12
+ console.log(chalk.blue('šŸš€ Starting Clubz Development Environment...'));
13
+
14
+ // 1. Start User's Widget Server (Vite)
15
+ const widgetPort = 3001;
16
+ console.log(chalk.dim(` Starting widget server on port ${widgetPort}...`));
17
+
18
+ const widgetProcess = spawn('npm', ['run', 'dev', '--', '--port', widgetPort.toString()], {
19
+ stdio: 'inherit',
20
+ env: { ...process.env, FORCE_COLOR: 'true' }
21
+ });
22
+
23
+ widgetProcess.on('error', (err) => {
24
+ console.error(chalk.red('āŒ Failed to start widget server'), err);
25
+ });
26
+
27
+ // 2. Start Simulator Server
28
+ const app = express();
29
+ const simulatorPort = 3000;
30
+
31
+ // Resolve path to built simulator client assets
32
+ // In dist structure: dist/src/commands/dev.js -> dist/simulator/client
33
+ // adjustable based on actual build output structure
34
+ let simulatorDist = path.join(__dirname, '../../simulator/client');
35
+
36
+ // Fallback for dev execution (ts-node)
37
+ if (!fs.existsSync(simulatorDist) || !fs.existsSync(path.join(simulatorDist, 'index.html'))) {
38
+ // If not built, maybe we can warn or try source (but source needs building)
39
+ // Check relative to src: src/commands/dev.ts -> src/simulator/client (source)
40
+ // For now, let's assume we need to build it.
41
+ // We can point to a mocked path or ensure build happens.
42
+ console.warn(chalk.yellow('āš ļø Simulator build not found. Ensure CLI is built.'));
43
+ // Try pointing to package root dist if structure is flattened
44
+ simulatorDist = path.join(__dirname, '../../../dist/simulator/client');
45
+ }
46
+
47
+ app.use(express.static(simulatorDist));
48
+
49
+ // Fallback to index.html for SPA routing
50
+ app.get('*', (req, res) => {
51
+ res.sendFile(path.join(simulatorDist, 'index.html'));
52
+ });
53
+
54
+ app.listen(simulatorPort, () => {
55
+ console.log(chalk.green(`\nšŸ“± Simulator running at http://localhost:${simulatorPort}`));
56
+ console.log(chalk.cyan(` Widget running at http://localhost:${widgetPort}`));
57
+ console.log(chalk.dim(' Press Ctrl+C to stop.'));
58
+ });
59
+
60
+ // Handle cleanup
61
+ process.on('SIGINT', () => {
62
+ widgetProcess.kill();
63
+ process.exit();
64
+ });
65
+ }
@@ -0,0 +1,83 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import inquirer from 'inquirer';
4
+ import chalk from 'chalk';
5
+ import { execSync } from 'child_process';
6
+
7
+ // In CommonJS, __dirname is available globally
8
+
9
+ export async function initCommand(name: string) {
10
+ const targetDir = path.resolve(process.cwd(), name);
11
+
12
+ if (fs.existsSync(targetDir)) {
13
+ console.error(chalk.red(`āŒ Directory ${name} already exists.`));
14
+ process.exit(1);
15
+ }
16
+
17
+ console.log(chalk.blue(`šŸš€ Creating new Clubz widget project: ${name}`));
18
+
19
+ // 1. Copy Template
20
+ // In dev (ts-node/src), template is in src/templates
21
+ // In prod (dist), template is in dist/templates (need to ensure build copies it)
22
+ // For now we assume we are running from src or built dist where structure is preserved.
23
+ // Adjust path based on where we are executing.
24
+
25
+ // We need to resolve the template path relative to this file.
26
+ // If we are in dist/commands/init.js, template should be in ../templates/react-vite-ts relative to this file?
27
+ // No, standard is usually to keep templates as assets.
28
+ // Let's assume we are in src/commands/init.ts for dev flow or dist/commands/init.js
29
+
30
+ // Quick fix for path resolution in both envs: look for templates dir up the tree
31
+ const templateName = 'react-vite-ts';
32
+ let templateDir = path.join(__dirname, '../../templates', templateName); // from dist/commands or src/commands
33
+
34
+ // Fallback if structure is different (e.g. src vs dist) - explicit check
35
+ if (!fs.existsSync(templateDir)) {
36
+ // Try source path if we are running ts-node directly?
37
+ templateDir = path.join(__dirname, '../../../src/templates', templateName);
38
+ }
39
+
40
+ if (!fs.existsSync(templateDir)) {
41
+ console.error(chalk.red(`āŒ Template not found at ${templateDir}`));
42
+ process.exit(1);
43
+ }
44
+
45
+ try {
46
+ await fs.copy(templateDir, targetDir);
47
+
48
+ // 2. Customize package.json
49
+ const pkgPath = path.join(targetDir, 'package.json');
50
+ const pkg = await fs.readJson(pkgPath);
51
+ pkg.name = name;
52
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
53
+
54
+ // 3. Rename .gitignore if needed (npm often renames it to .npmignore on publish)
55
+ const gitignorePath = path.join(targetDir, '_gitignore');
56
+ if (fs.existsSync(gitignorePath)) {
57
+ await fs.move(gitignorePath, path.join(targetDir, '.gitignore'));
58
+ }
59
+
60
+ console.log(chalk.green(`āœ… Project created in ${targetDir}`));
61
+
62
+ // 4. Prompt for install
63
+ const { install } = await inquirer.prompt([{
64
+ type: 'confirm',
65
+ name: 'install',
66
+ message: 'Install dependencies now?',
67
+ default: true
68
+ }]);
69
+
70
+ if (install) {
71
+ console.log(chalk.yellow('šŸ“¦ Installing dependencies...'));
72
+ execSync('npm install', { cwd: targetDir, stdio: 'inherit' });
73
+ console.log(chalk.green('āœ… Dependencies installed'));
74
+ }
75
+
76
+ console.log('\nNext steps:');
77
+ console.log(chalk.cyan(` cd ${name}`));
78
+ console.log(chalk.cyan(' npm run dev'));
79
+
80
+ } catch (e: any) {
81
+ console.error(chalk.red('āŒ Failed to create project'), e);
82
+ }
83
+ }
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { Command } from 'commander';
2
+ import { initCommand } from './commands/init';
3
+ import { devCommand } from './commands/dev';
4
+ import { deployCommand } from './commands/deploy';
5
+
6
+ const program = new Command();
7
+
8
+ program
9
+ .name('clubz')
10
+ .description('Clubz Developer CLI')
11
+ .version('0.1.0');
12
+
13
+ program
14
+ .command('init')
15
+ .description('Initialize a new Clubz widget project')
16
+ .argument('<name>', 'Project name')
17
+ .action(initCommand);
18
+
19
+ program
20
+ .command('dev')
21
+ .description('Start development server with Simulator')
22
+ .action(devCommand);
23
+
24
+ program
25
+ .command('deploy')
26
+ .description('Build and deploy widget to Clubz')
27
+ .option('--submit', 'Submit for validation immediately')
28
+ .action(deployCommand);
29
+
30
+ program.parse();
@@ -0,0 +1,16 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Clubz Simulator</title>
8
+ </head>
9
+
10
+ <body class="bg-slate-50 h-screen w-screen overflow-hidden">
11
+ <div id="root"></div>
12
+ <script type="module" src="/src/main.tsx"></script>
13
+ <script src="https://cdn.tailwindcss.com"></script>
14
+ </body>
15
+
16
+ </html>
@@ -0,0 +1,127 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+
3
+ // Mock Bridge Types (replicated from SDK for independence)
4
+ type BridgeAction = 'VIBRATE' | 'NAVIGATE' | 'GET_USER' | 'STORAGE_SET' | 'STORAGE_GET';
5
+ interface BridgeRequest { id: string; action: BridgeAction; payload?: any; source?: string; }
6
+ interface BridgeResponse { id: string; success: boolean; data?: any; error?: string; }
7
+
8
+ export default function SimulatorApp() {
9
+ // State
10
+ const [widgetUrl, setWidgetUrl] = useState('http://localhost:3001'); // Default widget port
11
+ const [logs, setLogs] = useState<{ time: string, type: 'req' | 'res', msg: string }[]>([]);
12
+
13
+ // Iframe ref to send responses back
14
+ const iframeRef = useRef<HTMLIFrameElement>(null);
15
+
16
+ // Initial load
17
+ useEffect(() => {
18
+ const handleMessage = (event: MessageEvent) => {
19
+ const data = event.data as BridgeRequest;
20
+
21
+ // Filter only widget messages
22
+ if (data?.source !== 'clubz-widget') return;
23
+
24
+ log('req', `${data.action} ${data.payload ? JSON.stringify(data.payload) : ''}`);
25
+
26
+ // Process Request
27
+ processBridgeRequest(data, event.source as Window);
28
+ };
29
+
30
+ window.addEventListener('message', handleMessage);
31
+ return () => window.removeEventListener('message', handleMessage);
32
+ }, []);
33
+
34
+ const log = (type: 'req' | 'res', msg: string) => {
35
+ setLogs(prev => [{
36
+ time: new Date().toLocaleTimeString().split(' ')[0],
37
+ type,
38
+ msg
39
+ }, ...prev]);
40
+ };
41
+
42
+ const processBridgeRequest = (req: BridgeRequest, sourceWindow: Window) => {
43
+ let responseData: any = null;
44
+ let success = true;
45
+
46
+ switch (req.action) {
47
+ case 'GET_USER':
48
+ responseData = { id: 'sim-user-1', name: 'Simulator User', role: 'admin' };
49
+ break;
50
+ case 'VIBRATE':
51
+ // Visual feedback
52
+ if (navigator.vibrate) navigator.vibrate(200);
53
+ alert('šŸ“³ VIBRATION TRIGGERED');
54
+ break;
55
+ case 'NAVIGATE':
56
+ alert(`🧭 Navigating to: ${req.payload?.route}`);
57
+ break;
58
+ default:
59
+ console.warn('Unknown action', req.action);
60
+ }
61
+
62
+ // Send Response
63
+ const response: BridgeResponse = {
64
+ id: req.id,
65
+ success,
66
+ data: responseData
67
+ };
68
+
69
+ sourceWindow.postMessage(response, '*');
70
+ log('res', `Sent data for ${req.id}`);
71
+ };
72
+
73
+ return (
74
+ <div className="flex h-screen w-full">
75
+ {/* Left: Device Simulator */}
76
+ <div className="flex-1 flex flex-col items-center justify-center bg-slate-100 p-8">
77
+ <div className="relative border-gray-800 bg-gray-800 border-[14px] rounded-[2.5rem] h-[600px] w-[300px] shadow-xl">
78
+ <div className="w-[148px] h-[18px] bg-gray-800 top-0 rounded-b-[1rem] left-1/2 -translate-x-1/2 absolute"></div>
79
+ <div className="h-[32px] w-[3px] bg-gray-800 absolute -start-[17px] top-[72px] rounded-s-lg"></div>
80
+ <div className="h-[46px] w-[3px] bg-gray-800 absolute -start-[17px] top-[124px] rounded-s-lg"></div>
81
+ <div className="h-[46px] w-[3px] bg-gray-800 absolute -start-[17px] top-[178px] rounded-s-lg"></div>
82
+ <div className="h-[64px] w-[3px] bg-gray-800 absolute -end-[17px] top-[142px] rounded-e-lg"></div>
83
+ <div className="rounded-[2rem] overflow-hidden w-full h-full bg-white relative">
84
+ {/* Iframe Content */}
85
+ <iframe
86
+ ref={iframeRef}
87
+ src={widgetUrl}
88
+ className="w-full h-full border-0"
89
+ title="Widget Simulator"
90
+ />
91
+ </div>
92
+ </div>
93
+ <p className="mt-4 text-slate-400 text-sm">iPhone 14 Pro Simulator</p>
94
+ </div>
95
+
96
+ {/* Right: Debug Console */}
97
+ <div className="w-96 bg-white border-l border-slate-200 flex flex-col">
98
+ <div className="p-4 border-b border-slate-200 bg-slate-50">
99
+ <h2 className="font-bold text-slate-700">Bridge Debugger</h2>
100
+ </div>
101
+
102
+ <div className="p-4 border-b border-slate-200">
103
+ <label className="text-xs font-bold text-slate-500 uppercase">Widget URL</label>
104
+ <input
105
+ type="text"
106
+ value={widgetUrl}
107
+ onChange={e => setWidgetUrl(e.target.value)}
108
+ className="w-full mt-1 px-3 py-2 border rounded text-sm font-mono"
109
+ />
110
+ </div>
111
+
112
+ <div className="flex-1 overflow-y-auto p-4 space-y-2 bg-slate-900 font-mono text-xs">
113
+ {logs.length === 0 && <p className="text-slate-500 italic">Waiting for events...</p>}
114
+ {logs.map((l, i) => (
115
+ <div key={i} className="flex gap-2">
116
+ <span className="text-slate-500">[{l.time}]</span>
117
+ <span className={l.type === 'req' ? 'text-blue-400' : 'text-green-400'}>
118
+ {l.type === 'req' ? '→' : '←'}
119
+ </span>
120
+ <span className="text-slate-300 break-all">{l.msg}</span>
121
+ </div>
122
+ ))}
123
+ </div>
124
+ </div>
125
+ </div>
126
+ );
127
+ }
@@ -0,0 +1,9 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import SimulatorApp from './SimulatorApp'
4
+
5
+ ReactDOM.createRoot(document.getElementById('root')!).render(
6
+ <React.StrictMode>
7
+ <SimulatorApp />
8
+ </React.StrictMode>,
9
+ )
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import path from 'path'
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ root: __dirname, // Ensure root is set to this directory
8
+ build: {
9
+ outDir: '../../../dist/simulator/client', // Build into the CLI dist folder
10
+ emptyOutDir: true
11
+ }
12
+ })
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "My Amazing Widget",
3
+ "description": "A widget built for Clubz",
4
+ "version": "1.0.0",
5
+ "type": "widget",
6
+ "config": {
7
+ "props": [
8
+ {
9
+ "name": "title",
10
+ "type": "string",
11
+ "label": "Titre du widget",
12
+ "default": "Hello World"
13
+ }
14
+ ]
15
+ },
16
+ "permissions": [],
17
+ "tags": [
18
+ "beta",
19
+ "react"
20
+ ]
21
+ }
@@ -0,0 +1,13 @@
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>Clubz Widget</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ <script src="https://cdn.tailwindcss.com"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "my-clubz-widget",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "react": "^18.2.0",
13
+ "react-dom": "^18.2.0",
14
+ "@clubz/sdk": "latest"
15
+ },
16
+ "devDependencies": {
17
+ "@types/react": "^18.2.66",
18
+ "@types/react-dom": "^18.2.22",
19
+ "@vitejs/plugin-react": "^4.2.1",
20
+ "typescript": "^5.2.2",
21
+ "vite": "^5.2.0"
22
+ }
23
+ }
@@ -0,0 +1,35 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { bridge } from '@clubz/sdk'
3
+
4
+ function App() {
5
+ const [user, setUser] = useState<{ name: string } | null>(null)
6
+
7
+ useEffect(() => {
8
+ // Determine user context via Bridge
9
+ bridge.getUser().then((u: any) => setUser(u)).catch(() => console.log('Guest mode'))
10
+ }, [])
11
+
12
+ const handleVibrate = () => {
13
+ bridge.vibrate();
14
+ }
15
+
16
+ return (
17
+ <div className="w-full h-full min-h-[200px] bg-white rounded-xl p-6 border border-slate-200 flex flex-col items-center justify-center">
18
+ <h1 className="text-xl font-bold text-slate-800 mb-2">
19
+ Hello {user ? user.name : 'Guest'}!
20
+ </h1>
21
+ <p className="text-slate-500 text-center mb-4">
22
+ Welcome to your new Clubz Widget.
23
+ </p>
24
+
25
+ <button
26
+ onClick={handleVibrate}
27
+ className="px-4 py-2 bg-blue-600 text-white rounded-lg active:bg-blue-700 transition"
28
+ >
29
+ Test Vibration
30
+ </button>
31
+ </div>
32
+ )
33
+ }
34
+
35
+ export default App
@@ -0,0 +1,9 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import App from './App.tsx'
4
+
5
+ ReactDOM.createRoot(document.getElementById('root')!).render(
6
+ <React.StrictMode>
7
+ <App />
8
+ </React.StrictMode>,
9
+ )
@@ -0,0 +1,18 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ // https://vitejs.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ build: {
8
+ rollupOptions: {
9
+ output: {
10
+ // Ensure we get a single recognizable entry point if possible,
11
+ // though standard vite build is fine for the platform zipper.
12
+ entryFileNames: 'assets/[name].js',
13
+ chunkFileNames: 'assets/[name].js',
14
+ assetFileNames: 'assets/[name].[ext]'
15
+ }
16
+ }
17
+ }
18
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2020",
4
+ "module": "commonjs",
5
+ "outDir": "./dist",
6
+ "rootDir": "./src",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true
11
+ },
12
+ "include": [
13
+ "src/**/*"
14
+ ],
15
+ "exclude": [
16
+ "src/templates/**/*",
17
+ "src/simulator/client/**/*"
18
+ ]
19
+ }