From dcd507fba7218d27809486b3b667344779120c81 Mon Sep 17 00:00:00 2001 From: Thea Nguyen Date: Wed, 3 Jul 2024 18:11:09 -0600 Subject: [PATCH 1/2] Fixed InputCode, InputField, Login Modal components --- src/App.tsx | 20 +- src/components/Button.tsx | 2 +- src/components/InputCode.tsx | 232 +++++++++++++----------- src/components/InputField.tsx | 12 +- src/components/LoginModal.tsx | 82 ++++----- src/routes/admin/AdminLogin.tsx | 4 +- src/routes/caregiver/CaregiverLogin.tsx | 59 ++++++ 7 files changed, 251 insertions(+), 160 deletions(-) create mode 100644 src/routes/caregiver/CaregiverLogin.tsx diff --git a/src/App.tsx b/src/App.tsx index 724f929..971c786 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -22,11 +22,11 @@ import MediaUploadStatusTestRoute from "./routes/test/MediaUploadStatusTestRoute import ModalTestRoute from "@/routes/test/ModalTestRoute"; import GalleryTestRoute from "./routes/test/GalleryTestRoute"; import { MemberInformation } from "./components/MemberInformation"; -import { LoginModal } from "./components/LoginModal"; import MemberProfilePictureTest from "@/routes/test/MemberProfilePictureTest"; // routes import AdminLogin from "./routes/admin/AdminLogin"; +import CaregiverLogin from "./routes/caregiver/CaregiverLogin"; const router = createBrowserRouter([ { @@ -122,12 +122,12 @@ const router = createBrowserRouter([ element: , }, - // Admin routes + // Admin Routes { path: "/admin", element: (
-

Home Page

+

