@fireberry/cli 0.2.3-beta.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/CLAUDE.md CHANGED
@@ -58,6 +58,7 @@ Each CLI command is in [src/commands/](src/commands/):
58
58
 
59
59
  - **init**: Stores Fireberry API token in local config using `env-paths`
60
60
  - **create**: Creates new Fireberry app from templates with generated UUIDs
61
+ - **create-component**: Scaffolds new Vite React component, installs Fireberry packages, builds it, and adds to manifest
61
62
  - **push**: Validates manifest, zips components, uploads to Fireberry API
62
63
  - **install**: Installs app on user's Fireberry account
63
64
  - **delete**: Deletes app from Fireberry platform (requires confirmation)
@@ -117,6 +118,42 @@ Templates use mustache-style placeholders (`{{appName}}`, `{{appId}}`, `{{compon
117
118
 
118
119
  - **manifest.yml**: Default manifest with single component
119
120
  - **index.html**: Basic HTML template
121
+ - **App-record.jsx**: React component template for record-type components
122
+ - **App-other.jsx**: React component template for global-menu and side-menu components
123
+
124
+ ### Create Component Command
125
+
126
+ The `create-component` command ([src/commands/create-component.ts](src/commands/create-component.ts)) scaffolds a new React component within an existing Fireberry app:
127
+
128
+ **Usage**: `fireberry create-component [name] [--type <type>]`
129
+
130
+ **Process**:
131
+
132
+ 1. Validates component name doesn't already exist in manifest.yml
133
+ 2. Prompts for component type if not provided (record, global-menu, side-menu)
134
+ 3. Prompts for type-specific settings:
135
+ - **record**: objectType (number), height (S/M/L), sets default iconName and iconColor
136
+ - **global-menu**: displayName, sets default iconName
137
+ - **side-menu**: width (S/M/L), sets default iconName
138
+ 4. Creates Vite React app in `static/<componentName>` using `npm create vite@latest`
139
+ 5. Installs standard dependencies with `npm install`
140
+ 6. Installs Fireberry packages: `@fireberry/ds` and `@fireberry/sdk`
141
+ 7. Copies appropriate App.jsx template based on component type
142
+ 8. Builds the component with `npm run build`
143
+ 9. Generates UUID for component
144
+ 10. Adds component entry to manifest.yml with path `static/<componentName>/dist`
145
+ 11. Displays success message with component details and next steps
146
+
147
+ **Requirements**:
148
+
149
+ - Must be run from directory containing `manifest.yml`
150
+ - Component name must be unique within the app
151
+
152
+ **Output**:
153
+
154
+ - Creates component directory at `static/<componentName>/`
155
+ - Updates manifest.yml with new component entry
156
+ - Built component ready at `static/<componentName>/dist/`
120
157
 
121
158
  ## Key Patterns
122
159
 
@@ -144,13 +181,14 @@ Component paths in manifest.yml are relative to the current working directory, n
144
181
 
145
182
  ## Important Notes
146
183
 
147
- - **Manifest Required**: `push`, `install`, and `delete` commands must be run from a directory containing `manifest.yml`
148
- - **Token Required**: Most commands require prior `init` to store API token
184
+ - **Manifest Required**: `push`, `install`, `delete`, and `create-component` commands must be run from a directory containing `manifest.yml`
185
+ - **Token Required**: Most commands (`push`, `install`, `delete`) require prior `init` to store API token
149
186
  - **Component IDs**: Must be unique UUIDs within a manifest
150
187
  - **Component Settings Validation**: Each component type has required settings that are validated during `push`:
151
188
  - `record`: Must have `iconName`, `iconColor`, and `objectType`
152
189
  - `global-menu`: Must have `displayName`
153
190
  - `side-menu`: Must have `iconName` and `width` (S/M/L)
191
+ - **Component Creation**: `create-component` automatically scaffolds a Vite React app, installs `@fireberry/ds` and `@fireberry/sdk`, builds it, and adds it to the manifest
154
192
  - **Build Zipping**: Single files are wrapped in a directory before tar.gz creation
155
193
  - **Template Location**: Templates are resolved from `src/templates/` at compile time, copied to `dist/templates/`
156
194
  - **Delete Safety**: Delete command requires user confirmation before executing (cannot be undone)
@@ -8,6 +8,7 @@ import { runPush } from "../commands/push.js";
8
8
  import { runInstall } from "../commands/install.js";
9
9
  import { runDelete } from "../commands/delete.js";
10
10
  import { runDebug } from "../commands/debug.js";
11
+ import { runCreateComponent } from "../commands/create-component.js";
11
12
  const program = new Command();
12
13
  program
13
14
  .name("fireberry")
@@ -28,6 +29,14 @@ program
28
29
  const name = nameArgs ? nameArgs.join("-") : undefined;
29
30
  await runCreate({ name });
30
31
  });
32
+ program
33
+ .command("create-component")
34
+ .argument("[name]", "Component name")
35
+ .argument("[type]", "Component type (record, global-menu, side-menu)")
36
+ .description("Add a new component to the existing app manifest")
37
+ .action(async (name, type) => {
38
+ await runCreateComponent({ name, type });
39
+ });
31
40
  program
32
41
  .command("push")
33
42
  .description("Push app to Fireberry")
@@ -0,0 +1,6 @@
1
+ interface CreateComponentOptions {
2
+ name?: string;
3
+ type?: string;
4
+ }
5
+ export declare function runCreateComponent({ type, name, }: CreateComponentOptions): Promise<void>;
6
+ export {};
@@ -0,0 +1,211 @@
1
+ import inquirer from "inquirer";
2
+ import path from "node:path";
3
+ import fs from "fs-extra";
4
+ import { v4 as uuidv4 } from "uuid";
5
+ import { fileURLToPath } from "node:url";
6
+ import { spawnSync } from "node:child_process";
7
+ import ora from "ora";
8
+ import chalk from "chalk";
9
+ import yaml from "js-yaml";
10
+ import { getManifest } from "../utils/components.utils.js";
11
+ import { COMPONENT_TYPE } from "../constants/component-types.js";
12
+ import { HEIGHT_OPTIONS } from "../constants/height-options.js";
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+ const VALID_COMPONENT_TYPES = Object.values(COMPONENT_TYPE);
16
+ function validateComponentType(type) {
17
+ if (!VALID_COMPONENT_TYPES.includes(type)) {
18
+ throw new Error(`Invalid component type: "${type}". Valid types are: ${VALID_COMPONENT_TYPES.join(", ")}`);
19
+ }
20
+ return type;
21
+ }
22
+ async function promptForSettings(type) {
23
+ switch (type) {
24
+ case COMPONENT_TYPE.RECORD: {
25
+ const answers = await inquirer.prompt([
26
+ {
27
+ type: "number",
28
+ name: "objectType",
29
+ message: "Object type:",
30
+ default: 0,
31
+ },
32
+ {
33
+ type: "list",
34
+ name: "height",
35
+ message: "Component height:",
36
+ choices: HEIGHT_OPTIONS,
37
+ default: "M",
38
+ },
39
+ ]);
40
+ return {
41
+ ...answers,
42
+ iconName: "related-single",
43
+ iconColor: "#7aae7f",
44
+ };
45
+ }
46
+ case COMPONENT_TYPE.GLOBAL_MENU: {
47
+ const answers = await inquirer.prompt([
48
+ {
49
+ type: "input",
50
+ name: "displayName",
51
+ message: "Display name:",
52
+ default: "Global Menu",
53
+ },
54
+ ]);
55
+ return {
56
+ ...answers,
57
+ iconName: "related-single",
58
+ };
59
+ }
60
+ case COMPONENT_TYPE.SIDE_MENU: {
61
+ const answers = await inquirer.prompt([
62
+ {
63
+ type: "list",
64
+ name: "width",
65
+ message: "Component width:",
66
+ choices: ["S", "M", "L"],
67
+ default: "M",
68
+ },
69
+ ]);
70
+ return {
71
+ ...answers,
72
+ iconName: "related-single",
73
+ };
74
+ }
75
+ default:
76
+ return {};
77
+ }
78
+ }
79
+ export async function runCreateComponent({ type, name, }) {
80
+ let componentName = name;
81
+ let componentType = type;
82
+ const manifestPath = path.join(process.cwd(), "manifest.yml");
83
+ const manifest = await getManifest();
84
+ const components = manifest.components;
85
+ const existingComponent = components?.find((comp) => comp.title === componentName);
86
+ if (existingComponent) {
87
+ throw new Error(`Component with name "${componentName}" already exists in manifest.yml`);
88
+ }
89
+ if (!componentName || !componentType) {
90
+ const questions = [];
91
+ if (!componentName) {
92
+ questions.push({
93
+ type: "input",
94
+ name: "name",
95
+ message: "Component name:",
96
+ });
97
+ }
98
+ if (!componentType) {
99
+ questions.push({
100
+ type: "list",
101
+ name: "type",
102
+ message: "Component type:",
103
+ choices: VALID_COMPONENT_TYPES,
104
+ });
105
+ }
106
+ const answers = await inquirer.prompt(questions);
107
+ if (!componentName) {
108
+ componentName = (answers.name || "").trim();
109
+ }
110
+ if (!componentType) {
111
+ componentType = answers.type;
112
+ }
113
+ }
114
+ if (!componentName) {
115
+ throw new Error("Missing component name.");
116
+ }
117
+ if (!componentType) {
118
+ throw new Error("Missing component type.");
119
+ }
120
+ const validatedType = validateComponentType(componentType);
121
+ const spinner = ora();
122
+ try {
123
+ const componentSettings = await promptForSettings(validatedType);
124
+ spinner.text = chalk.cyan(`Creating Vite React app for "${chalk.cyan(componentName)}"...`);
125
+ spinner.start();
126
+ const componentDir = path.join(process.cwd(), "static", componentName);
127
+ await fs.ensureDir(componentDir);
128
+ // Create Vite app with React template
129
+ spinner.text = `Running npm create vite@latest...`;
130
+ const viteResult = spawnSync(`npm create vite@latest ${componentName} -- --template react --no-interactive`, {
131
+ cwd: path.join(process.cwd(), "static"),
132
+ stdio: "inherit",
133
+ shell: true,
134
+ });
135
+ if (viteResult.error || viteResult.status !== 0) {
136
+ throw new Error(`Failed to create Vite app: ${viteResult.error?.message || `Exit code ${viteResult.status}`}`);
137
+ }
138
+ spinner.text = `Installing dependencies...`;
139
+ const installResult = spawnSync("npm install", {
140
+ cwd: componentDir,
141
+ stdio: "inherit",
142
+ shell: true,
143
+ });
144
+ if (installResult.error || installResult.status !== 0) {
145
+ throw new Error("Failed to install dependencies");
146
+ }
147
+ spinner.text = `Installing Fireberry packages...`;
148
+ const fireberryResult = spawnSync("npm install @fireberry/ds @fireberry/sdk", {
149
+ cwd: componentDir,
150
+ stdio: "inherit",
151
+ shell: true,
152
+ });
153
+ if (fireberryResult.error || fireberryResult.status !== 0) {
154
+ throw new Error("Failed to install Fireberry packages");
155
+ }
156
+ spinner.text = `Configuring component...`;
157
+ const templatesDir = path.join(__dirname, "..", "..", "src", "templates");
158
+ // Choose the right App.jsx template based on component type
159
+ const appTemplateFile = validatedType === COMPONENT_TYPE.RECORD
160
+ ? "App-record.jsx"
161
+ : "App-other.jsx";
162
+ const appTemplate = await fs.readFile(path.join(templatesDir, appTemplateFile), "utf-8");
163
+ await fs.writeFile(path.join(componentDir, "src", "App.jsx"), appTemplate);
164
+ // Build the component
165
+ spinner.text = `Building component...`;
166
+ const buildResult = spawnSync("npm run build", {
167
+ cwd: componentDir,
168
+ stdio: "inherit",
169
+ shell: true,
170
+ });
171
+ if (buildResult.error || buildResult.status !== 0) {
172
+ throw new Error("Failed to build component");
173
+ }
174
+ spinner.text = `Adding component to manifest...`;
175
+ const componentId = uuidv4();
176
+ const newComponent = {
177
+ type: validatedType,
178
+ title: componentName,
179
+ id: componentId,
180
+ path: `static/${componentName}/dist`,
181
+ settings: componentSettings,
182
+ };
183
+ if (!manifest.components) {
184
+ manifest.components = [];
185
+ }
186
+ manifest.components.push(newComponent);
187
+ const updatedYaml = yaml.dump(manifest, {
188
+ indent: 2,
189
+ lineWidth: -1,
190
+ noRefs: true,
191
+ });
192
+ await fs.writeFile(manifestPath, updatedYaml, "utf-8");
193
+ spinner.succeed(`Successfully created component "${chalk.cyan(componentName)}"!`);
194
+ console.log(chalk.gray(`Component ID: ${componentId}`));
195
+ console.log(chalk.gray(`Type: ${validatedType}`));
196
+ console.log(chalk.gray(`Path: static/${componentName}/dist`));
197
+ console.log(chalk.green("\nšŸŽ‰ Your component is ready!"));
198
+ console.log(chalk.white(` cd static/${componentName}`));
199
+ console.log(chalk.white(` npm run dev # Start development server`));
200
+ console.log(chalk.white(` npm run build # Build for production`));
201
+ }
202
+ catch (error) {
203
+ if (error instanceof Error) {
204
+ spinner.fail(chalk.red(`Failed to add component: ${error.message}`));
205
+ }
206
+ else {
207
+ spinner.fail(chalk.red(`Failed to add component "${chalk.cyan(componentName || "unknown")}"`));
208
+ }
209
+ throw error;
210
+ }
211
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fireberry/cli",
3
- "version": "0.2.3-beta.0",
3
+ "version": "0.3.0",
4
4
  "description": "Fireberry CLI tool",
