create-firem 1.0.1

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 (33) hide show
  1. package/.github/workflows/auto-update.yml +42 -0
  2. package/.github/workflows/npm-publish.yml +42 -0
  3. package/LICENSE +21 -0
  4. package/agents.md +32 -0
  5. package/bin/index.js +405 -0
  6. package/package.json +32 -0
  7. package/readme.md +90 -0
  8. package/scripts/auto-update-packages.js +110 -0
  9. package/specs/cli.md +83 -0
  10. package/specs/fireabse.md +66 -0
  11. package/specs/render.md +140 -0
  12. package/specs/template.md +170 -0
  13. package/templates/basic/package.json +32 -0
  14. package/templates/basic/src/App.jsx +28 -0
  15. package/templates/basic/src/main.jsx +15 -0
  16. package/templates/common/gitignore +27 -0
  17. package/templates/common/index.html +15 -0
  18. package/templates/common/src/components/AppRouter.jsx +23 -0
  19. package/templates/common/src/config/firebase.ts +30 -0
  20. package/templates/common/src/config/routerConfig.js +6 -0
  21. package/templates/common/src/theme/theme.js +14 -0
  22. package/templates/common/vite.config.js +28 -0
  23. package/templates/dashboard/package.json +32 -0
  24. package/templates/dashboard/src/App.jsx +50 -0
  25. package/templates/dashboard/src/components/Login.jsx +67 -0
  26. package/templates/dashboard/src/context/AuthContext.jsx +43 -0
  27. package/templates/dashboard/src/main.jsx +15 -0
  28. package/templates/dashboard/src/pages/DashboardOverview.jsx +21 -0
  29. package/templates/dashboard/src/pages/Profile.jsx +23 -0
  30. package/templates/landing/package.json +32 -0
  31. package/templates/landing/src/App.jsx +131 -0
  32. package/templates/landing/src/context/AuthContext.jsx +43 -0
  33. package/templates/landing/src/main.jsx +15 -0
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>{{PROJECT_NAME}}</title>
8
+ <!-- Roboto Font for Material UI -->
9
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ <script type="module" src="/src/main.jsx"></script>
14
+ </body>
15
+ </html>
@@ -0,0 +1,23 @@
1
+ import React, { Suspense } from 'react';
2
+ import { BrowserRouter, HashRouter } from 'react-router-dom';
3
+ import { ROUTER_MODE } from '../config/routerConfig';
4
+
5
+ // Common fallback loader for Suspense during lazy chunk retrieval
6
+ const PageLoader = () => (
7
+ <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh', fontFamily: 'sans-serif' }}>
8
+ <h3>Loading...</h3>
9
+ </div>
10
+ );
11
+
12
+ export function AppRouter({ children }) {
13
+ // Select router type dynamically
14
+ const Router = ROUTER_MODE === 'hash' ? HashRouter : BrowserRouter;
15
+
16
+ return (
17
+ <Router>
18
+ <Suspense fallback={<PageLoader />}>
19
+ {children}
20
+ </Suspense>
21
+ </Router>
22
+ );
23
+ }
@@ -0,0 +1,30 @@
1
+ import { initializeApp, FirebaseApp } from 'firebase/app';
2
+ import { getAuth, Auth } from 'firebase/auth';
3
+ import { getFirestore, Firestore } from 'firebase/firestore';
4
+
5
+ interface FirebaseConfig {
6
+ apiKey: string;
7
+ authDomain: string;
8
+ projectId: string;
9
+ storageBucket: string;
10
+ messagingSenderId: string;
11
+ appId: string;
12
+ measurementId?: string;
13
+ }
14
+
15
+ const firebaseConfig: FirebaseConfig = {
16
+ apiKey: "YOUR_API_KEY",
17
+ authDomain: "YOUR_AUTH_DOMAIN",
18
+ projectId: "YOUR_PROJECT_ID",
19
+ storageBucket: "YOUR_STORAGE_BUCKET",
20
+ messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
21
+ appId: "YOUR_APP_ID",
22
+ measurementId: "YOUR_MEASUREMENT_ID"
23
+ };
24
+
25
+ const app: FirebaseApp = initializeApp(firebaseConfig);
26
+
27
+ export const auth: Auth = getAuth(app);
28
+ export const db: Firestore = getFirestore(app);
29
+
30
+ export default app;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Configure the rendering route structure for the application.
3
+ * - 'browser': Standard HTML5 history routing. Requires hosting server redirects (rewrite to index.html).
4
+ * - 'hash': Hash-based routing (#/path). Works on any hosting setup out-of-the-box (zero-config).
5
+ */
6
+ export const ROUTER_MODE = 'browser'; // 'browser' | 'hash'
@@ -0,0 +1,14 @@
1
+ import { createTheme } from '@mui/material/styles';
2
+
3
+ const theme = createTheme({
4
+ palette: {
5
+ primary: {
6
+ main: '#1976d2',
7
+ },
8
+ secondary: {
9
+ main: '#9c27b0',
10
+ },
11
+ },
12
+ });
13
+
14
+ export default theme;
@@ -0,0 +1,28 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ // https://vite.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ build: {
8
+ rollupOptions: {
9
+ output: {
10
+ manualChunks(id) {
11
+ if (id.includes('node_modules')) {
12
+ if (id.includes('firebase')) {
13
+ return 'vendor-firebase';
14
+ }
15
+ if (id.includes('@mui') || id.includes('@emotion')) {
16
+ return 'vendor-mui';
17
+ }
18
+ if (id.includes('react') || id.includes('react-dom') || id.includes('react-router')) {
19
+ return 'vendor-core';
20
+ }
21
+ return 'vendor-helpers';
22
+ }
23
+ }
24
+ }
25
+ },
26
+ chunkSizeWarningLimit: 800,
27
+ }
28
+ })
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@emotion/react": "^11.14.0",
14
+ "@emotion/styled": "^11.14.1",
15
+ "@mui/icons-material": "^9.0.1",
16
+ "@mui/material": "^9.0.1",
17
+ "firebase": "^12.13.0",
18
+ "react": "^19.2.6",
19
+ "react-dom": "^19.2.6",
20
+ "react-router-dom": "^7.15.1"
21
+ },
22
+ "devDependencies": {
23
+ "@types/react": "^19.2.15",
24
+ "@types/react-dom": "^19.2.3",
25
+ "@vitejs/plugin-react": "^6.0.2",
26
+ "eslint": "^8.57.0",
27
+ "eslint-plugin-react": "^7.34.2",
28
+ "eslint-plugin-react-hooks": "^4.6.2",
29
+ "eslint-plugin-react-refresh": "^0.4.7",
30
+ "vite": "^8.0.13"
31
+ }
32
+ }
@@ -0,0 +1,50 @@
1
+ import React, { Suspense } from 'react';
2
+ import { Routes, Route, Navigate } from 'react-router-dom';
3
+ import { AuthProvider, useAuth } from './context/AuthContext';
4
+ import Login from './components/Login';
5
+ import Navigation from './components/Navigation';
6
+ import { Box, Container } from '@mui/material';
7
+ import { AppRouter } from './components/AppRouter';
8
+
9
+ // Modular loading: lazy import the sub-views of the dashboard to keep the initial build lightweight
10
+ const DashboardOverview = React.lazy(() => import('./pages/DashboardOverview'));
11
+ const Profile = React.lazy(() => import('./pages/Profile'));
12
+
13
+ function DashboardLayout() {
14
+ const { logout } = useAuth();
15
+
16
+ return (
17
+ <Box sx={{ display: 'flex', minHeight: '100vh', flexDirection: 'column' }}>
18
+ <Navigation onLogout={logout} isAuthenticated={true} />
19
+ <Box component="main" sx={{ flexGrow: 1, p: 3, pb: 10 }}>
20
+ <Container maxWidth="lg" sx={{ mt: 4 }}>
21
+ <Routes>
22
+ <Route path="/" element={<DashboardOverview />} />
23
+ <Route path="/profile" element={<Profile />} />
24
+ <Route path="*" element={<Navigate to="/" replace />} />
25
+ </Routes>
26
+ </Container>
27
+ </Box>
28
+ </Box>
29
+ );
30
+ }
31
+
32
+ function MainApp() {
33
+ const { currentUser } = useAuth();
34
+
35
+ if (!currentUser) {
36
+ return <Login />;
37
+ }
38
+
39
+ return <DashboardLayout />;
40
+ }
41
+
42
+ export default function App() {
43
+ return (
44
+ <AuthProvider>
45
+ <AppRouter>
46
+ <MainApp />
47
+ </AppRouter>
48
+ </AuthProvider>
49
+ );
50
+ }
@@ -0,0 +1,67 @@
1
+ import React, { useState } from 'react';
2
+ import { useAuth } from '../context/AuthContext';
3
+ import { Box, Button, TextField, Typography, Paper, Container, Alert } from '@mui/material';
4
+
5
+ export default function Login() {
6
+ const [email, setEmail] = useState('');
7
+ const [password, setPassword] = useState('');
8
+ const [error, setError] = useState('');
9
+ const [loading, setLoading] = useState(false);
10
+ const { login } = useAuth();
11
+
12
+ async function handleSubmit(e) {
13
+ e.preventDefault();
14
+ try {
15
+ setError('');
16
+ setLoading(true);
17
+ await login(email, password);
18
+ } catch (err) {
19
+ setError('Failed to log in. Please check your credentials. (Ensure Firebase config is set up correctly in .env)');
20
+ }
21
+ setLoading(false);
22
+ }
23
+
24
+ return (
25
+ <Container maxWidth="xs">
26
+ <Box sx={{ mt: 8, display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
27
+ <Paper elevation={3} sx={{ p: 4, width: '100%' }}>
28
+ <Typography variant="h5" align="center" gutterBottom>
29
+ Sign In
30
+ </Typography>
31
+ {error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
32
+ <Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
33
+ <TextField
34
+ margin="normal"
35
+ required
36
+ fullWidth
37
+ label="Email Address"
38
+ autoComplete="email"
39
+ autoFocus
40
+ value={email}
41
+ onChange={(e) => setEmail(e.target.value)}
42
+ />
43
+ <TextField
44
+ margin="normal"
45
+ required
46
+ fullWidth
47
+ label="Password"
48
+ type="password"
49
+ autoComplete="current-password"
50
+ value={password}
51
+ onChange={(e) => setPassword(e.target.value)}
52
+ />
53
+ <Button
54
+ type="submit"
55
+ fullWidth
56
+ variant="contained"
57
+ disabled={loading}
58
+ sx={{ mt: 3, mb: 2 }}
59
+ >
60
+ Sign In
61
+ </Button>
62
+ </Box>
63
+ </Paper>
64
+ </Box>
65
+ </Container>
66
+ );
67
+ }
@@ -0,0 +1,43 @@
1
+ import React, { createContext, useContext, useEffect, useState } from 'react';
2
+ import { auth } from '../config/firebase';
3
+ import { onAuthStateChanged, signInWithEmailAndPassword, signOut } from 'firebase/auth';
4
+
5
+ const AuthContext = createContext();
6
+
7
+ export function AuthProvider({ children }) {
8
+ const [currentUser, setCurrentUser] = useState(null);
9
+ const [loading, setLoading] = useState(true);
10
+
11
+ function login(email, password) {
12
+ return signInWithEmailAndPassword(auth, email, password);
13
+ }
14
+
15
+ function logout() {
16
+ return signOut(auth);
17
+ }
18
+
19
+ useEffect(() => {
20
+ const unsubscribe = onAuthStateChanged(auth, (user) => {
21
+ setCurrentUser(user);
22
+ setLoading(false);
23
+ });
24
+
25
+ return unsubscribe;
26
+ }, []);
27
+
28
+ const value = {
29
+ currentUser,
30
+ login,
31
+ logout
32
+ };
33
+
34
+ return (
35
+ <AuthContext.Provider value={value}>
36
+ {!loading && children}
37
+ </AuthContext.Provider>
38
+ );
39
+ }
40
+
41
+ export function useAuth() {
42
+ return useContext(AuthContext);
43
+ }
@@ -0,0 +1,15 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import App from './App.jsx'
4
+ import { ThemeProvider } from '@mui/material/styles'
5
+ import CssBaseline from '@mui/material/CssBaseline'
6
+ import theme from './theme/theme'
7
+
8
+ ReactDOM.createRoot(document.getElementById('root')).render(
9
+ <React.StrictMode>
10
+ <ThemeProvider theme={theme}>
11
+ <CssBaseline />
12
+ <App />
13
+ </ThemeProvider>
14
+ </React.StrictMode>,
15
+ )
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { Typography, Paper, Button } from '@mui/material';
3
+ import { useAuth } from '../context/AuthContext';
4
+
5
+ export default function DashboardOverview() {
6
+ const { currentUser, logout } = useAuth();
7
+
8
+ return (
9
+ <Paper sx={{ p: 4 }}>
10
+ <Typography variant="h4" gutterBottom>
11
+ Dashboard Overview
12
+ </Typography>
13
+ <Typography variant="body1" sx={{ mb: 2 }}>
14
+ Welcome back, <strong>{currentUser?.email}</strong>! You are successfully authenticated.
15
+ </Typography>
16
+ <Button variant="contained" color="secondary" onClick={logout}>
17
+ Sign Out
18
+ </Button>
19
+ </Paper>
20
+ );
21
+ }
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import { Typography, Paper, Box } from '@mui/material';
3
+ import { useAuth } from '../context/AuthContext';
4
+
5
+ export default function Profile() {
6
+ const { currentUser } = useAuth();
7
+
8
+ return (
9
+ <Paper sx={{ p: 4 }}>
10
+ <Typography variant="h4" gutterBottom>
11
+ User Profile
12
+ </Typography>
13
+ <Box sx={{ mt: 2 }}>
14
+ <Typography variant="body1">
15
+ <strong>Email:</strong> {currentUser?.email}
16
+ </Typography>
17
+ <Typography variant="body1" sx={{ mt: 1 }}>
18
+ <strong>UID:</strong> {currentUser?.uid}
19
+ </Typography>
20
+ </Box>
21
+ </Paper>
22
+ );
23
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@emotion/react": "^11.14.0",
14
+ "@emotion/styled": "^11.14.1",
15
+ "@mui/icons-material": "^9.0.1",
16
+ "@mui/material": "^9.0.1",
17
+ "firebase": "^12.13.0",
18
+ "react": "^19.2.6",
19
+ "react-dom": "^19.2.6",
20
+ "react-router-dom": "^7.15.1"
21
+ },
22
+ "devDependencies": {
23
+ "@types/react": "^19.2.15",
24
+ "@types/react-dom": "^19.2.3",
25
+ "@vitejs/plugin-react": "^6.0.2",
26
+ "eslint": "^8.57.0",
27
+ "eslint-plugin-react": "^7.34.2",
28
+ "eslint-plugin-react-hooks": "^4.6.2",
29
+ "eslint-plugin-react-refresh": "^0.4.7",
30
+ "vite": "^8.0.13"
31
+ }
32
+ }
@@ -0,0 +1,131 @@
1
+ import React, { useState } from 'react';
2
+ import { Container, Typography, Box, Button, Dialog, DialogTitle, DialogContent, DialogActions, TextField, Alert } from '@mui/material';
3
+ import Navigation from './components/Navigation';
4
+ import { AppRouter } from './components/AppRouter';
5
+ /* AUTH_START */
6
+ import { AuthProvider, useAuth } from './context/AuthContext';
7
+ /* AUTH_END */
8
+
9
+ function LandingContent() {
10
+ /* AUTH_START */
11
+ const { currentUser, logout, login } = useAuth();
12
+ const [openLogin, setOpenLogin] = useState(false);
13
+ const [email, setEmail] = useState('');
14
+ const [password, setPassword] = useState('');
15
+ const [error, setError] = useState('');
16
+ const [loading, setLoading] = useState(false);
17
+
18
+ const handleLoginSubmit = async (e) => {
19
+ e.preventDefault();
20
+ try {
21
+ setError('');
22
+ setLoading(true);
23
+ await login(email, password);
24
+ setOpenLogin(false);
25
+ setEmail('');
26
+ setPassword('');
27
+ } catch (err) {
28
+ setError('Login failed. Please check credentials or Firebase setup.');
29
+ }
30
+ setLoading(false);
31
+ };
32
+ /* AUTH_END */
33
+
34
+ return (
35
+ <Box sx={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
36
+ <Navigation
37
+ /* AUTH_START */
38
+ onLogout={logout}
39
+ isAuthenticated={!!currentUser}
40
+ /* AUTH_END */
41
+ /* AUTH_NO_START */
42
+ // isAuthenticated={false}
43
+ /* AUTH_NO_END */
44
+ />
45
+
46
+ <Container maxWidth="md" sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', py: 4, textAlign: 'center', pb: 10 }}>
47
+ <Box sx={{ my: 4 }}>
48
+ <Typography variant="h2" component="h1" gutterBottom fontWeight="bold" color="primary">
49
+ Welcome to Firem Landing
50
+ </Typography>
51
+ <Typography variant="h5" color="text.secondary" paragraph>
52
+ A high-performance landing template using React, Material UI, and Firebase.
53
+ </Typography>
54
+
55
+ <Box sx={{ mt: 4, display: 'flex', justifyContent: 'center', gap: 2 }}>
56
+ <Button variant="contained" size="large">
57
+ Get Started
58
+ </Button>
59
+
60
+ {/* AUTH_START */}
61
+ {!currentUser ? (
62
+ <Button variant="outlined" size="large" onClick={() => setOpenLogin(true)}>
63
+ Sign In
64
+ </Button>
65
+ ) : (
66
+ <Button variant="outlined" color="secondary" size="large" onClick={logout}>
67
+ Sign Out ({currentUser.email})
68
+ </Button>
69
+ )}
70
+ {/* AUTH_END */}
71
+ </Box>
72
+ </Box>
73
+ </Container>
74
+
75
+ {/* AUTH_START */}
76
+ <Dialog open={openLogin} onClose={() => setOpenLogin(false)} maxWidth="xs" fullWidth>
77
+ <DialogTitle>Sign In</DialogTitle>
78
+ <form onSubmit={handleLoginSubmit}>
79
+ <DialogContent>
80
+ {error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
81
+ <TextField
82
+ margin="dense"
83
+ required
84
+ fullWidth
85
+ label="Email Address"
86
+ type="email"
87
+ value={email}
88
+ onChange={(e) => setEmail(e.target.value)}
89
+ autoFocus
90
+ />
91
+ <TextField
92
+ margin="dense"
93
+ required
94
+ fullWidth
95
+ label="Password"
96
+ type="password"
97
+ value={password}
98
+ onChange={(e) => setPassword(e.target.value)}
99
+ />
100
+ </DialogContent>
101
+ <DialogActions sx={{ px: 3, pb: 3 }}>
102
+ <Button onClick={() => setOpenLogin(false)}>Cancel</Button>
103
+ <Button type="submit" variant="contained" disabled={loading}>Sign In</Button>
104
+ </DialogActions>
105
+ </form>
106
+ </Dialog>
107
+ {/* AUTH_END */}
108
+ </Box>
109
+ );
110
+ }
111
+
112
+ /* AUTH_START */
113
+ export default function App() {
114
+ return (
115
+ <AuthProvider>
116
+ <AppRouter>
117
+ <LandingContent />
118
+ </AppRouter>
119
+ </AuthProvider>
120
+ );
121
+ }
122
+ /* AUTH_END */
123
+ /* AUTH_NO_START */
124
+ // export default function App() {
125
+ // return (
126
+ // <AppRouter>
127
+ // <LandingContent />
128
+ // </AppRouter>
129
+ // );
130
+ // }
131
+ /* AUTH_NO_END */
@@ -0,0 +1,43 @@
1
+ import React, { createContext, useContext, useEffect, useState } from 'react';
2
+ import { auth } from '../config/firebase';
3
+ import { onAuthStateChanged, signInWithEmailAndPassword, signOut } from 'firebase/auth';
4
+
5
+ const AuthContext = createContext();
6
+
7
+ export function AuthProvider({ children }) {
8
+ const [currentUser, setCurrentUser] = useState(null);
9
+ const [loading, setLoading] = useState(true);
10
+
11
+ function login(email, password) {
12
+ return signInWithEmailAndPassword(auth, email, password);
13
+ }
14
+
15
+ function logout() {
16
+ return signOut(auth);
17
+ }
18
+
19
+ useEffect(() => {
20
+ const unsubscribe = onAuthStateChanged(auth, (user) => {
21
+ setCurrentUser(user);
22
+ setLoading(false);
23
+ });
24
+
25
+ return unsubscribe;
26
+ }, []);
27
+
28
+ const value = {
29
+ currentUser,
30
+ login,
31
+ logout
32
+ };
33
+
34
+ return (
35
+ <AuthContext.Provider value={value}>
36
+ {!loading && children}
37
+ </AuthContext.Provider>
38
+ );
39
+ }
40
+
41
+ export function useAuth() {
42
+ return useContext(AuthContext);
43
+ }
@@ -0,0 +1,15 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import App from './App.jsx'
4
+ import { ThemeProvider } from '@mui/material/styles'
5
+ import CssBaseline from '@mui/material/CssBaseline'
6
+ import theme from './theme/theme'
7
+
8
+ ReactDOM.createRoot(document.getElementById('root')).render(
9
+ <React.StrictMode>
10
+ <ThemeProvider theme={theme}>
11
+ <CssBaseline />
12
+ <App />
13
+ </ThemeProvider>
14
+ </React.StrictMode>,
15
+ )