improved reset and change password pages

This commit is contained in:
subnub
2025-02-24 09:49:11 -05:00
parent 56f0fe934f
commit 28bcc44ea2
5 changed files with 120 additions and 34 deletions
@@ -203,6 +203,10 @@ class UserService {
};
sendPasswordReset = async (email: string) => {
if (env.emailVerification !== "true") {
throw new ForbiddenError("Email Verification Not Enabled");
}
const user = await User.findOne({ email });
if (!user) throw new NotFoundError("User Not Found Password Reset Email");
+63 -25
View File
@@ -10,6 +10,12 @@ import dayjs from "dayjs";
import { getFileColor, getFileExtension } from "../../utils/files";
import { FileInterface } from "../../types/file";
import bytes from "bytes";
import LockIcon from "../../icons/LockIcon";
import OneIcon from "../../icons/OneIcon";
import StorageIcon from "../../icons/StorageIcon";
import CalendarIcon from "../../icons/CalendarIcon";
import DownloadIcon from "../../icons/DownloadIcon";
import PublicIcon from "../../icons/PublicIcon";
const PublicDownloadPage = () => {
const [file, setFile] = useState<FileInterface | null>(null);
@@ -33,6 +39,22 @@ const PublicDownloadPage = () => {
downloadPublicFileAPI(id, tempToken);
};
const permissionText = (() => {
if (!file) return "";
if (file.metadata.linkType === "one") {
return `Temporary`;
} else if (file.metadata.linkType === "public") {
return "Public";
} else {
return "Private";
}
})();
const copyName = () => {
navigator.clipboard.writeText(file!.filename);
toast.success("Filename Copied");
};
useEffect(() => {
getFile();
}, [getFile]);
@@ -77,32 +99,48 @@ const PublicDownloadPage = () => {
</p>
</div>
</div>
<div className="w-[300px] p-4 bg-white rounded-md border shadow-lg text-xs flex flex-col space-y-3">
<div className="flex justify-between">
<span className="text-gray-primary font-normal">Type</span>
<span className="text-black font-normal ">{fileExtension}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-primary font-normal">Size</span>
<span className="text-black font-normal ">{fileSize}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-primary font-normal ">Created</span>
<span className="text-black font-normal ">{formattedDate}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-primary font-normal ">Access</span>
<span className="text-black font-normal ">
{file.metadata.link ? "Public" : "Private"}
</span>
</div>
<div className="flex justify-center">
<a
onClick={downloadItem}
className="px-5 py-2.5 inline-flex items-center justify-center border border-primary rounded-md text-primary text-sm font-medium no-underline animate mr-4 cursor-pointer hover:bg-white-hover"
<div className="w-[90%] sm:w-[500px] p-6 bg-white rounded-md animate-easy">
<div className="bg-light-primary p-6 rounded-md flex items-center space-x-2">
<input
className="rounded-md w-full text-xs h-10 p-2"
value={file.filename}
/>
<button
className="bg-primary text-white hover:bg-primary-hover text-xs w-24 min-w-20 p-1 py-3 rounded-md"
onClick={copyName}
>
Download
</a>
Copy name
</button>
</div>
<p className="mt-4">File details</p>
<div className="mt-2 text-xs space-y-2">
<div className="flex flex-row items-center">
{!file.metadata.linkType && <LockIcon className="w-5 h-5" />}
{file.metadata.linkType === "one" && (
<OneIcon className="w-5 h-5" />
)}
{file.metadata.linkType === "public" && (
<PublicIcon className="w-5 h-5" />
)}
<p className="ml-2 text-gray-500">{permissionText}</p>
</div>
<div className="flex flex-row items-center">
<StorageIcon className="w-5 h-5" />
<p className="ml-2 text-gray-500">{fileSize}</p>
</div>
<div className="flex flex-row items-center" items-center>
<CalendarIcon className="w-5 h-5" />
<p className="ml-2 text-gray-500">{formattedDate}</p>
</div>
<div className="flex w-full justify-center items-center pt-4">
<button
className="bg-primary text-white hover:bg-primary-hover text-xs p-1 py-3 rounded-md flex items-center justify-center w-40 space-x-2"
onClick={downloadItem}
>
<DownloadIcon className="w-5 h-5" />
<p>Download</p>
</button>
</div>
</div>
</div>
</div>
+7 -1
View File
@@ -123,7 +123,13 @@ const LoginPage = () => {
} catch (e) {
console.log("Create Account Error", e);
setLoadingLogin(false);
setError("Create Account Failed");
if (e instanceof AxiosError && e.response?.status === 404) {
setError("Email does not exist");
} else if (e instanceof AxiosError && e.response?.status === 403) {
setError("Email Verification Not Enabled");
} else {
setError("Create Account Failed");
}
}
};
@@ -4,6 +4,7 @@ import { ToastContainer, toast } from "react-toastify";
import { useState } from "react";
import { resetPasswordAPI } from "../../api/userAPI";
import AlertIcon from "../../icons/AlertIcon";
import { AxiosError } from "axios";
const ResetPasswordPage = () => {
const token = useParams().token!;
@@ -13,7 +14,30 @@ const ResetPasswordPage = () => {
const [error, setError] = useState("");
const navigate = useNavigate();
const isSubmitDisabled = password !== verifyPassword;
const errorMessage = (() => {
if (password.length === 0) {
return "";
}
if (password.length < 6) {
return "Password must be at least 6 characters";
} else if (password.length > 256) {
return "Password must be less than 256 characters";
}
if (
password.length &&
verifyPassword.length &&
password !== verifyPassword
) {
return "Passwords do not match";
}
return "";
})();
const isSubmitDisabled =
!password.length || password !== verifyPassword || errorMessage;
const onSubmit = async (e: any) => {
try {
@@ -22,15 +46,18 @@ const ResetPasswordPage = () => {
await toast.promise(resetPasswordAPI(password, token), {
pending: "Resetting password...",
success: "Password Reset",
error: "Error Resetting Password",
});
setTimeout(() => {
navigate("/");
}, 1500);
} catch (e) {
if (e instanceof AxiosError && e.response?.status === 401) {
setError("Invalid token error");
} else {
setError("Reset Password Failed");
}
console.log("Reset Password Error", e);
setLoadingLogin(false);
setError("Reset Password Failed");
}
};
@@ -70,17 +97,17 @@ const ResetPasswordPage = () => {
<input
type="submit"
value="Reset"
disabled={isSubmitDisabled || loadingLogin}
disabled={!!isSubmitDisabled || loadingLogin}
className="bg-[#3c85ee] border border-[#3c85ee] hover:bg-[#326bcc] rounded-[5px] text-white text-[15px] font-medium cursor-pointer py-2 px-4 disabled:opacity-50 disabled:cursor-not-allowed"
/>
</div>
{(isSubmitDisabled || error) && (
{(error || errorMessage) && (
<div className="mt-4">
<div className="flex justify-center items-center">
<AlertIcon className="w-[20px] text-red-600 mr-2" />
<p className="text-[#637381] text-[15px]">
{isSubmitDisabled ? "Passwords do not match" : error}
{error ? error : errorMessage}
</p>
</div>
</div>
@@ -3,6 +3,7 @@ import CloseIcon from "../../icons/CloseIcon";
import classNames from "classnames";
import { toast } from "react-toastify";
import { changePasswordAPI } from "../../api/userAPI";
import { AxiosError } from "axios";
interface SettingsChangePasswordPopupProps {
closePopup: () => void;
@@ -44,9 +45,15 @@ const SettingsChangePasswordPopup: React.FC<
if (newPassword.length < 6) {
return "Password must be at least 6 characters";
} else if (newPassword.length > 256) {
return "Password must be less than 256 characters";
}
if (verifyNewPassword.length !== 0 && newPassword !== verifyNewPassword) {
if (
newPassword.length &&
verifyNewPassword.length &&
newPassword !== verifyNewPassword
) {
return "Passwords do not match";
}
@@ -60,10 +67,14 @@ const SettingsChangePasswordPopup: React.FC<
await toast.promise(changePasswordAPI(currentPassword, newPassword), {
pending: "Changing password...",
success: "Password Changed",
error: "Error Changing Password",
});
closePopup();
} catch (e) {
if (e instanceof AxiosError && e.response?.status === 401) {
toast.error("Incorrect password");
} else {
toast.error("Error changing password");
}
console.log("Error changing password", e);
} finally {
setLoadingChangePassword(false);