fix(ui): autocomplete und Formulare für Passwort-Felder

PIN, Backup-Export/Import und API-Key entsprechen Chrome-DOM-Empfehlungen.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-30 16:36:38 +02:00
co-authored by Cursor
parent 0ed9ac6941
commit a586fcbfba
3 changed files with 108 additions and 84 deletions
+4
View File
@@ -273,6 +273,7 @@ export default function AuthOnboarding({ onAuthenticated, onOpenDemo }: AuthOnbo
</label> </label>
<input <input
type="password" type="password"
name="new-pin"
inputMode="numeric" inputMode="numeric"
pattern="[0-9]*" pattern="[0-9]*"
maxLength={8} maxLength={8}
@@ -282,6 +283,7 @@ export default function AuthOnboarding({ onAuthenticated, onOpenDemo }: AuthOnbo
onChange={(e) => setPinInput(e.target.value.replace(/\D/g, ''))} onChange={(e) => setPinInput(e.target.value.replace(/\D/g, ''))}
disabled={loading} disabled={loading}
required required
autoComplete="new-password"
style={{ width: '100%', padding: '12px', boxSizing: 'border-box' }} style={{ width: '100%', padding: '12px', boxSizing: 'border-box' }}
/> />
</div> </div>
@@ -322,6 +324,7 @@ export default function AuthOnboarding({ onAuthenticated, onOpenDemo }: AuthOnbo
<div className="input-group"> <div className="input-group">
<input <input
type="password" type="password"
name="pin"
inputMode="numeric" inputMode="numeric"
pattern="[0-9]*" pattern="[0-9]*"
maxLength={8} maxLength={8}
@@ -331,6 +334,7 @@ export default function AuthOnboarding({ onAuthenticated, onOpenDemo }: AuthOnbo
onChange={(e) => setPinLoginInput(e.target.value.replace(/\D/g, ''))} onChange={(e) => setPinLoginInput(e.target.value.replace(/\D/g, ''))}
disabled={loading} disabled={loading}
required required
autoComplete="current-password"
style={{ width: '100%', padding: '12px', boxSizing: 'border-box' }} style={{ width: '100%', padding: '12px', boxSizing: 'border-box' }}
/> />
</div> </div>
+102 -84
View File
@@ -58,6 +58,16 @@ export default function LogbookBackupPanel({ logbookId, onRestored }: LogbookBac
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState<string | null>(null) const [success, setSuccess] = useState<string | null>(null)
const handleExportSubmit = async (e: React.FormEvent) => {
e.preventDefault()
await handleExport()
}
const handleImportSubmit = async (e: React.FormEvent) => {
e.preventDefault()
await handleRestore()
}
const handleExport = async () => { const handleExport = async () => {
setError(null) setError(null)
setSuccess(null) setSuccess(null)
@@ -209,40 +219,45 @@ export default function LogbookBackupPanel({ logbookId, onRestored }: LogbookBac
</h4> </h4>
<p className="text-muted backup-section-desc">{t('settings.backup_export_desc')}</p> <p className="text-muted backup-section-desc">{t('settings.backup_export_desc')}</p>
<div className="input-group"> <form onSubmit={handleExportSubmit} className="backup-export-form">
<label htmlFor="backup-export-passphrase">{t('settings.backup_passphrase')}</label> <div className="input-group">
<input <label htmlFor="backup-export-passphrase">{t('settings.backup_passphrase')}</label>
id="backup-export-passphrase" <input
type="password" id="backup-export-passphrase"
className="input-text" name="backup-export-passphrase"
value={exportPassphrase} type="password"
onChange={(e) => setExportPassphrase(e.target.value)} className="input-text"
placeholder={t('settings.backup_passphrase_placeholder')} value={exportPassphrase}
autoComplete="new-password" onChange={(e) => setExportPassphrase(e.target.value)}
disabled={exporting} placeholder={t('settings.backup_passphrase_placeholder')}
/> autoComplete="new-password"
</div> disabled={exporting}
<div className="input-group"> required
<label htmlFor="backup-export-confirm">{t('settings.backup_passphrase_confirm')}</label> />
<input </div>
id="backup-export-confirm" <div className="input-group">
type="password" <label htmlFor="backup-export-confirm">{t('settings.backup_passphrase_confirm')}</label>
className="input-text" <input
value={exportConfirm} id="backup-export-confirm"
onChange={(e) => setExportConfirm(e.target.value)} name="backup-export-confirm"
autoComplete="new-password" type="password"
disabled={exporting} className="input-text"
/> value={exportConfirm}
</div> onChange={(e) => setExportConfirm(e.target.value)}
<button autoComplete="new-password"
type="button" disabled={exporting}
className="btn primary" required
onClick={handleExport} />
disabled={exporting || !exportPassphrase || !exportConfirm} </div>
> <button
<Download size={16} /> type="submit"
{exporting ? t('settings.backup_exporting') : t('settings.backup_export_btn')} className="btn primary"
</button> disabled={exporting || !exportPassphrase || !exportConfirm}
>
<Download size={16} />
{exporting ? t('settings.backup_exporting') : t('settings.backup_export_btn')}
</button>
</form>
</section> </section>
<section className="backup-section backup-section--import" aria-labelledby="backup-import-heading"> <section className="backup-section backup-section--import" aria-labelledby="backup-import-heading">
@@ -252,58 +267,61 @@ export default function LogbookBackupPanel({ logbookId, onRestored }: LogbookBac
</h4> </h4>
<p className="text-muted backup-section-desc">{t('settings.backup_restore_desc')}</p> <p className="text-muted backup-section-desc">{t('settings.backup_restore_desc')}</p>
<div className="input-group"> <form onSubmit={handleImportSubmit} className="backup-import-form">
<label htmlFor="backup-import-file">{t('settings.backup_file_label')}</label> <div className="input-group">
<input <label htmlFor="backup-import-file">{t('settings.backup_file_label')}</label>
id="backup-import-file" <input
ref={fileInputRef} id="backup-import-file"
type="file" ref={fileInputRef}
accept=".daagbok.json,application/json" type="file"
className="input-text" accept=".daagbok.json,application/json"
onChange={handleFileChange} className="input-text"
disabled={importing} onChange={handleFileChange}
/> disabled={importing}
</div> />
</div>
{importFile && ( {importFile && (
<> <>
<div className="input-group"> <div className="input-group">
<label htmlFor="backup-import-passphrase">{t('settings.backup_passphrase')}</label> <label htmlFor="backup-import-passphrase">{t('settings.backup_passphrase')}</label>
<input <input
id="backup-import-passphrase" id="backup-import-passphrase"
type="password" name="backup-import-passphrase"
className="input-text" type="password"
value={importPassphrase} className="input-text"
onChange={(e) => { value={importPassphrase}
setImportPassphrase(e.target.value) onChange={(e) => {
setImportPreview(null) setImportPassphrase(e.target.value)
}} setImportPreview(null)
autoComplete="current-password" }}
disabled={importing} autoComplete="current-password"
/> disabled={importing}
</div> required
/>
</div>
<div className="backup-actions-row"> <div className="backup-actions-row">
<button <button
type="button" type="button"
className="btn secondary" className="btn secondary"
onClick={handlePreviewImport} onClick={handlePreviewImport}
disabled={previewing || importing || !importPassphrase} disabled={previewing || importing || !importPassphrase}
> >
{previewing ? t('settings.backup_previewing') : t('settings.backup_preview_btn')} {previewing ? t('settings.backup_previewing') : t('settings.backup_preview_btn')}
</button> </button>
<button <button
type="button" type="submit"
className="btn primary" className="btn primary"
onClick={() => handleRestore()} disabled={importing || !importPassphrase}
disabled={importing || !importPassphrase} >
> <Upload size={16} />
<Upload size={16} /> {importing ? t('settings.backup_restoring') : t('settings.backup_restore_btn')}
{importing ? t('settings.backup_restoring') : t('settings.backup_restore_btn')} </button>
</button> </div>
</div> </>
</> )}
)} </form>
{importPreview && ( {importPreview && (
<div className="backup-preview glass"> <div className="backup-preview glass">
+2
View File
@@ -293,12 +293,14 @@ export default function SettingsForm({ logbookId, onLogbookRestored }: SettingsF
</label> </label>
<input <input
id="owm-api-key" id="owm-api-key"
name="owm-api-key"
type="password" type="password"
className="input-text" className="input-text"
placeholder="e.g. 8b6a7f...d8" placeholder="e.g. 8b6a7f...d8"
value={apiKey} value={apiKey}
onChange={(e) => setApiKey(e.target.value)} onChange={(e) => setApiKey(e.target.value)}
disabled={saving} disabled={saving}
autoComplete="off"
/> />
</div> </div>
</div> </div>