feat: refactor authentication forms to use <form> elements for better submission handling

This commit is contained in:
shuaiplus
2026-03-15 18:26:36 +08:00
parent 722d3db0e9
commit 171f3c5d71
4 changed files with 172 additions and 134 deletions
+108 -87
View File
@@ -64,22 +64,29 @@ export default function AuthViews(props: AuthViewsProps) {
return (
<div className="auth-page">
<StandalonePageFrame title={t('txt_unlock_vault')}>
<p className="muted standalone-muted">{props.emailForLock}</p>
<PasswordField
label={t('txt_master_password')}
value={props.unlockPassword}
autoFocus
onInput={props.onChangeUnlock}
/>
<button type="button" className="btn btn-primary full" onClick={props.onSubmitUnlock}>
<Unlock size={16} className="btn-icon" />
{t('txt_unlock')}
</button>
<div className="or">{t('txt_or')}</div>
<button type="button" className="btn btn-secondary full" onClick={props.onLogout}>
<LogOut size={16} className="btn-icon" />
{t('txt_log_out')}
</button>
<form
onSubmit={(e) => {
e.preventDefault();
props.onSubmitUnlock();
}}
>
<p className="muted standalone-muted">{props.emailForLock}</p>
<PasswordField
label={t('txt_master_password')}
value={props.unlockPassword}
autoFocus
onInput={props.onChangeUnlock}
/>
<button type="submit" className="btn btn-primary full">
<Unlock size={16} className="btn-icon" />
{t('txt_unlock')}
</button>
<div className="or">{t('txt_or')}</div>
<button type="button" className="btn btn-secondary full" onClick={props.onLogout}>
<LogOut size={16} className="btn-icon" />
{t('txt_log_out')}
</button>
</form>
</StandalonePageFrame>
</div>
);
@@ -89,56 +96,63 @@ export default function AuthViews(props: AuthViewsProps) {
return (
<div className="auth-page">
<StandalonePageFrame title={t('txt_create_account')}>
<label className="field">
<span>{t('txt_name')}</span>
<input
className="input"
value={props.registerValues.name}
onInput={(e) =>
props.onChangeRegister({ ...props.registerValues, name: (e.currentTarget as HTMLInputElement).value })
}
<form
onSubmit={(e) => {
e.preventDefault();
props.onSubmitRegister();
}}
>
<label className="field">
<span>{t('txt_name')}</span>
<input
className="input"
value={props.registerValues.name}
onInput={(e) =>
props.onChangeRegister({ ...props.registerValues, name: (e.currentTarget as HTMLInputElement).value })
}
/>
</label>
<label className="field">
<span>{t('txt_email')}</span>
<input
className="input"
type="email"
value={props.registerValues.email}
onInput={(e) =>
props.onChangeRegister({ ...props.registerValues, email: (e.currentTarget as HTMLInputElement).value })
}
/>
</label>
<PasswordField
label={t('txt_master_password')}
value={props.registerValues.password}
onInput={(v) => props.onChangeRegister({ ...props.registerValues, password: v })}
/>
</label>
<label className="field">
<span>{t('txt_email')}</span>
<input
className="input"
type="email"
value={props.registerValues.email}
onInput={(e) =>
props.onChangeRegister({ ...props.registerValues, email: (e.currentTarget as HTMLInputElement).value })
}
<PasswordField
label={t('txt_confirm_master_password')}
value={props.registerValues.password2}
onInput={(v) => props.onChangeRegister({ ...props.registerValues, password2: v })}
/>
</label>
<PasswordField
label={t('txt_master_password')}
value={props.registerValues.password}
onInput={(v) => props.onChangeRegister({ ...props.registerValues, password: v })}
/>
<PasswordField
label={t('txt_confirm_master_password')}
value={props.registerValues.password2}
onInput={(v) => props.onChangeRegister({ ...props.registerValues, password2: v })}
/>
<label className="field">
<span>{t('txt_invite_code_optional')}</span>
<input
className="input"
value={props.registerValues.inviteCode}
onInput={(e) =>
props.onChangeRegister({ ...props.registerValues, inviteCode: (e.currentTarget as HTMLInputElement).value })
}
/>
</label>
<button type="button" className="btn btn-primary full" onClick={props.onSubmitRegister}>
<UserPlus size={16} className="btn-icon" />
{t('txt_create_account')}
</button>
<div className="or">{t('txt_or')}</div>
<button type="button" className="btn btn-secondary full" onClick={props.onGotoLogin}>
<ArrowLeft size={16} className="btn-icon" />
{t('txt_back_to_login')}
</button>
<label className="field">
<span>{t('txt_invite_code_optional')}</span>
<input
className="input"
value={props.registerValues.inviteCode}
onInput={(e) =>
props.onChangeRegister({ ...props.registerValues, inviteCode: (e.currentTarget as HTMLInputElement).value })
}
/>
</label>
<button type="submit" className="btn btn-primary full">
<UserPlus size={16} className="btn-icon" />
{t('txt_create_account')}
</button>
<div className="or">{t('txt_or')}</div>
<button type="button" className="btn btn-secondary full" onClick={props.onGotoLogin}>
<ArrowLeft size={16} className="btn-icon" />
{t('txt_back_to_login')}
</button>
</form>
</StandalonePageFrame>
</div>
);
@@ -147,30 +161,37 @@ export default function AuthViews(props: AuthViewsProps) {
return (
<div className="auth-page">
<StandalonePageFrame title={t('txt_log_in')}>
<label className="field">
<span>{t('txt_email')}</span>
<input
className="input"
type="email"
value={props.loginValues.email}
onInput={(e) => props.onChangeLogin({ ...props.loginValues, email: (e.currentTarget as HTMLInputElement).value })}
<form
onSubmit={(e) => {
e.preventDefault();
props.onSubmitLogin();
}}
>
<label className="field">
<span>{t('txt_email')}</span>
<input
className="input"
type="email"
value={props.loginValues.email}
onInput={(e) => props.onChangeLogin({ ...props.loginValues, email: (e.currentTarget as HTMLInputElement).value })}
/>
</label>
<PasswordField
label={t('txt_master_password')}
value={props.loginValues.password}
onInput={(v) => props.onChangeLogin({ ...props.loginValues, password: v })}
autoFocus
/>
</label>
<PasswordField
label={t('txt_master_password')}
value={props.loginValues.password}
onInput={(v) => props.onChangeLogin({ ...props.loginValues, password: v })}
autoFocus
/>
<button type="button" className="btn btn-primary full" onClick={props.onSubmitLogin}>
<LogIn size={16} className="btn-icon" />
{t('txt_log_in')}
</button>
<div className="or">{t('txt_or')}</div>
<button type="button" className="btn btn-secondary full" onClick={props.onGotoRegister}>
<UserPlus size={16} className="btn-icon" />
{t('txt_create_account')}
</button>
<button type="submit" className="btn btn-primary full">
<LogIn size={16} className="btn-icon" />
{t('txt_log_in')}
</button>
<div className="or">{t('txt_or')}</div>
<button type="button" className="btn btn-secondary full" onClick={props.onGotoRegister}>
<UserPlus size={16} className="btn-icon" />
{t('txt_create_account')}
</button>
</form>
</StandalonePageFrame>
</div>
);
+9 -4
View File
@@ -20,14 +20,19 @@ export default function ConfirmDialog(props: ConfirmDialogProps) {
if (!props.open) return null;
return (
<div className="dialog-mask">
<div className="dialog-card">
<form
className="dialog-card"
onSubmit={(e) => {
e.preventDefault();
props.onConfirm();
}}
>
<h3 className="dialog-title">{props.title}</h3>
<div className="dialog-message">{props.message}</div>
{props.children}
<button
type="button"
type="submit"
className={`btn ${props.danger ? 'btn-danger' : 'btn-primary'} dialog-btn`}
onClick={props.onConfirm}
>
<Check size={14} className="btn-icon" />
{props.confirmText || t('txt_yes')}
@@ -37,7 +42,7 @@ export default function ConfirmDialog(props: ConfirmDialogProps) {
{props.cancelText || t('txt_no')}
</button>
{props.afterActions}
</div>
</form>
</div>
);
}
+8 -3
View File
@@ -92,7 +92,12 @@ export default function PublicSendPage(props: PublicSendPageProps) {
{loading && <p className="muted">{t('txt_loading')}</p>}
{!loading && needPassword && (
<>
<form
onSubmit={(e) => {
e.preventDefault();
void loadSend(password);
}}
>
<label className="field">
<span>{t('txt_password')}</span>
<div className="password-wrap">
@@ -104,10 +109,10 @@ export default function PublicSendPage(props: PublicSendPageProps) {
/>
</div>
</label>
<button type="button" className="btn btn-primary full" disabled={busy} onClick={() => void loadSend(password)}>
<button type="submit" className="btn btn-primary full" disabled={busy}>
<Lock size={14} className="btn-icon" /> {t('txt_unlock_send')}
</button>
</>
</form>
)}
{!loading && sendData && (
+47 -40
View File
@@ -16,52 +16,59 @@ export default function RecoverTwoFactorPage(props: RecoverTwoFactorPageProps) {
return (
<div className="auth-page">
<StandalonePageFrame title={t('txt_recover_two_step_login')}>
<p className="muted standalone-muted">{t('txt_use_your_one_time_recovery_code_to_disable_two_step_verification')}</p>
<form
onSubmit={(e) => {
e.preventDefault();
props.onSubmit();
}}
>
<p className="muted standalone-muted">{t('txt_use_your_one_time_recovery_code_to_disable_two_step_verification')}</p>
<label className="field">
<span>{t('txt_email')}</span>
<input
className="input"
type="email"
value={props.values.email}
onInput={(e) => props.onChange({ ...props.values, email: (e.currentTarget as HTMLInputElement).value })}
/>
</label>
<label className="field">
<span>{t('txt_master_password')}</span>
<div className="password-wrap">
<label className="field">
<span>{t('txt_email')}</span>
<input
className="input"
type={showPassword ? 'text' : 'password'}
value={props.values.password}
onInput={(e) => props.onChange({ ...props.values, password: (e.currentTarget as HTMLInputElement).value })}
type="email"
value={props.values.email}
onInput={(e) => props.onChange({ ...props.values, email: (e.currentTarget as HTMLInputElement).value })}
/>
<button type="button" className="eye-btn" onClick={() => setShowPassword((v) => !v)}>
{showPassword ? <EyeOff size={16} /> : <Eye size={16} />}
</label>
<label className="field">
<span>{t('txt_master_password')}</span>
<div className="password-wrap">
<input
className="input"
type={showPassword ? 'text' : 'password'}
value={props.values.password}
onInput={(e) => props.onChange({ ...props.values, password: (e.currentTarget as HTMLInputElement).value })}
/>
<button type="button" className="eye-btn" onClick={() => setShowPassword((v) => !v)}>
{showPassword ? <EyeOff size={16} /> : <Eye size={16} />}
</button>
</div>
</label>
<label className="field">
<span>{t('txt_recovery_code')}</span>
<input
className="input"
value={props.values.recoveryCode}
onInput={(e) => props.onChange({ ...props.values, recoveryCode: (e.currentTarget as HTMLInputElement).value.toUpperCase() })}
/>
</label>
<div className="field-grid">
<button type="submit" className="btn btn-primary">
<Send size={14} className="btn-icon" />
{t('txt_submit')}
</button>
<button type="button" className="btn btn-secondary" onClick={props.onCancel}>
<X size={14} className="btn-icon" />
{t('txt_cancel')}
</button>
</div>
</label>
<label className="field">
<span>{t('txt_recovery_code')}</span>
<input
className="input"
value={props.values.recoveryCode}
onInput={(e) => props.onChange({ ...props.values, recoveryCode: (e.currentTarget as HTMLInputElement).value.toUpperCase() })}
/>
</label>
<div className="field-grid">
<button type="button" className="btn btn-primary" onClick={props.onSubmit}>
<Send size={14} className="btn-icon" />
{t('txt_submit')}
</button>
<button type="button" className="btn btn-secondary" onClick={props.onCancel}>
<X size={14} className="btn-icon" />
{t('txt_cancel')}
</button>
</div>
</form>
</StandalonePageFrame>
</div>
);