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