improved reset and change password pages
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user