63
common/resources/client/auth/ui/confirm-password/confirm-password-dialog.tsx
Executable file
63
common/resources/client/auth/ui/confirm-password/confirm-password-dialog.tsx
Executable file
@@ -0,0 +1,63 @@
|
||||
import {Dialog} from '@common/ui/overlays/dialog/dialog';
|
||||
import {useDialogContext} from '@common/ui/overlays/dialog/dialog-context';
|
||||
import {
|
||||
ConfirmPasswordPayload,
|
||||
useConfirmPassword,
|
||||
} from '@common/auth/ui/confirm-password/requests/use-confirm-password';
|
||||
import {useForm} from 'react-hook-form';
|
||||
import {DialogHeader} from '@common/ui/overlays/dialog/dialog-header';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {DialogBody} from '@common/ui/overlays/dialog/dialog-body';
|
||||
import {Form} from '@common/ui/forms/form';
|
||||
import {FormTextField} from '@common/ui/forms/input-field/text-field/text-field';
|
||||
import {DialogFooter} from '@common/ui/overlays/dialog/dialog-footer';
|
||||
import {Button} from '@common/ui/buttons/button';
|
||||
|
||||
export function ConfirmPasswordDialog() {
|
||||
const {close, formId} = useDialogContext();
|
||||
const form = useForm<ConfirmPasswordPayload>();
|
||||
const confirmPassword = useConfirmPassword(form);
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogHeader>
|
||||
<Trans message="Confirm password" />
|
||||
</DialogHeader>
|
||||
<DialogBody>
|
||||
<p className="text-sm mb-16">
|
||||
<Trans message="For your security, please confirm your password to continue." />
|
||||
</p>
|
||||
<Form
|
||||
id={formId}
|
||||
form={form}
|
||||
onSubmit={values =>
|
||||
confirmPassword.mutate(values, {
|
||||
onSuccess: () => close(values.password),
|
||||
})
|
||||
}
|
||||
>
|
||||
<FormTextField
|
||||
name="password"
|
||||
label={<Trans message="Password" />}
|
||||
type="password"
|
||||
required
|
||||
autoFocus
|
||||
/>
|
||||
</Form>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button onClick={() => close()}>
|
||||
<Trans message="Cancel" />
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="flat"
|
||||
color="primary"
|
||||
form={formId}
|
||||
disabled={confirmPassword.isPending}
|
||||
>
|
||||
<Trans message="Confirm" />
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import {useMutation} from '@tanstack/react-query';
|
||||
import {BackendResponse} from '@common/http/backend-response/backend-response';
|
||||
import {apiClient} from '@common/http/query-client';
|
||||
import {UseFormReturn} from 'react-hook-form';
|
||||
import {onFormQueryError} from '@common/errors/on-form-query-error';
|
||||
|
||||
interface Response extends BackendResponse {}
|
||||
|
||||
export interface ConfirmPasswordPayload {
|
||||
password: string;
|
||||
}
|
||||
|
||||
export function useConfirmPassword(
|
||||
form: UseFormReturn<ConfirmPasswordPayload>,
|
||||
) {
|
||||
return useMutation({
|
||||
mutationFn: (payload: ConfirmPasswordPayload) => confirm(payload),
|
||||
onError: r => onFormQueryError(r, form),
|
||||
});
|
||||
}
|
||||
|
||||
function confirm(payload: ConfirmPasswordPayload): Promise<Response> {
|
||||
return apiClient
|
||||
.post('auth/user/confirm-password', payload)
|
||||
.then(response => response.data);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import {useQuery} from '@tanstack/react-query';
|
||||
import {BackendResponse} from '@common/http/backend-response/backend-response';
|
||||
import {apiClient, queryClient} from '@common/http/query-client';
|
||||
|
||||
interface Response extends BackendResponse {
|
||||
confirmed: boolean;
|
||||
}
|
||||
|
||||
export function usePasswordConfirmationStatus() {
|
||||
return useQuery({
|
||||
queryKey: ['password-confirmation-status'],
|
||||
queryFn: () => fetchStatus(),
|
||||
});
|
||||
}
|
||||
|
||||
function fetchStatus(): Promise<Response> {
|
||||
return apiClient
|
||||
.get('auth/user/confirmed-password-status', {params: {seconds: 9000}})
|
||||
.then(response => response.data);
|
||||
}
|
||||
|
||||
export function setPasswordConfirmationStatus(confirmed: boolean) {
|
||||
queryClient.setQueryData(['password-confirmation-status'], {confirmed});
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
setPasswordConfirmationStatus,
|
||||
usePasswordConfirmationStatus,
|
||||
} from '@common/auth/ui/confirm-password/requests/use-password-confirmation-status';
|
||||
import {openDialog} from '@common/ui/overlays/store/dialog-store';
|
||||
import {ConfirmPasswordDialog} from '@common/auth/ui/confirm-password/confirm-password-dialog';
|
||||
import {useCallback, useRef} from 'react';
|
||||
|
||||
interface Props {
|
||||
needsPassword?: boolean;
|
||||
}
|
||||
export function usePasswordConfirmedAction({needsPassword}: Props = {}) {
|
||||
const {data, isLoading} = usePasswordConfirmationStatus();
|
||||
const passwordRef = useRef<string>();
|
||||
|
||||
const withConfirmedPassword = useCallback(
|
||||
async (action: (password?: string) => void) => {
|
||||
if (data?.confirmed && (passwordRef.current || !needsPassword)) {
|
||||
action(passwordRef.current);
|
||||
} else {
|
||||
const password = await openDialog(ConfirmPasswordDialog);
|
||||
if (password) {
|
||||
passwordRef.current = password;
|
||||
setPasswordConfirmationStatus(true);
|
||||
action(passwordRef.current);
|
||||
}
|
||||
}
|
||||
},
|
||||
[data?.confirmed, needsPassword]
|
||||
);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
withConfirmedPassword,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user