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.
- package/dist/FirebaseOS.d.ts +15 -0
- package/dist/firebase-os.cjs.js +5 -20
- package/dist/firebase-os.es.js +95 -90
- package/dist/index.d.ts +3 -0
- package/dist/lib/ConfigContext.d.ts +12 -0
- package/package.json +3 -2
- package/scripts/postinstall.js +86 -15
- package/src/App.css +184 -0
- package/src/App.tsx +214 -0
- package/src/FirebaseOS.tsx +81 -0
- package/src/assets/hero.png +0 -0
- package/src/assets/react.svg +1 -0
- package/src/assets/vite.svg +1 -0
- package/src/components/AdminNotifications.test.tsx +98 -0
- package/src/components/AdminNotifications.tsx +194 -0
- package/src/components/Button.test.tsx +22 -0
- package/src/components/Button.tsx +53 -0
- package/src/components/ConfirmModal.test.tsx +98 -0
- package/src/components/ConfirmModal.tsx +73 -0
- package/src/components/ContactPopup.test.tsx +98 -0
- package/src/components/ContactPopup.tsx +437 -0
- package/src/components/CustomSelect.test.tsx +47 -0
- package/src/components/CustomSelect.tsx +89 -0
- package/src/components/DashboardNav.test.tsx +98 -0
- package/src/components/DashboardNav.tsx +281 -0
- package/src/components/Input.test.tsx +33 -0
- package/src/components/Input.tsx +61 -0
- package/src/components/JsonEditor.tsx +579 -0
- package/src/components/Navbar.test.tsx +98 -0
- package/src/components/Navbar.tsx +563 -0
- package/src/configs/forms/contactForm.config.ts +15 -0
- package/src/configs/forms/index.ts +29 -0
- package/src/configs/forms/pubForm.config.ts +11 -0
- package/src/configs/forms/supportForm.config.ts +14 -0
- package/src/configs/forms/userForm.config.ts +11 -0
- package/src/configs/pages/admin.config.ts +29 -0
- package/src/configs/pages/contact.config.ts +6 -0
- package/src/configs/pages/home.config.ts +18 -0
- package/src/configs/pages/mem.config.ts +2 -0
- package/src/configs/pages/menuOrders.config.ts +11 -0
- package/src/configs/pages/pub.config.ts +11 -0
- package/src/configs/pages/shared.config.ts +29 -0
- package/src/configs/pages/support.config.ts +7 -0
- package/src/configs/pages/tabOrders.config.ts +33 -0
- package/src/configs/pages/user.config.ts +29 -0
- package/src/configs/theme.config.ts +93 -0
- package/src/index.css +403 -0
- package/src/index.ts +22 -0
- package/src/lib/AuthContext.test.tsx +88 -0
- package/src/lib/AuthContext.tsx +191 -0
- package/src/lib/ConfigContext.tsx +45 -0
- package/src/lib/ThemeContext.tsx +233 -0
- package/src/lib/firebase.ts +91 -0
- package/src/main.tsx +22 -0
- package/src/microcomponents/AdminExampleContent.tsx +44 -0
- package/src/microcomponents/PrivateExampleContent.tsx +39 -0
- package/src/microcomponents/Public.tsx +126 -0
- package/src/microcomponents/SharedExampleContent.tsx +53 -0
- package/src/pages/Dashboard.test.tsx +98 -0
- package/src/pages/Dashboard.tsx +60 -0
- package/src/pages/DynamicPage.tsx +237 -0
- package/src/pages/FormsAdmin.test.tsx +98 -0
- package/src/pages/FormsAdmin.tsx +459 -0
- package/src/pages/Home.test.tsx +98 -0
- package/src/pages/Home.tsx +144 -0
- package/src/pages/Login.test.tsx +98 -0
- package/src/pages/Login.tsx +108 -0
- package/src/pages/PagesAdmin.test.tsx +98 -0
- package/src/pages/PagesAdmin.tsx +1022 -0
- package/src/pages/Profile.test.tsx +98 -0
- package/src/pages/Profile.tsx +319 -0
- package/src/pages/Register.test.tsx +98 -0
- package/src/pages/Register.tsx +116 -0
- package/src/pages/Requests.test.tsx +95 -0
- package/src/pages/Requests.tsx +422 -0
- package/src/pages/ResetPassword.test.tsx +98 -0
- package/src/pages/ResetPassword.tsx +92 -0
- package/src/pages/Settings.test.tsx +98 -0
- package/src/pages/Settings.tsx +393 -0
- package/src/pages/Setup.tsx +407 -0
- package/src/pages/StorageAdmin.test.tsx +150 -0
- package/src/pages/StorageAdmin.tsx +769 -0
- package/src/pages/Submissions.test.tsx +95 -0
- package/src/pages/Submissions.tsx +378 -0
- package/src/pages/Templates.test.tsx +98 -0
- package/src/pages/Templates.tsx +103 -0
- package/src/pages/ThemeAdmin.test.tsx +144 -0
- package/src/pages/ThemeAdmin.tsx +1000 -0
- package/src/pages/Users.test.tsx +95 -0
- package/src/pages/Users.tsx +334 -0
- package/src/pages/Verify.test.tsx +98 -0
- package/src/pages/Verify.tsx +95 -0
- package/src/prompts/index.ts +13 -0
- package/src/prompts/pages/publicPage.ts +44 -0
- package/src/prompts/sharedConstants.ts +12 -0
- package/src/prompts/tabs/board/adminboard.ts +32 -0
- package/src/prompts/tabs/board/privateboard.ts +36 -0
- package/src/prompts/tabs/board/publicboard.ts +36 -0
- package/src/prompts/tabs/calendar/admincalendar.ts +32 -0
- package/src/prompts/tabs/calendar/privatecalendar.ts +36 -0
- package/src/prompts/tabs/calendar/publiccalendar.ts +36 -0
- package/src/prompts/tabs/crud/admin.ts +54 -0
- package/src/prompts/tabs/crud/private.ts +55 -0
- package/src/prompts/tabs/crud/shared.ts +53 -0
- package/src/prompts/tabs/table/admintable.ts +32 -0
- package/src/prompts/tabs/table/privatetable.ts +36 -0
- package/src/prompts/tabs/table/publictable.ts +36 -0
- package/src/setupTests.ts +1 -0
- package/src/templates/AdminPageTemplate.tsx +678 -0
- package/src/templates/PrivatePageTemplate.tsx +594 -0
- package/src/templates/PublicPageTemplate.tsx +92 -0
- package/src/templates/SharedPageTemplate.tsx +551 -0
- package/src/templates/TemplateBoard.test.tsx +106 -0
- package/src/templates/TemplateBoard.tsx +642 -0
- package/src/templates/TemplateCalendar.test.tsx +106 -0
- package/src/templates/TemplateCalendar.tsx +848 -0
- package/src/templates/TemplateConfirmation.test.tsx +106 -0
- package/src/templates/TemplateConfirmation.tsx +145 -0
- package/src/templates/TemplateInlineForm.test.tsx +106 -0
- package/src/templates/TemplateInlineForm.tsx +129 -0
- package/src/templates/TemplatePopupForm.test.tsx +106 -0
- package/src/templates/TemplatePopupForm.tsx +174 -0
- package/src/templates/TemplateTable.test.tsx +106 -0
- 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
|
+
});
|