mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2025-01-09 19:52:36 +00:00
Make 2FA input play nicer with phone keyboards (#2199)
This commit is contained in:
parent
2b5068187c
commit
25b06124fd
2 changed files with 33 additions and 67 deletions
|
@ -1 +1 @@
|
||||||
Subproject commit abd40d4737fa732321fd7b62e42bbfcd51081cb6
|
Subproject commit 6fbc86932a03c4d40829ee4a3395259b2a7660e5
|
|
@ -25,64 +25,42 @@ interface TotpModalState {
|
||||||
|
|
||||||
const TOTP_LENGTH = 6;
|
const TOTP_LENGTH = 6;
|
||||||
|
|
||||||
async function handleSubmit(modal: TotpModal, totp: string) {
|
async function handleSubmit(i: TotpModal, totp: string) {
|
||||||
const successful = await modal.props.onSubmit(totp);
|
const successful = await i.props.onSubmit(totp);
|
||||||
|
|
||||||
if (!successful) {
|
if (!successful) {
|
||||||
modal.setState({ totp: "" });
|
i.setState({ totp: "" });
|
||||||
for (const inputRef of modal.inputRefs) {
|
i.inputRef.current?.focus();
|
||||||
inputRef!.value = "";
|
|
||||||
}
|
|
||||||
modal.inputRefs[0]?.focus();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleInput(
|
function handleInput(i: TotpModal, event: any) {
|
||||||
{ modal, i }: { modal: TotpModal; i: number },
|
|
||||||
event: any,
|
|
||||||
) {
|
|
||||||
if (isNaN(event.target.value)) {
|
if (isNaN(event.target.value)) {
|
||||||
modal.inputRefs[i]!.value = "";
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
modal.setState(prev => ({ ...prev, totp: prev.totp + event.target.value }));
|
i.setState({
|
||||||
modal.inputRefs[i + 1]?.focus();
|
totp: event.target.value,
|
||||||
|
});
|
||||||
|
|
||||||
const { totp } = modal.state;
|
const { totp } = i.state;
|
||||||
if (totp.length >= TOTP_LENGTH) {
|
if (totp.length >= TOTP_LENGTH) {
|
||||||
handleSubmit(modal, totp);
|
handleSubmit(i, totp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeyUp(
|
function handlePaste(i: TotpModal, event: any) {
|
||||||
{ modal, i }: { modal: TotpModal; i: number },
|
|
||||||
event: any,
|
|
||||||
) {
|
|
||||||
if (event.key === "Backspace" && i > 0) {
|
|
||||||
modal.setState(prev => ({
|
|
||||||
...prev,
|
|
||||||
totp: prev.totp.slice(0, prev.totp.length - 1),
|
|
||||||
}));
|
|
||||||
modal.inputRefs[i - 1]?.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handlePaste(modal: TotpModal, event: any) {
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const text: string = event.clipboardData.getData("text")?.trim();
|
const text: string = event.clipboardData.getData("text")?.trim();
|
||||||
|
|
||||||
if (text.length > TOTP_LENGTH || isNaN(Number(text))) {
|
if (text.length > TOTP_LENGTH || isNaN(Number(text))) {
|
||||||
toast(I18NextService.i18n.t("invalid_totp_code"), "danger");
|
toast(I18NextService.i18n.t("invalid_totp_code"), "danger");
|
||||||
modal.clearTotp();
|
i.clearTotp();
|
||||||
} else {
|
} else {
|
||||||
[...text].forEach((num, i) => {
|
i.setState({ totp: text });
|
||||||
modal.inputRefs[i]!.value = num;
|
|
||||||
});
|
|
||||||
modal.setState({ totp: text });
|
|
||||||
|
|
||||||
if (text.length === TOTP_LENGTH) {
|
if (text.length === TOTP_LENGTH) {
|
||||||
handleSubmit(modal, text);
|
handleSubmit(i, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,8 +69,8 @@ export default class TotpModal extends Component<
|
||||||
TotpModalProps,
|
TotpModalProps,
|
||||||
TotpModalState
|
TotpModalState
|
||||||
> {
|
> {
|
||||||
private readonly modalDivRef: RefObject<HTMLDivElement>;
|
readonly modalDivRef: RefObject<HTMLDivElement>;
|
||||||
inputRefs: (HTMLInputElement | null)[] = [];
|
readonly inputRef: RefObject<HTMLInputElement>;
|
||||||
modal: Modal;
|
modal: Modal;
|
||||||
state: TotpModalState = {
|
state: TotpModalState = {
|
||||||
totp: "",
|
totp: "",
|
||||||
|
@ -102,6 +80,7 @@ export default class TotpModal extends Component<
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.modalDivRef = createRef();
|
this.modalDivRef = createRef();
|
||||||
|
this.inputRef = createRef();
|
||||||
|
|
||||||
this.clearTotp = this.clearTotp.bind(this);
|
this.clearTotp = this.clearTotp.bind(this);
|
||||||
this.handleShow = this.handleShow.bind(this);
|
this.handleShow = this.handleShow.bind(this);
|
||||||
|
@ -207,34 +186,24 @@ export default class TotpModal extends Component<
|
||||||
<form id="totp-form">
|
<form id="totp-form">
|
||||||
<label
|
<label
|
||||||
className="form-label ms-2 mt-4 fw-bold"
|
className="form-label ms-2 mt-4 fw-bold"
|
||||||
id="totp-input-label"
|
htmlFor="totp-input"
|
||||||
htmlFor="totp-input-0"
|
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("enter_totp_code")}
|
{I18NextService.i18n.t("enter_totp_code")}
|
||||||
</label>
|
</label>
|
||||||
<div className="d-flex justify-content-between align-items-center p-2">
|
<div className="d-flex justify-content-between align-items-center p-2">
|
||||||
{Array.from(Array(TOTP_LENGTH).keys()).map(i => (
|
|
||||||
<input
|
<input
|
||||||
key={
|
|
||||||
i /*While using indices as keys is usually bad practice, in this case we don't have to worry about the order of the list items changing.*/
|
|
||||||
}
|
|
||||||
type="text"
|
type="text"
|
||||||
inputMode="numeric"
|
inputMode="numeric"
|
||||||
autoComplete="one-time-code"
|
autoComplete="one-time-code"
|
||||||
maxLength={1}
|
maxLength={TOTP_LENGTH}
|
||||||
disabled={totp.length !== i}
|
id="totp-input"
|
||||||
aria-labelledby="totp-input-label"
|
|
||||||
id={`totp-input-${i}`}
|
|
||||||
className="form-control form-control-lg mx-2 p-1 p-md-2 text-center"
|
className="form-control form-control-lg mx-2 p-1 p-md-2 text-center"
|
||||||
onInput={linkEvent({ modal: this, i }, handleInput)}
|
onInput={linkEvent(this, handleInput)}
|
||||||
onKeyUp={linkEvent({ modal: this, i }, handleKeyUp)}
|
|
||||||
onPaste={linkEvent(this, handlePaste)}
|
onPaste={linkEvent(this, handlePaste)}
|
||||||
ref={element => {
|
ref={this.inputRef}
|
||||||
this.inputRefs[i] = element;
|
|
||||||
}}
|
|
||||||
enterKeyHint="done"
|
enterKeyHint="done"
|
||||||
|
value={totp}
|
||||||
/>
|
/>
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -255,13 +224,10 @@ export default class TotpModal extends Component<
|
||||||
|
|
||||||
clearTotp() {
|
clearTotp() {
|
||||||
this.setState({ totp: "" });
|
this.setState({ totp: "" });
|
||||||
for (const inputRef of this.inputRefs) {
|
|
||||||
inputRef!.value = "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleShow() {
|
async handleShow() {
|
||||||
this.inputRefs[0]?.focus();
|
this.inputRef.current?.focus();
|
||||||
|
|
||||||
if (this.props.type === "generate") {
|
if (this.props.type === "generate") {
|
||||||
const { getSVG } = await import("@shortcm/qr-image/lib/svg");
|
const { getSVG } = await import("@shortcm/qr-image/lib/svg");
|
||||||
|
|
Loading…
Reference in a new issue