gaojintao01
Add files using Git LFS
f8b5d42
import React, { useEffect, useState } from "react";
import System from "../../../models/system";
import { AUTH_TOKEN, AUTH_USER } from "../../../utils/constants";
import paths from "../../../utils/paths";
import showToast from "@/utils/toast";
import ModalWrapper from "@/components/ModalWrapper";
import { useModal } from "@/hooks/useModal";
import RecoveryCodeModal from "@/components/Modals/DisplayRecoveryCodeModal";
import { useTranslation } from "react-i18next";
import { t } from "i18next";
const RecoveryForm = ({ onSubmit, setShowRecoveryForm }) => {
const [username, setUsername] = useState("");
const [recoveryCodeInputs, setRecoveryCodeInputs] = useState(
Array(2).fill("")
);
const handleRecoveryCodeChange = (index, value) => {
const updatedCodes = [...recoveryCodeInputs];
updatedCodes[index] = value;
setRecoveryCodeInputs(updatedCodes);
};
const handleSubmit = (e) => {
e.preventDefault();
const recoveryCodes = recoveryCodeInputs.filter(
(code) => code.trim() !== ""
);
onSubmit(username, recoveryCodes);
};
return (
<form
onSubmit={handleSubmit}
className="flex flex-col justify-center items-center relative rounded-2xl border-none bg-theme-bg-secondary md:shadow-[0_4px_14px_rgba(0,0,0,0.25)] md:px-8 px-0 py-4 w-full md:w-fit mt-10 md:mt-0"
>
<div className="flex items-start justify-between pt-11 pb-9 w-screen md:w-full md:px-12 px-6 ">
<div className="flex flex-col gap-y-4 w-full">
<h3 className="text-4xl md:text-lg font-bold text-theme-text-primary text-center md:text-left">
{t("login.password-reset.title")}
</h3>
<p className="text-sm text-theme-text-secondary md:text-left md:max-w-[300px] px-4 md:px-0 text-center">
{t("login.password-reset.description")}
</p>
</div>
</div>
<div className="md:px-12 px-6 space-y-6 flex h-full w-full">
<div className="w-full flex flex-col gap-y-4">
<div className="flex flex-col gap-y-2">
<label className="text-white text-sm font-bold">
{t("login.multi-user.placeholder-username")}
</label>
<input
name="username"
type="text"
placeholder={t("login.multi-user.placeholder-username")}
value={username}
onChange={(e) => setUsername(e.target.value)}
className="border-none bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder focus:outline-primary-button active:outline-primary-button outline-none text-sm rounded-md p-2.5 w-full h-[48px] md:w-[300px] md:h-[34px]"
required
/>
</div>
<div className="flex flex-col gap-y-2">
<label className="text-white text-sm font-bold">
{t("login.password-reset.recovery-codes")}
</label>
{recoveryCodeInputs.map((code, index) => (
<div key={index}>
<input
type="text"
name={`recoveryCode${index + 1}`}
placeholder={t("login.password-reset.recovery-code", {
index: index + 1,
})}
value={code}
onChange={(e) =>
handleRecoveryCodeChange(index, e.target.value)
}
className="border-none bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder focus:outline-primary-button active:outline-primary-button outline-none text-sm rounded-md p-2.5 w-full h-[48px] md:w-[300px] md:h-[34px]"
required
/>
</div>
))}
</div>
</div>
</div>
<div className="flex items-center md:p-12 md:px-0 px-6 mt-12 md:mt-0 space-x-2 border-gray-600 w-full flex-col gap-y-8">
<button
type="submit"
className="md:text-primary-button md:bg-transparent md:w-[300px] text-dark-text text-sm font-bold focus:ring-4 focus:outline-none rounded-md border-[1.5px] border-primary-button md:h-[34px] h-[48px] md:hover:text-white md:hover:bg-primary-button bg-primary-button focus:z-10 w-full"
>
{t("login.password-reset.title")}
</button>
<button
type="button"
className="text-white text-sm flex gap-x-1 hover:text-primary-button hover:underline -mb-8"
onClick={() => setShowRecoveryForm(false)}
>
{t("login.password-reset.back-to-login")}
</button>
</div>
</form>
);
};
const ResetPasswordForm = ({ onSubmit }) => {
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
onSubmit(newPassword, confirmPassword);
};
return (
<form
onSubmit={handleSubmit}
className="flex flex-col justify-center items-center relative rounded-2xl bg-theme-bg-secondary md:shadow-[0_4px_14px_rgba(0,0,0,0.25)] md:px-8 px-0 py-4 w-full md:w-fit mt-10 md:mt-0"
>
<div className="flex items-start justify-between pt-11 pb-9 w-screen md:w-full md:px-12 px-6">
<div className="flex flex-col gap-y-4 w-full">
<h3 className="text-4xl md:text-2xl font-bold text-white text-center md:text-left">
Reset Password
</h3>
<p className="text-sm text-white/90 md:text-left md:max-w-[300px] px-4 md:px-0 text-center">
Enter your new password.
</p>
</div>
</div>
<div className="md:px-12 px-6 space-y-6 flex h-full w-full">
<div className="w-full flex flex-col gap-y-4">
<div>
<input
type="password"
name="newPassword"
placeholder="New Password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
required
/>
</div>
<div>
<input
type="password"
name="confirmPassword"
placeholder="Confirm Password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
required
/>
</div>
</div>
</div>
<div className="flex items-center md:p-12 md:px-0 px-6 mt-12 md:mt-0 space-x-2 border-gray-600 w-full flex-col gap-y-8">
<button
type="submit"
className="md:text-primary-button md:bg-transparent md:w-[300px] text-dark-text text-sm font-bold focus:ring-4 focus:outline-none rounded-md border-[1.5px] border-primary-button md:h-[34px] h-[48px] md:hover:text-white md:hover:bg-primary-button bg-primary-button focus:z-10 w-full"
>
Reset Password
</button>
</div>
</form>
);
};
export default function MultiUserAuth() {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [recoveryCodes, setRecoveryCodes] = useState([]);
const [downloadComplete, setDownloadComplete] = useState(false);
const [user, setUser] = useState(null);
const [token, setToken] = useState(null);
const [showRecoveryForm, setShowRecoveryForm] = useState(false);
const [showResetPasswordForm, setShowResetPasswordForm] = useState(false);
const [customAppName, setCustomAppName] = useState(null);
const {
isOpen: isRecoveryCodeModalOpen,
openModal: openRecoveryCodeModal,
closeModal: closeRecoveryCodeModal,
} = useModal();
const handleLogin = async (e) => {
setError(null);
setLoading(true);
e.preventDefault();
const data = {};
const form = new FormData(e.target);
for (var [key, value] of form.entries()) data[key] = value;
const { valid, user, token, message, recoveryCodes } =
await System.requestToken(data);
if (valid && !!token && !!user) {
setUser(user);
setToken(token);
if (recoveryCodes) {
setRecoveryCodes(recoveryCodes);
openRecoveryCodeModal();
} else {
window.localStorage.setItem(AUTH_USER, JSON.stringify(user));
window.localStorage.setItem(AUTH_TOKEN, token);
window.location = paths.home();
}
} else {
setError(message);
setLoading(false);
}
setLoading(false);
};
const handleDownloadComplete = () => setDownloadComplete(true);
const handleResetPassword = () => setShowRecoveryForm(true);
const handleRecoverySubmit = async (username, recoveryCodes) => {
const { success, resetToken, error } = await System.recoverAccount(
username,
recoveryCodes
);
if (success && resetToken) {
window.localStorage.setItem("resetToken", resetToken);
setShowRecoveryForm(false);
setShowResetPasswordForm(true);
} else {
showToast(error, "error", { clear: true });
}
};
const handleResetSubmit = async (newPassword, confirmPassword) => {
const resetToken = window.localStorage.getItem("resetToken");
if (resetToken) {
const { success, error } = await System.resetPassword(
resetToken,
newPassword,
confirmPassword
);
if (success) {
window.localStorage.removeItem("resetToken");
setShowResetPasswordForm(false);
showToast("Password reset successful", "success", { clear: true });
} else {
showToast(error, "error", { clear: true });
}
} else {
showToast("Invalid reset token", "error", { clear: true });
}
};
useEffect(() => {
if (downloadComplete && user && token) {
window.localStorage.setItem(AUTH_USER, JSON.stringify(user));
window.localStorage.setItem(AUTH_TOKEN, token);
window.location = paths.home();
}
}, [downloadComplete, user, token]);
useEffect(() => {
const fetchCustomAppName = async () => {
const { appName } = await System.fetchCustomAppName();
setCustomAppName(appName || "");
setLoading(false);
};
fetchCustomAppName();
}, []);
if (showRecoveryForm) {
return (
<RecoveryForm
onSubmit={handleRecoverySubmit}
setShowRecoveryForm={setShowRecoveryForm}
/>
);
}
if (showResetPasswordForm)
return <ResetPasswordForm onSubmit={handleResetSubmit} />;
return (
<>
<form onSubmit={handleLogin}>
<div className="flex flex-col justify-center items-center relative rounded-2xl bg-theme-bg-secondary md:shadow-[0_4px_14px_rgba(0,0,0,0.25)] md:px-12 py-12 -mt-4 md:mt-0">
<div className="flex items-start justify-between pt-11 pb-9 rounded-t">
<div className="flex items-center flex-col gap-y-4">
<div className="flex gap-x-1">
<h3 className="text-md md:text-2xl font-bold text-white text-center white-space-nowrap hidden md:block">
{t("login.multi-user.welcome")}
</h3>
<p className="text-4xl md:text-2xl font-bold bg-gradient-to-r from-[#75D6FF] via-[#FFFFFF] light:via-[#75D6FF] to-[#FFFFFF] light:to-[#75D6FF] bg-clip-text text-transparent">
{customAppName || "AnythingLLM"}
</p>
</div>
<p className="text-sm text-theme-text-secondary text-center">
{t("login.sign-in.start")} {customAppName || "AnythingLLM"}{" "}
{t("login.sign-in.end")}
</p>
</div>
</div>
<div className="w-full px-4 md:px-12">
<div className="w-full flex flex-col gap-y-4">
<div className="w-screen md:w-full md:px-0 px-6">
<input
name="username"
type="text"
placeholder={t("login.multi-user.placeholder-username")}
className="border-none bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder focus:outline-primary-button active:outline-primary-button outline-none text-sm rounded-md p-2.5 w-full h-[48px] md:w-[300px] md:h-[34px]"
required={true}
autoComplete="off"
/>
</div>
<div className="w-screen md:w-full md:px-0 px-6">
<input
name="password"
type="password"
placeholder={t("login.multi-user.placeholder-password")}
className="border-none bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder focus:outline-primary-button active:outline-primary-button outline-none text-sm rounded-md p-2.5 w-full h-[48px] md:w-[300px] md:h-[34px]"
required={true}
autoComplete="off"
/>
</div>
{error && <p className="text-red-400 text-sm">Error: {error}</p>}
</div>
</div>
<div className="flex items-center md:p-12 px-10 mt-12 md:mt-0 space-x-2 border-gray-600 w-full flex-col gap-y-8">
<button
disabled={loading}
type="submit"
className="md:text-primary-button md:bg-transparent text-dark-text text-sm font-bold focus:ring-4 focus:outline-none rounded-md border-[1.5px] border-primary-button md:h-[34px] h-[48px] md:hover:text-white md:hover:bg-primary-button bg-primary-button focus:z-10 w-full"
>
{loading
? t("login.multi-user.validating")
: t("login.multi-user.login")}
</button>
<button
type="button"
className="text-white text-sm flex gap-x-1 hover:text-primary-button hover:underline"
onClick={handleResetPassword}
>
{t("login.multi-user.forgot-pass")}?
<b>{t("login.multi-user.reset")}</b>
</button>
</div>
</div>
</form>
<ModalWrapper isOpen={isRecoveryCodeModalOpen} noPortal={true}>
<RecoveryCodeModal
recoveryCodes={recoveryCodes}
onDownloadComplete={handleDownloadComplete}
onClose={closeRecoveryCodeModal}
/>
</ModalWrapper>
</>
);
}