David Headrick 4180e50c9c Refactor authentication and implement target management
- 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.
2025-02-26 17:42:40 -06:00

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;