mirror of
https://github.com/quantum5/totp.git
synced 2025-04-24 13:41:58 -04:00
Avoid pointless React namespace
This commit is contained in:
parent
8e02568a43
commit
bd2166e825
|
@ -1,9 +1,9 @@
|
||||||
import React from 'react';
|
import React, {HTMLProps, SyntheticEvent, useCallback} from 'react';
|
||||||
|
|
||||||
type HTMLAnchorProps = Omit<React.HTMLProps<HTMLAnchorElement>, 'href' | 'onClick'>;
|
type HTMLAnchorProps = Omit<HTMLProps<HTMLAnchorElement>, 'href' | 'onClick'>;
|
||||||
|
|
||||||
export default function ActionLink({onClick, className, ...props}: HTMLAnchorProps & { onClick: () => void }) {
|
export default function ActionLink({onClick, className, ...props}: HTMLAnchorProps & { onClick: () => void }) {
|
||||||
const handleClick = React.useCallback((e: React.SyntheticEvent) => {
|
const handleClick = useCallback((e: SyntheticEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onClick();
|
onClick();
|
||||||
}, [onClick]);
|
}, [onClick]);
|
||||||
|
|
34
src/App.tsx
34
src/App.tsx
|
@ -1,4 +1,4 @@
|
||||||
import React, {useState} from 'react';
|
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||||
import base32Decode from 'base32-decode';
|
import base32Decode from 'base32-decode';
|
||||||
import {NumberInput, TextInput} from './Input';
|
import {NumberInput, TextInput} from './Input';
|
||||||
import OTPOutput from './OTPOutput';
|
import OTPOutput from './OTPOutput';
|
||||||
|
@ -18,12 +18,12 @@ function parseState() {
|
||||||
function App() {
|
function App() {
|
||||||
const [advanced, setAdvanced] = useState(false);
|
const [advanced, setAdvanced] = useState(false);
|
||||||
|
|
||||||
const [state, setState] = React.useState(() => parseState() || defaults);
|
const [state, setState] = useState(() => parseState() || defaults);
|
||||||
const {secret, step, digits, algorithm} = state;
|
const {secret, step, digits, algorithm} = state;
|
||||||
const [offset, setOffset] = React.useState(0);
|
const [offset, setOffset] = useState(0);
|
||||||
const [remaining, setRemaining] = React.useState(0);
|
const [remaining, setRemaining] = useState(0);
|
||||||
|
|
||||||
const [validSecret, decoded] = React.useMemo(() => {
|
const [validSecret, decoded] = useMemo(() => {
|
||||||
try {
|
try {
|
||||||
return [true, base32Decode(secret.toUpperCase(), 'RFC4648')];
|
return [true, base32Decode(secret.toUpperCase(), 'RFC4648')];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -35,13 +35,13 @@ function App() {
|
||||||
const validDigits = digits > 0 && digits <= 10;
|
const validDigits = digits > 0 && digits <= 10;
|
||||||
const valid = validSecret && validStep && validDigits && !!secret;
|
const valid = validSecret && validStep && validDigits && !!secret;
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (!validStep) return;
|
if (!validStep) return;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
setOffset(Math.floor(now / (1000 * step)));
|
setOffset(Math.floor(now / (1000 * step)));
|
||||||
}, [validStep, step]);
|
}, [validStep, step]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (!validStep) return;
|
if (!validStep) return;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const nextUpdate = Math.floor(now / 1000) * 1000 + 1000;
|
const nextUpdate = Math.floor(now / 1000) * 1000 + 1000;
|
||||||
|
@ -55,50 +55,50 @@ function App() {
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [validStep, step, offset, remaining]);
|
}, [validStep, step, offset, remaining]);
|
||||||
|
|
||||||
const showAdvanced = React.useCallback(() => {
|
const showAdvanced = useCallback(() => {
|
||||||
setAdvanced(true);
|
setAdvanced(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const hideAdvanced = React.useCallback(() => {
|
const hideAdvanced = useCallback(() => {
|
||||||
setAdvanced(false);
|
setAdvanced(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setSecret = React.useCallback(
|
const setSecret = useCallback(
|
||||||
(secret: string) => setState((state) => ({...state, secret})),
|
(secret: string) => setState((state) => ({...state, secret})),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const setStep = React.useCallback(
|
const setStep = useCallback(
|
||||||
(step: number) => setState((state) => ({...state, step})),
|
(step: number) => setState((state) => ({...state, step})),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const setDigits = React.useCallback(
|
const setDigits = useCallback(
|
||||||
(digits: number) => setState((state) => ({...state, digits})),
|
(digits: number) => setState((state) => ({...state, digits})),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const setAlgorithm = React.useCallback(
|
const setAlgorithm = useCallback(
|
||||||
(algorithm: string) => setState((state) => ({...state, algorithm})),
|
(algorithm: string) => setState((state) => ({...state, algorithm})),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onReset = React.useCallback(
|
const onReset = useCallback(
|
||||||
() => setState((state) => ({...state, step: 30, digits: 6, algorithm: 'sha1'})),
|
() => setState((state) => ({...state, step: 30, digits: 6, algorithm: 'sha1'})),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
const value = serializeState(state);
|
const value = serializeState(state);
|
||||||
history.replaceState(null, '', window.location.pathname + window.location.search + (value && `#!${value}`));
|
history.replaceState(null, '', window.location.pathname + window.location.search + (value && `#!${value}`));
|
||||||
}, [state]);
|
}, [state]);
|
||||||
|
|
||||||
const onHashChange = React.useCallback(() => {
|
const onHashChange = useCallback(() => {
|
||||||
const state = parseState();
|
const state = parseState();
|
||||||
state && setState(state);
|
state && setState(state);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener('hashchange', onHashChange);
|
window.addEventListener('hashchange', onHashChange);
|
||||||
return () => window.removeEventListener('hashchange', onHashChange);
|
return () => window.removeEventListener('hashchange', onHashChange);
|
||||||
}, [onHashChange]);
|
}, [onHashChange]);
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import React from 'react';
|
import React, {ReactNode, useEffect, useId} from 'react';
|
||||||
import {Collapse} from 'bootstrap';
|
import {Collapse} from 'bootstrap';
|
||||||
|
|
||||||
export default function Collapsible({children, show}: { children: React.ReactNode; show: boolean }) {
|
export default function Collapsible({children, show}: { children: ReactNode; show: boolean }) {
|
||||||
const id = React.useId();
|
const id = useId();
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
const collapse = Collapse.getOrCreateInstance(`#${id}`, {toggle: show});
|
const collapse = Collapse.getOrCreateInstance(`#${id}`, {toggle: show});
|
||||||
if (show)
|
if (show)
|
||||||
collapse.show();
|
collapse.show();
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import React from 'react';
|
import React, {useCallback, useEffect, useId, useState} from 'react';
|
||||||
import Copy from './copy.svg?react';
|
import Copy from './copy.svg?react';
|
||||||
import {Popover} from 'bootstrap';
|
import {Popover} from 'bootstrap';
|
||||||
|
|
||||||
export default function CopyButton({text}: { text: string }) {
|
export default function CopyButton({text}: { text: string }) {
|
||||||
const id = React.useId();
|
const id = useId();
|
||||||
const [popover, setPopover] = React.useState(false);
|
const [popover, setPopover] = useState(false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (!popover) return;
|
if (!popover) return;
|
||||||
const instance = Popover.getOrCreateInstance(`#${id}`, {
|
const instance = Popover.getOrCreateInstance(`#${id}`, {
|
||||||
trigger: 'manual',
|
trigger: 'manual',
|
||||||
|
@ -20,7 +20,7 @@ export default function CopyButton({text}: { text: string }) {
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}, [id, popover]);
|
}, [id, popover]);
|
||||||
|
|
||||||
const onCopy = React.useCallback(async () => {
|
const onCopy = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
setPopover(true);
|
setPopover(true);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React from 'react';
|
import React, {ReactNode, HTMLProps, useCallback, useId} from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
type CommonProps = {
|
type CommonProps = {
|
||||||
label: React.ReactNode;
|
label: ReactNode;
|
||||||
error?: React.ReactNode;
|
error?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TextInputProps = {
|
type TextInputProps = {
|
||||||
|
@ -11,12 +11,12 @@ type TextInputProps = {
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTMLInputProps = Omit<React.HTMLProps<HTMLInputElement>, 'onChange' | 'label'>;
|
type HTMLInputProps = Omit<HTMLProps<HTMLInputElement>, 'onChange' | 'label'>;
|
||||||
|
|
||||||
function BaseInput({label, value, onChange, error, ...props}: CommonProps & HTMLInputProps & TextInputProps) {
|
function BaseInput({label, value, onChange, error, ...props}: CommonProps & HTMLInputProps & TextInputProps) {
|
||||||
const id = React.useId();
|
const id = useId();
|
||||||
const handleChange = React.useCallback(
|
const handleChange = useCallback(
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => onChange(e.target.value),
|
(e: ChangeEvent<HTMLInputElement>) => onChange(e.target.value),
|
||||||
[onChange],
|
[onChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -38,6 +38,6 @@ export function NumberInput({value, onChange, ...props}: CommonProps & {
|
||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
}) {
|
}) {
|
||||||
const handleChange = React.useCallback((value: string) => onChange(+value), [onChange]);
|
const handleChange = useCallback((value: string) => onChange(+value), [onChange]);
|
||||||
return <BaseInput type="number" value={`${value}`} onChange={handleChange} {...props}/>;
|
return <BaseInput type="number" value={`${value}`} onChange={handleChange} {...props}/>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, {useMemo} from 'react';
|
||||||
import {HOTP, HOTPOptions} from '@otplib/core';
|
import {HOTP, HOTPOptions} from '@otplib/core';
|
||||||
import {createDigest} from '@otplib/plugin-crypto-js';
|
import {createDigest} from '@otplib/plugin-crypto-js';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
@ -25,7 +25,7 @@ export default function OTPOutput({secret, offset, algorithm, digits}: {
|
||||||
algorithm: HashAlgorithm;
|
algorithm: HashAlgorithm;
|
||||||
digits: number;
|
digits: number;
|
||||||
}) {
|
}) {
|
||||||
const otp = React.useMemo(() => new HOTP<HOTPOptions>({
|
const otp = useMemo(() => new HOTP<HOTPOptions>({
|
||||||
createDigest,
|
createDigest,
|
||||||
digits,
|
digits,
|
||||||
algorithm: ALGORITHMS[algorithm],
|
algorithm: ALGORITHMS[algorithm],
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import React from 'react';
|
import React, {ChangeEvent, ReactNode, useCallback, useId} from 'react';
|
||||||
|
|
||||||
export default function Select<T extends string>({label, options, value, onChange}: {
|
export default function Select<T extends string>({label, options, value, onChange}: {
|
||||||
label: React.ReactNode;
|
label: ReactNode;
|
||||||
options: Record<T, string>;
|
options: Record<T, string>;
|
||||||
value: T;
|
value: T;
|
||||||
onChange: (value: T) => void;
|
onChange: (value: T) => void;
|
||||||
}) {
|
}) {
|
||||||
const id = React.useId();
|
const id = useId();
|
||||||
const handleChange = React.useCallback(
|
const handleChange = useCallback(
|
||||||
(e: React.ChangeEvent<HTMLSelectElement>) => onChange(e.target.value as T),
|
(e: ChangeEvent<HTMLSelectElement>) => onChange(e.target.value as T),
|
||||||
[onChange],
|
[onChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue