Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(settings): refactor change password and add form-handling to modal #765

Merged
merged 5 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/components/CapsLockWarning.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Alert } from "@/components/Alert";
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import { useTranslation } from "react-i18next";

const CapsLockWarning = () => {
const { t } = useTranslation();
return (
<p className="flex items-center gap-1 text-yellow-500">
<ExclamationTriangleIcon className="size-4" />
<Alert as="p" color="warning">
<ExclamationTriangleIcon className="size-4 inline" />
&nbsp;
{t("login.caps_lock")}
</p>
</Alert>
);
};

Expand Down
40 changes: 25 additions & 15 deletions src/components/ConfirmModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import { useTranslation } from "react-i18next";
export type Props = {
headline: string;
body?: ReactNode;
onConfirm: () => void;
onConfirm?: () => void;
disclosure: UseDisclosureReturn;
isLoading?: boolean;
isFormModal?: ReactNode;
};

export const ConfirmModal = ({
Expand All @@ -24,6 +25,7 @@ export const ConfirmModal = ({
onConfirm,
disclosure,
isLoading,
isFormModal,
}: Props) => {
const { t } = useTranslation();
const { isOpen, onOpenChange, onClose } = disclosure;
Expand All @@ -38,21 +40,29 @@ export const ConfirmModal = ({
{headline}
</ModalHeader>

{!!body && <ModalBody>{body}</ModalBody>}
{isFormModal || (
<>
{!!body && <ModalBody>{body}</ModalBody>}

<ModalFooter>
<Button variant="light" onClick={onClose} disabled={isLoading}>
{t("settings.cancel")}
</Button>
<Button
color="primary"
onClick={onConfirm}
disabled={isLoading}
isLoading={isLoading}
>
{t("settings.confirm")}
</Button>
</ModalFooter>
<ModalFooter>
<Button
variant="light"
onClick={onClose}
disabled={isLoading}
>
{t("settings.cancel")}
</Button>
<Button
color="primary"
onClick={onConfirm}
disabled={isLoading}
isLoading={isLoading}
>
{t("settings.confirm")}
</Button>
</ModalFooter>
</>
)}
</>
)}
</ModalContent>
Expand Down
10 changes: 5 additions & 5 deletions src/hooks/use-caps-lock.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import { KeyboardEvent, useState } from "react";

const useCapsLock = () => {
const [isCapsLockOn, setIsCapsLockOn] = useState(false);
const [isCapsLockEnabled, setIsCapsLockEnabled] = useState(false);

const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
if (event.getModifierState("CapsLock")) {
setIsCapsLockOn(true);
setIsCapsLockEnabled(true);
}
};

const onKeyUp = (event: KeyboardEvent<HTMLInputElement>) => {
if (isCapsLockOn) {
if (isCapsLockEnabled) {
if (!event.getModifierState("CapsLock")) {
setIsCapsLockOn(false);
setIsCapsLockEnabled(false);
}
}
};

return {
isCapsLockOn,
isCapsLockEnabled,
keyHandlers: {
onKeyDown,
onKeyUp,
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Home/UnlockModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function UnlockModal({ onClose }: Props) {
const { setWalletLocked } = useContext(AppContext);
const [isLoading, setIsLoading] = useState(false);
const [passwordWrong, setPasswordWrong] = useState(false);
const { isCapsLockOn, keyHandlers } = useCapsLock();
const { isCapsLockEnabled, keyHandlers } = useCapsLock();

const {
register,
Expand Down Expand Up @@ -81,7 +81,7 @@ export default function UnlockModal({ onClose }: Props) {
disabled={isLoading}
{...keyHandlers}
/>
{isCapsLockOn && <CapsLockWarning />}
{isCapsLockEnabled && <CapsLockWarning />}
<ButtonWithSpinner
type="submit"
className="bd-button my-5 p-3"
Expand Down
209 changes: 111 additions & 98 deletions src/pages/Settings/ChangePwModal.tsx
Original file line number Diff line number Diff line change
@@ -1,142 +1,155 @@
import ButtonWithSpinner from "@/components/ButtonWithSpinner/ButtonWithSpinner";
import ActionBox from "./ActionBox";
import { Button } from "@/components/Button";
import CapsLockWarning from "@/components/CapsLockWarning";
import InputField from "@/components/InputField";
import ConfirmModal from "@/components/ConfirmModal";
import useCapsLock from "@/hooks/use-caps-lock";
import ModalDialog from "@/layouts/ModalDialog";
import { MODAL_ROOT } from "@/utils";
import { checkError } from "@/utils/checkError";
import { instance } from "@/utils/interceptor";
import { ArrowPathIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { ChangeEvent, FC, useState } from "react";
import { createPortal } from "react-dom";
import { Input, useDisclosure } from "@nextui-org/react";
import { ModalFooter, ModalBody } from "@nextui-org/react";
import { type FC, useState } from "react";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { toast } from "react-toastify";

export type Props = {
onClose: () => void;
};
interface IFormInputs {
oldPassword: string;
newPassword: string;
}

const ChangePwModal: FC<Props> = ({ onClose }) => {
const ChangePwModal: FC = () => {
const { t } = useTranslation();
const [oldPassword, setOldPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [isLoading, setIsLoading] = useState(false);
const { isCapsLockOn, keyHandlers } = useCapsLock();
const confirmModal = useDisclosure();

const { isCapsLockEnabled, keyHandlers } = useCapsLock();

const changePwHandler = () => {
const changePwHandler = (data: IFormInputs) => {
setIsLoading(true);

const params = {
type: "a",
old_password: oldPassword,
new_password: newPassword,
old_password: data.oldPassword,
new_password: data.newPassword,
};

instance
.post("/system/change-password", {}, { params })
.then(() => {
toast.success(t("settings.pass_a_changed"));
onClose();
confirmModal.onClose();
})
.catch((err) => {
toast.error(checkError(err));
})
.finally(() => {
setIsLoading(false);
reset();
});
};

const onChangeOldPw = (event: ChangeEvent<HTMLInputElement>) => {
setOldPassword(event.target.value);
};

const onChangeNewPw = (event: ChangeEvent<HTMLInputElement>) => {
setNewPassword(event.target.value);
};

const {
register,
handleSubmit,
reset,
formState: { errors, isValid },
} = useForm<IFormInputs>({
mode: "onChange",
});

return createPortal(
<ModalDialog close={onClose}>
<h3 className="font-bold">{t("settings.change_pw_a")}</h3>
<form
className="my-5 flex flex-col items-center justify-center text-center"
onSubmit={handleSubmit(changePwHandler)}
>
<article className="w-full py-2 md:w-10/12">
<InputField
{...register("oldPassword", {
required: t("setup.password_error_empty"),
pattern: {
value: /^[a-zA-Z0-9.-]*$/,
message: t("setup.password_error_chars"),
},
minLength: {
value: 8,
message: t("setup.password_error_length"),
},
onChange: onChangeOldPw,
})}
type="password"
value={oldPassword}
label={t("settings.old_pw")}
errorMessage={errors.oldPassword}
{...keyHandlers}
/>
</article>
<article className="w-full py-2 md:w-10/12">
<InputField
{...register("newPassword", {
required: t("setup.password_error_empty"),
pattern: {
value: /^[a-zA-Z0-9.-]*$/,
message: t("setup.password_error_chars"),
},
minLength: {
value: 8,
message: t("setup.password_error_length"),
},
onChange: onChangeNewPw,
})}
type="password"
value={newPassword}
label={t("settings.new_pw")}
errorMessage={errors.newPassword}
{...keyHandlers}
/>
{isCapsLockOn && <CapsLockWarning />}
</article>
<article className="flex w-full flex-col justify-around gap-6 pt-8 text-white md:w-2/3 xl:flex-row">
<button
className="bd-button-red flex items-center justify-center px-2"
onClick={onClose}
type="button"
>
<XMarkIcon className="inline h-6 w-6" />
<span className="p-2">{t("settings.cancel")}</span>
</button>
<ButtonWithSpinner
type="submit"
className="bd-button flex items-center justify-center px-2"
disabled={!isValid}
loading={isLoading}
icon={<ArrowPathIcon className="inline h-6 w-6" />}
>
<span className="p-2">{t("settings.change_pw_a")}</span>
</ButtonWithSpinner>
</article>
</form>
</ModalDialog>,
MODAL_ROOT,
return (
<>
<ConfirmModal
disclosure={confirmModal}
headline={t("settings.change_pw_a")}
isLoading={isLoading}
isFormModal={
<form onSubmit={handleSubmit(changePwHandler)}>
escapedcat marked this conversation as resolved.
Show resolved Hide resolved
<ModalBody>
{isCapsLockEnabled && <CapsLockWarning />}

<fieldset className="flex w-full flex-col gap-4">
<Input
className="w-full"
classNames={{
inputWrapper:
"bg-tertiary group-data-[focus=true]:bg-tertiary group-data-[hover=true]:bg-tertiary",
}}
type="password"
label={t("settings.old_pw")}
isInvalid={!!errors.oldPassword}
errorMessage={errors.oldPassword?.message}
{...register("oldPassword", {
required: t("setup.password_error_empty"),
pattern: {
value: /^[a-zA-Z0-9.-]*$/,
message: t("setup.password_error_chars"),
},
minLength: {
value: 8,
message: t("setup.password_error_length"),
},
})}
{...keyHandlers}
/>

<Input
className="w-full"
classNames={{
inputWrapper:
"bg-tertiary group-data-[focus=true]:bg-tertiary group-data-[hover=true]:bg-tertiary",
}}
type="password"
label={t("settings.new_pw")}
isInvalid={!!errors.newPassword}
errorMessage={errors.newPassword?.message}
{...register("newPassword", {
required: t("setup.password_error_empty"),
pattern: {
value: /^[a-zA-Z0-9.-]*$/,
message: t("setup.password_error_chars"),
},
minLength: {
value: 8,
message: t("setup.password_error_length"),
},
})}
{...keyHandlers}
/>
</fieldset>
</ModalBody>

<ModalFooter>
<Button
variant="light"
onClick={() => {
confirmModal.onClose();
reset();
}}
disabled={isLoading}
>
{t("settings.cancel")}
</Button>
<Button
color="primary"
type="submit"
disabled={isLoading || !isValid}
isLoading={isLoading}
>
{t("settings.confirm")}
</Button>
</ModalFooter>
</form>
}
/>

<ActionBox
name={t("settings.change_pw_a")}
actionName={t("settings.change")}
action={() => confirmModal.onOpen()}
showChild={false}
/>
</>
);
};

Expand Down
Loading