You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
joint_exc/frontend/src/pages/LoginPage.tsx

127 lines
4.4 KiB

/**
* ユーザーログイン機能を提供するページコンポーネント
* ユーザー名とパスワードによる認証フォームを表示し、認証APIと連携
*/
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Container,
Box,
Typography,
TextField,
Button,
Paper,
Alert,
Link,
Grid,
} from '@mui/material';
import { LoginCredentials } from '../types/types';
import { authApi } from '../services/api';
import { GENERAL_ERRORS } from '../constants/errorMessages';
const LoginPage: React.FC = () => {
const navigate = useNavigate();
// ログイン情報の状態管理
const [credentials, setCredentials] = useState<LoginCredentials>({
username: '',
password: '',
});
// エラーメッセージの状態管理
const [error, setError] = useState<string>('');
/**
* フォーム入力値の変更を処理するハンドラー
* 入力フィールドの変更をcredentials状態に反映
*/
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setCredentials(prev => ({
...prev,
[name]: value, // 動的にプロパティ名を使用して状態を更新
}));
};
/**
* ログインフォームの送信を処理するハンドラー
* 認証APIを呼び出し、成功時はトークンを保存してタスク一覧ページに遷移
* 失敗時はエラーメッセージを表示
*/
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); // フォームのデフォルト送信動作を防止
try {
const response = await authApi.login(credentials);
localStorage.setItem('token', response.token); // 認証トークンをローカルストレージに保存
navigate('/tasks'); // タスク一覧ページにリダイレクト
} catch (err) {
setError(err instanceof Error ? err.message : GENERAL_ERRORS.UNEXPECTED_ERROR);
}
};
return (
<Container maxWidth="md" sx={{ mt: 8, height: '100vh'}}>
<Grid container justifyContent="center" alignItems="center" sx={{ height: '100%' }}>
<Grid item xs={12} md={5}>
<Paper elevation={3} sx={{ p: 4 }}>
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<Typography component="h1" variant="h5" gutterBottom>
ToDoアプリ
</Typography>
{/* エラーがある場合のみアラートを表示 */}
{error && (
<Alert severity="error" sx={{ mb: 2, width: '100%' }}>
{error}
</Alert>
)}
{/* ログインフォーム */}
<Box component="form" onSubmit={handleSubmit} sx={{ width: '100%' }}>
{/* ユーザー名入力フィールド */}
<TextField
margin="normal"
required
fullWidth
id="username"
label="ユーザー名"
name="username"
autoComplete="username"
autoFocus
value={credentials.username}
onChange={handleChange}
/>
{/* パスワード入力フィールド */}
<TextField
margin="normal"
required
fullWidth
name="password"
label="パスワード"
type="password"
id="password"
autoComplete="current-password"
value={credentials.password}
onChange={handleChange}
/>
{/* ログインボタン */}
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
</Button>
{/* 新規登録ページへのリンク */}
<Box sx={{ textAlign: 'center' }}>
<Link href="/register" variant="body2">
</Link>
</Box>
</Box>
</Box>
</Paper>
</Grid>
</Grid>
</Container>
);
};
export default LoginPage;