EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
Login.jsx
Go to the documentation of this file.
1// dashboard/src/pages/Login.jsx
2import { useState } from 'react';
3import { Link, useNavigate } from 'react-router-dom';
4import PublicLayout from '../components/Layout/PublicLayout';
5import './Login.css';
6import { apiFetch } from '../lib/api';
7
8function Login() {
9 const [email, setEmail] = useState('');
10 const [password, setPassword] = useState('');
11 const [loading, setLoading] = useState(false);
12 const navigate = useNavigate();
13
14 const handleLogin = async (e) => {
15 e.preventDefault();
16 setLoading(true);
17
18 try {
19 // ✅ Use direct fetch so future requests inherit Authorization logic
20 const res = await apiFetch('/users/login', {
21 method: 'POST',
22 headers: { 'Content-Type': 'application/json' },
23 body: JSON.stringify({ email, password, mfaCode: window._mfaCode || undefined }),
24 });
25
26 const contentType = res.headers.get('content-type') || '';
27 let data;
28 if (contentType.includes('application/json')) {
29 data = await res.json();
30 } else {
31 const text = await res.text();
32 try { data = JSON.parse(text); } catch { data = { error: text }; }
33 }
34
35 if (!res.ok) {
36 alert((data && (data.error || data.message)) || 'Login failed');
37 setLoading(false);
38 return;
39 }
40
41 // ✅ Save JWT token for future API calls
42 if (data.token) {
43 localStorage.setItem('token', data.token);
44 } else {
45 alert('Login succeeded but no token returned.');
46 setLoading(false);
47 return;
48 }
49
50 // Always redirect to dashboard after login
51 navigate('/dashboard');
52 } catch (err) {
53 console.error('Login error:', err);
54 alert('Cannot reach API. Please ensure the backend and database are running.');
55 } finally {
56 setLoading(false);
57 }
58 };
59
60 return (
61 <PublicLayout>
62 <section className="login-hero">
63 <div className="login-hero-container">
64 <div className="login-hero-content">
65 <h1 className="login-hero-title">Welcome Back</h1>
66 <p className="login-hero-subtitle">Sign in to access your dashboard and manage your MSP clients.</p>
67 <form onSubmit={handleLogin} className="login-form">
68 <div className="form-group">
69 <label htmlFor="email">Email</label>
70 <input
71 id="email"
72 name="email"
73 type="email"
74 placeholder="Enter your email"
75 value={email}
76 onChange={(e) => setEmail(e.target.value)}
77 required
78 disabled={loading}
79 />
80 </div>
81 <div className="form-group">
82 <label htmlFor="password">Password</label>
83 <input
84 id="password"
85 name="password"
86 type="password"
87 placeholder="Enter your password"
88 value={password}
89 onChange={(e) => setPassword(e.target.value)}
90 required
91 disabled={loading}
92 />
93 </div>
94 <div className="form-group">
95 <label htmlFor="mfa">MFA Code (if enabled)</label>
96 <input
97 id="mfa"
98 name="mfa"
99 type="text"
100 placeholder="123456"
101 inputMode="numeric"
102 pattern="[0-9]*"
103 disabled={loading}
104 onChange={(e) => (window._mfaCode = e.target.value)}
105 />
106 </div>
107 <button type="submit" className="login-button" disabled={loading}>
108 {loading ? 'Signing In...' : 'Sign In'}
109 </button>
110 </form>
111 <div className="login-footer">
112 <p><Link to="/forgot-password">Forgot your password?</Link></p>
113 <p>
114 Don't have an account? <Link to="/">Contact Sales</Link>
115 </p>
116 </div>
117 </div>
118 <div className="login-hero-image">
119 <div className="dashboard-preview">
120 <img
121 src="/dashboard-preview.png"
122 alt="RMM+PSA Dashboard Preview - Ticket Management Interface"
123 className="preview-screenshot"
124 />
125 </div>
126 </div>
127 </div>
128 </section>
129 </PublicLayout>
130 );
131}
132
133export default Login;