115 lines
3.4 KiB
TypeScript
115 lines
3.4 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { motion } from 'framer-motion';
|
|
import { authService } from '../services/auth';
|
|
import '../styles/Login.css';
|
|
|
|
interface LoginProps {
|
|
onLogin: (did: string, handle: string) => void;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export const Login: React.FC<LoginProps> = ({ onLogin, onClose }) => {
|
|
const [identifier, setIdentifier] = useState('');
|
|
const [password, setPassword] = useState('');
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setError(null);
|
|
setIsLoading(true);
|
|
|
|
try {
|
|
const response = await authService.login(identifier, password);
|
|
onLogin(response.did, response.handle);
|
|
} catch (err) {
|
|
setError('ログインに失敗しました。認証情報を確認してください。');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<motion.div
|
|
className="login-overlay"
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
onClick={onClose}
|
|
>
|
|
<motion.div
|
|
className="login-modal"
|
|
initial={{ scale: 0.9, opacity: 0 }}
|
|
animate={{ scale: 1, opacity: 1 }}
|
|
transition={{ type: "spring", duration: 0.5 }}
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<h2>atprotoログイン</h2>
|
|
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="form-group">
|
|
<label htmlFor="identifier">ハンドル または DID</label>
|
|
<input
|
|
id="identifier"
|
|
type="text"
|
|
value={identifier}
|
|
onChange={(e) => setIdentifier(e.target.value)}
|
|
placeholder="your.handle または did:plc:..."
|
|
required
|
|
disabled={isLoading}
|
|
/>
|
|
</div>
|
|
|
|
<div className="form-group">
|
|
<label htmlFor="password">アプリパスワード</label>
|
|
<input
|
|
id="password"
|
|
type="password"
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
placeholder="アプリパスワード"
|
|
required
|
|
disabled={isLoading}
|
|
/>
|
|
<small>
|
|
メインパスワードではなく、
|
|
<a href="https://bsky.app/settings/app-passwords" target="_blank" rel="noopener noreferrer">
|
|
アプリパスワード
|
|
</a>
|
|
を使用してください
|
|
</small>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="error-message">{error}</div>
|
|
)}
|
|
|
|
<div className="button-group">
|
|
<button
|
|
type="submit"
|
|
className="login-button"
|
|
disabled={isLoading}
|
|
>
|
|
{isLoading ? 'ログイン中...' : 'ログイン'}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="cancel-button"
|
|
onClick={onClose}
|
|
disabled={isLoading}
|
|
>
|
|
キャンセル
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
<div className="login-info">
|
|
<p>
|
|
ai.cardはatprotoアカウントを使用します。
|
|
データはあなたのPDSに保存されます。
|
|
</p>
|
|
</div>
|
|
</motion.div>
|
|
</motion.div>
|
|
);
|
|
}; |