5
5
  "type": "module",
6
6
  "author": "",
@@ -8,6 +8,7 @@ import { runPush } from "../commands/push.js";
8
8
  import { runInstall } from "../commands/install.js";
9
9
  import { runDelete } from "../commands/delete.js";
10
10
  import { runDebug } from "../commands/debug.js";
11
+ import { runCreateComponent } from "../commands/create-component.js";
11
12
 
12
13
  const program = new Command();
13
14
 
@@ -33,6 +34,15 @@ program
33
34
  await runCreate({ name });
34
35
  });
35
36
 
37
+ program
38
+ .command("create-component")
39
+ .argument("[name]", "Component name")
40
+ .argument("[type]", "Component type (record, global-menu, side-menu)")
41
+ .description("Add a new component to the existing app manifest")
42
+ .action(async (name, type) => {
43
+ await runCreateComponent({ name, type });
44
+ });
45
+
36
46
  program
37
47
  .command("push")
38
48
  .description("Push app to Fireberry")
@@ -0,0 +1,292 @@
1
+ import inquirer, { QuestionCollection } from "inquirer";
2
+ import path from "node:path";
3
+ import fs from "fs-extra";
4
+ import { v4 as uuidv4 } from "uuid";
5
+ import { fileURLToPath } from "node:url";
6
+ import { spawnSync } from "node:child_process";
7
+ import ora from "ora";
8
+ import chalk from "chalk";
9
+ import yaml from "js-yaml";
10
+ import { getManifest } from "../utils/components.utils.js";
11
+ import { COMPONENT_TYPE, ComponentType } from "../constants/component-types.js";
12
+
13
+ import { HEIGHT_OPTIONS } from "../constants/height-options.js";
14
+ import { UntypedManifestComponent } from "../api/types.js";
15
+ const __filename = fileURLToPath(import.meta.url);
16
+
17
+ const __dirname = path.dirname(__filename);
18
+ interface CreateComponentOptions {
19
+ name?: string;
20
+ type?: string;
21
+ }
22
+
23
+ const VALID_COMPONENT_TYPES = Object.values(COMPONENT_TYPE);
24
+
25
+ function validateComponentType(type: string): ComponentType {
26
+ if (!VALID_COMPONENT_TYPES.includes(type as ComponentType)) {
27
+ throw new Error(
28
+ `Invalid component type: "${type}". Valid types are: ${VALID_COMPONENT_TYPES.join(
29
+ ", "
30
+ )}`
31
+ );
32
+ }
33
+ return type as ComponentType;
34
+ }
35
+
36
+ async function promptForSettings(
37
+ type: ComponentType
38
+ ): Promise<Record<string, unknown>> {
39
+ switch (type) {
40
+ case COMPONENT_TYPE.RECORD: {
41
+ const answers = await inquirer.prompt([
42
+ {
43
+ type: "number",
44
+ name: "objectType",
45
+ message: "Object type:",
46
+ default: 0,
47
+ },
48
+ {
49
+ type: "list",
50
+ name: "height",
51
+ message: "Component height:",
52
+ choices: HEIGHT_OPTIONS,
53
+ default: "M",
54
+ },
55
+ ]);
56
+ return {
57
+ ...answers,
58
+ iconName: "related-single",
59
+ iconColor: "#7aae7f",
60
+ };
61
+ }
62
+ case COMPONENT_TYPE.GLOBAL_MENU: {
63
+ const answers = await inquirer.prompt([
64
+ {
65
+ type: "input",
66
+ name: "displayName",
67
+ message: "Display name:",
68
+ default: "Global Menu",
69
+ },
70
+ ]);
71
+ return {
72
+ ...answers,
73
+ iconName: "related-single",
74
+ };
75
+ }
76
+ case COMPONENT_TYPE.SIDE_MENU: {
77
+ const answers = await inquirer.prompt([
78
+ {
79
+ type: "list",
80
+ name: "width",
81
+ message: "Component width:",
82
+ choices: ["S", "M", "L"],
83
+ default: "M",
84
+ },
85
+ ]);
86
+ return {
87
+ ...answers,
88
+ iconName: "related-single",
89
+ };
90
+ }
91
+ default:
92
+ return {};
93
+ }
94
+ }
95
+
96
+ export async function runCreateComponent({
97
+ type,
98
+ name,
99
+ }: CreateComponentOptions): Promise<void> {
100
+ let componentName = name;
101
+ let componentType = type;
102
+
103
+ const manifestPath = path.join(process.cwd(), "manifest.yml");
104
+ const manifest = await getManifest();
105
+
106
+ const components = manifest.components as unknown as
107
+ | UntypedManifestComponent[]
108
+ | undefined;
109
+
110
+ const existingComponent = components?.find(
111
+ (comp) => comp.title === componentName
112
+ );
113
+ if (existingComponent) {
114
+ throw new Error(
115
+ `Component with name "${componentName}" already exists in manifest.yml`
116
+ );
117
+ }
118
+
119
+ if (!componentName || !componentType) {
120
+ const questions: QuestionCollection<{ name: string; type: string }>[] = [];
121
+
122
+ if (!componentName) {
123
+ questions.push({
124
+ type: "input",
125
+ name: "name",
126
+ message: "Component name:",
127
+ });
128
+ }
129
+
130
+ if (!componentType) {
131
+ questions.push({
132
+ type: "list",
133
+ name: "type",
134
+ message: "Component type:",
135
+ choices: VALID_COMPONENT_TYPES,
136
+ });
137
+ }
138
+
139
+ const answers = await inquirer.prompt(questions);
140
+
141
+ if (!componentName) {
142
+ componentName = (answers.name || "").trim();
143
+ }
144
+ if (!componentType) {
145
+ componentType = answers.type;
146
+ }
147
+ }
148
+
149
+ if (!componentName) {
150
+ throw new Error("Missing component name.");
151
+ }
152
+
153
+ if (!componentType) {
154
+ throw new Error("Missing component type.");
155
+ }
156
+
157
+ const validatedType = validateComponentType(componentType);
158
+
159
+ const spinner = ora();
160
+
161
+ try {
162
+ const componentSettings = await promptForSettings(validatedType);
163
+
164
+ spinner.text = chalk.cyan(
165
+ `Creating Vite React app for "${chalk.cyan(componentName)}"...`
166
+ );
167
+ spinner.start();
168
+
169
+ const componentDir = path.join(process.cwd(), "static", componentName);
170
+ await fs.ensureDir(componentDir);
171
+
172
+ // Create Vite app with React template
173
+ spinner.text = `Running npm create vite@latest...`;
174
+ const viteResult = spawnSync(
175
+ `npm create vite@latest ${componentName} -- --template react --no-interactive`,
176
+ {
177
+ cwd: path.join(process.cwd(), "static"),
178
+ stdio: "inherit",
179
+ shell: true,
180
+ }
181
+ );
182
+
183
+ if (viteResult.error || viteResult.status !== 0) {
184
+ throw new Error(
185
+ `Failed to create Vite app: ${
186
+ viteResult.error?.message || `Exit code ${viteResult.status}`
187
+ }`
188
+ );
189
+ }
190
+
191
+ spinner.text = `Installing dependencies...`;
192
+ const installResult = spawnSync("npm install", {
193
+ cwd: componentDir,
194
+ stdio: "inherit",
195
+ shell: true,
196
+ });
197
+
198
+ if (installResult.error || installResult.status !== 0) {
199
+ throw new Error("Failed to install dependencies");
200
+ }
201
+
202
+ spinner.text = `Installing Fireberry packages...`;
203
+ const fireberryResult = spawnSync(
204
+ "npm install @fireberry/ds @fireberry/sdk",
205
+ {
206
+ cwd: componentDir,
207
+ stdio: "inherit",
208
+ shell: true,
209
+ }
210
+ );
211
+
212
+ if (fireberryResult.error || fireberryResult.status !== 0) {
213
+ throw new Error("Failed to install Fireberry packages");
214
+ }
215
+ spinner.text = `Configuring component...`;
216
+
217
+ const templatesDir = path.join(__dirname, "..", "..", "src", "templates");
218
+
219
+ // Choose the right App.jsx template based on component type
220
+ const appTemplateFile =
221
+ validatedType === COMPONENT_TYPE.RECORD
222
+ ? "App-record.jsx"
223
+ : "App-other.jsx";
224
+ const appTemplate = await fs.readFile(
225
+ path.join(templatesDir, appTemplateFile),
226
+ "utf-8"
227
+ );
228
+ await fs.writeFile(path.join(componentDir, "src", "App.jsx"), appTemplate);
229
+
230
+ // Build the component
231
+ spinner.text = `Building component...`;
232
+ const buildResult = spawnSync("npm run build", {
233
+ cwd: componentDir,
234
+ stdio: "inherit",
235
+ shell: true,
236
+ });
237
+
238
+ if (buildResult.error || buildResult.status !== 0) {
239
+ throw new Error("Failed to build component");
240
+ }
241
+
242
+ spinner.text = `Adding component to manifest...`;
243
+
244
+ const componentId = uuidv4();
245
+
246
+ const newComponent: UntypedManifestComponent = {
247
+ type: validatedType,
248
+ title: componentName,
249
+ id: componentId,
250
+ path: `static/${componentName}/dist`,
251
+ settings: componentSettings,
252
+ };
253
+
254
+ if (!manifest.components) {
255
+ manifest.components = [];
256
+ }
257
+
258
+ (manifest.components as unknown as UntypedManifestComponent[]).push(
259
+ newComponent
260
+ );
261
+
262
+ const updatedYaml = yaml.dump(manifest, {
263
+ indent: 2,
264
+ lineWidth: -1,
265
+ noRefs: true,
266
+ });
267
+
268
+ await fs.writeFile(manifestPath, updatedYaml, "utf-8");
269
+
270
+ spinner.succeed(
271
+ `Successfully created component "${chalk.cyan(componentName)}"!`
272
+ );
273
+ console.log(chalk.gray(`Component ID: ${componentId}`));
274
+ console.log(chalk.gray(`Type: ${validatedType}`));
275
+ console.log(chalk.gray(`Path: static/${componentName}/dist`));
276
+ console.log(chalk.green("\nšŸŽ‰ Your component is ready!"));
277
+ console.log(chalk.white(` cd static/${componentName}`));
278
+ console.log(chalk.white(` npm run dev # Start development server`));
279
+ console.log(chalk.white(` npm run build # Build for production`));
280
+ } catch (error) {
281
+ if (error instanceof Error) {
282
+ spinner.fail(chalk.red(`Failed to add component: ${error.message}`));
283
+ } else {
284
+ spinner.fail(
285
+ chalk.red(
286
+ `Failed to add component "${chalk.cyan(componentName || "unknown")}"`
287
+ )
288
+ );
289
+ }
290
+ throw error;
291
+ }
292
+ }
@@ -0,0 +1,28 @@
1
+ import { useState } from "react";
2
+ import { Button, Typography, DSThemeContextProvider } from "@fireberry/ds";
3
+
4
+ function App() {
5
+ const [isLoading, setIsLoading] = useState(false);
6
+
7
+ const handleButtonClick = async () => {
8
+ setIsLoading(true);
9
+ // Add your logic here
10
+ setTimeout(() => {
11
+ setIsLoading(false);
12
+ }, 1000);
13
+ };
14
+
15
+ return (
16
+ <DSThemeContextProvider isRtl={false}>
17
+ <div>
18
+ <Typography type="h3" color="information">Hello from Fireberry!</Typography>
19
+ <Typography>This is a Fireberry component built with React + Vite</Typography>
20
+ <p style={{ marginBottom: "10px" }}>
21
+ <Button onClick={handleButtonClick} label="Click Me" isLoading={isLoading}></Button>
22
+ </p>
23
+ </div>
24
+ </DSThemeContextProvider>
25
+ );
26
+ }
27
+
28
+ export default App;
@@ -0,0 +1,64 @@
1
+ import { useState, useEffect, useMemo } from "react";
2
+ import FireberryClientSDK from "@fireberry/sdk/client";
3
+ import { Button, Typography, DSThemeContextProvider } from "@fireberry/ds";
4
+
5
+ function App() {
6
+ const [isInitialized, setIsInitialized] = useState(false);
7
+ const [initError, setInitError] = useState(null);
8
+ const [context, setContext] = useState(null);
9
+ const [api, setApi] = useState(null);
10
+ const [isLoading, setIsLoading] = useState(false);
11
+
12
+ // Create a new instance (memoized to avoid recreating on every render)
13
+ const client = useMemo(() => new FireberryClientSDK(), []);
14
+
15
+ // Initialize context on component mount
16
+ useEffect(() => {
17
+ const initializeContext = async () => {
18
+ try {
19
+ await client.initializeContext();
20
+ setIsInitialized(true);
21
+ setContext(client.context);
22
+ setApi(client.api);
23
+ } catch (error) {
24
+ setInitError(error);
25
+ console.error("Failed to initialize context:", error);
26
+ }
27
+ };
28
+
29
+ initializeContext();
30
+ }, [client]);
31
+
32
+ const handleButtonClick = async () => {
33
+ setIsLoading(true)
34
+ if (context.record && context?.user.id) {
35
+ await api.update(context.record.type, context.record.id, {
36
+ ownerid: context?.user.id,
37
+ });
38
+ }
39
+ setIsLoading(false)
40
+ };
41
+
42
+ return (
43
+ <DSThemeContextProvider isRtl={false}>
44
+ {initError ? (
45
+ <div>
46
+ <h1>Error Initializing Context</h1>
47
+ <p>Error: {initError.message || String(initError)}</p>
48
+ </div>
49
+ ) : isInitialized ? (
50
+ <div>
51
+ <Typography type="h3" color="information">Hello {context?.user.fullName}!</Typography>
52
+ <Typography >Click <Typography color="success" >Assign</Typography> button to assign record to current user ({context?.user.fullName})</Typography>
53
+ <p style={{ marginBottom: "10px" }}><Button onClick={handleButtonClick} label="Assign" isLoading={isLoading}></Button></p>
54
+ </div>
55
+ ) : (
56
+ <div>
57
+ <h1>Initializing Context...</h1>
58
+ </div>
59
+ )}
60
+ </DSThemeContextProvider>
61
+ );
62
+ }
63
+
64
+ export default App;