your-ai-workflow-firebase-os 1.2.6 → 1.2.7
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/your-ai-workflow-firebase-os.cjs.js +13 -13
- package/dist/your-ai-workflow-firebase-os.es.js +13384 -13553
- package/package.json +4 -3
- package/src/pages/PagesAdmin.tsx +8 -122
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "your-ai-workflow-firebase-os",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
4
4
|
"description": "A complete Firebase-powered admin OS — one React component.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/your-ai-workflow-firebase-os.cjs.js",
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"build:lib": "vite build",
|
|
33
33
|
"lint": "eslint .",
|
|
34
34
|
"preview": "vite preview",
|
|
35
|
-
"postinstall": "node scripts/postinstall.js"
|
|
35
|
+
"postinstall": "node scripts/postinstall.js",
|
|
36
|
+
"release": "npm version patch && npm run build:lib && npm pack"
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
39
|
"@eslint/js": "^9.39.4",
|
|
@@ -78,4 +79,4 @@
|
|
|
78
79
|
"react-dom": ">=18",
|
|
79
80
|
"react-router-dom": ">=6"
|
|
80
81
|
}
|
|
81
|
-
}
|
|
82
|
+
}
|
package/src/pages/PagesAdmin.tsx
CHANGED
|
@@ -9,8 +9,6 @@ import { doc, onSnapshot, setDoc, collection, getDocs, getDoc } from 'firebase/f
|
|
|
9
9
|
import { ConfirmModal } from '../components/ConfirmModal';
|
|
10
10
|
import { JsonNode } from './ThemeAdmin';
|
|
11
11
|
import { defaultHomeConfig } from '../configs/pages/home.config';
|
|
12
|
-
import { defaultContactConfig } from '../configs/pages/contact.config';
|
|
13
|
-
import { defaultSupportConfig } from '../configs/pages/support.config';
|
|
14
12
|
import { defaultPublicConfig } from '../configs/pages/pub.config';
|
|
15
13
|
import { userPageConfig as defaultPrivateConfig } from '../configs/pages/user.config';
|
|
16
14
|
import { adminPageConfig as defaultAdminConfig } from '../configs/pages/admin.config';
|
|
@@ -31,10 +29,6 @@ interface PageItem {
|
|
|
31
29
|
const initialPages: PageItem[] = [
|
|
32
30
|
// Public
|
|
33
31
|
{ id: 'home', title: 'Home Page', path: '/', type: 'public', isSystem: true, enabled: true },
|
|
34
|
-
{ id: 'contact', title: 'Contact', path: '/contact', type: 'public', isSystem: true, enabled: true },
|
|
35
|
-
|
|
36
|
-
// Private
|
|
37
|
-
{ id: 'support', title: 'Request Support', path: '/support', type: 'private', isSystem: true, enabled: true },
|
|
38
32
|
];
|
|
39
33
|
|
|
40
34
|
const Toggle = ({ enabled, onChange, disabled, className = '' }: { enabled: boolean, onChange: (val: boolean) => void, disabled?: boolean, className?: string }) => (
|
|
@@ -76,14 +70,6 @@ export function PagesAdmin() {
|
|
|
76
70
|
const [homeConfig, setHomeConfig] = useState<any>(null);
|
|
77
71
|
const [savingHome, setSavingHome] = useState(false);
|
|
78
72
|
const [resettingHome, setResettingHome] = useState(false);
|
|
79
|
-
|
|
80
|
-
const [contactConfig, setContactConfig] = useState<any>(null);
|
|
81
|
-
const [savingContact, setSavingContact] = useState(false);
|
|
82
|
-
const [resettingContact, setResettingContact] = useState(false);
|
|
83
|
-
|
|
84
|
-
const [supportConfig, setSupportConfig] = useState<any>(null);
|
|
85
|
-
const [savingSupport, setSavingSupport] = useState(false);
|
|
86
|
-
const [resettingSupport, setResettingSupport] = useState(false);
|
|
87
73
|
// ── Order-based visibility (single source of truth) ─────────────────────
|
|
88
74
|
const [pageHiddenIds, setPageHiddenIds] = useState<Set<string>>(new Set());
|
|
89
75
|
const [tabHiddenIds, setTabHiddenIds] = useState<Set<string>>(new Set());
|
|
@@ -126,13 +112,7 @@ export function PagesAdmin() {
|
|
|
126
112
|
const unsubHome = onSnapshot(doc(db, 'sys_pages', 'home'), (docSnap) => {
|
|
127
113
|
setHomeConfig(docSnap.exists() ? mergeConfig(defaultHomeConfig, docSnap.data()) : defaultHomeConfig);
|
|
128
114
|
});
|
|
129
|
-
|
|
130
|
-
setContactConfig(docSnap.exists() ? mergeConfig(defaultContactConfig, docSnap.data()) : defaultContactConfig);
|
|
131
|
-
});
|
|
132
|
-
const unsubSupport = onSnapshot(doc(db, 'sys_tabs', 'support'), (docSnap) => {
|
|
133
|
-
setSupportConfig(docSnap.exists() ? mergeConfig(defaultSupportConfig, docSnap.data()) : defaultSupportConfig);
|
|
134
|
-
});
|
|
135
|
-
return () => { unsubHome(); unsubContact(); unsubSupport(); };
|
|
115
|
+
return () => { unsubHome(); };
|
|
136
116
|
}, []);
|
|
137
117
|
|
|
138
118
|
useEffect(() => {
|
|
@@ -218,14 +198,12 @@ export function PagesAdmin() {
|
|
|
218
198
|
val.pageType = pageType;
|
|
219
199
|
}
|
|
220
200
|
|
|
221
|
-
const isSystemPage = ['home'
|
|
201
|
+
const isSystemPage = ['home'].includes(id);
|
|
222
202
|
const targetId = isSystemPage
|
|
223
203
|
? id
|
|
224
204
|
: (val.route ? val.route.replace(/^\/+/, '').replace(/[^a-zA-Z0-9_\-]/g, '_').toLowerCase() : id);
|
|
225
205
|
|
|
226
206
|
if (id === 'home') setSavingHome(true);
|
|
227
|
-
else if (id === 'contact') setSavingContact(true);
|
|
228
|
-
else if (id === 'support') setSavingSupport(true);
|
|
229
207
|
else setSavingConfigMap(p => ({ ...p, [id]: true, [targetId]: true }));
|
|
230
208
|
|
|
231
209
|
try {
|
|
@@ -267,8 +245,6 @@ export function PagesAdmin() {
|
|
|
267
245
|
} else {
|
|
268
246
|
await setDoc(doc(db, collectionName, id), val);
|
|
269
247
|
if (id === 'home') setHomeConfig(val);
|
|
270
|
-
else if (id === 'contact') setContactConfig(val);
|
|
271
|
-
else if (id === 'support') setSupportConfig(val);
|
|
272
248
|
else {
|
|
273
249
|
setDraftConfigs(p => ({ ...p, [id]: val }));
|
|
274
250
|
setPages(prev => prev.map(p => p.id === id ? { ...p, path: val.route || p.path, title: val.pageName || val.tabName || val.title || p.title } : p));
|
|
@@ -279,8 +255,6 @@ export function PagesAdmin() {
|
|
|
279
255
|
} finally {
|
|
280
256
|
setTimeout(() => {
|
|
281
257
|
if (id === 'home') setSavingHome(false);
|
|
282
|
-
else if (id === 'contact') setSavingContact(false);
|
|
283
|
-
else if (id === 'support') setSavingSupport(false);
|
|
284
258
|
else setSavingConfigMap(p => ({ ...p, [id]: false, [targetId]: false }));
|
|
285
259
|
}, 500);
|
|
286
260
|
}
|
|
@@ -292,17 +266,6 @@ export function PagesAdmin() {
|
|
|
292
266
|
setTimeout(() => setResettingHome(false), 500);
|
|
293
267
|
};
|
|
294
268
|
|
|
295
|
-
const handleResetContact = async (e: React.MouseEvent) => {
|
|
296
|
-
e.stopPropagation(); setResettingContact(true);
|
|
297
|
-
await handleSaveConfig('contact', defaultContactConfig);
|
|
298
|
-
setTimeout(() => setResettingContact(false), 500);
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
const handleResetSupport = async (e: React.MouseEvent) => {
|
|
302
|
-
e.stopPropagation(); setResettingSupport(true);
|
|
303
|
-
await handleSaveConfig('support', defaultSupportConfig);
|
|
304
|
-
setTimeout(() => setResettingSupport(false), 500);
|
|
305
|
-
};
|
|
306
269
|
|
|
307
270
|
const handleResetPage = async (e: React.MouseEvent, pageId: string, defConfig: any) => {
|
|
308
271
|
e.stopPropagation(); setResettingMap(p => ({ ...p, [pageId]: true }));
|
|
@@ -428,12 +391,11 @@ export function PagesAdmin() {
|
|
|
428
391
|
setPages(prev => [...prev, newPage]);
|
|
429
392
|
|
|
430
393
|
let sourceConfig = draftConfigs[originalPage.id];
|
|
431
|
-
if (!sourceConfig) {
|
|
432
|
-
|
|
433
|
-
if (originalPage.id === 'support') sourceConfig = supportConfig || defaultSupportConfig;
|
|
394
|
+
if (!sourceConfig && originalPage.id === 'home') {
|
|
395
|
+
sourceConfig = homeConfig;
|
|
434
396
|
}
|
|
435
397
|
|
|
436
|
-
const newConfig = { ...sourceConfig, route: newPage.path, pageName: newPage.title };
|
|
398
|
+
const newConfig = { ...(sourceConfig || {}), route: newPage.path, pageName: newPage.title };
|
|
437
399
|
if (newPage.type !== 'public') {
|
|
438
400
|
newConfig.tabName = newPage.title;
|
|
439
401
|
newConfig.pageType = newPage.type;
|
|
@@ -483,13 +445,9 @@ export function PagesAdmin() {
|
|
|
483
445
|
try {
|
|
484
446
|
const { deleteDoc } = await import('firebase/firestore');
|
|
485
447
|
await Promise.all([
|
|
486
|
-
deleteDoc(doc(db, 'sys_pages', 'home'))
|
|
487
|
-
deleteDoc(doc(db, 'sys_pages', 'contact')),
|
|
488
|
-
deleteDoc(doc(db, 'sys_tabs', 'support'))
|
|
448
|
+
deleteDoc(doc(db, 'sys_pages', 'home'))
|
|
489
449
|
]);
|
|
490
450
|
setHomeConfig(defaultHomeConfig);
|
|
491
|
-
setContactConfig(defaultContactConfig);
|
|
492
|
-
setSupportConfig(defaultSupportConfig);
|
|
493
451
|
} catch (e) {
|
|
494
452
|
console.error("Error resetting defaults:", e);
|
|
495
453
|
}
|
|
@@ -699,10 +657,8 @@ export function PagesAdmin() {
|
|
|
699
657
|
|
|
700
658
|
const renderPage = (page: PageItem, pIdx: number, isHidden: boolean) => {
|
|
701
659
|
const isHome = page.id === 'home';
|
|
702
|
-
const isContact = page.id === 'contact';
|
|
703
|
-
const isSupport = page.id === 'support';
|
|
704
660
|
const isExpanded = expandedPageId === page.id;
|
|
705
|
-
const isConfigurable = isHome ||
|
|
661
|
+
const isConfigurable = isHome || !page.isSystem;
|
|
706
662
|
|
|
707
663
|
return (
|
|
708
664
|
<div
|
|
@@ -721,7 +677,7 @@ export function PagesAdmin() {
|
|
|
721
677
|
<div className="flex items-center gap-1 w-full flex-1">
|
|
722
678
|
<div className="flex items-center gap-2">
|
|
723
679
|
<h3 className="text-[14px] font-bold text-foreground truncate">{page.title}</h3>
|
|
724
|
-
{
|
|
680
|
+
{isHome && savingHome && <Loader2 className="w-3 h-3 animate-spin text-accent ml-2" />}
|
|
725
681
|
</div>
|
|
726
682
|
<div className={`text-[11px] font-medium font-mono px-2 py-0.5 rounded w-fit ml-6 border ${isHidden ? 'text-foreground/50 border-foreground/10 bg-foreground/5' : 'text-accent/70 bg-accent/5 border-accent/10'}`}>
|
|
727
683
|
{window.location.host}{page.path}
|
|
@@ -795,77 +751,7 @@ export function PagesAdmin() {
|
|
|
795
751
|
</div>
|
|
796
752
|
</motion.div>
|
|
797
753
|
)}
|
|
798
|
-
{isExpanded && isContact && contactConfig && (
|
|
799
|
-
<motion.div
|
|
800
|
-
initial={{ height: 0, opacity: 0 }}
|
|
801
|
-
animate={{ height: "auto", opacity: 1 }}
|
|
802
|
-
exit={{ height: 0, opacity: 0 }}
|
|
803
|
-
className="overflow-hidden mt-4 pt-4 border-t border-[var(--panel-border)]/50 pb-4"
|
|
804
|
-
onClick={e => e.stopPropagation()}
|
|
805
|
-
>
|
|
806
|
-
<div className="flex flex-col font-mono text-[13px] leading-relaxed relative bg-background/50 p-4 py-6 rounded-xl border border-[var(--panel-border)] shadow-inner">
|
|
807
754
|
|
|
808
|
-
<div className="absolute top-4 right-4 flex items-center gap-1 z-20">
|
|
809
|
-
<button onClick={handleResetContact} className="w-8 h-8 p-0 border-transparent bg-transparent hover:bg-red-500/10 rounded-lg flex items-center justify-center shrink-0 transition-colors group" title="Reset to Defaults">
|
|
810
|
-
{resettingContact ? <Loader2 className="w-3.5 h-3.5 text-red-500 animate-spin" /> : <RotateCcw className="w-3.5 h-3.5 text-foreground/40 group-hover:text-red-500 transition-colors" />}
|
|
811
|
-
</button>
|
|
812
|
-
<button onClick={(e) => { e.stopPropagation(); handleCopyBtn('contact', contactConfig); }} className="w-8 h-8 p-0 border-transparent bg-transparent hover:bg-foreground/10 rounded-lg flex items-center justify-center shrink-0 transition-colors" title="Copy JSON">
|
|
813
|
-
{copyStateId === 'contact' ? <CheckCircle2 className="w-3.5 h-3.5 text-emerald-500" /> : <Copy className="w-3.5 h-3.5 text-foreground/40 hover:text-foreground transition-colors" />}
|
|
814
|
-
</button>
|
|
815
|
-
</div>
|
|
816
|
-
<JsonNode
|
|
817
|
-
value={contactConfig}
|
|
818
|
-
keysToRender={contactConfig.showButton !== false ? ['pageName', 'route', 'template', 'showButton', 'form'] : ['pageName', 'route', 'template', 'showButton']}
|
|
819
|
-
disabledKeys={['route']}
|
|
820
|
-
onChange={(val: any) => handleSaveConfig('contact', val)}
|
|
821
|
-
isLast={true}
|
|
822
|
-
isGlobalSaving={savingContact}
|
|
823
|
-
customEnums={{ form: publicForms, template: ['popup_form', 'inline_form'] }}
|
|
824
|
-
customExplanations={{
|
|
825
|
-
pageName: "This defines the text that actually appears globally in the Navigation menus.",
|
|
826
|
-
template: "This just simply tells our app when a user clicks on the contact page item what type of form do we load? is it a page or popup? this doesn't define form content! this setting just defines page layout.",
|
|
827
|
-
form: "This is the form that will be used. Go to /forms to edit it."
|
|
828
|
-
}}
|
|
829
|
-
/>
|
|
830
|
-
</div>
|
|
831
|
-
</motion.div>
|
|
832
|
-
)}
|
|
833
|
-
{isExpanded && isSupport && supportConfig && (
|
|
834
|
-
<motion.div
|
|
835
|
-
initial={{ height: 0, opacity: 0 }}
|
|
836
|
-
animate={{ height: "auto", opacity: 1 }}
|
|
837
|
-
exit={{ height: 0, opacity: 0 }}
|
|
838
|
-
className="overflow-hidden mt-4 pt-4 border-t border-[var(--panel-border)]/50 pb-4"
|
|
839
|
-
onClick={e => e.stopPropagation()}
|
|
840
|
-
>
|
|
841
|
-
<div className="flex flex-col font-mono text-[13px] leading-relaxed relative bg-background/50 p-4 py-6 rounded-xl border border-[var(--panel-border)] shadow-inner">
|
|
842
|
-
|
|
843
|
-
<div className="absolute top-4 right-4 flex items-center gap-1 z-20">
|
|
844
|
-
<button onClick={handleResetSupport} className="w-8 h-8 p-0 border-transparent bg-transparent hover:bg-red-500/10 rounded-lg flex items-center justify-center shrink-0 transition-colors group" title="Reset to Defaults">
|
|
845
|
-
{resettingSupport ? <Loader2 className="w-3.5 h-3.5 text-red-500 animate-spin" /> : <RotateCcw className="w-3.5 h-3.5 text-foreground/40 group-hover:text-red-500 transition-colors" />}
|
|
846
|
-
</button>
|
|
847
|
-
<button onClick={(e) => { e.stopPropagation(); handleCopyBtn('support', supportConfig); }} className="w-8 h-8 p-0 border-transparent bg-transparent hover:bg-foreground/10 rounded-lg flex items-center justify-center shrink-0 transition-colors" title="Copy JSON">
|
|
848
|
-
{copyStateId === 'support' ? <CheckCircle2 className="w-3.5 h-3.5 text-emerald-500" /> : <Copy className="w-3.5 h-3.5 text-foreground/40 hover:text-foreground transition-colors" />}
|
|
849
|
-
</button>
|
|
850
|
-
</div>
|
|
851
|
-
<JsonNode
|
|
852
|
-
value={supportConfig}
|
|
853
|
-
keysToRender={supportConfig.showButton !== false ? ['pageName', 'iconName', 'route', 'template', 'showButton', 'form'] : ['pageName', 'iconName', 'route', 'template', 'showButton']}
|
|
854
|
-
disabledKeys={['route']}
|
|
855
|
-
onChange={(val: any) => handleSaveConfig('support', val)}
|
|
856
|
-
isLast={true}
|
|
857
|
-
isGlobalSaving={savingSupport}
|
|
858
|
-
customEnums={{ form: privateForms, template: ['popup_form', 'inline_form'] }}
|
|
859
|
-
customExplanations={{
|
|
860
|
-
pageName: "Defines the textual title label rendering in internal sidebars.",
|
|
861
|
-
iconName: "To modify this icon, visit https://iconoir.com/, find an icon, and copy its exact name (e.g. 'learning', 'server'). Then paste it here.",
|
|
862
|
-
template: "This just simply tells our app when a user clicks on the support page item what type of form do we load? is it a page or popup? this doesn't define form content! this setting just defines page layout.",
|
|
863
|
-
form: "This is the form that will be used. Go to /forms to edit it."
|
|
864
|
-
}}
|
|
865
|
-
/>
|
|
866
|
-
</div>
|
|
867
|
-
</motion.div>
|
|
868
|
-
)}
|
|
869
755
|
{isExpanded && !page.isSystem && draftConfigs[page.id] && (
|
|
870
756
|
<motion.div
|
|
871
757
|
initial={{ height: 0, opacity: 0 }}
|