Allow fixing an account's credentials from a failed test
An imported account with a wrong password could only be deleted and re-added, which loses its folder mapping and its migration journal. Clicking either FAIL badge now opens a dialog for both logins and both passwords. Passwords are never sent to the browser, so the password fields start empty and an empty field keeps the stored ciphertext — one side can be corrected without retyping the other. Saving resets both test verdicts to unknown: they described the previous credentials, and the run gate requires a passing test on both sides, so the account cannot start on an unverified password. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import { useEffect, useState, type FormEvent } from 'react'
|
||||
import { Modal } from './Modal'
|
||||
import type { Account } from '../api'
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
busy: boolean
|
||||
account: Account | null
|
||||
onClose: () => void
|
||||
onSubmit: (body: { src_login: string; src_pass: string; dst_login: string; dst_pass: string }) => void
|
||||
}
|
||||
|
||||
// Fixes the credentials of an account that failed its connection test — usually
|
||||
// a wrong password that came in through a CSV import. Passwords are never sent
|
||||
// back to the browser, so the fields start empty and an empty field means
|
||||
// "keep the stored password".
|
||||
export function AccountCredentialsModal({ open, busy, account, onClose, onSubmit }: Props) {
|
||||
const [srcLogin, setSrcLogin] = useState('')
|
||||
const [dstLogin, setDstLogin] = useState('')
|
||||
const [srcPass, setSrcPass] = useState('')
|
||||
const [dstPass, setDstPass] = useState('')
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || !account) return
|
||||
setSrcLogin(account.src_login)
|
||||
setDstLogin(account.dst_login)
|
||||
setSrcPass('')
|
||||
setDstPass('')
|
||||
setError(null)
|
||||
}, [open, account])
|
||||
|
||||
function submit(e: FormEvent) {
|
||||
e.preventDefault()
|
||||
if (srcLogin.trim() === '' || dstLogin.trim() === '') {
|
||||
setError('Both logins are required')
|
||||
return
|
||||
}
|
||||
setError(null)
|
||||
onSubmit({
|
||||
src_login: srcLogin.trim(),
|
||||
src_pass: srcPass,
|
||||
dst_login: dstLogin.trim(),
|
||||
dst_pass: dstPass,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={open} title={account ? `Edit credentials — ${account.src_login}` : 'Edit credentials'} onClose={onClose}>
|
||||
<form onSubmit={submit}>
|
||||
<p className="map-hint">
|
||||
Leave a password field empty to keep the stored one. Saving resets both connection tests, so re-run{' '}
|
||||
<strong>Test connections</strong> afterwards.
|
||||
</p>
|
||||
<div className="field-row">
|
||||
<div className="field">
|
||||
<label htmlFor="edit_src_login">Source login</label>
|
||||
<input
|
||||
id="edit_src_login"
|
||||
data-modal-autofocus
|
||||
value={srcLogin}
|
||||
onChange={(e) => setSrcLogin(e.target.value)}
|
||||
disabled={busy}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="edit_src_pass">Source password</label>
|
||||
<input
|
||||
id="edit_src_pass"
|
||||
type="password"
|
||||
value={srcPass}
|
||||
onChange={(e) => setSrcPass(e.target.value)}
|
||||
placeholder="unchanged"
|
||||
autoComplete="new-password"
|
||||
disabled={busy}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field-row">
|
||||
<div className="field">
|
||||
<label htmlFor="edit_dst_login">Destination login</label>
|
||||
<input
|
||||
id="edit_dst_login"
|
||||
value={dstLogin}
|
||||
onChange={(e) => setDstLogin(e.target.value)}
|
||||
disabled={busy}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="edit_dst_pass">Destination password</label>
|
||||
<input
|
||||
id="edit_dst_pass"
|
||||
type="password"
|
||||
value={dstPass}
|
||||
onChange={(e) => setDstPass(e.target.value)}
|
||||
placeholder="unchanged"
|
||||
autoComplete="new-password"
|
||||
disabled={busy}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{error && <div className="error-banner">{error}</div>}
|
||||
<div className="modal-actions">
|
||||
<button type="button" className="btn" onClick={onClose} disabled={busy}>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="btn btn-primary" disabled={busy}>
|
||||
{busy ? 'Saving…' : 'Save credentials'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user