diff --git a/client/src/pages/Login.jsx b/client/src/pages/Login.jsx index e8f8078..27ceb2c 100644 --- a/client/src/pages/Login.jsx +++ b/client/src/pages/Login.jsx @@ -1,8 +1,9 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { useAuth } from '../contexts/AuthContext' import { useLanguage } from '../i18n/LanguageContext' -import { Megaphone, Lock, Mail, AlertCircle } from 'lucide-react' +import { Megaphone, Lock, Mail, AlertCircle, User, CheckCircle } from 'lucide-react' +import api from '../utils/api' export default function Login() { const navigate = useNavigate() @@ -13,6 +14,16 @@ export default function Login() { const [loading, setLoading] = useState(false) const [error, setError] = useState('') + const [needsSetup, setNeedsSetup] = useState(null) + const [setupName, setSetupName] = useState('') + const [setupEmail, setSetupEmail] = useState('') + const [setupPassword, setSetupPassword] = useState('') + const [setupDone, setSetupDone] = useState(false) + + useEffect(() => { + api.get('/setup/status').then(data => setNeedsSetup(data.needsSetup)).catch(() => setNeedsSetup(false)) + }, []) + const handleSubmit = async (e) => { e.preventDefault() setError('') @@ -28,6 +39,31 @@ export default function Login() { } } + const handleSetup = async (e) => { + e.preventDefault() + setError('') + setLoading(true) + + try { + await api.post('/setup', { name: setupName, email: setupEmail, password: setupPassword }) + setSetupDone(true) + setNeedsSetup(false) + setEmail(setupEmail) + } catch (err) { + setError(err.message || 'Setup failed') + } finally { + setLoading(false) + } + } + + if (needsSetup === null) { + return ( +
+
+
+ ) + } + return (
@@ -36,82 +72,175 @@ export default function Login() {
-

{t('login.title')}

-

{t('login.subtitle')}

+

+ {needsSetup ? 'Initial Setup' : t('login.title')} +

+

+ {needsSetup ? 'Create your superadmin account to get started' : t('login.subtitle')} +

- {/* Login Card */} + {/* Success Message */} + {setupDone && ( +
+ +

Account created. You can now log in.

+
+ )} + + {/* Card */}
-
- {/* Email */} -
- -
- - setEmail(e.target.value)} - dir="auto" - className="w-full pl-11 pr-4 py-3 bg-slate-900/50 border border-slate-700 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all" - placeholder="user@company.com" - required - autoFocus - /> + {needsSetup ? ( + + {/* Name */} +
+ +
+ + setSetupName(e.target.value)} + className="w-full pl-11 pr-4 py-3 bg-slate-900/50 border border-slate-700 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all" + placeholder="Your name" + required + autoFocus + /> +
-
- {/* Password */} -
- -
- - setPassword(e.target.value)} - className="w-full pl-11 pr-4 py-3 bg-slate-900/50 border border-slate-700 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all" - placeholder="••••••••" - required - /> + {/* Email */} +
+ +
+ + setSetupEmail(e.target.value)} + dir="auto" + className="w-full pl-11 pr-4 py-3 bg-slate-900/50 border border-slate-700 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all" + placeholder="admin@company.com" + required + /> +
-
- {/* Error Message */} - {error && ( -
- -

{error}

+ {/* Password */} +
+ +
+ + setSetupPassword(e.target.value)} + className="w-full pl-11 pr-4 py-3 bg-slate-900/50 border border-slate-700 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all" + placeholder="Choose a strong password" + required + minLength={6} + /> +
- )} - {/* Submit Button */} - - + + {/* Submit */} + + + ) : ( +
+ {/* Email */} +
+ +
+ + setEmail(e.target.value)} + dir="auto" + className="w-full pl-11 pr-4 py-3 bg-slate-900/50 border border-slate-700 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all" + placeholder="user@company.com" + required + autoFocus + /> +
+
+ + {/* Password */} +
+ +
+ + setPassword(e.target.value)} + className="w-full pl-11 pr-4 py-3 bg-slate-900/50 border border-slate-700 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all" + placeholder="••••••••" + required + /> +
+
+ + {/* Error */} + {error && ( +
+ +

{error}

+
+ )} + + {/* Submit */} + +
+ )} {/* Footer */} -
-

- {t('login.forgotPassword')} -

-
+ {!needsSetup && ( +
+

+ {t('login.forgotPassword')} +

+
+ )}
diff --git a/server/server.js b/server/server.js index c9b3832..8c68801 100644 --- a/server/server.js +++ b/server/server.js @@ -468,6 +468,32 @@ async function getRecordName(table, id) { // Clear name cache periodically (every 60s) setInterval(() => { Object.keys(_nameCache).forEach(k => delete _nameCache[k]); }, 60000); +// ─── SETUP ROUTES ─────────────────────────────────────────────── + +app.get('/api/setup/status', (req, res) => { + const count = authDb.prepare('SELECT COUNT(*) as cnt FROM auth_credentials').get().cnt; + res.json({ needsSetup: count === 0 }); +}); + +app.post('/api/setup', async (req, res) => { + const count = authDb.prepare('SELECT COUNT(*) as cnt FROM auth_credentials').get().cnt; + if (count > 0) return res.status(403).json({ error: 'Setup already completed' }); + + const { name, email, password } = req.body; + if (!name || !email || !password) return res.status(400).json({ error: 'Name, email, and password are required' }); + + try { + const created = await nocodb.create('Users', { name, email, role: 'superadmin' }); + const passwordHash = await bcrypt.hash(password, 10); + authDb.prepare('INSERT INTO auth_credentials (email, password_hash, nocodb_user_id) VALUES (?, ?, ?)').run(email, passwordHash, created.Id); + console.log(`[SETUP] Superadmin created: ${email} (NocoDB Id: ${created.Id})`); + res.status(201).json({ message: 'Superadmin account created. You can now log in.' }); + } catch (err) { + console.error('Setup error:', err); + res.status(500).json({ error: 'Failed to create superadmin account' }); + } +}); + // ─── AUTH ROUTES ──────────────────────────────────────────────── app.post('/api/auth/login', async (req, res) => {