firebase-os 1.1.4 → 1.1.6

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.
Files changed (124) hide show
  1. package/dist/FirebaseOS.d.ts +15 -0
  2. package/dist/firebase-os.cjs.js +5 -20
  3. package/dist/firebase-os.es.js +95 -90
  4. package/dist/index.d.ts +3 -0
  5. package/dist/lib/ConfigContext.d.ts +12 -0
  6. package/package.json +3 -2
  7. package/scripts/postinstall.js +86 -15
  8. package/src/App.css +184 -0
  9. package/src/App.tsx +214 -0
  10. package/src/FirebaseOS.tsx +81 -0
  11. package/src/assets/hero.png +0 -0
  12. package/src/assets/react.svg +1 -0
  13. package/src/assets/vite.svg +1 -0
  14. package/src/components/AdminNotifications.test.tsx +98 -0
  15. package/src/components/AdminNotifications.tsx +194 -0
  16. package/src/components/Button.test.tsx +22 -0
  17. package/src/components/Button.tsx +53 -0
  18. package/src/components/ConfirmModal.test.tsx +98 -0
  19. package/src/components/ConfirmModal.tsx +73 -0
  20. package/src/components/ContactPopup.test.tsx +98 -0
  21. package/src/components/ContactPopup.tsx +437 -0
  22. package/src/components/CustomSelect.test.tsx +47 -0
  23. package/src/components/CustomSelect.tsx +89 -0
  24. package/src/components/DashboardNav.test.tsx +98 -0
  25. package/src/components/DashboardNav.tsx +281 -0
  26. package/src/components/Input.test.tsx +33 -0
  27. package/src/components/Input.tsx +61 -0
  28. package/src/components/JsonEditor.tsx +579 -0
  29. package/src/components/Navbar.test.tsx +98 -0
  30. package/src/components/Navbar.tsx +563 -0
  31. package/src/configs/forms/contactForm.config.ts +15 -0
  32. package/src/configs/forms/index.ts +29 -0
  33. package/src/configs/forms/pubForm.config.ts +11 -0
  34. package/src/configs/forms/supportForm.config.ts +14 -0
  35. package/src/configs/forms/userForm.config.ts +11 -0
  36. package/src/configs/pages/admin.config.ts +29 -0
  37. package/src/configs/pages/contact.config.ts +6 -0
  38. package/src/configs/pages/home.config.ts +18 -0
  39. package/src/configs/pages/mem.config.ts +2 -0
  40. package/src/configs/pages/menuOrders.config.ts +11 -0
  41. package/src/configs/pages/pub.config.ts +11 -0
  42. package/src/configs/pages/shared.config.ts +29 -0
  43. package/src/configs/pages/support.config.ts +7 -0
  44. package/src/configs/pages/tabOrders.config.ts +33 -0
  45. package/src/configs/pages/user.config.ts +29 -0
  46. package/src/configs/theme.config.ts +93 -0
  47. package/src/index.css +403 -0
  48. package/src/index.ts +22 -0
  49. package/src/lib/AuthContext.test.tsx +88 -0
  50. package/src/lib/AuthContext.tsx +191 -0
  51. package/src/lib/ConfigContext.tsx +45 -0
  52. package/src/lib/ThemeContext.tsx +233 -0
  53. package/src/lib/firebase.ts +91 -0
  54. package/src/main.tsx +22 -0
  55. package/src/microcomponents/AdminExampleContent.tsx +44 -0
  56. package/src/microcomponents/PrivateExampleContent.tsx +39 -0
  57. package/src/microcomponents/Public.tsx +126 -0
  58. package/src/microcomponents/SharedExampleContent.tsx +53 -0
  59. package/src/pages/Dashboard.test.tsx +98 -0
  60. package/src/pages/Dashboard.tsx +60 -0
  61. package/src/pages/DynamicPage.tsx +237 -0
  62. package/src/pages/FormsAdmin.test.tsx +98 -0
  63. package/src/pages/FormsAdmin.tsx +459 -0
  64. package/src/pages/Home.test.tsx +98 -0
  65. package/src/pages/Home.tsx +144 -0
  66. package/src/pages/Login.test.tsx +98 -0
  67. package/src/pages/Login.tsx +108 -0
  68. package/src/pages/PagesAdmin.test.tsx +98 -0
  69. package/src/pages/PagesAdmin.tsx +1022 -0
  70. package/src/pages/Profile.test.tsx +98 -0
  71. package/src/pages/Profile.tsx +319 -0
  72. package/src/pages/Register.test.tsx +98 -0
  73. package/src/pages/Register.tsx +116 -0
  74. package/src/pages/Requests.test.tsx +95 -0
  75. package/src/pages/Requests.tsx +422 -0
  76. package/src/pages/ResetPassword.test.tsx +98 -0
  77. package/src/pages/ResetPassword.tsx +92 -0
  78. package/src/pages/Settings.test.tsx +98 -0
  79. package/src/pages/Settings.tsx +393 -0
  80. package/src/pages/Setup.tsx +407 -0
  81. package/src/pages/StorageAdmin.test.tsx +150 -0
  82. package/src/pages/StorageAdmin.tsx +769 -0
  83. package/src/pages/Submissions.test.tsx +95 -0
  84. package/src/pages/Submissions.tsx +378 -0
  85. package/src/pages/Templates.test.tsx +98 -0
  86. package/src/pages/Templates.tsx +103 -0
  87. package/src/pages/ThemeAdmin.test.tsx +144 -0
  88. package/src/pages/ThemeAdmin.tsx +1000 -0
  89. package/src/pages/Users.test.tsx +95 -0
  90. package/src/pages/Users.tsx +334 -0
  91. package/src/pages/Verify.test.tsx +98 -0
  92. package/src/pages/Verify.tsx +95 -0
  93. package/src/prompts/index.ts +13 -0
  94. package/src/prompts/pages/publicPage.ts +44 -0
  95. package/src/prompts/sharedConstants.ts +12 -0
  96. package/src/prompts/tabs/board/adminboard.ts +32 -0
  97. package/src/prompts/tabs/board/privateboard.ts +36 -0
  98. package/src/prompts/tabs/board/publicboard.ts +36 -0
  99. package/src/prompts/tabs/calendar/admincalendar.ts +32 -0
  100. package/src/prompts/tabs/calendar/privatecalendar.ts +36 -0
  101. package/src/prompts/tabs/calendar/publiccalendar.ts +36 -0
  102. package/src/prompts/tabs/crud/admin.ts +54 -0
  103. package/src/prompts/tabs/crud/private.ts +55 -0
  104. package/src/prompts/tabs/crud/shared.ts +53 -0
  105. package/src/prompts/tabs/table/admintable.ts +32 -0
  106. package/src/prompts/tabs/table/privatetable.ts +36 -0
  107. package/src/prompts/tabs/table/publictable.ts +36 -0
  108. package/src/setupTests.ts +1 -0
  109. package/src/templates/AdminPageTemplate.tsx +678 -0
  110. package/src/templates/PrivatePageTemplate.tsx +594 -0
  111. package/src/templates/PublicPageTemplate.tsx +92 -0
  112. package/src/templates/SharedPageTemplate.tsx +551 -0
  113. package/src/templates/TemplateBoard.test.tsx +106 -0
  114. package/src/templates/TemplateBoard.tsx +642 -0
  115. package/src/templates/TemplateCalendar.test.tsx +106 -0
  116. package/src/templates/TemplateCalendar.tsx +848 -0
  117. package/src/templates/TemplateConfirmation.test.tsx +106 -0
  118. package/src/templates/TemplateConfirmation.tsx +145 -0
  119. package/src/templates/TemplateInlineForm.test.tsx +106 -0
  120. package/src/templates/TemplateInlineForm.tsx +129 -0
  121. package/src/templates/TemplatePopupForm.test.tsx +106 -0
  122. package/src/templates/TemplatePopupForm.tsx +174 -0
  123. package/src/templates/TemplateTable.test.tsx +106 -0
  124. package/src/templates/TemplateTable.tsx +675 -0
