Change password
This commit is contained in:
77
AceJobAgency.client/src/components/ChangePasswordView.tsx
Normal file
77
AceJobAgency.client/src/components/ChangePasswordView.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Button, Divider, Input } from "@heroui/react";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import http from "../http";
|
||||||
|
import { validatePassword } from "./SignupView";
|
||||||
|
|
||||||
|
export default function ChangePasswordView({
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
onClose: () => void;
|
||||||
|
}) {
|
||||||
|
const [oldPassword, setOldPassword] = useState("");
|
||||||
|
const [newPassword, setNewPassword] = useState("");
|
||||||
|
const [confirmNewPassword, setConfirmNewPassword] = useState("");
|
||||||
|
|
||||||
|
const handleChangePassword = async () => {
|
||||||
|
if (newPassword !== confirmNewPassword) {
|
||||||
|
toast.error("New password and confirm new password do not match.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validatePassword(newPassword)) {
|
||||||
|
toast.error(
|
||||||
|
"Password must be at least 12 characters long and include uppercase, lowercase, number, and special character."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const changePasswordRequest = {
|
||||||
|
currentPassword: oldPassword,
|
||||||
|
newPassword: newPassword,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await http.put(
|
||||||
|
"/User/change-password",
|
||||||
|
changePasswordRequest
|
||||||
|
);
|
||||||
|
toast.success(response.data);
|
||||||
|
onClose();
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(
|
||||||
|
(error as any).response?.data ||
|
||||||
|
"Something went wrong! Please try again."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-8">
|
||||||
|
<p className="text-xl">Change password</p>
|
||||||
|
<Divider />
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Input
|
||||||
|
label="Old password"
|
||||||
|
type="password"
|
||||||
|
value={oldPassword}
|
||||||
|
onChange={(e) => setOldPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
<div className="w-full h-4"></div>
|
||||||
|
<Input
|
||||||
|
label="New password"
|
||||||
|
type="password"
|
||||||
|
value={newPassword}
|
||||||
|
onChange={(e) => setNewPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Confirm new password"
|
||||||
|
type="password"
|
||||||
|
value={confirmNewPassword}
|
||||||
|
onChange={(e) => setConfirmNewPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button onClick={handleChangePassword}>Save</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,6 +4,12 @@ import { useState } from "react";
|
|||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import http, { login } from "../http";
|
import http, { login } from "../http";
|
||||||
|
|
||||||
|
export const validatePassword = (password: string): boolean => {
|
||||||
|
const passwordComplexityRegex =
|
||||||
|
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z\d])[A-Za-z\d@$!%*?&\\]{12,}$/;
|
||||||
|
return passwordComplexityRegex.test(password);
|
||||||
|
};
|
||||||
|
|
||||||
export default function SignupView({
|
export default function SignupView({
|
||||||
onLogin,
|
onLogin,
|
||||||
email = "",
|
email = "",
|
||||||
@@ -21,12 +27,6 @@ export default function SignupView({
|
|||||||
const [confirmPassword, setConfirmPassword] = useState("");
|
const [confirmPassword, setConfirmPassword] = useState("");
|
||||||
const [signupEnabled, setSignupEnabled] = useState(true);
|
const [signupEnabled, setSignupEnabled] = useState(true);
|
||||||
|
|
||||||
const validatePassword = (password: string): boolean => {
|
|
||||||
const passwordComplexityRegex =
|
|
||||||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z\d])[A-Za-z\d@$!%*?&\\]{12,}$/;
|
|
||||||
return passwordComplexityRegex.test(password);
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateFields = () => {
|
const validateFields = () => {
|
||||||
if (
|
if (
|
||||||
!firstName ||
|
!firstName ||
|
||||||
|
|||||||
@@ -2,15 +2,28 @@ import { useEffect, useState } from "react";
|
|||||||
import http, { getAccessToken, logout } from "../http";
|
import http, { getAccessToken, logout } from "../http";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { UserProfile } from "../models/user-profile";
|
import { UserProfile } from "../models/user-profile";
|
||||||
import { Button, Card, Divider, Input } from "@heroui/react";
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Divider,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
useDisclosure,
|
||||||
|
} from "@heroui/react";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
import { IconDownload, IconUpload } from "@tabler/icons-react";
|
import { IconDownload, IconEdit, IconUpload } from "@tabler/icons-react";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
import ChangePasswordView from "../components/ChangePasswordView";
|
||||||
|
|
||||||
export default function MemberPage() {
|
export default function MemberPage() {
|
||||||
const accessToken = getAccessToken();
|
const accessToken = getAccessToken();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { isOpen, onOpen, onOpenChange } = useDisclosure();
|
||||||
|
|
||||||
const [userProfile, setUserProfile] = useState<UserProfile | null>(null);
|
const [userProfile, setUserProfile] = useState<UserProfile | null>(null);
|
||||||
|
|
||||||
@@ -137,7 +150,20 @@ export default function MemberPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<p>Who am I</p>
|
<div className="flex flex-row justify-between *:my-auto">
|
||||||
|
<p>Who am I</p>
|
||||||
|
<Button
|
||||||
|
variant="flat"
|
||||||
|
size="sm"
|
||||||
|
className="text-md"
|
||||||
|
startContent={<IconEdit size={18} />}
|
||||||
|
onPress={() => {
|
||||||
|
navigate("edit");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Edit bio
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<Card className="bg-white dark:bg-black h-full max-h-[352px] min-w-96">
|
<Card className="bg-white dark:bg-black h-full max-h-[352px] min-w-96">
|
||||||
{userProfile.whoAmI.length > 0 ? (
|
{userProfile.whoAmI.length > 0 ? (
|
||||||
<Markdown
|
<Markdown
|
||||||
@@ -159,17 +185,25 @@ export default function MemberPage() {
|
|||||||
<Button variant="light" color="danger" onPress={logout}>
|
<Button variant="light" color="danger" onPress={logout}>
|
||||||
Log out
|
Log out
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button variant="light" color="primary" onPress={onOpen}>
|
||||||
color="primary"
|
Change password
|
||||||
onPress={() => {
|
|
||||||
navigate("edit");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Edit profile
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||||
|
<ModalContent>
|
||||||
|
{(onClose) => (
|
||||||
|
<>
|
||||||
|
<ModalHeader />
|
||||||
|
<ModalBody>
|
||||||
|
<ChangePasswordView onClose={onClose} />
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user