86 lines
3.1 KiB
TypeScript
86 lines
3.1 KiB
TypeScript
import { useEffect, useRef, useState, type SyntheticEvent } from 'react'
|
||
import { login } from '../api/connections'
|
||
import type { LoginRequest } from '../types/connection'
|
||
import './ConnectModal.scss'
|
||
|
||
interface Props {
|
||
serviceType: string
|
||
label: string
|
||
onClose: () => void
|
||
onSuccess: () => void
|
||
}
|
||
|
||
export function ConnectModal({ serviceType, label, onClose, onSuccess }: Readonly<Props>) {
|
||
const [appUrl, setAppUrl] = useState('')
|
||
const [username, setUsername] = useState('')
|
||
const [password, setPassword] = useState('')
|
||
const [loading, setLoading] = useState(false)
|
||
const [error, setError] = useState<string | null>(null)
|
||
const firstInputRef = useRef<HTMLInputElement>(null)
|
||
|
||
const dialogRef = useRef<HTMLDialogElement>(null)
|
||
|
||
useEffect(() => {
|
||
const dialog = dialogRef.current
|
||
if (!dialog) return
|
||
|
||
dialog.showModal()
|
||
firstInputRef.current?.focus()
|
||
|
||
const handleCancel = (e: Event) => {
|
||
e.preventDefault()
|
||
onClose()
|
||
}
|
||
dialog.addEventListener('cancel', handleCancel)
|
||
return () => dialog.removeEventListener('cancel', handleCancel)
|
||
}, [onClose])
|
||
|
||
const handleSubmit = async (e: SyntheticEvent<HTMLFormElement>) => {
|
||
e.preventDefault()
|
||
setError(null)
|
||
setLoading(true)
|
||
try {
|
||
const req: LoginRequest = { appUrl, serviceType, username, password, stayLoggedIn: true }
|
||
await login(req)
|
||
onSuccess()
|
||
} catch (err) {
|
||
setError(err instanceof Error ? err.message : 'Login failed')
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<dialog className="modal" ref={dialogRef}>
|
||
<div className="modal__header">
|
||
<h2 className="modal__title" id="modal-title">Connect to {label}</h2>
|
||
<button className="modal__close" onClick={onClose} aria-label="Close">×</button>
|
||
</div>
|
||
<form className="modal__form" onSubmit={handleSubmit}>
|
||
<div className="modal__field">
|
||
<label className="modal__label" htmlFor="appUrl">App URL</label>
|
||
<input id="appUrl" ref={firstInputRef} className="modal__input" type="url"
|
||
placeholder="https://homebox.example.com"
|
||
value={appUrl} onChange={e => setAppUrl(e.target.value)} required />
|
||
</div>
|
||
<div className="modal__field">
|
||
<label className="modal__label" htmlFor="username">Username</label>
|
||
<input id="username" className="modal__input" type="text"
|
||
autoComplete="username"
|
||
value={username} onChange={e => setUsername(e.target.value)} required />
|
||
</div>
|
||
<div className="modal__field">
|
||
<label className="modal__label" htmlFor="password">Password</label>
|
||
<input id="password" className="modal__input" type="password"
|
||
autoComplete="current-password"
|
||
value={password} onChange={e => setPassword(e.target.value)} required />
|
||
</div>
|
||
{error && <p className="modal__error">{error}</p>}
|
||
<button className="modal__submit" type="submit" disabled={loading}>
|
||
{loading ? 'Connecting…' : 'Connect'}
|
||
</button>
|
||
</form>
|
||
</dialog>
|
||
)
|
||
}
|