your-ai-workflow-firebase-os 1.2.21 → 1.2.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/FirebaseOS.d.ts +2 -0
- package/dist/lib/ConfigContext.d.ts +2 -0
- package/dist/your-ai-workflow-firebase-os.cjs.js +58 -201
- package/dist/your-ai-workflow-firebase-os.css +1 -1
- package/dist/your-ai-workflow-firebase-os.es.js +1925 -2087
- package/package.json +1 -1
- package/scripts/postinstall.js +19 -0
- package/src/FirebaseOS.tsx +4 -0
- package/src/lib/ConfigContext.tsx +2 -0
- package/src/microcomponents/registry.ts +39 -0
- package/src/pages/DynamicPage.tsx +18 -7
- package/src/pages/PagesAdmin.tsx +64 -56
- package/src/prompts/pages/publicPage.ts +11 -36
- package/src/prompts/tabs/crud/admin.ts +13 -55
- package/src/prompts/tabs/crud/private.ts +14 -58
- package/src/prompts/tabs/crud/shared.ts +13 -55
- package/src/templates/AdminPageTemplate.tsx +116 -110
- package/src/templates/PrivatePageTemplate.tsx +46 -40
- package/src/templates/PublicPageTemplate.tsx +13 -7
- package/src/templates/SharedPageTemplate.tsx +24 -18
- package/src/microcomponents/AdminExampleContent.tsx +0 -44
- package/src/microcomponents/PrivateExampleContent.tsx +0 -39
- package/src/microcomponents/Public.tsx +0 -126
- package/src/microcomponents/SharedExampleContent.tsx +0 -53
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -114,6 +114,21 @@ copyConfig('forms/contactForm.config.ts', 'contactForm.config.ts');
|
|
|
114
114
|
copyConfig('forms/supportForm.config.ts', 'supportForm.config.ts');
|
|
115
115
|
console.log(' ✓ Copied configuration files to your src/your-ai-workflow-firebase-os/configs directory');
|
|
116
116
|
|
|
117
|
+
// Copy microcomponents auto-registry
|
|
118
|
+
const microcomponentsSourceDir = path.join(__dirname, '..', 'src', 'microcomponents');
|
|
119
|
+
const microcomponentsDestDir = path.join(fbosDir, 'microcomponents');
|
|
120
|
+
if (fs.existsSync(microcomponentsSourceDir)) {
|
|
121
|
+
if (!fs.existsSync(microcomponentsDestDir)) fs.mkdirSync(microcomponentsDestDir, { recursive: true });
|
|
122
|
+
const files = fs.readdirSync(microcomponentsSourceDir);
|
|
123
|
+
for (const file of files) {
|
|
124
|
+
// Only copy the registry — user creates their own content files
|
|
125
|
+
if (file === 'registry.ts') {
|
|
126
|
+
fs.copyFileSync(path.join(microcomponentsSourceDir, file), path.join(microcomponentsDestDir, file));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
console.log(' ✓ Copied microcomponents auto-registry to your src/your-ai-workflow-firebase-os/microcomponents directory');
|
|
130
|
+
}
|
|
131
|
+
|
|
117
132
|
// Read and rewrite Home.tsx
|
|
118
133
|
const sourceHomePath = path.join(__dirname, '..', 'src', 'pages', 'Home.tsx');
|
|
119
134
|
if (fs.existsSync(sourceHomePath)) {
|
|
@@ -135,6 +150,9 @@ const appContent = `import { FirebaseOS } from 'your-ai-workflow-firebase-os';
|
|
|
135
150
|
import { Home } from './your-ai-workflow-firebase-os/Home';
|
|
136
151
|
import { themeConfig } from './your-ai-workflow-firebase-os/configs/theme.config';
|
|
137
152
|
|
|
153
|
+
// Auto-discovered microcomponents (zero-config: just create a .tsx file in microcomponents/)
|
|
154
|
+
import { microcomponentRegistry } from './your-ai-workflow-firebase-os/microcomponents/registry';
|
|
155
|
+
|
|
138
156
|
// Local Configuration Defaults (The Final Boss)
|
|
139
157
|
import { defaultHomeConfig } from './your-ai-workflow-firebase-os/configs/home.config';
|
|
140
158
|
import { contactFormConfig } from './your-ai-workflow-firebase-os/configs/contactForm.config';
|
|
@@ -161,6 +179,7 @@ export default function App() {
|
|
|
161
179
|
firebaseConfig={firebaseConfig}
|
|
162
180
|
adminEmails={adminEmails}
|
|
163
181
|
themeConfig={themeConfig}
|
|
182
|
+
microcomponents={microcomponentRegistry}
|
|
164
183
|
components={{
|
|
165
184
|
Home: Home
|
|
166
185
|
}}
|
package/src/FirebaseOS.tsx
CHANGED
|
@@ -36,6 +36,9 @@ export interface FirebaseOSProps {
|
|
|
36
36
|
/** Custom page components mapped by route (e.g. { "/focus": FocusTimerPage }) */
|
|
37
37
|
customRoutes?: Record<string, React.ComponentType<any>>;
|
|
38
38
|
|
|
39
|
+
/** Auto-discovered microcomponents mapped by parsedTabName (e.g. { timer: TimerContent }) */
|
|
40
|
+
microcomponents?: Record<string, React.ComponentType<any>>;
|
|
41
|
+
|
|
39
42
|
/** Component overrides for the hybrid template architecture */
|
|
40
43
|
components?: {
|
|
41
44
|
Home?: React.ComponentType<any>;
|
|
@@ -80,6 +83,7 @@ export function FirebaseOS(props: FirebaseOSProps) {
|
|
|
80
83
|
themeConfig: props.themeConfig,
|
|
81
84
|
components: props.components,
|
|
82
85
|
customRoutes: props.customRoutes,
|
|
86
|
+
microcomponents: props.microcomponents,
|
|
83
87
|
defaultConfigs: props.defaultConfigs,
|
|
84
88
|
};
|
|
85
89
|
|
|
@@ -20,6 +20,8 @@ export interface FirebaseOSConfig {
|
|
|
20
20
|
onAuthChange?: (user: any) => void;
|
|
21
21
|
themeConfig?: any;
|
|
22
22
|
customRoutes?: Record<string, React.ComponentType<any>>;
|
|
23
|
+
/** Auto-discovered microcomponents mapped by parsedTabName (e.g. { timer: TimerContent }) */
|
|
24
|
+
microcomponents?: Record<string, React.ComponentType<any>>;
|
|
23
25
|
components?: {
|
|
24
26
|
Home?: React.ComponentType<any>;
|
|
25
27
|
TemplateBoard?: React.ComponentType<any>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Microcomponent Auto-Registry
|
|
3
|
+
*
|
|
4
|
+
* Uses Vite's import.meta.glob to auto-discover all .tsx files in this folder.
|
|
5
|
+
* Each file is matched to a tab by convention:
|
|
6
|
+
* - "TimerContent.tsx" → matches tab named "Timer" (parsedTabName: "timer")
|
|
7
|
+
* - "MyDashboardContent.tsx" → matches "My Dashboard" (parsedTabName: "my_dashboard")
|
|
8
|
+
*
|
|
9
|
+
* The AI only needs to create a file here. No routing or App.tsx changes needed.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const modules = import.meta.glob('./*.tsx', { eager: true }) as Record<string, { default: React.ComponentType<any> }>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Auto-generated registry mapping parsedTabName → React component.
|
|
16
|
+
* e.g. { timer: TimerContent, my_dashboard: MyDashboardContent }
|
|
17
|
+
*/
|
|
18
|
+
export const microcomponentRegistry: Record<string, React.ComponentType<any>> = {};
|
|
19
|
+
|
|
20
|
+
for (const filePath in modules) {
|
|
21
|
+
const mod = modules[filePath];
|
|
22
|
+
if (!mod?.default) continue;
|
|
23
|
+
|
|
24
|
+
// Extract component name from path: "./TimerContent.tsx" → "TimerContent"
|
|
25
|
+
const fileName = filePath.replace('./', '').replace('.tsx', '');
|
|
26
|
+
|
|
27
|
+
// Skip the registry file itself
|
|
28
|
+
if (fileName === 'registry') continue;
|
|
29
|
+
|
|
30
|
+
// Convert PascalCase to snake_case tab name:
|
|
31
|
+
// "TimerContent" → strip "Content" → "Timer" → "timer"
|
|
32
|
+
// "MyDashboardContent" → "MyDashboard" → "my_dashboard"
|
|
33
|
+
const baseName = fileName.replace(/Content$/, '');
|
|
34
|
+
const tabName = baseName
|
|
35
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
36
|
+
.toLowerCase();
|
|
37
|
+
|
|
38
|
+
microcomponentRegistry[tabName] = mod.default;
|
|
39
|
+
}
|
|
@@ -212,25 +212,36 @@ export function DynamicPage() {
|
|
|
212
212
|
return <Navigate to="/login" state={{ from: location }} />;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
// Explicit customRoutes override (manual registration in App.tsx)
|
|
215
216
|
const CustomRouteComponent = globalConfig.customRoutes?.[location.pathname] || (config && config.route ? globalConfig.customRoutes?.[config.route] : null);
|
|
216
|
-
if (CustomRouteComponent) {
|
|
217
|
-
return <CustomRouteComponent config={config} />;
|
|
218
|
-
}
|
|
219
217
|
|
|
220
218
|
const parsedPageName = config.pageName?.toLowerCase().replace(/\s+/g, '_') || config.pageId || 'custom_page';
|
|
221
219
|
const prefix = `pub_${parsedPageName}`;
|
|
222
220
|
|
|
221
|
+
// Auto-discovered microcomponent by tab name (zero-config)
|
|
222
|
+
const parsedTabName = (config.tabName || config.pageId || config.pageName || 'custom')
|
|
223
|
+
.toLowerCase().replace(/[^a-z0-9]+/g, '_');
|
|
224
|
+
const AutoComponent = globalConfig.microcomponents?.[parsedTabName];
|
|
225
|
+
|
|
226
|
+
// Priority: explicit customRoute > auto-discovered microcomponent
|
|
227
|
+
const ContentOverride = CustomRouteComponent || AutoComponent || undefined;
|
|
228
|
+
|
|
223
229
|
if (config.template === 'none') {
|
|
224
230
|
if (config.isAdmin) {
|
|
225
|
-
return <AdminPageTemplate config={config} />;
|
|
231
|
+
return <AdminPageTemplate config={config} customContent={ContentOverride} />;
|
|
226
232
|
}
|
|
227
233
|
if (config.isShared) {
|
|
228
|
-
return <SharedPageTemplate config={config} />;
|
|
234
|
+
return <SharedPageTemplate config={config} customContent={ContentOverride} />;
|
|
229
235
|
}
|
|
230
236
|
if (config.isPrivate) {
|
|
231
|
-
return <PrivatePageTemplate config={config} />;
|
|
237
|
+
return <PrivatePageTemplate config={config} customContent={ContentOverride} />;
|
|
232
238
|
}
|
|
233
|
-
return <PublicPageTemplate config={config} />;
|
|
239
|
+
return <PublicPageTemplate config={config} customContent={ContentOverride} />;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// For non-'none' templates, customRoutes still replace the full page
|
|
243
|
+
if (CustomRouteComponent) {
|
|
244
|
+
return <CustomRouteComponent config={config} />;
|
|
234
245
|
}
|
|
235
246
|
|
|
236
247
|
if (config.template === 'board') {
|
package/src/pages/PagesAdmin.tsx
CHANGED
|
@@ -437,73 +437,81 @@ export function PagesAdmin() {
|
|
|
437
437
|
[`user_${parsedTabName}_records`]: { prefix: 'user_files', driveCollection: 'user_files' },
|
|
438
438
|
};
|
|
439
439
|
|
|
440
|
+
const cleanupPromises: Promise<any>[] = [];
|
|
441
|
+
|
|
440
442
|
for (const col of collectionsClean) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
443
|
+
cleanupPromises.push((async () => {
|
|
444
|
+
try {
|
|
445
|
+
const recordsSnap = await getDocs(collection(db, col));
|
|
446
|
+
const mapping = storageMap[col];
|
|
447
|
+
const filePromises: Promise<any>[] = [];
|
|
448
|
+
|
|
449
|
+
// Delete files from Storage and Drive for file-type records
|
|
450
|
+
for (const d of recordsSnap.docs) {
|
|
451
|
+
const data = d.data();
|
|
452
|
+
if (data.recordType === 'file' && data.downloadURL && mapping) {
|
|
453
|
+
filePromises.push((async () => {
|
|
454
|
+
try {
|
|
455
|
+
const encodedPrefix = `${mapping.prefix}%2F`;
|
|
456
|
+
const fileName = data.downloadURL.split(encodedPrefix)[1]?.split('?')[0];
|
|
457
|
+
if (fileName) {
|
|
458
|
+
const fileRef = ref(storage, `${mapping.prefix}/${decodeURIComponent(fileName)}`);
|
|
459
|
+
await deleteObject(fileRef).catch(() => {});
|
|
460
|
+
}
|
|
461
|
+
// Remove matching Drive metadata doc by downloadURL
|
|
462
|
+
const driveSnap = await getDocs(query(
|
|
463
|
+
collection(db, mapping.driveCollection),
|
|
464
|
+
where('downloadURL', '==', data.downloadURL)
|
|
465
|
+
));
|
|
466
|
+
await Promise.all(driveSnap.docs.map(driveDoc => deleteDoc(driveDoc.ref)));
|
|
467
|
+
} catch (fileErr) {
|
|
468
|
+
console.error('Error deleting storage file:', fileErr);
|
|
469
|
+
}
|
|
470
|
+
})());
|
|
466
471
|
}
|
|
467
472
|
}
|
|
468
|
-
|
|
473
|
+
await Promise.all(filePromises);
|
|
469
474
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
await Promise.all(deletePromises);
|
|
475
|
+
// Delete all record documents in this collection
|
|
476
|
+
await Promise.all(recordsSnap.docs.map(d => deleteDoc(d.ref)));
|
|
473
477
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
478
|
+
// Clear localStorage cache for this collection
|
|
479
|
+
try { localStorage.removeItem(`fbos_records_${col}`); } catch {}
|
|
480
|
+
} catch (colErr) {
|
|
481
|
+
console.error(`Error cleaning up collection ${col}:`, colErr);
|
|
482
|
+
}
|
|
483
|
+
})());
|
|
479
484
|
}
|
|
480
485
|
|
|
481
486
|
// Also clean Drive collections by sourceTab (catches any orphaned file docs)
|
|
482
487
|
const driveCollections = ['admin_files', 'mem_files', 'user_files'];
|
|
483
488
|
for (const driveColl of driveCollections) {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
489
|
+
cleanupPromises.push((async () => {
|
|
490
|
+
try {
|
|
491
|
+
const orphanedSnap = await getDocs(query(
|
|
492
|
+
collection(db, driveColl),
|
|
493
|
+
where('sourceTab', '==', parsedTabName)
|
|
494
|
+
));
|
|
495
|
+
await Promise.all(orphanedSnap.docs.map(async (orphanDoc) => {
|
|
496
|
+
const data = orphanDoc.data();
|
|
497
|
+
// Also delete the actual file from Storage if we haven't already
|
|
498
|
+
if (data.downloadURL) {
|
|
499
|
+
try {
|
|
500
|
+
const encodedPrefix = `${driveColl}%2F`;
|
|
501
|
+
const fName = data.downloadURL.split(encodedPrefix)[1]?.split('?')[0];
|
|
502
|
+
if (fName) {
|
|
503
|
+
const fRef = ref(storage, `${driveColl}/${decodeURIComponent(fName)}`);
|
|
504
|
+
await deleteObject(fRef).catch(() => {});
|
|
505
|
+
}
|
|
506
|
+
} catch {}
|
|
507
|
+
}
|
|
508
|
+
await deleteDoc(orphanDoc.ref);
|
|
509
|
+
}));
|
|
510
|
+
} catch {}
|
|
511
|
+
})());
|
|
506
512
|
}
|
|
513
|
+
|
|
514
|
+
await Promise.all(cleanupPromises);
|
|
507
515
|
}
|
|
508
516
|
|
|
509
517
|
// Delete the tab/page document itself
|
|
@@ -1,44 +1,19 @@
|
|
|
1
1
|
export const publicPagePrompt = (config: any, forms: string[] = []) => {
|
|
2
2
|
const parsedPageName = (config.pageName || config.pageId || 'custom_page')
|
|
3
3
|
.toLowerCase().replace(/[^a-z0-9]+/g, '_');
|
|
4
|
-
const
|
|
4
|
+
const pascalName = parsedPageName
|
|
5
|
+
.split('_')
|
|
6
|
+
.map((w: string) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
7
|
+
.join('');
|
|
8
|
+
const fileName = `${pascalName}Content`;
|
|
5
9
|
|
|
6
|
-
return `
|
|
10
|
+
return `Create file: src/your-ai-workflow-firebase-os/microcomponents/${fileName}.tsx
|
|
7
11
|
|
|
8
|
-
|
|
12
|
+
export default function ${fileName}({ config }: { config: any }) {
|
|
13
|
+
return ( <div>your content here</div> );
|
|
14
|
+
}
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
\`\`\`json
|
|
12
|
-
${JSON.stringify({
|
|
13
|
-
pageName: config.pageName,
|
|
14
|
-
pageTitle: config.pageTitle,
|
|
15
|
-
route: config.route,
|
|
16
|
-
template: config.template,
|
|
17
|
-
}, null, 2)}
|
|
18
|
-
\`\`\`
|
|
16
|
+
No page titles, no nav, no routing — already handled. Default export only.
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
The following public forms are available in the system. **These are for your information only—do NOT use them unless specifically needed.** Your priority is to listen to the user's specific input and decide if a particular modal, form, or component is actually required for the context of this page:
|
|
22
|
-
${forms.length > 0 ? forms.map(f => `- ${f}`).join('\n') : '- No public forms found'}
|
|
23
|
-
|
|
24
|
-
### ── Your Mission ────────────────────────────────────────────────────────────
|
|
25
|
-
1. **Create the Content Layer**: Design a new micro-component at \`src/microcomponents/${microName}.tsx\`.
|
|
26
|
-
2. **Build the UI**: Inside this component, build the landing page content (hero sections, feature grids, contact sections, etc.).
|
|
27
|
-
3. **Inject into Template**: Once created, import and place \`<${microName} />\` inside the main content area of \`src/templates/PublicPageTemplate.tsx\`, replacing the placeholder robot icon and default text.
|
|
28
|
-
|
|
29
|
-
### ── Guardrails ─────────────────────────────────────────────────────────────
|
|
30
|
-
- **DO NOT** modify the outer layout of \`PublicPageTemplate.tsx\` (back buttons, page header, footer, dynamic title).
|
|
31
|
-
- **DO NOT** change the routing or page exports.
|
|
32
|
-
- Focus exclusively on the inner content box using the micro-component strategy.
|
|
33
|
-
|
|
34
|
-
### ── Aesthetic & Performance (Theme System) ───────────────────────────────
|
|
35
|
-
Build with the user's active theme in mind:
|
|
36
|
-
- **CSS Variables**: \`var(--bg-color)\`, \`var(--fg-color)\`, \`var(--accent-color)\`, \`var(--panel-bg)\`, \`var(--panel-border)\`.
|
|
37
|
-
- **Components**: Re-use \`<Button />\`, \`<Input />\`, \`<ConfirmModal />\`, and \`<ContactPopup />\` natively.
|
|
38
|
-
- **Styles**: Use the \`glass-panel\` class for sections and \`text-gradient\` for headers.
|
|
39
|
-
|
|
40
|
-
### ── Important Formatting Rules ──────────────────────────────────────────────
|
|
41
|
-
- **NO REDUNDANT HEADERS**: The parent template already injects a large dynamic page title and action buttons. DO NOT add a main \`<h1>\` or \`<h2>\` page title at the top of your micro-component. Start directly with your cards, lists, or sub-sections.
|
|
42
|
-
|
|
43
|
-
[WHAT SHOULD I BUILD ON THIS PUBLIC PAGE?]`;
|
|
18
|
+
[WHAT TO BUILD?]`;
|
|
44
19
|
};
|
|
@@ -1,63 +1,21 @@
|
|
|
1
1
|
export const adminCrudPrompt = (config: any, recordsCollection: string) => {
|
|
2
2
|
const parsedTabName = (config.tabName || config.pageId || 'admin_page')
|
|
3
3
|
.toLowerCase().replace(/[^a-z0-9]+/g, '_');
|
|
4
|
-
const
|
|
4
|
+
const pascalName = parsedTabName
|
|
5
|
+
.split('_')
|
|
6
|
+
.map((w: string) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
7
|
+
.join('');
|
|
8
|
+
const fileName = `${pascalName}Content`;
|
|
5
9
|
|
|
6
|
-
return `
|
|
10
|
+
return `Create file: src/your-ai-workflow-firebase-os/microcomponents/${fileName}.tsx
|
|
7
11
|
|
|
8
|
-
|
|
12
|
+
export default function ${fileName}({ config }: { config: any }) {
|
|
13
|
+
return ( <div>your content here</div> );
|
|
14
|
+
}
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
viewName: config.tabName,
|
|
14
|
-
viewTitle: config.tabTitle,
|
|
15
|
-
route: config.route,
|
|
16
|
-
template: config.template,
|
|
17
|
-
storageEnabled: !!config.storage,
|
|
18
|
-
showActionButton: !!config.showButton,
|
|
19
|
-
buttonStyle: config.buttonAction || 'primary',
|
|
20
|
-
}, null, 2)}
|
|
21
|
-
\`\`\`
|
|
16
|
+
Firestore collection: \`${recordsCollection}\`
|
|
17
|
+
Firebase imports: \`import { db } from 'your-ai-workflow-firebase-os'\`
|
|
18
|
+
No page titles, no nav, no routing — already handled. Default export only.
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
**No forms are needed for this view.** Focus on building the administrative dashboard and management tools requested by the user.
|
|
25
|
-
|
|
26
|
-
### ── Data & Storage Architecture ─────────────────────────────────────────────
|
|
27
|
-
The app is already configured to handle data for this specific view using these paths:
|
|
28
|
-
- **Firestore Collection**: \`${recordsCollection}\` (Use this for all view data).
|
|
29
|
-
- **Storage Layer**: ${config.storage ? "Enabled. Uploads are handled via \`admin_files/\` folder in Storage and mirrored to a shared Firestore collection for tracking." : "Disabled for this view."}
|
|
30
|
-
- **Access Level**: Full Admin Access. This view is only visible to users with the 'admin' role.
|
|
31
|
-
|
|
32
|
-
### ── Your Mission ────────────────────────────────────────────────────────────
|
|
33
|
-
1. **Create the Page**: Create a full page component at \`src/pages/\${microName}Page.tsx\`. Include \`<DashboardNav />\` from \`your-ai-workflow-firebase-os\` at the top of the content area.
|
|
34
|
-
2. **Handle UX/UI**: Inside this component, build the interface (lists, charts, management forms, or any feature requested).
|
|
35
|
-
3. **Register the Route**: Open \`src/App.tsx\` and map this component to the \`customRoutes\` prop of \`<FirebaseOS>\`:
|
|
36
|
-
\`\`\`tsx
|
|
37
|
-
import { \${microName}Page } from './pages/\${microName}Page';
|
|
38
|
-
|
|
39
|
-
// Inside App.tsx:
|
|
40
|
-
<FirebaseOS
|
|
41
|
-
customRoutes={{
|
|
42
|
-
'\${config.route}': \${microName}Page
|
|
43
|
-
}}
|
|
44
|
-
// ...
|
|
45
|
-
/>
|
|
46
|
-
\`\`\`
|
|
47
|
-
|
|
48
|
-
### ── Guardrails ─────────────────────────────────────────────────────────────
|
|
49
|
-
- **CRITICAL: DO NOT CREATE A NEW ROUTE IN REACT ROUTER.** You are only mapping a component to an existing dynamic route via \\\`customRoutes\\\`.
|
|
50
|
-
- **Database Logic**: You must write your own database queries inside the component using the provided collection names.
|
|
51
|
-
|
|
52
|
-
### ── Aesthetic & Performance (Theme System) ───────────────────────────────
|
|
53
|
-
Build with the user's active theme in mind:
|
|
54
|
-
- **CSS Variables**: \`var(--bg-color)\`, \`var(--fg-color)\`, \`var(--accent-color)\`, \`var(--panel-bg)\`, \`var(--panel-border)\`.
|
|
55
|
-
- **Components**: Re-use \`<Button />\`, \`<Input />\`, \`<ConfirmModal />\`, and \`<DashboardNav />\` natively.
|
|
56
|
-
- **Styles**: Use \`glass-panel\` and \`text-gradient\` for a premium look.
|
|
57
|
-
- **Example**: See \`src/microcomponents/AdminExampleContent.tsx\` for a structural reference.
|
|
58
|
-
|
|
59
|
-
### ── Important Formatting Rules ──────────────────────────────────────────────
|
|
60
|
-
- **NO REDUNDANT HEADERS**: The parent template already injects a large dynamic page title and action buttons. DO NOT add a main \`<h1>\` or \`<h2>\` page title at the top of your micro-component. Start directly with your cards, lists, or sub-sections.
|
|
61
|
-
|
|
62
|
-
[WHAT FEATURES SHOULD I ADD TO THIS ADMIN VIEW?]`;
|
|
20
|
+
[WHAT TO BUILD?]`;
|
|
63
21
|
};
|
|
@@ -1,65 +1,21 @@
|
|
|
1
|
-
export const privateCrudPrompt = (config: any, recordsCollection: string
|
|
1
|
+
export const privateCrudPrompt = (config: any, recordsCollection: string) => {
|
|
2
2
|
const parsedTabName = (config.tabName || config.pageId || 'private_page')
|
|
3
3
|
.toLowerCase().replace(/[^a-z0-9]+/g, '_');
|
|
4
|
-
const
|
|
4
|
+
const pascalName = parsedTabName
|
|
5
|
+
.split('_')
|
|
6
|
+
.map((w: string) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
7
|
+
.join('');
|
|
8
|
+
const fileName = `${pascalName}Content`;
|
|
5
9
|
|
|
6
|
-
return `
|
|
10
|
+
return `Create file: src/your-ai-workflow-firebase-os/microcomponents/${fileName}.tsx
|
|
7
11
|
|
|
8
|
-
|
|
12
|
+
export default function ${fileName}({ config }: { config: any }) {
|
|
13
|
+
return ( <div>your content here</div> );
|
|
14
|
+
}
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
viewName: config.tabName,
|
|
14
|
-
viewTitle: config.tabTitle,
|
|
15
|
-
route: config.route,
|
|
16
|
-
template: config.template,
|
|
17
|
-
storageEnabled: !!config.storage,
|
|
18
|
-
showActionButton: !!config.showButton,
|
|
19
|
-
availablePrivateForms: forms,
|
|
20
|
-
}, null, 2)}
|
|
21
|
-
\`\`\`
|
|
16
|
+
Firestore collection: \`${recordsCollection}\` — filter by \`where('uid', '==', user.uid)\`
|
|
17
|
+
Firebase: \`import { db, useAuth } from 'your-ai-workflow-firebase-os'\`
|
|
18
|
+
No page titles, no nav, no routing — already handled. Default export only.
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
The following private forms are available in the system. **These are for your information only—do NOT use them unless specifically needed.** Your priority is to listen to the user's specific input and decide if a particular modal, form, or component is actually required for the context of this page:
|
|
25
|
-
${forms.length > 0 ? forms.map(f => `- ${f}`).join('\n') : '- No private forms found'}
|
|
26
|
-
|
|
27
|
-
### ── Data & Storage Architecture ─────────────────────────────────────────────
|
|
28
|
-
Every user sees their own unique data on this page. The system is rigged to handle this isolation:
|
|
29
|
-
- **Firestore Collection**: \`${recordsCollection}\`. The template already filters this by \`uid == current_user.uid\`. Use this for all record storage.
|
|
30
|
-
- **Storage Layer**: ${config.storage ? "Enabled. User files are saved to \`user_files/\` and strictly isolated by user ID." : "Disabled for this view."}
|
|
31
|
-
- **Access Level**: Private. Only the owner of the data can view or modify these records.
|
|
32
|
-
|
|
33
|
-
### ── Your Mission ────────────────────────────────────────────────────────────
|
|
34
|
-
1. **Create the Page**: Create a full page component at \`src/pages/\${microName}Page.tsx\`. Include \`<DashboardNav />\` from \`your-ai-workflow-firebase-os\` at the top of the content area.
|
|
35
|
-
2. **Handle UX/UI**: Inside this component, build the interface (lists, charts, forms, etc).
|
|
36
|
-
3. **Register the Route**: Open \`src/App.tsx\` and map this component to the \`customRoutes\` prop of \`<FirebaseOS>\`:
|
|
37
|
-
\`\`\`tsx
|
|
38
|
-
import { \${microName}Page } from './pages/\${microName}Page';
|
|
39
|
-
|
|
40
|
-
// Inside App.tsx:
|
|
41
|
-
<FirebaseOS
|
|
42
|
-
customRoutes={{
|
|
43
|
-
'\${config.route}': \${microName}Page
|
|
44
|
-
}}
|
|
45
|
-
// ...
|
|
46
|
-
/>
|
|
47
|
-
\`\`\`
|
|
48
|
-
|
|
49
|
-
### ── Guardrails ─────────────────────────────────────────────────────────────
|
|
50
|
-
- **CRITICAL: DO NOT CREATE A NEW ROUTE IN REACT ROUTER.** You are only mapping a component to an existing dynamic route via \\\`customRoutes\\\`.
|
|
51
|
-
- **Database Logic**: You must write your own database queries inside the component using the provided collection names.
|
|
52
|
-
- Re-use the \`ContactPopup\` component with the available private forms if the user needs to submit requests.
|
|
53
|
-
|
|
54
|
-
### ── Aesthetic & Performance (Theme System) ───────────────────────────────
|
|
55
|
-
Build with the user's active theme in mind:
|
|
56
|
-
- **CSS Variables**: Use \`var(--bg-color)\`, \`var(--fg-color)\`, \`var(--accent-color)\`, \`var(--panel-bg)\`, \`var(--panel-border)\`.
|
|
57
|
-
- **Components**: Re-use \`<Button />\`, \`<Input />\`, \`<ConfirmModal />\`, and \`<DashboardNav />\` natively.
|
|
58
|
-
- **Styles**: Use \`glass-panel\` and \`text-gradient\` for a premium look.
|
|
59
|
-
- **Example**: See \`src/microcomponents/PrivateExampleContent.tsx\` for a structural reference.
|
|
60
|
-
|
|
61
|
-
### ── Important Formatting Rules ──────────────────────────────────────────────
|
|
62
|
-
- **NO REDUNDANT HEADERS**: The parent template already injects a large dynamic page title and action buttons. DO NOT add a main \`<h1>\` or \`<h2>\` page title at the top of your micro-component. Start directly with your cards, lists, or sub-sections.
|
|
63
|
-
|
|
64
|
-
[WHAT SHOULD I BUILD INSIDE THIS PRIVATE VIEW?]`;
|
|
20
|
+
[WHAT TO BUILD?]`;
|
|
65
21
|
};
|
|
@@ -1,63 +1,21 @@
|
|
|
1
1
|
export const sharedCrudPrompt = (config: any, recordsCollection: string) => {
|
|
2
2
|
const parsedTabName = (config.tabName || config.pageId || 'shared_page')
|
|
3
3
|
.toLowerCase().replace(/[^a-z0-9]+/g, '_');
|
|
4
|
-
const
|
|
4
|
+
const pascalName = parsedTabName
|
|
5
|
+
.split('_')
|
|
6
|
+
.map((w: string) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
7
|
+
.join('');
|
|
8
|
+
const fileName = `${pascalName}Content`;
|
|
5
9
|
|
|
6
|
-
return `
|
|
10
|
+
return `Create file: src/your-ai-workflow-firebase-os/microcomponents/${fileName}.tsx
|
|
7
11
|
|
|
8
|
-
|
|
12
|
+
export default function ${fileName}({ config }: { config: any }) {
|
|
13
|
+
return ( <div>your content here</div> );
|
|
14
|
+
}
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
viewName: config.tabName,
|
|
14
|
-
viewTitle: config.tabTitle,
|
|
15
|
-
route: config.route,
|
|
16
|
-
template: config.template,
|
|
17
|
-
storageEnabled: !!config.storage,
|
|
18
|
-
showActionButton: !!config.showButton,
|
|
19
|
-
}, null, 2)}
|
|
20
|
-
\`\`\`
|
|
16
|
+
Firestore collection: \`${recordsCollection}\` — shared between all members.
|
|
17
|
+
Firebase: \`import { db, useAuth } from 'your-ai-workflow-firebase-os'\`
|
|
18
|
+
No page titles, no nav, no routing — already handled. Default export only.
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
**No forms are needed for this view.** Focus on building the collaborative features and shared content areas requested by the user.
|
|
24
|
-
|
|
25
|
-
### ── Data & Storage Architecture ─────────────────────────────────────────────
|
|
26
|
-
All logged-in members share the data on this page. The system is configured for collaboration:
|
|
27
|
-
- **Firestore Collection**: \`${recordsCollection}\`. All authenticated users can read and create records here. The template ensures only creators can edit/delete their own items.
|
|
28
|
-
- **Storage Layer**: ${config.storage ? "Enabled. Shared files are saved to \`mem_files/\` and are visible to all members." : "Disabled for this view."}
|
|
29
|
-
- **Access Level**: Shared Member Access. Requires authentication.
|
|
30
|
-
|
|
31
|
-
### ── Your Mission ────────────────────────────────────────────────────────────
|
|
32
|
-
1. **Create the Page**: Create a full page component at \`src/pages/\${microName}Page.tsx\`. Include \`<DashboardNav />\` from \`your-ai-workflow-firebase-os\` at the top of the content area.
|
|
33
|
-
2. **Handle UX/UI**: Inside this component, build the collaborative interface (group lists, message boards, shared resource trackers).
|
|
34
|
-
3. **Register the Route**: Open \`src/App.tsx\` and map this component to the \`customRoutes\` prop of \`<FirebaseOS>\`:
|
|
35
|
-
\`\`\`tsx
|
|
36
|
-
import { \${microName}Page } from './pages/\${microName}Page';
|
|
37
|
-
|
|
38
|
-
// Inside App.tsx:
|
|
39
|
-
<FirebaseOS
|
|
40
|
-
customRoutes={{
|
|
41
|
-
'\${config.route}': \${microName}Page
|
|
42
|
-
}}
|
|
43
|
-
// ...
|
|
44
|
-
/>
|
|
45
|
-
\`\`\`
|
|
46
|
-
|
|
47
|
-
### ── Guardrails ─────────────────────────────────────────────────────────────
|
|
48
|
-
- **CRITICAL: DO NOT CREATE A NEW ROUTE IN REACT ROUTER.** You are only mapping a component to an existing dynamic route via \\\`customRoutes\\\`.
|
|
49
|
-
- **Database Logic**: You must write your own database queries inside the component using the provided collection names.
|
|
50
|
-
- **NEVER** expose administrative control settings in this shared view.
|
|
51
|
-
|
|
52
|
-
### ── Aesthetic & Performance (Theme System) ───────────────────────────────
|
|
53
|
-
Build with the user's active theme in mind:
|
|
54
|
-
- **CSS Variables**: \`var(--bg-color)\`, \`var(--fg-color)\`, \`var(--accent-color)\`, \`var(--panel-bg)\`, \`var(--panel-border)\`.
|
|
55
|
-
- **Components**: Re-use \`<Button />\`, \`<Input />\`, \`<ConfirmModal />\`, and \`<DashboardNav />\` natively.
|
|
56
|
-
- **Styles**: Use \`glass-panel\` and \`text-gradient\` for a premium look.
|
|
57
|
-
- **Example**: See \`src/microcomponents/SharedExampleContent.tsx\` for a structural reference.
|
|
58
|
-
|
|
59
|
-
### ── Important Formatting Rules ──────────────────────────────────────────────
|
|
60
|
-
- **NO REDUNDANT HEADERS**: The parent template already injects a large dynamic page title and action buttons. DO NOT add a main \`<h1>\` or \`<h2>\` page title at the top of your micro-component. Start directly with your cards, lists, or sub-sections.
|
|
61
|
-
|
|
62
|
-
[WHAT SHARED FEATURE SHOULD I BUILD IN THIS VIEW?]`;
|
|
20
|
+
[WHAT TO BUILD?]`;
|
|
63
21
|
};
|