码桶

发现社区成员的开源项目

main
AddSessionModal.jsx5.5 KB
import React, { useState, useEffect } from 'react';
import { User, Key, RefreshCw, LogOut, X } from 'lucide-react';
import { hashPassword } from '../utils/auth.ts';

const AddSessionModal = ({ show, onClose, auth, t, showToast, onSessionAdded }) => {
    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState('');

    useEffect(() => {
        if (!show) return;
        const handleKeyDown = (e) => { if (e.key === 'Escape') onClose(); };
        document.addEventListener('keydown', handleKeyDown);
        return () => document.removeEventListener('keydown', handleKeyDown);
    }, [show, onClose]);

    if (!show) return null;

    const handleClose = () => {
        setError('');
        onClose();
    };

    const handleAdd = async () => {
        if (!username.trim() || !password.trim()) return;
        setLoading(true);
        setError('');
        try {
            const hashedPwd = await hashPassword(password);
            const res = await fetch('/api/login', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ username: username.trim(), password: hashedPwd })
            });
            const data = await res.json();
            if (res.ok && data.token) {
                const newSession = { token: data.token, username: data.username, role: data.role, accounts: data.accounts || [] };
                const existing = (auth.sessions || []).findIndex(s => s.username === newSession.username);
                let newSessions;
                if (existing >= 0) {
                    newSessions = [...auth.sessions];
                    newSessions[existing] = newSession;
                } else {
                    newSessions = [...(auth.sessions || []), newSession];
                }
                onSessionAdded({ ...auth, sessions: newSessions });
                showToast(t('sessionAdded'), 'success');
                setUsername('');
                setPassword('');
                onClose();
            } else {
                setError(data.error || t('loginFailed'));
            }
        } catch (err) {
            setError(t('errorOccurred'));
        }
        setLoading(false);
    };

    return (
        <div className="modal-overlay" style={{ zIndex: 200 }}
            onClick={(e) => { if (e.target === e.currentTarget) handleClose(); }}>
            <div className="glass-card fade-in modal-content" role="dialog" aria-label={t('addSessionTitle')} style={{ width: '100%', maxWidth: '380px', padding: '1.5rem' }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1.5rem' }}>
                    <h3 style={{ fontSize: '1rem', margin: 0 }}>{t('addSessionTitle')}</h3>
                    <button onClick={handleClose} style={{ border: 'none', background: 'transparent', cursor: 'pointer', padding: '4px', display: 'flex' }} aria-label="Close">
                        <X size={18} color="var(--text-muted)" />
                    </button>
                </div>
                <div className="input-group" style={{ marginBottom: '0.75rem' }}>
                    <label style={{ fontSize: '0.8rem', color: 'var(--text-muted)', marginBottom: '0.4rem', display: 'block' }}>{t('usernameLabel')}</label>
                    <div style={{ position: 'relative' }}>
                        <User size={16} style={{ position: 'absolute', left: '12px', top: '50%', transform: 'translateY(-50%)', color: 'var(--text-muted)' }} />
                        <input type="text" placeholder={t('usernamePlaceholder')} value={username}
                            onChange={(e) => setUsername(e.target.value)} style={{ paddingLeft: '38px', width: '100%' }} />
                    </div>
                </div>
                <div className="input-group" style={{ marginBottom: '0.75rem' }}>
                    <label style={{ fontSize: '0.8rem', color: 'var(--text-muted)', marginBottom: '0.4rem', display: 'block' }}>{t('passwordLabel')}</label>
                    <div style={{ position: 'relative' }}>
                        <Key size={16} style={{ position: 'absolute', left: '12px', top: '50%', transform: 'translateY(-50%)', color: 'var(--text-muted)' }} />
                        <input type="password" placeholder={t('passwordPlaceholder')} value={password}
                            onChange={(e) => { setPassword(e.target.value); setError(''); }}
                            onKeyDown={(e) => { if (e.key === 'Enter') handleAdd(); }}
                            style={{ paddingLeft: '38px', width: '100%' }} />
                    </div>
                </div>
                {error && <p style={{ color: 'var(--error)', fontSize: '0.75rem', marginBottom: '0.75rem' }}>{error}</p>}
                <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}>
                    <button className="btn btn-outline" onClick={handleClose}>{t('cancel')}</button>
                    <button className="btn btn-primary" onClick={handleAdd}
                        disabled={loading || !username.trim() || !password.trim()}
                        style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
                        {loading ? <RefreshCw className="spin" size={14} /> : <LogOut size={14} style={{ transform: 'rotate(180deg)' }} />}
                        {t('loginBtn')}
                    </button>
                </div>
            </div>
        </div>
    );
};

export default AddSessionModal;