- Updated `AuthenticationController` to return user data instead of token. - Added `ITargetService` and `ITargetRepository` to `Program.cs`. - Enhanced `appsettings.json` with connection timeout and security notes. - Modified `IAuthService` to reflect new authentication response structure. - Implemented connection retry mechanism in `DataAccess.cs`. - Refactored UI components to use MUI styling in `Layout.tsx`, `App.tsx`, and others. - Created new files for target management, including `TargetsController`, `TargetUpdateDto`, and related services. - Added `TargetEdit` modal for editing target details and `LineChartSample` for data visualization. - Updated package dependencies in `package-lock.json` and project file.
200 lines
7.3 KiB
TypeScript
200 lines
7.3 KiB
TypeScript
import { useState } from 'react';
|
|
import { Button, Form, Spinner } from 'react-bootstrap';
|
|
import { AuthResponse, AuthErrorResponse, User, isAuthErrorResponse } from '@/types/auth';
|
|
//import { Helmet, HelmetProvider } from 'react-helmet-async';
|
|
|
|
import utils from '@/ts/utils.ts';
|
|
import ForgotPasswordModal from '@/components/modals/ForgotPasswordModal';
|
|
|
|
type SpinnerState = Record<string, boolean>;
|
|
type FormErrors = Record<string, string>;
|
|
|
|
function Login() {
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [spinners, setSpinnersState] = useState<SpinnerState>({});
|
|
const [formErrors, setFormErrors] = useState<FormErrors>({});
|
|
const [username, setUsername] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const [showForgotPasswordModal, setShowForgotPasswordModal] = useState(false);
|
|
//const [user, setUser] = useState<User | null>(null);
|
|
const [loginError, setLoginError] = useState<boolean>(false);
|
|
const [loginErrorMessage, setLoginErrorMessage] = useState<string>('');
|
|
|
|
//const setSpinners = (newValues: Partial<SpinnerState>) => {
|
|
// setSpinnersState((prevSpinners) => ({
|
|
// ...prevSpinners,
|
|
// ...newValues,
|
|
// }));
|
|
//};
|
|
const setSpinners = (newValues: Partial<SpinnerState>) => {
|
|
setSpinnersState((prevSpinners) => {
|
|
const updatedSpinners: SpinnerState = { ...prevSpinners };
|
|
for (const key in newValues) {
|
|
if (newValues[key] !== undefined) {
|
|
updatedSpinners[key] = newValues[key] as boolean;
|
|
}
|
|
}
|
|
return updatedSpinners;
|
|
});
|
|
};
|
|
|
|
const handleCloseForgotPasswordModal = () => {
|
|
setShowForgotPasswordModal(false);
|
|
};
|
|
|
|
const validateLoginForm = () => {
|
|
setFormErrors({});
|
|
|
|
const errors: FormErrors = {};
|
|
if (!username.trim()) {
|
|
errors.username = 'Username is required';
|
|
//} else if (!/\S+@\S+\.\S+/.test(email)) {
|
|
// errors.email = 'Invalid email address';
|
|
}
|
|
if (!password.trim()) {
|
|
errors.password = 'Password is required';
|
|
}
|
|
|
|
if (Object.keys(errors).length > 0) {
|
|
setFormErrors(errors);
|
|
}
|
|
};
|
|
|
|
const handleLogin = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setIsLoading(true);
|
|
spinners.Login = true;
|
|
setSpinners(spinners);
|
|
|
|
validateLoginForm();
|
|
|
|
if (Object.keys(formErrors).length > 0) return;
|
|
|
|
//setUser(null);
|
|
setLoginError(false);
|
|
setLoginErrorMessage('');
|
|
let loggedInUser: User | null = null;
|
|
let hadLoginError: boolean = false;
|
|
let hadLoginErrorMessage: string = '';
|
|
await utils.webMethod<AuthResponse>({
|
|
methodPage: 'authentication',
|
|
methodName: 'authenticate',
|
|
parameters: { username, password },
|
|
success: (json: AuthResponse) => {
|
|
try {
|
|
loggedInUser = json.user;
|
|
//setUser(loggedInUser);
|
|
}
|
|
catch {
|
|
const errorMsg: string = "Unexpected Error";
|
|
hadLoginError = true;
|
|
hadLoginErrorMessage = errorMsg;
|
|
}
|
|
},
|
|
error: (err: unknown) => {
|
|
let errorMsg: string = "Unexpected Error";
|
|
if (isAuthErrorResponse(err)) {
|
|
if (err && err as AuthErrorResponse) {
|
|
if (err.data) {
|
|
if (err.data.message)
|
|
errorMsg = err.data.message;
|
|
}
|
|
console.error(errorMsg);
|
|
setLoginErrorMessage(errorMsg);
|
|
}
|
|
}
|
|
hadLoginError = true;
|
|
hadLoginErrorMessage = errorMsg;
|
|
}
|
|
});
|
|
|
|
if (hadLoginError) {
|
|
setLoginErrorMessage(hadLoginErrorMessage);
|
|
setLoginError(true);
|
|
setIsLoading(false);
|
|
spinners.Login = false;
|
|
setSpinners(spinners);
|
|
return;
|
|
}
|
|
|
|
if (loggedInUser == null) {
|
|
setLoginError(true);
|
|
setIsLoading(false);
|
|
spinners.Login = false;
|
|
setSpinners(spinners);
|
|
} else {
|
|
await finishUserLogin(loggedInUser);
|
|
}
|
|
};
|
|
|
|
const finishUserLogin = async (loggedInUser: User) => {
|
|
setIsLoading(false);
|
|
spinners.Login = false;
|
|
spinners.LoginWithPasskey = false;
|
|
setSpinners(spinners);
|
|
|
|
utils.localStorage("session_currentUser", loggedInUser);
|
|
|
|
const redirectUrl = utils.sessionStorage("redirect_url");
|
|
if (redirectUrl) {
|
|
utils.sessionStorage("redirect_url", null);
|
|
document.location.href = redirectUrl;
|
|
} else {
|
|
document.location.href = '/home';
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="container">
|
|
<div className="row text-center mt-5">
|
|
<h1>surge365 - React</h1>
|
|
</div>
|
|
<div className="row text-center" style={{ maxWidth: '400px', margin: 'auto' }}>
|
|
<h3 className="form-signin-heading mt-3 mb-1">Please sign in</h3>
|
|
<Form id="frmLogin" onSubmit={handleLogin}>
|
|
{loginError && (
|
|
<Form.Label style={{ color: 'red' }}>{loginErrorMessage ?? "Login error"}</Form.Label>
|
|
)}
|
|
<Form.Group className="mb-3" controlId="txtUsernamel">
|
|
<Form.Label className="visually-hidden">Username</Form.Label>
|
|
<Form.Control
|
|
type="username"
|
|
placeholder="Username"
|
|
value={username}
|
|
onChange={(e) => setUsername(e.target.value)}
|
|
required
|
|
autoFocus
|
|
size="sm"
|
|
/>
|
|
{spinners.Username && <Spinner animation="border" size="sm" />}
|
|
</Form.Group>
|
|
|
|
<Form.Group className="mb-3" controlId="txtPassword">
|
|
<Form.Label className="visually-hidden">Password</Form.Label>
|
|
<Form.Control
|
|
type="password"
|
|
placeholder="Password"
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
required
|
|
size="sm"
|
|
/>
|
|
</Form.Group>
|
|
|
|
<Button className="bg-orange w-100" type="submit" disabled={isLoading}>
|
|
{spinners.Login && <Spinner animation="border" size="sm" className="me-2" />}
|
|
{isLoading && spinners.Login ? 'Signing in...' : 'Sign in'}
|
|
</Button>
|
|
<Button variant="secondary" className="w-100 mt-2" onClick={() => setShowForgotPasswordModal(true)}>
|
|
Forgot Password
|
|
</Button>
|
|
</Form>
|
|
</div>
|
|
|
|
<ForgotPasswordModal show={showForgotPasswordModal} onClose={handleCloseForgotPasswordModal} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default Login;
|