@@ -0,0 +1,106 @@
1
+ // @generated-test
2
+ import { describe, it, expect, vi } from 'vitest';
3
+ import { render, screen, act } from '@testing-library/react';
4
+ import { BrowserRouter } from 'react-router-dom';
5
+ import { AuthProvider } from '../lib/AuthContext';
6
+ import { ThemeProvider } from '../lib/ThemeContext';
7
+ import { TemplateConfirmation } from './TemplateConfirmation';
8
+
9
+ // Global mocks
10
+ Object.defineProperty(window, 'matchMedia', {
11
+ writable: true,
12
+ value: vi.fn().mockImplementation(query => ({
13
+ matches: false,
14
+ media: query,
15
+ onchange: null,
16
+ addListener: vi.fn(),
17
+ removeListener: vi.fn(),
18
+ addEventListener: vi.fn(),
19
+ removeEventListener: vi.fn(),
20
+ dispatchEvent: vi.fn(),
21
+ })),
22
+ });
23
+
24
+ const mockAuthContext = {
25
+ user: { uid: 'mock-user-123', email: 'test@example.com' },
26
+ userWorkspaces: [],
27
+ activeWorkspace: null,
28
+ activeOrg: null,
29
+ loading: false
30
+ };
31
+
32
+ const mockThemeContext = {
33
+ themeMode: 'light',
34
+ setThemeMode: vi.fn(),
35
+ activeConfig: {}
36
+ };
37
+
38
+ vi.mock('../lib/AuthContext', () => ({
39
+ AuthProvider: ({ children }: any) => <>{children}</>,
40
+ useAuth: () => mockAuthContext
41
+ }));
42
+
43
+ vi.mock('../lib/ThemeContext', () => ({
44
+ ThemeProvider: ({ children }: any) => <>{children}</>,
45
+ useTheme: () => mockThemeContext
46
+ }));
47
+
48
+ vi.mock('firebase/auth', () => ({
49
+ getAuth: vi.fn(() => ({})),
50
+ onAuthStateChanged: vi.fn((auth, cb) => { cb({ uid: 'mock-user-123', email: 'test@example.com', getIdToken: vi.fn(() => Promise.resolve('mock-token')) }); return () => {}; })
51
+ }));
52
+ vi.mock('firebase/firestore', () => ({
53
+ getFirestore: vi.fn(() => ({})),
54
+ collection: vi.fn(),
55
+ doc: vi.fn(),
56
+ setDoc: vi.fn(() => Promise.resolve()),
57
+ addDoc: vi.fn(() => Promise.resolve()),
58
+ updateDoc: vi.fn(() => Promise.resolve()),
59
+ deleteDoc: vi.fn(() => Promise.resolve()),
60
+ query: vi.fn(),
61
+ where: vi.fn(),
62
+ orderBy: vi.fn(),
63
+ limit: vi.fn(),
64
+ getDoc: vi.fn(() => Promise.resolve({ exists: () => true, data: () => ({ role: 'super_admin' }) })),
65
+ getDocs: vi.fn(() => Promise.resolve({ docs: [], forEach: vi.fn() })),
66
+ onSnapshot: vi.fn((...args: any[]) => {
67
+ let cb = args[1];
68
+ if (typeof args[2] === 'function') {
69
+ cb = args[2];
70
+ }
71
+ if (typeof cb === 'function') {
72
+ cb({docs: [], forEach: vi.fn(), data: () => ({}), exists: () => true});
73
+ }
74
+ return () => {};
75
+ })
76
+ }));
77
+ vi.mock('firebase/storage', () => ({
78
+ getStorage: vi.fn(() => ({})),
79
+ ref: vi.fn(),
80
+ listAll: vi.fn(() => Promise.resolve({ items: [], prefixes: [] })),
81
+ getDownloadURL: vi.fn(() => Promise.resolve('mock-url')),
82
+ getMetadata: vi.fn(() => Promise.resolve({ size: 1024, timeCreated: new Date().toISOString() }))
83
+ }));
84
+
85
+ describe('TemplateConfirmation Component', () => {
86
+ it('renders without crashing', async () => {
87
+ // Wrap in standard application providers inside act to process async side effects and prevent warnings
88
+ await act(async () => {
89
+ render(
90
+ <BrowserRouter>
91
+ <AuthProvider>
92
+ <ThemeProvider>
93
+ {/* @ts-ignore */}
94
+ <TemplateConfirmation />
95
+ </ThemeProvider>
96
+ </AuthProvider>
97
+ </BrowserRouter>
98
+ );
99
+ // Wait a tick to flush background state updates
100
+ await new Promise(resolve => setTimeout(resolve, 0));
101
+ });
102
+
103
+ // Check if the document has anything rendered without throwing
104
+ expect(document.body).toBeDefined();
105
+ });
106
+ });
@@ -0,0 +1,145 @@
1
+ import { motion, AnimatePresence } from 'framer-motion';
2
+ import { ArrowLeft, MessageSquare, Check, X, AlertTriangle } from 'lucide-react';
3
+ import { Link } from 'react-router-dom';
4
+ import { Button } from '../components/Button';
5
+ import { useState } from 'react';
6
+
7
+ export function TemplateConfirmation() {
8
+ const [confirmed, setConfirmed] = useState<boolean | null | undefined>(undefined);
9
+
10
+ const reset = () => setConfirmed(null);
11
+
12
+ return (
13
+ <main className="flex-1 w-full max-w-7xl mx-auto p-4 md:p-8 z-10 flex flex-col pt-12 items-center">
14
+ <div className="w-full max-w-5xl mb-8">
15
+ <Link to="/templates" className="inline-flex items-center gap-2 text-foreground/50 hover:text-accent transition-colors font-medium text-[14px]">
16
+ <ArrowLeft className="w-4 h-4" /> Back to Templates
17
+ </Link>
18
+ </div>
19
+
20
+ {/* Simulated Background Dashboard */}
21
+ <div className="w-full max-w-5xl mx-auto mt-2 glass-panel rounded-3xl border border-[var(--panel-border)] p-6 md:p-10 relative overflow-hidden shadow-2xl">
22
+ <div className="flex items-center justify-between mb-8 pb-6 border-b border-[var(--panel-border)]/50">
23
+ <div>
24
+ <h3 className="text-2xl font-extrabold text-foreground tracking-tight">System Files</h3>
25
+ <p className="text-[14px] text-foreground/50 mt-1">Review and manage core application assets.</p>
26
+ </div>
27
+ </div>
28
+
29
+ <div className="flex flex-col gap-4">
30
+ <div className="glass-panel p-5 rounded-2xl border border-[var(--panel-border)]/50 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
31
+ <div className="flex items-center gap-4 w-full sm:w-auto">
32
+ <div className="w-10 h-10 rounded-xl bg-red-500/10 flex items-center justify-center text-red-500 shrink-0">
33
+ <AlertTriangle className="w-5 h-5" />
34
+ </div>
35
+ <div className="min-w-0 pr-2">
36
+ <h4 className="font-bold text-[14px] sm:text-[15px] text-foreground truncate">Critical Configuration File</h4>
37
+ <p className="text-[12px] sm:text-[13px] text-foreground/50 font-medium mt-0.5 truncate">Size: 4.2 MB • Modified 2 hours ago</p>
38
+ </div>
39
+ </div>
40
+
41
+ <Button
42
+ variant="secondary"
43
+ onClick={() => setConfirmed(null)}
44
+ className="w-full sm:w-auto shrink-0 flex items-center justify-center px-6 py-3 sm:py-2.5 text-[13px] font-bold rounded-xl text-red-500 hover:bg-red-500/10 hover:border-red-500/20 bg-red-500/5 sm:bg-transparent"
45
+ >
46
+ Delete File
47
+ </Button>
48
+ </div>
49
+
50
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4 opacity-50 pointer-events-none mt-2">
51
+ {[1, 2].map((i) => (
52
+ <div key={i} className="glass-panel p-5 rounded-2xl border border-[var(--panel-border)]/20 flex items-center gap-4">
53
+ <div className="w-10 h-10 rounded-xl bg-foreground/5 shrink-0" />
54
+ <div className="flex flex-col gap-2 flex-1">
55
+ <div className="w-32 h-3 rounded-full bg-foreground/10" />
56
+ <div className="w-20 h-2 rounded-full bg-foreground/5" />
57
+ </div>
58
+ </div>
59
+ ))}
60
+ </div>
61
+ </div>
62
+ </div>
63
+
64
+ {/* Confirmation Modal */}
65
+ <AnimatePresence>
66
+ {confirmed !== undefined && (
67
+ <div className="fixed inset-0 z-[100] flex items-center justify-center p-4">
68
+ <motion.div
69
+ initial={{ opacity: 0 }}
70
+ animate={{ opacity: 1 }}
71
+ exit={{ opacity: 0 }}
72
+ className="absolute inset-0 bg-background/60 backdrop-blur-xl"
73
+ />
74
+ <motion.div
75
+ initial={{ opacity: 0, scale: 0.95, y: 10 }}
76
+ animate={{ opacity: 1, scale: 1, y: 0 }}
77
+ exit={{ opacity: 0, scale: 0.95, y: 10 }}
78
+ className="w-full max-w-[400px] p-6 rounded-3xl glass-panel border border-[var(--panel-border)] shadow-2xl relative z-10 text-center"
79
+ >
80
+ <AnimatePresence mode="wait">
81
+ {confirmed === null ? (
82
+ <motion.div
83
+ key="confirm"
84
+ initial={{ opacity: 0 }}
85
+ animate={{ opacity: 1 }}
86
+ exit={{ opacity: 0 }}
87
+ className="w-full flex flex-col items-center"
88
+ >
89
+ <h3 className="text-xl font-extrabold mb-2 text-foreground tracking-tight">Confirm Deletion</h3>
90
+ <p className="text-[14px] font-medium text-foreground/60 leading-relaxed mb-6">
91
+ Are you entirely sure you wish to delete this configuration? This action is permanent.
92
+ </p>
93
+ <div className="flex items-center gap-3 w-full">
94
+ <Button
95
+ variant="secondary"
96
+ onClick={() => setConfirmed(false)}
97
+ className="flex-1 py-2.5 text-[13px] font-bold rounded-xl"
98
+ >
99
+ Cancel
100
+ </Button>
101
+ <Button
102
+ onClick={() => setConfirmed(true)}
103
+ className="flex-1 py-2.5 text-[13px] font-bold rounded-xl bg-red-500 hover:bg-red-600 border-none text-white shadow-xl hover:shadow-red-500/20 glow-hover"
104
+ >
105
+ Yes, Delete
106
+ </Button>
107
+ </div>
108
+ </motion.div>
109
+ ) : confirmed === true ? (
110
+ <motion.div
111
+ key="success"
112
+ initial={{ opacity: 0 }}
113
+ animate={{ opacity: 1 }}
114
+ exit={{ opacity: 0 }}
115
+ className="w-full flex flex-col items-center py-2"
116
+ >
117
+ <h3 className="text-xl font-extrabold mb-1 text-green-500 tracking-tight flex items-center gap-2">
118
+ <Check className="w-5 h-5" /> Verified
119
+ </h3>
120
+ <p className="text-[14px] font-medium text-foreground/60 mb-6">Action successfully executed.</p>
121
+ <Button variant="secondary" onClick={() => setConfirmed(undefined)} className="px-6 py-2 rounded-xl text-[13px] font-bold">Close Panel</Button>
122
+ </motion.div>
123
+ ) : (
124
+ <motion.div
125
+ key="error"
126
+ initial={{ opacity: 0 }}
127
+ animate={{ opacity: 1 }}
128
+ exit={{ opacity: 0 }}
129
+ className="w-full flex flex-col items-center py-2"
130
+ >
131
+ <h3 className="text-xl font-extrabold mb-1 text-foreground/80 tracking-tight flex items-center gap-2">
132
+ <X className="w-5 h-5" /> Aborted
133
+ </h3>
134
+ <p className="text-[14px] font-medium text-foreground/60 mb-6">Cancellation securely caught.</p>
135
+ <Button variant="secondary" onClick={() => setConfirmed(undefined)} className="px-6 py-2 rounded-xl text-[13px] font-bold">Close Panel</Button>
136
+ </motion.div>
137
+ )}
138
+ </AnimatePresence>
139
+ </motion.div>
140
+ </div>
141
+ )}
142
+ </AnimatePresence>
143
+ </main>
144
+ );
145
+ }
@@ -0,0 +1,106 @@
1
+ // @generated-test
2
+ import { describe, it, expect, vi } from 'vitest';
3
+ import { render, screen, act } from '@testing-library/react';
4
+ import { BrowserRouter } from 'react-router-dom';
5
+ import { AuthProvider } from '../lib/AuthContext';
6
+ import { ThemeProvider } from '../lib/ThemeContext';
7
+ import { TemplateInlineForm } from './TemplateInlineForm';
8
+
9
+ // Global mocks
10
+ Object.defineProperty(window, 'matchMedia', {
11
+ writable: true,
12
+ value: vi.fn().mockImplementation(query => ({
13
+ matches: false,
14
+ media: query,
15
+ onchange: null,
16
+ addListener: vi.fn(),
17
+ removeListener: vi.fn(),
18
+ addEventListener: vi.fn(),
19
+ removeEventListener: vi.fn(),
20
+ dispatchEvent: vi.fn(),
21
+ })),
22
+ });
23
+
24
+ const mockAuthContext = {
25
+ user: { uid: 'mock-user-123', email: 'test@example.com' },
26
+ userWorkspaces: [],
27
+ activeWorkspace: null,
28
+ activeOrg: null,
29
+ loading: false
30
+ };
31
+
32
+ const mockThemeContext = {
33
+ themeMode: 'light',
34
+ setThemeMode: vi.fn(),
35
+ activeConfig: {}
36
+ };
37
+
38
+ vi.mock('../lib/AuthContext', () => ({
39
+ AuthProvider: ({ children }: any) => <>{children}</>,
40
+ useAuth: () => mockAuthContext
41
+ }));
42
+
43
+ vi.mock('../lib/ThemeContext', () => ({
44
+ ThemeProvider: ({ children }: any) => <>{children}</>,
45
+ useTheme: () => mockThemeContext
46
+ }));
47
+
48
+ vi.mock('firebase/auth', () => ({
49
+ getAuth: vi.fn(() => ({})),
50
+ onAuthStateChanged: vi.fn((auth, cb) => { cb({ uid: 'mock-user-123', email: 'test@example.com', getIdToken: vi.fn(() => Promise.resolve('mock-token')) }); return () => {}; })
51
+ }));
52
+ vi.mock('firebase/firestore', () => ({
53
+ getFirestore: vi.fn(() => ({})),
54
+ collection: vi.fn(),
55
+ doc: vi.fn(),
56
+ setDoc: vi.fn(() => Promise.resolve()),
57
+ addDoc: vi.fn(() => Promise.resolve()),
58
+ updateDoc: vi.fn(() => Promise.resolve()),
59
+ deleteDoc: vi.fn(() => Promise.resolve()),
60
+ query: vi.fn(),
61
+ where: vi.fn(),
62
+ orderBy: vi.fn(),
63
+ limit: vi.fn(),
64
+ getDoc: vi.fn(() => Promise.resolve({ exists: () => true, data: () => ({ role: 'super_admin' }) })),
65
+ getDocs: vi.fn(() => Promise.resolve({ docs: [], forEach: vi.fn() })),
66
+ onSnapshot: vi.fn((...args: any[]) => {
67
+ let cb = args[1];
68
+ if (typeof args[2] === 'function') {
69
+ cb = args[2];
70
+ }
71
+ if (typeof cb === 'function') {
72
+ cb({docs: [], forEach: vi.fn(), data: () => ({}), exists: () => true});
73
+ }
74
+ return () => {};
75
+ })
76
+ }));
77
+ vi.mock('firebase/storage', () => ({
78
+ getStorage: vi.fn(() => ({})),
79
+ ref: vi.fn(),
80
+ listAll: vi.fn(() => Promise.resolve({ items: [], prefixes: [] })),
81
+ getDownloadURL: vi.fn(() => Promise.resolve('mock-url')),
82
+ getMetadata: vi.fn(() => Promise.resolve({ size: 1024, timeCreated: new Date().toISOString() }))
83
+ }));
84
+
85
+ describe('TemplateInlineForm Component', () => {
86
+ it('renders without crashing', async () => {
87
+ // Wrap in standard application providers inside act to process async side effects and prevent warnings
88
+ await act(async () => {
89
+ render(
90
+ <BrowserRouter>
91
+ <AuthProvider>
92
+ <ThemeProvider>
93
+ {/* @ts-ignore */}
94
+ <TemplateInlineForm />
95
+ </ThemeProvider>
96
+ </AuthProvider>
97
+ </BrowserRouter>
98
+ );
99
+ // Wait a tick to flush background state updates
100
+ await new Promise(resolve => setTimeout(resolve, 0));
101
+ });
102
+
103
+ // Check if the document has anything rendered without throwing
104
+ expect(document.body).toBeDefined();
105
+ });
106
+ });
@@ -0,0 +1,129 @@
1
+ import { ArrowLeft, CheckCircle2 } from 'lucide-react';
2
+ import { Link } from 'react-router-dom';
3
+ import { motion } from 'framer-motion';
4
+ import { Button } from '../components/Button';
5
+ import { Input } from '../components/Input';
6
+ import { useState } from 'react';
7
+ import { auth, db } from '../lib/firebase';
8
+ import { collection, addDoc, serverTimestamp } from 'firebase/firestore';
9
+
10
+ export function TemplateInlineForm() {
11
+ const [formData, setFormData] = useState({ favoriteService: '', satisfactionRating: '', detailedThoughts: '' });
12
+ const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
13
+
14
+ const handleSubmit = async (e: React.FormEvent) => {
15
+ e.preventDefault();
16
+ setStatus('loading');
17
+
18
+ try {
19
+ const currentUserId = auth.currentUser?.uid || 'guest';
20
+ await addDoc(collection(db, 'user_submissions'), {
21
+ uid: currentUserId,
22
+ ...formData,
23
+ type: 'template_simulation',
24
+ submittedAt: serverTimestamp(),
25
+ });
26
+
27
+ setStatus('success');
28
+ setTimeout(() => {
29
+ setStatus('idle');
30
+ setFormData({ favoriteService: '', satisfactionRating: '', detailedThoughts: '' });
31
+ }, 3000);
32
+ } catch (err: any) {
33
+ setStatus('error');
34
+ }
35
+ };
36
+
37
+ return (
38
+ <main className="flex-1 w-full max-w-7xl mx-auto p-4 md:p-8 z-10 flex flex-col pt-12">
39
+ <div className="w-full max-w-5xl mx-auto mb-6">
40
+ <Link to="/templates" className="inline-flex items-center gap-2 text-foreground/50 hover:text-accent transition-colors font-medium text-[14px]">
41
+ <ArrowLeft className="w-4 h-4" /> Back to Templates
42
+ </Link>
43
+ </div>
44
+
45
+ <div className="w-full max-w-5xl mx-auto mt-6">
46
+ <motion.div
47
+ initial={{ opacity: 0, y: 10 }}
48
+ animate={{ opacity: 1, y: 0 }}
49
+ className="w-full max-w-[500px] p-6 sm:p-8 lg:p-10 rounded-3xl md:rounded-3xl glass-panel relative glow-hover shadow-[0_8px_32px_rgba(0,0,0,0.08)] z-10 mx-auto"
50
+ >
51
+ <div className="absolute -inset-[1px] bg-gradient-to-br from-[var(--primary-glow)] to-transparent opacity-30 blur-[20px] rounded-3xl md:rounded-3xl -z-10" />
52
+
53
+ <div className="relative z-10 w-full">
54
+ {status === 'success' ? (
55
+ <motion.div
56
+ initial={{ opacity: 0, y: 10 }}
57
+ animate={{ opacity: 1, y: 0 }}
58
+ className="flex flex-col items-center justify-center py-8 text-center"
59
+ >
60
+ <div className="w-16 h-16 rounded-full bg-green-500/10 border border-green-500/20 flex items-center justify-center text-green-500 mb-4">
61
+ <CheckCircle2 className="w-8 h-8" />
62
+ </div>
63
+ <h4 className="text-lg font-bold text-foreground mb-2">Simulation Complete!</h4>
64
+ <p className="text-[14px] text-foreground/60 leading-relaxed">Your test data was sent successfully. Log in as an admin and check the Submissions tab!</p>
65
+ </motion.div>
66
+ ) : status === 'loading' ? (
67
+ <motion.div
68
+ initial={{ opacity: 0, scale: 0.95 }}
69
+ animate={{ opacity: 1, scale: 1 }}
70
+ className="flex flex-col items-center justify-center py-16 text-center"
71
+ >
72
+ <div className="w-10 h-10 border-4 border-accent border-r-transparent rounded-full animate-spin mb-4" />
73
+ <h4 className="text-lg font-bold text-foreground tracking-tight">Processing</h4>
74
+ <p className="text-[13px] text-foreground/50 mt-1">Simulating submission...</p>
75
+ </motion.div>
76
+ ) : (
77
+ <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
78
+ <div className="mb-6 text-center">
79
+ <h3 className="text-2xl sm:text-3xl font-extrabold mb-2 text-foreground tracking-tight break-words">Inline Form Template</h3>
80
+ <p className="text-[14px] md:text-[15px] font-medium text-foreground/60 tracking-wide leading-relaxed">Try interacting with this simulated inline template block.</p>
81
+ </div>
82
+
83
+ <form onSubmit={handleSubmit} className="space-y-4 w-full">
84
+ <Input
85
+ label="Favorite Service"
86
+ placeholder="e.g. Design Consulting"
87
+ value={formData.favoriteService}
88
+ onChange={e => setFormData({ ...formData, favoriteService: e.target.value })}
89
+ />
90
+
91
+ <Input
92
+ label="Satisfaction Rating"
93
+ type="number"
94
+ placeholder="1 to 10"
95
+ value={formData.satisfactionRating}
96
+ onChange={e => setFormData({ ...formData, satisfactionRating: e.target.value })}
97
+ />
98
+
99
+ <div className="flex flex-col w-full relative">
100
+ <label className="text-[13px] font-medium text-foreground/50 select-none mb-1.5 ml-1">
101
+ Detailed Thoughts
102
+ </label>
103
+ <textarea
104
+ rows={3}
105
+ placeholder="Type something here..."
106
+ value={formData.detailedThoughts}
107
+ onChange={e => setFormData({ ...formData, detailedThoughts: e.target.value })}
108
+ className="flex w-full rounded-xl text-[15px] text-foreground px-4 py-3 transition-all duration-300 placeholder:text-foreground/40 focus:outline-none focus:border-accent glow-focus disabled:cursor-not-allowed disabled:opacity-50 glass-panel min-h-[100px] resize-none"
109
+ />
110
+ </div>
111
+
112
+ <div className="pt-2">
113
+ <Button
114
+ type="submit"
115
+ className="w-full px-6 md:px-8 py-3 md:py-3.5 rounded-xl font-semibold tracking-wide btn-primary text-[15px] shadow-xl transition-all duration-300 hover:-translate-y-1 active:scale-95 flex items-center justify-center"
116
+ isLoading={false}
117
+ >
118
+ Trigger Simulation
119
+ </Button>
120
+ </div>
121
+ </form>
122
+ </motion.div>
123
+ )}
124
+ </div>
125
+ </motion.div>
126
+ </div>
127
+ </main>
128
+ );
129
+ }
@@ -0,0 +1,106 @@
1
+ // @generated-test
2
+ import { describe, it, expect, vi } from 'vitest';
3
+ import { render, screen, act } from '@testing-library/react';
4
+ import { BrowserRouter } from 'react-router-dom';
5
+ import { AuthProvider } from '../lib/AuthContext';
6
+ import { ThemeProvider } from '../lib/ThemeContext';
7
+ import { TemplatePopupForm } from './TemplatePopupForm';
8
+
9
+ // Global mocks
10
+ Object.defineProperty(window, 'matchMedia', {
11
+ writable: true,
12
+ value: vi.fn().mockImplementation(query => ({
13
+ matches: false,
14
+ media: query,
15
+ onchange: null,
16
+ addListener: vi.fn(),
17
+ removeListener: vi.fn(),
18
+ addEventListener: vi.fn(),
19
+ removeEventListener: vi.fn(),
20
+ dispatchEvent: vi.fn(),
21
+ })),
22
+ });
23
+
24
+ const mockAuthContext = {
25
+ user: { uid: 'mock-user-123', email: 'test@example.com' },
26
+ userWorkspaces: [],
27
+ activeWorkspace: null,
28
+ activeOrg: null,
29
+ loading: false
30
+ };
31
+
32
+ const mockThemeContext = {
33
+ themeMode: 'light',
34
+ setThemeMode: vi.fn(),
35
+ activeConfig: {}
36
+ };
37
+
38
+ vi.mock('../lib/AuthContext', () => ({
39
+ AuthProvider: ({ children }: any) => <>{children}</>,
40
+ useAuth: () => mockAuthContext
41
+ }));
42
+
43
+ vi.mock('../lib/ThemeContext', () => ({
44
+ ThemeProvider: ({ children }: any) => <>{children}</>,
45
+ useTheme: () => mockThemeContext
46
+ }));
47
+
48
+ vi.mock('firebase/auth', () => ({
49
+ getAuth: vi.fn(() => ({})),
50
+ onAuthStateChanged: vi.fn((auth, cb) => { cb({ uid: 'mock-user-123', email: 'test@example.com', getIdToken: vi.fn(() => Promise.resolve('mock-token')) }); return () => {}; })
51
+ }));
52
+ vi.mock('firebase/firestore', () => ({
53
+ getFirestore: vi.fn(() => ({})),
54
+ collection: vi.fn(),
55
+ doc: vi.fn(),
56
+ setDoc: vi.fn(() => Promise.resolve()),
57
+ addDoc: vi.fn(() => Promise.resolve()),
58
+ updateDoc: vi.fn(() => Promise.resolve()),
59
+ deleteDoc: vi.fn(() => Promise.resolve()),
60
+ query: vi.fn(),
61
+ where: vi.fn(),
62
+ orderBy: vi.fn(),
63
+ limit: vi.fn(),
64
+ getDoc: vi.fn(() => Promise.resolve({ exists: () => true, data: () => ({ role: 'super_admin' }) })),
65
+ getDocs: vi.fn(() => Promise.resolve({ docs: [], forEach: vi.fn() })),
66
+ onSnapshot: vi.fn((...args: any[]) => {
67
+ let cb = args[1];
68
+ if (typeof args[2] === 'function') {
69
+ cb = args[2];
70
+ }
71
+ if (typeof cb === 'function') {
72
+ cb({docs: [], forEach: vi.fn(), data: () => ({}), exists: () => true});
73
+ }
74
+ return () => {};
75
+ })
76
+ }));
77
+ vi.mock('firebase/storage', () => ({
78
+ getStorage: vi.fn(() => ({})),
79
+ ref: vi.fn(),
80
+ listAll: vi.fn(() => Promise.resolve({ items: [], prefixes: [] })),
81
+ getDownloadURL: vi.fn(() => Promise.resolve('mock-url')),
82
+ getMetadata: vi.fn(() => Promise.resolve({ size: 1024, timeCreated: new Date().toISOString() }))
83
+ }));
84
+
85
+ describe('TemplatePopupForm Component', () => {
86
+ it('renders without crashing', async () => {
87
+ // Wrap in standard application providers inside act to process async side effects and prevent warnings
88
+ await act(async () => {
89
+ render(
90
+ <BrowserRouter>
91
+ <AuthProvider>
92
+ <ThemeProvider>
93
+ {/* @ts-ignore */}
94
+ <TemplatePopupForm />
95
+ </ThemeProvider>
96
+ </AuthProvider>
97
+ </BrowserRouter>
98
+ );
99
+ // Wait a tick to flush background state updates
100
+ await new Promise(resolve => setTimeout(resolve, 0));
101
+ });
102
+
103
+ // Check if the document has anything rendered without throwing
104
+ expect(document.body).toBeDefined();
105
+ });
106
+ });