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
|
@@ -31,7 +31,7 @@ interface SavedRecord {
|
|
|
31
31
|
|
|
32
32
|
import { adminCrudPrompt } from '../prompts';
|
|
33
33
|
|
|
34
|
-
export function AdminPageTemplate({ config }: { config: any }) {
|
|
34
|
+
export function AdminPageTemplate({ config, customContent: CustomContent }: { config: any; customContent?: React.ComponentType<any> }) {
|
|
35
35
|
const { user } = useAuth();
|
|
36
36
|
const { config: themeConfig } = useTheme();
|
|
37
37
|
const [records, setRecords] = useState<SavedRecord[]>([]);
|
|
@@ -591,122 +591,128 @@ export function AdminPageTemplate({ config }: { config: any }) {
|
|
|
591
591
|
</div>
|
|
592
592
|
)}
|
|
593
593
|
|
|
594
|
-
{
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
<AnimatePresence>
|
|
615
|
-
{records.map((record, i) => {
|
|
616
|
-
const isEditing = editingId === record.id;
|
|
617
|
-
const owned = isOwner(record);
|
|
618
|
-
return (
|
|
619
|
-
<motion.div
|
|
620
|
-
key={record.id}
|
|
621
|
-
initial={false}
|
|
622
|
-
animate={{ opacity: 1, y: 0 }}
|
|
623
|
-
exit={{ opacity: 0, scale: 0.95 }}
|
|
624
|
-
onClick={() => startEdit(record)}
|
|
625
|
-
className={`group flex flex-col bg-background border rounded-2xl p-5 transition-all relative cursor-pointer ${isEditing ? 'border-accent/40 shadow-lg ring-1 ring-accent/20' : 'border-[var(--panel-border)] hover:border-accent/30 hover:shadow-lg'
|
|
626
|
-
}`}
|
|
627
|
-
>
|
|
628
|
-
{/* Top-right action icons — download + edit + delete */}
|
|
629
|
-
{true && (
|
|
630
|
-
<div className="absolute top-3 right-3 flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-all z-10">
|
|
631
|
-
{record.recordType === 'file' && record.downloadURL && (
|
|
632
|
-
<>
|
|
633
|
-
<button onClick={(e) => { e.stopPropagation(); navigator.clipboard.writeText(record.downloadURL!); setCopiedLink(record.id); setTimeout(() => setCopiedLink(null), 2000); }} className="p-1.5 rounded-lg text-foreground/25 hover:text-emerald-500 hover:bg-emerald-500/10 transition-all cursor-pointer" title="Copy Link">
|
|
634
|
-
{copiedLink === record.id ? <Check className="w-3.5 h-3.5 text-emerald-500" /> : <Link className="w-3.5 h-3.5" />}
|
|
635
|
-
</button>
|
|
636
|
-
<a href={record.downloadURL} target="_blank" rel="noreferrer" onClick={e => e.stopPropagation()} className="p-1.5 rounded-lg text-foreground/25 hover:text-blue-500 hover:bg-blue-500/10 transition-all" title="Download">
|
|
637
|
-
<Download className="w-3.5 h-3.5" />
|
|
638
|
-
</a>
|
|
639
|
-
</>
|
|
640
|
-
)}
|
|
641
|
-
{owned && (
|
|
642
|
-
<>
|
|
643
|
-
<button onClick={(e) => { e.stopPropagation(); startEdit(record); }} className="p-1.5 rounded-lg text-foreground/25 hover:text-accent hover:bg-accent/10 transition-all cursor-pointer" title="Edit">
|
|
644
|
-
<Edit2 className="w-3.5 h-3.5" />
|
|
645
|
-
</button>
|
|
646
|
-
<button onClick={(e) => { e.stopPropagation(); setDeleteConfirm(record.id); }} className="p-1.5 rounded-lg text-foreground/25 hover:text-red-500 hover:bg-red-500/10 transition-all cursor-pointer" title="Delete">
|
|
647
|
-
<Trash2 className="w-3.5 h-3.5" />
|
|
648
|
-
</button>
|
|
649
|
-
</>
|
|
650
|
-
)}
|
|
651
|
-
</div>
|
|
652
|
-
)}
|
|
594
|
+
{CustomContent ? (
|
|
595
|
+
<CustomContent config={config} />
|
|
596
|
+
) : (
|
|
597
|
+
<>
|
|
598
|
+
{/* Empty State */}
|
|
599
|
+
{!loading && records.length === 0 && (
|
|
600
|
+
<motion.div
|
|
601
|
+
initial={{ opacity: 0, y: 10 }}
|
|
602
|
+
animate={{ opacity: 1, y: 0 }}
|
|
603
|
+
className="flex-1 flex flex-col items-center justify-center border border-[var(--panel-border)]/50 border-dashed rounded-2xl p-8 py-20 text-center min-h-[350px]"
|
|
604
|
+
>
|
|
605
|
+
<div className="w-16 h-16 rounded-2xl bg-accent/5 border border-accent/10 flex items-center justify-center mb-5">
|
|
606
|
+
<Plus className="w-6 h-6 text-accent/40" />
|
|
607
|
+
</div>
|
|
608
|
+
<p className="text-[15px] font-bold mb-1.5 text-foreground/50">No items yet</p>
|
|
609
|
+
<p className="text-[13px] font-medium text-foreground/35 max-w-xs leading-relaxed">
|
|
610
|
+
Use the buttons above to add records or upload files. All admins can view and contribute.
|
|
611
|
+
</p>
|
|
612
|
+
</motion.div>
|
|
613
|
+
)}
|
|
653
614
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
615
|
+
{/* Records Grid */}
|
|
616
|
+
{!loading && records.length > 0 && (
|
|
617
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
618
|
+
<AnimatePresence>
|
|
619
|
+
{records.map((record, i) => {
|
|
620
|
+
const isEditing = editingId === record.id;
|
|
621
|
+
const owned = isOwner(record);
|
|
622
|
+
return (
|
|
623
|
+
<motion.div
|
|
624
|
+
key={record.id}
|
|
625
|
+
initial={false}
|
|
626
|
+
animate={{ opacity: 1, y: 0 }}
|
|
627
|
+
exit={{ opacity: 0, scale: 0.95 }}
|
|
628
|
+
onClick={() => startEdit(record)}
|
|
629
|
+
className={`group flex flex-col bg-background border rounded-2xl p-5 transition-all relative cursor-pointer ${isEditing ? 'border-accent/40 shadow-lg ring-1 ring-accent/20' : 'border-[var(--panel-border)] hover:border-accent/30 hover:shadow-lg'
|
|
630
|
+
}`}
|
|
658
631
|
>
|
|
659
|
-
{
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
632
|
+
{/* Top-right action icons — download + edit + delete */}
|
|
633
|
+
{true && (
|
|
634
|
+
<div className="absolute top-3 right-3 flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-all z-10">
|
|
635
|
+
{record.recordType === 'file' && record.downloadURL && (
|
|
636
|
+
<>
|
|
637
|
+
<button onClick={(e) => { e.stopPropagation(); navigator.clipboard.writeText(record.downloadURL!); setCopiedLink(record.id); setTimeout(() => setCopiedLink(null), 2000); }} className="p-1.5 rounded-lg text-foreground/25 hover:text-emerald-500 hover:bg-emerald-500/10 transition-all cursor-pointer" title="Copy Link">
|
|
638
|
+
{copiedLink === record.id ? <Check className="w-3.5 h-3.5 text-emerald-500" /> : <Link className="w-3.5 h-3.5" />}
|
|
639
|
+
</button>
|
|
640
|
+
<a href={record.downloadURL} target="_blank" rel="noreferrer" onClick={e => e.stopPropagation()} className="p-1.5 rounded-lg text-foreground/25 hover:text-blue-500 hover:bg-blue-500/10 transition-all" title="Download">
|
|
641
|
+
<Download className="w-3.5 h-3.5" />
|
|
642
|
+
</a>
|
|
643
|
+
</>
|
|
644
|
+
)}
|
|
645
|
+
{owned && (
|
|
646
|
+
<>
|
|
647
|
+
<button onClick={(e) => { e.stopPropagation(); startEdit(record); }} className="p-1.5 rounded-lg text-foreground/25 hover:text-accent hover:bg-accent/10 transition-all cursor-pointer" title="Edit">
|
|
648
|
+
<Edit2 className="w-3.5 h-3.5" />
|
|
649
|
+
</button>
|
|
650
|
+
<button onClick={(e) => { e.stopPropagation(); setDeleteConfirm(record.id); }} className="p-1.5 rounded-lg text-foreground/25 hover:text-red-500 hover:bg-red-500/10 transition-all cursor-pointer" title="Delete">
|
|
651
|
+
<Trash2 className="w-3.5 h-3.5" />
|
|
652
|
+
</button>
|
|
653
|
+
</>
|
|
654
|
+
)}
|
|
655
|
+
</div>
|
|
677
656
|
)}
|
|
678
|
-
{record.recordType === 'file' && <div className="absolute inset-0 z-0 cursor-pointer" onClick={() => setPreviewFile(record)} />}
|
|
679
|
-
<span className="text-[11px] text-foreground/35 font-bold uppercase tracking-wider mt-0.5">
|
|
680
|
-
{formatDate(record.createdAt)}
|
|
681
|
-
</span>
|
|
682
|
-
</div>
|
|
683
|
-
</div>
|
|
684
657
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
658
|
+
<div className="flex items-start gap-3 mb-2">
|
|
659
|
+
<div
|
|
660
|
+
onClick={(e) => { e.stopPropagation(); if (record.recordType === 'file') setPreviewFile(record); }}
|
|
661
|
+
className={`w-9 h-9 rounded-xl flex items-center justify-center shrink-0 ${record.recordType === 'file' ? `cursor-pointer hover:scale-105 transition-transform ${renderFileIconBg(record.fileType)}` : 'bg-accent/10 text-accent'}`}
|
|
662
|
+
>
|
|
663
|
+
{record.recordType === 'file' ? renderFileIcon(record.fileType, record.downloadURL) : <div className="w-2 h-2 rounded-full bg-current" />}
|
|
664
|
+
</div>
|
|
665
|
+
<div className="flex flex-col min-w-0 flex-1 relative">
|
|
666
|
+
{renamingFileId === record.id ? (
|
|
667
|
+
<input
|
|
668
|
+
autoFocus
|
|
669
|
+
type="text"
|
|
670
|
+
value={renameFileName}
|
|
671
|
+
onChange={e => setRenameFileName(e.target.value)}
|
|
672
|
+
onBlur={() => handleRenameFile()}
|
|
673
|
+
onKeyDown={e => { if (e.key === 'Enter') handleRenameFile(); if (e.key === 'Escape') setRenamingFileId(null); }}
|
|
674
|
+
onClick={e => e.stopPropagation()}
|
|
675
|
+
className="no-glow text-[14px] font-bold text-foreground bg-transparent border-b border-accent/30 outline-none pb-0.5 w-full pr-12"
|
|
676
|
+
/>
|
|
677
|
+
) : (
|
|
678
|
+
<span className="text-[14px] font-bold text-foreground truncate pr-12">
|
|
679
|
+
{record.name}
|
|
680
|
+
</span>
|
|
681
|
+
)}
|
|
682
|
+
{record.recordType === 'file' && <div className="absolute inset-0 z-0 cursor-pointer" onClick={() => setPreviewFile(record)} />}
|
|
683
|
+
<span className="text-[11px] text-foreground/35 font-bold uppercase tracking-wider mt-0.5">
|
|
684
|
+
{formatDate(record.createdAt)}
|
|
685
|
+
</span>
|
|
686
|
+
</div>
|
|
698
687
|
</div>
|
|
699
|
-
))
|
|
700
|
-
}
|
|
701
688
|
|
|
689
|
+
{record.note && (
|
|
690
|
+
<p className="text-[13px] text-foreground/55 font-medium leading-relaxed line-clamp-3 mb-1">
|
|
691
|
+
{record.note}
|
|
692
|
+
</p>
|
|
693
|
+
)}
|
|
694
|
+
{/* Extra custom fields display */}
|
|
695
|
+
{Object.entries(record as any)
|
|
696
|
+
.filter(([k]) => !['name','note','uid','recordType','fileName','fileType','fileSize','downloadURL','createdAt','creatorName','creatorEmail','creatorAvatar'].includes(k) && k !== 'id')
|
|
697
|
+
.slice(0, 3)
|
|
698
|
+
.map(([k, v]) => (
|
|
699
|
+
<div key={k} className="flex items-center gap-1.5 mt-1">
|
|
700
|
+
<span className="text-[10px] font-bold text-foreground/30 uppercase tracking-wider shrink-0">{k}:</span>
|
|
701
|
+
<span className="text-[11px] font-semibold text-foreground/60 truncate">{String(v)}</span>
|
|
702
|
+
</div>
|
|
703
|
+
))
|
|
704
|
+
}
|
|
702
705
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
706
|
+
|
|
707
|
+
{/* Creator profile badge */}
|
|
708
|
+
<CreatorBadge record={record} />
|
|
709
|
+
</motion.div>
|
|
710
|
+
);
|
|
711
|
+
})}
|
|
712
|
+
</AnimatePresence>
|
|
713
|
+
</div>
|
|
714
|
+
)}
|
|
715
|
+
</>
|
|
710
716
|
)}
|
|
711
717
|
</div>
|
|
712
718
|
|
|
@@ -27,7 +27,7 @@ interface SavedRecord {
|
|
|
27
27
|
|
|
28
28
|
import { privateCrudPrompt } from '../prompts';
|
|
29
29
|
|
|
30
|
-
export function PrivatePageTemplate({ config }: { config: any }) {
|
|
30
|
+
export function PrivatePageTemplate({ config, customContent: CustomContent }: { config: any; customContent?: React.ComponentType<any> }) {
|
|
31
31
|
const { user } = useAuth();
|
|
32
32
|
const { config: themeConfig } = useTheme();
|
|
33
33
|
const [records, setRecords] = useState<SavedRecord[]>([]);
|
|
@@ -56,7 +56,7 @@ export function PrivatePageTemplate({ config }: { config: any }) {
|
|
|
56
56
|
const [editNote, setEditNote] = useState('');
|
|
57
57
|
const [editFields, setEditFields] = useState<{key: string; value: string}[]>([]);
|
|
58
58
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
|
|
61
61
|
const parsedTabName = (config.tabName || config.pageId || 'private_page')
|
|
62
62
|
.toLowerCase().replace(/[^a-z0-9]+/g, '_');
|
|
@@ -65,7 +65,7 @@ export function PrivatePageTemplate({ config }: { config: any }) {
|
|
|
65
65
|
useEffect(() => {
|
|
66
66
|
document.title = config.tabTitle || config.tabName || themeConfig.appName || 'Firebase OS';
|
|
67
67
|
getDocs(collection(db, 'sys_forms')).then(snap => {
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
});
|
|
70
70
|
}, [config.tabTitle, config.tabName, themeConfig.appName]);
|
|
71
71
|
|
|
@@ -411,7 +411,7 @@ export function PrivatePageTemplate({ config }: { config: any }) {
|
|
|
411
411
|
};
|
|
412
412
|
|
|
413
413
|
// ── Prompt text ───────────────────────────────────────────────────────────
|
|
414
|
-
const promptText = privateCrudPrompt(config, recordsCollection
|
|
414
|
+
const promptText = privateCrudPrompt(config, recordsCollection);
|
|
415
415
|
|
|
416
416
|
const handleCopyPrompt = () => {
|
|
417
417
|
navigator.clipboard.writeText(promptText);
|
|
@@ -547,40 +547,44 @@ export function PrivatePageTemplate({ config }: { config: any }) {
|
|
|
547
547
|
</div>
|
|
548
548
|
)}
|
|
549
549
|
|
|
550
|
-
{
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
550
|
+
{CustomContent ? (
|
|
551
|
+
<CustomContent config={config} />
|
|
552
|
+
) : (
|
|
553
|
+
<>
|
|
554
|
+
{/* Empty State */}
|
|
555
|
+
{!loading && records.length === 0 && (
|
|
556
|
+
<motion.div
|
|
557
|
+
initial={{ opacity: 0, y: 10 }}
|
|
558
|
+
animate={{ opacity: 1, y: 0 }}
|
|
559
|
+
className="flex-1 flex flex-col items-center justify-center border border-[var(--panel-border)]/50 border-dashed rounded-2xl p-8 py-20 text-center min-h-[350px]"
|
|
560
|
+
>
|
|
561
|
+
<div className="w-16 h-16 rounded-2xl bg-accent/5 border border-accent/10 flex items-center justify-center mb-5">
|
|
562
|
+
<Plus className="w-6 h-6 text-accent/40" />
|
|
563
|
+
</div>
|
|
564
|
+
<p className="text-[15px] font-bold mb-1.5 text-foreground/50">No items yet</p>
|
|
565
|
+
<p className="text-[13px] font-medium text-foreground/35 max-w-xs leading-relaxed">
|
|
566
|
+
Use the buttons above to add records or upload files to get started
|
|
567
|
+
</p>
|
|
568
|
+
</motion.div>
|
|
569
|
+
)}
|
|
570
|
+
|
|
571
|
+
{/* Records Grid */}
|
|
572
|
+
{!loading && records.length > 0 && (
|
|
573
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
574
|
+
<AnimatePresence>
|
|
575
|
+
{records.map((record, i) => {
|
|
576
|
+
const isEditing = editingId === record.id;
|
|
577
|
+
return (
|
|
578
|
+
<motion.div
|
|
579
|
+
key={record.id}
|
|
580
|
+
initial={{ opacity: 0, y: 10 }}
|
|
581
|
+
animate={{ opacity: 1, y: 0 }}
|
|
582
|
+
exit={{ opacity: 0, scale: 0.95 }}
|
|
583
|
+
transition={{ delay: i * 0.03, duration: 0.25 }}
|
|
584
|
+
onClick={() => startEdit(record)}
|
|
585
|
+
className={`group flex flex-col bg-background border rounded-2xl p-5 transition-all relative cursor-pointer ${isEditing ? 'border-accent/40 shadow-lg ring-1 ring-accent/20' : 'border-[var(--panel-border)] hover:border-accent/30 hover:shadow-lg'
|
|
586
|
+
}`}
|
|
587
|
+
>
|
|
584
588
|
{/* Top-right action icons — download + edit + delete */}
|
|
585
589
|
{true && (
|
|
586
590
|
<div className="absolute top-3 right-3 flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-all z-10">
|
|
@@ -655,8 +659,10 @@ export function PrivatePageTemplate({ config }: { config: any }) {
|
|
|
655
659
|
</motion.div>
|
|
656
660
|
);
|
|
657
661
|
})}
|
|
658
|
-
|
|
659
|
-
|
|
662
|
+
</AnimatePresence>
|
|
663
|
+
</div>
|
|
664
|
+
)}
|
|
665
|
+
</>
|
|
660
666
|
)}
|
|
661
667
|
</div>
|
|
662
668
|
|
|
@@ -7,7 +7,7 @@ import { db } from '../lib/firebase';
|
|
|
7
7
|
import { collection, getDocs } from 'firebase/firestore';
|
|
8
8
|
import { publicPagePrompt } from '../prompts';
|
|
9
9
|
|
|
10
|
-
export function PublicPageTemplate({ config }: { config: any }) {
|
|
10
|
+
export function PublicPageTemplate({ config, customContent: CustomContent }: { config: any; customContent?: React.ComponentType<any> }) {
|
|
11
11
|
const { config: themeConfig } = useTheme();
|
|
12
12
|
const [copied, setCopied] = useState(false);
|
|
13
13
|
const [showForm, setShowForm] = useState<string | null>(null);
|
|
@@ -78,13 +78,19 @@ export function PublicPageTemplate({ config }: { config: any }) {
|
|
|
78
78
|
animate={{ opacity: 1, y: 0 }}
|
|
79
79
|
className="glass-panel border border-[var(--panel-border)] rounded-3xl shadow-2xl relative flex flex-col flex-1 bg-background/50 h-full min-h-[400px]"
|
|
80
80
|
>
|
|
81
|
-
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
<div className="mt-8 text-left bg-background p-6 rounded-2xl text-[12px] font-mono whitespace-pre-wrap max-w-2xl border border-[var(--panel-border)]/50 text-foreground/60 shadow-inner">
|
|
85
|
-
{promptText}
|
|
81
|
+
{CustomContent ? (
|
|
82
|
+
<div className="flex-1 p-4 md:p-8">
|
|
83
|
+
<CustomContent config={config} />
|
|
86
84
|
</div>
|
|
87
|
-
|
|
85
|
+
) : (
|
|
86
|
+
<div className="flex-1 flex flex-col items-center justify-center opacity-40 glass-panel border border-[var(--panel-border)]/50 border-dashed m-4 md:m-8 rounded-2xl p-8 py-20 text-center">
|
|
87
|
+
<Bot className="w-8 h-8 md:w-12 md:h-12 mb-4 text-foreground/80 opacity-50" />
|
|
88
|
+
<p className="text-[14px] font-bold mb-2">Click copy prompt at the top to start adding changes</p>
|
|
89
|
+
<div className="mt-8 text-left bg-background p-6 rounded-2xl text-[12px] font-mono whitespace-pre-wrap max-w-2xl border border-[var(--panel-border)]/50 text-foreground/60 shadow-inner">
|
|
90
|
+
{promptText}
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
)}
|
|
88
94
|
</motion.div>
|
|
89
95
|
<AnimatePresence>
|
|
90
96
|
{showForm && <ContactPopup onClose={() => setShowForm(null)} formId={showForm} />}
|
|
@@ -30,7 +30,7 @@ interface SavedRecord {
|
|
|
30
30
|
|
|
31
31
|
import { sharedCrudPrompt } from '../prompts';
|
|
32
32
|
|
|
33
|
-
export function SharedPageTemplate({ config }: { config: any }) {
|
|
33
|
+
export function SharedPageTemplate({ config, customContent: CustomContent }: { config: any; customContent?: React.ComponentType<any> }) {
|
|
34
34
|
const { user } = useAuth();
|
|
35
35
|
const { config: themeConfig } = useTheme();
|
|
36
36
|
const [records, setRecords] = useState<SavedRecord[]>([]);
|
|
@@ -551,23 +551,27 @@ export function SharedPageTemplate({ config }: { config: any }) {
|
|
|
551
551
|
</div>
|
|
552
552
|
)}
|
|
553
553
|
|
|
554
|
-
{
|
|
555
|
-
<
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
554
|
+
{CustomContent ? (
|
|
555
|
+
<CustomContent config={config} />
|
|
556
|
+
) : (
|
|
557
|
+
<>
|
|
558
|
+
{!loading && records.length === 0 && (
|
|
559
|
+
<motion.div
|
|
560
|
+
initial={{ opacity: 0, y: 10 }}
|
|
561
|
+
animate={{ opacity: 1, y: 0 }}
|
|
562
|
+
className="flex-1 flex flex-col items-center justify-center border border-[var(--panel-border)]/50 border-dashed rounded-2xl p-8 py-20 text-center min-h-[350px]"
|
|
563
|
+
>
|
|
564
|
+
<div className="w-16 h-16 rounded-2xl bg-accent/5 border border-accent/10 flex items-center justify-center mb-5">
|
|
565
|
+
<Plus className="w-6 h-6 text-accent/40" />
|
|
566
|
+
</div>
|
|
567
|
+
<p className="text-[15px] font-bold mb-1.5 text-foreground/50">No items yet</p>
|
|
568
|
+
<p className="text-[13px] font-medium text-foreground/35 max-w-xs leading-relaxed">
|
|
569
|
+
Use the buttons above to add records or upload files. All team members can view and contribute.
|
|
570
|
+
</p>
|
|
571
|
+
</motion.div>
|
|
572
|
+
)}
|
|
573
|
+
|
|
574
|
+
{!loading && records.length > 0 && (
|
|
571
575
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
572
576
|
<AnimatePresence>
|
|
573
577
|
{records.map((record) => {
|
|
@@ -661,6 +665,8 @@ export function SharedPageTemplate({ config }: { config: any }) {
|
|
|
661
665
|
})}
|
|
662
666
|
</AnimatePresence>
|
|
663
667
|
</div>
|
|
668
|
+
)}
|
|
669
|
+
</>
|
|
664
670
|
)}
|
|
665
671
|
</div>
|
|
666
672
|
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { motion } from 'framer-motion';
|
|
3
|
-
import { Users, Server, ShieldAlert, Activity } from 'lucide-react';
|
|
4
|
-
|
|
5
|
-
export default function AdminExampleContent() {
|
|
6
|
-
return (
|
|
7
|
-
<div className="flex flex-col gap-6 p-4 md:p-8 w-full max-w-7xl mx-auto">
|
|
8
|
-
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
|
|
9
|
-
<div>
|
|
10
|
-
<h2 className="text-2xl font-bold tracking-tight text-foreground">System Overview</h2>
|
|
11
|
-
<p className="text-foreground/60 text-sm mt-1">Monitor your infrastructure and user metrics.</p>
|
|
12
|
-
</div>
|
|
13
|
-
<div className="flex bg-accent/10 text-accent px-4 py-2 rounded-xl text-sm font-bold border border-accent/20">
|
|
14
|
-
<Activity className="w-4 h-4 mr-2" />
|
|
15
|
-
System Healthy
|
|
16
|
-
</div>
|
|
17
|
-
</div>
|
|
18
|
-
|
|
19
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
20
|
-
<div className="glass-panel border border-[var(--panel-border)] p-6 rounded-3xl flex flex-col">
|
|
21
|
-
<div className="w-10 h-10 rounded-xl bg-blue-500/10 text-blue-500 flex items-center justify-center mb-4">
|
|
22
|
-
<Users className="w-5 h-5" />
|
|
23
|
-
</div>
|
|
24
|
-
<p className="text-foreground/60 text-sm font-medium">Total Users</p>
|
|
25
|
-
<p className="text-3xl font-bold text-foreground mt-1">1,204</p>
|
|
26
|
-
</div>
|
|
27
|
-
<div className="glass-panel border border-[var(--panel-border)] p-6 rounded-3xl flex flex-col">
|
|
28
|
-
<div className="w-10 h-10 rounded-xl bg-emerald-500/10 text-emerald-500 flex items-center justify-center mb-4">
|
|
29
|
-
<Server className="w-5 h-5" />
|
|
30
|
-
</div>
|
|
31
|
-
<p className="text-foreground/60 text-sm font-medium">Active Nodes</p>
|
|
32
|
-
<p className="text-3xl font-bold text-foreground mt-1">42</p>
|
|
33
|
-
</div>
|
|
34
|
-
<div className="glass-panel border border-[var(--panel-border)] p-6 rounded-3xl flex flex-col border-yellow-500/30">
|
|
35
|
-
<div className="w-10 h-10 rounded-xl bg-yellow-500/10 text-yellow-500 flex items-center justify-center mb-4">
|
|
36
|
-
<ShieldAlert className="w-5 h-5" />
|
|
37
|
-
</div>
|
|
38
|
-
<p className="text-foreground/60 text-sm font-medium">Security Alerts</p>
|
|
39
|
-
<p className="text-3xl font-bold text-yellow-500 mt-1">3 Needs Review</p>
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { motion } from 'framer-motion';
|
|
3
|
-
import { FileText, Clock, Star } from 'lucide-react';
|
|
4
|
-
import { Button } from '../components/Button';
|
|
5
|
-
|
|
6
|
-
export default function PrivateExampleContent() {
|
|
7
|
-
return (
|
|
8
|
-
<div className="flex flex-col gap-6 p-4 md:p-8 w-full max-w-5xl mx-auto">
|
|
9
|
-
<div className="glass-panel border border-[var(--panel-border)] p-8 rounded-3xl flex flex-col md:flex-row items-center justify-between gap-6">
|
|
10
|
-
<div>
|
|
11
|
-
<h2 className="text-2xl font-bold tracking-tight text-foreground">Welcome back!</h2>
|
|
12
|
-
<p className="text-foreground/60 text-sm mt-2 max-w-md">Here is your personal workspace. You can manage your private documents and settings here.</p>
|
|
13
|
-
</div>
|
|
14
|
-
<Button className="shrink-0 px-6 tracking-wide">Create New</Button>
|
|
15
|
-
</div>
|
|
16
|
-
|
|
17
|
-
<h3 className="text-lg font-bold text-foreground mt-4 ml-2">Recent Items</h3>
|
|
18
|
-
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
19
|
-
{[1, 2].map((i) => (
|
|
20
|
-
<div key={i} className="glass-panel border border-[var(--panel-border)] p-4 rounded-2xl flex items-center gap-4 hover:border-accent/40 transition-colors cursor-pointer group">
|
|
21
|
-
<div className="w-12 h-12 rounded-xl bg-accent/10 flex items-center justify-center text-accent shrink-0 group-hover:scale-110 transition-transform">
|
|
22
|
-
<FileText className="w-6 h-6" />
|
|
23
|
-
</div>
|
|
24
|
-
<div className="flex-1 min-w-0">
|
|
25
|
-
<p className="font-bold text-foreground truncate">Private Document {i}</p>
|
|
26
|
-
<div className="flex items-center gap-2 mt-1 text-xs text-foreground/40 font-medium">
|
|
27
|
-
<Clock className="w-3 h-3" />
|
|
28
|
-
<span>Updated 2 days ago</span>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
<button className="p-2 text-foreground/20 hover:text-yellow-400 transition-colors">
|
|
32
|
-
<Star className="w-5 h-5" />
|
|
33
|
-
</button>
|
|
34
|
-
</div>
|
|
35
|
-
))}
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
);
|
|
39
|
-
}
|