Admin Home Page

), }, @@ -135,6 +135,20 @@ const router = createBrowserRouter([ path: "/admin/login", element: , }, + + // Caregiver Routes + { + path: "/", + element: ( +
+

Caregiver Home Page

+
+ ), + }, + { + path: "/login", + element: , + }, ]); function App() { diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 712e4da..4d83cca 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -17,7 +17,7 @@ const Button: React.FC = ({ onClick, rounded = false, fill = true, - disabled = false, + disabled, fontSize = "1em", className = null, }) => { diff --git a/src/components/InputCode.tsx b/src/components/InputCode.tsx index 4706da1..060d927 100644 --- a/src/components/InputCode.tsx +++ b/src/components/InputCode.tsx @@ -6,23 +6,25 @@ import { WarningCircle } from "@phosphor-icons/react"; //#region Interfaces interface InputCodeProps { - /** - * The label of the input field. - * Defaults to an empty string if not provided. - */ - label?: string; + /** + * The label of the input field. + * Defaults to an empty string if not provided. + */ + label?: string; - /** - * Indicates whether the input field is required. - * If true, an asterisk will be displayed after the label, - * and the text length will be checked to ensure it is not 0. - */ - required: boolean; - className?: string; + /** + * Indicates whether the input field is required. + * If true, an asterisk will be displayed after the label, + * and the text length will be checked to ensure it is not 0. + */ + required: boolean; - // set Field value - input: Record; - setInput: React.Dispatch>>; + error: boolean; + className?: string; + + // set Field value + setError: React.Dispatch>; + setInput: React.Dispatch>>; } //#endregion @@ -38,101 +40,123 @@ interface InputCodeProps { * @returns {JSX.Element} The rendered input code component. */ export function InputCode({ - label = "", - required, - className, - input, - setInput, + label = "", + required, + error, + className, + setError, + setInput, }: InputCodeProps): JSX.Element { - const inputRefs = Array.from({ length: 6 }, () => - useRef(null) - ); + const inputRefs = Array.from({ length: 6 }, () => + useRef(null), + ); + + const [value, setValue] = useState(""); + const [showError, setShowError] = useState(false); + + //#region Functions + /** + * This function handles inputs based on the selected key. + * @param {React.KeyboardEvent} e - The keyboard event. + * @param {number} index - The index of the input field. + */ + const handleKeyDown = ( + e: React.KeyboardEvent, + index: number, + ) => { + setValue(e.key.toString()); + + // Erase the value of the current input field when the backspace key is pressed + if (e.key === "Backspace") { + if (inputRefs[index]?.current?.value === "" && index > 0) + inputRefs[index - 1]?.current?.focus(); + setValue(""); + } - const [value, setValue] = useState(""); - const [error, setError] = useState(false); + // Enter the value of the current input + else { + if (index <= 5 && value != "") { + // inputRefs[index + 1]?.current?.focus(); + inputRefs[index + 1]?.current?.select(); + } + } + }; - //#region useEffect - useEffect(() => { - checkError(); - }, [inputRefs]); - //#endregion + /** + * This function checks if there is an error in the input field. + * @returns None + */ + const checkError = (index: number) => { + setShowError(true); + inputRefs[index]?.current?.focus(); - //#region Functions - /** - * This function handles inputs based on the selected key. - * @param {React.KeyboardEvent} e - The keyboard event. - * @param {number} index - The index of the input field. - */ - const handleKeyDown = ( - e: React.KeyboardEvent, - index: number - ) => { - if (e.key === "Backspace" && index > 0) { - inputRefs[index]?.current?.value === "" - ? inputRefs[index - 1]?.current?.focus() - : setInput((prev) => ({ ...prev, [index]: "" })); - } else if (e.key === "Tab" && index > 0 && index < 5 && value !== "") { - setInput((prev) => ({ ...prev, [index]: value })); - setValue(""); - } else if (index < 5 && value !== "") { - setInput((prev) => ({ ...prev, [index]: value })); - inputRefs[index + 1]?.current?.focus(); - setValue(""); - } - }; + inputRefs.forEach((input) => { + if (input.current?.value == "") { + setError(true); + } else { + setError(false); + } + }); + }; - /** - * This function checks if there is an error in the input field. - * @returns None - */ - const checkError = () => { - inputRefs.forEach((input) => { - if (input.current?.value == "") { - setError(true); - } else { - setError(false); - } - }); - }; - //#endregion + /** + * This function handles the change event of the input field. + * @param {React.ChangeEvent} e - The change event. + * @returns None + */ + const handleInputChange = (index: number) => { + checkError(index); + setInput({ + 0: inputRefs[0]?.current?.value || "", + 1: inputRefs[1]?.current?.value || "", + 2: inputRefs[2]?.current?.value || "", + 3: inputRefs[3]?.current?.value || "", + 4: inputRefs[4]?.current?.value || "", + 5: inputRefs[5]?.current?.value || "", + }); + }; + //#endregion - return ( -
- {/* Title section */} -
-

{label}

- {required &&

*

} -
+ return ( +
+ {/* Title section */} +
+

{label}

+ {required &&

*

} +
- {/* Input Code Section */} -
- {[0, 1, 2, 3, 4, 5].map((_, index) => ( - setValue(e.target.value)} - onKeyDown={(e) => handleKeyDown(e, index)} - /> - ))} -
+ {/* Input Code Section */} +
+ {[0, 1, 2, 3, 4, 5].map((_, index) => ( + checkError(index)} + onChange={(e) => handleInputChange(index)} + onKeyDown={(e) => handleKeyDown(e, index)} + /> + ))} +
- {/* Error Message */} - {error && ( -
- -

Error passcode

-
- )} -
- ); + {/* Error Message */} + {showError && error && ( +
+ +

Error passcode

+
+ )} +
+ ); } diff --git a/src/components/InputField.tsx b/src/components/InputField.tsx index 32cbaba..3747d49 100644 --- a/src/components/InputField.tsx +++ b/src/components/InputField.tsx @@ -10,12 +10,14 @@ export interface InputFieldProps { placeholder?: string; // set Field value + setError: React.Dispatch>; setInput: React.Dispatch>; } function InputField({ type, error, + setError, label, required, placeholder, @@ -32,16 +34,20 @@ function InputField({ setInput(inputRef.current.value); if (inputRef.current.value.trim() === "" && required === true) { setShowError(true); + setError(true); } if (type === "email") { const email = inputRef.current.value.trim(); const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/; const valid = emailRegex.test(email); - setShowError(!valid); + setError(!valid); } else if (type === "password") { const password = inputRef.current.value.trim(); - password.length < 6 ? setShowError(true) : setShowError(false); + password.length < 6 ? setError(true) : setError(false); + } else if (type === "text") { + const text = inputRef.current.value.trim(); + text.length === 0 ? setError(true) : setError(false); } } } @@ -84,7 +90,7 @@ function InputField({ {/* Error */} - {showError && ( + {showError && error && (
>; - - /** - * The password of the user. - */ - password?: string; + onClick: React.MouseEventHandler; + setEmail?: React.Dispatch>; + setLastName?: React.Dispatch>; setPassword?: React.Dispatch>; - - /** - * The passcode of the user. - */ - passcode?: Record; setPasscode?: React.Dispatch>>; - - /** */ - onClick: React.MouseEventHandler; } //#endregion @@ -53,10 +41,8 @@ interface LoginModalProps { * @param {string} title - The title of the login modal. * @param {string} className - Additional TailwindCSS. * @param {string} type - The type of the login modal. - * @param {string} email - The email of the user. - * @param {string} password - The password of the user. - * @param {Record} passcode - The passcode of the user. * @param {Function} setEmail - Set the email of the user. + * @param {Function} setLastName - Set the last name of the user. * @param {Function} setPassword - Set the password of the user. * @param {Function} setPasscode - Set the passcode of the user. * @returns @@ -65,23 +51,14 @@ const LoginModal = ({ className, title, type, - email, setEmail, - password, + setLastName, setPassword, - passcode, setPasscode, onClick, }: LoginModalProps) => { - const [validEmail, setValidEmail] = useState(false); - // const [passcode, setPasscode] = useState>({ - // 0: "", - // 1: "", - // 2: "", - // 3: "", - // 4: "", - // 5: "", - // }); + const [invalidUsername, setInvalidUsername] = useState(true); + const [invalidPassword, setInvalidPassword] = useState(true); return (
- {/*Username field */} - + {/* Username field */} + {type === "admin" ? ( + + ) : ( + + )} {/* Password field */} {type === "admin" ? ( ) : ( )} @@ -133,9 +125,7 @@ const LoginModal = ({ {/* Login Button */}
diff --git a/src/routes/admin/AdminLogin.tsx b/src/routes/admin/AdminLogin.tsx index 858cb9a..6b530ae 100644 --- a/src/routes/admin/AdminLogin.tsx +++ b/src/routes/admin/AdminLogin.tsx @@ -7,7 +7,7 @@ import "react-toastify/dist/ReactToastify.css"; import { initializeApp } from "firebase/app"; import { getAuth, signInWithEmailAndPassword } from "firebase/auth"; -import { LoginModal } from "@/components/LoginModal.js"; +import { LoginModal } from "@/components/LoginModal"; import Toast from "@/components/Toast.js"; //#endregion @@ -68,9 +68,7 @@ export default function AdminLogin() { diff --git a/src/routes/caregiver/CaregiverLogin.tsx b/src/routes/caregiver/CaregiverLogin.tsx new file mode 100644 index 0000000..56cdcab --- /dev/null +++ b/src/routes/caregiver/CaregiverLogin.tsx @@ -0,0 +1,59 @@ +//#region imports +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { ToastContainer, toast } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; + +import { initializeApp } from "firebase/app"; +import { getFirestore } from "firebase/firestore"; + +import { LoginModal } from "@/components/LoginModal"; +import Toast from "@/components/Toast.js"; +//#endregion + +//#region firebase +// Initialize Firebase +const firebaseConfig = JSON.parse(import.meta.env.VITE_FIREBASE_CONFIG); +const app = initializeApp(firebaseConfig); + +// Initialize Cloud Firestore and get a reference to the service +const database = getFirestore(app); +//#endregion + +export default function CaregiverLogin() { + const navigate = useNavigate(); + const [lastName, setLastName] = useState(""); + const [passcode, setPasscode] = useState>({ + 0: "", + 1: "", + 2: "", + 3: "", + 4: "", + 5: "", + }); + + // sign in functionality + const signIn = () => { + console.log("Signing in as: ", lastName); + console.log("Passcode: ", passcode); + }; + + return ( +
+ + +
+ ); +} From 6bc686f5b531824e35ae9ec1b1459057eb86e45e Mon Sep 17 00:00:00 2001 From: Thea Nguyen Date: Wed, 3 Jul 2024 18:44:38 -0600 Subject: [PATCH 2/2] Added caregiver authentication using Firebase collection --- src/routes/caregiver/CaregiverLogin.tsx | 48 ++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/src/routes/caregiver/CaregiverLogin.tsx b/src/routes/caregiver/CaregiverLogin.tsx index 56cdcab..eefb4e6 100644 --- a/src/routes/caregiver/CaregiverLogin.tsx +++ b/src/routes/caregiver/CaregiverLogin.tsx @@ -5,7 +5,13 @@ import { ToastContainer, toast } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; import { initializeApp } from "firebase/app"; -import { getFirestore } from "firebase/firestore"; +import { + getFirestore, + collection, + where, + getDocs, + query, +} from "firebase/firestore"; import { LoginModal } from "@/components/LoginModal"; import Toast from "@/components/Toast.js"; @@ -32,10 +38,42 @@ export default function CaregiverLogin() { 5: "", }); - // sign in functionality - const signIn = () => { - console.log("Signing in as: ", lastName); - console.log("Passcode: ", passcode); + // Sign in functionality + const signIn = async () => { + const userLastName = lastName.trim(); + const userPasscode = Object.values(passcode).join(""); + + const q = query( + collection(database, "users"), + where("lastName", "==", userLastName), + where("passcode", "==", userPasscode), + ); + + const user = await getDocs(q); + + // User found + if (user.docs.length > 0) { + console.log("Signed in as: ", user.docs[0].data().lastName); + navigate("/"); + } + + // User not found + else { + console.log("Username", userLastName, "not found."); + toast( + , + { + toastId: "error", + style: { + background: "transparent", + boxShadow: "none", + }, + }, + ); + } }; return (