From 229bdc03918eef495f3b923c3bce0079340adc61 Mon Sep 17 00:00:00 2001 From: Rick Yang Date: Sat, 13 Jul 2024 02:08:03 -0700 Subject: [PATCH 1/6] Added 3 components(Drag and Drop, PasswordChecker, FixPassword) --- frontend/package-lock.json | 3 +- frontend/package.json | 2 +- frontend/src/components/BookImage.tsx | 13 +- frontend/src/components/DragDrop.tsx | 135 +++++++++++++++++++ frontend/src/components/FixPassword.tsx | 80 +++++++++++ frontend/src/components/PasswordChecker.tsx | 99 ++++++++++++++ frontend/src/util/componentEditorDefaults.ts | 25 ++++ 7 files changed, 354 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/DragDrop.tsx create mode 100644 frontend/src/components/FixPassword.tsx create mode 100644 frontend/src/components/PasswordChecker.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4b581e1c..7f8a7222 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,7 +24,7 @@ "react-error-boundary": "^4.0.13", "react-markdown": "^9.0.1", "react-router-dom": "^6.22.0", - "react-scripts": "5.0.1", + "react-scripts": "^5.0.1", "react-simple-code-editor": "^0.13.1", "react-use": "^17.5.0", "reagraph": "^4.15.10", @@ -17148,6 +17148,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", "integrity": "sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==", + "license": "MIT", "dependencies": { "@babel/core": "^7.16.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", diff --git a/frontend/package.json b/frontend/package.json index f49d891b..f8c8c4f4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,7 +19,7 @@ "react-error-boundary": "^4.0.13", "react-markdown": "^9.0.1", "react-router-dom": "^6.22.0", - "react-scripts": "5.0.1", + "react-scripts": "^5.0.1", "react-simple-code-editor": "^0.13.1", "react-use": "^17.5.0", "reagraph": "^4.15.10", diff --git a/frontend/src/components/BookImage.tsx b/frontend/src/components/BookImage.tsx index 73462b73..7764a811 100644 --- a/frontend/src/components/BookImage.tsx +++ b/frontend/src/components/BookImage.tsx @@ -32,7 +32,9 @@ import { ClothingActivity } from "./ClothingActivity"; import BookRushHour from "./BookRushHour"; import { ImageQuestion } from "./ImageQuestion"; import { DragMultiChoice } from "./DragMultiChoice"; - +import { PasswordChecker } from "./PasswordChecker"; +import { DragDrop } from "./DragDrop"; +import FixPassword from "./FixPassword"; export function BookImage({ image, page, @@ -141,6 +143,15 @@ export function BookImage({ {image === "DragMultiChoice" && ( )} + {image === "PasswordChecker" && ( + + )} + {image === "DragDrop" && ( + + )} + {image === "FixPassword" && ( + + )} ); } diff --git a/frontend/src/components/DragDrop.tsx b/frontend/src/components/DragDrop.tsx new file mode 100644 index 00000000..b198a9ab --- /dev/null +++ b/frontend/src/components/DragDrop.tsx @@ -0,0 +1,135 @@ +import { Dispatch, SetStateAction, useEffect, useState } from "react"; + +interface WordItem { + id: number; + text: string; + correctBox: string; +} + +interface DropAreaProps { + title: string; + words: WordItem[]; + onDrop: (item: WordItem) => void; + errorMessage: string | null; +} + +const DropArea = ({ title, words, onDrop, errorMessage }: DropAreaProps) => { + const handleDrop = (event: React.DragEvent) => { + event.preventDefault(); + const word = JSON.parse(event.dataTransfer.getData("word")); + onDrop(word); + }; + + const handleDragOver = (event: React.DragEvent) => { + event.preventDefault(); + }; + + return ( +
+

{title}

+ {errorMessage &&
{errorMessage}
} +
+ {words.map((word) => ( +
+ {word.text} +
+ ))} +
+
+ ); +}; + +const DraggableWord = ({ word }: { word: WordItem }) => { + const handleDragStart = (event: React.DragEvent) => { + event.dataTransfer.setData("word", JSON.stringify(word)); + }; + + return ( +
+ {word.text} +
+ ); +}; + +export function DragDrop({ + props, + setAllowNext, +}: { + props: any; + setAllowNext: Dispatch>; +}) { + const [strongWords, setStrongWords] = useState([]); + const [weakWords, setWeakWords] = useState([]); + const [wordBank, setWordBank] = useState(props.words); + const [strongErrorMessage, setStrongErrorMessage] = useState(null); + const [weakErrorMessage, setWeakErrorMessage] = useState(null); + + const boxNames = props.boxNames || { strong: "Strong", weak: "Weak" }; + + useEffect(() => { + validateWords(); + }, [strongWords, weakWords]); + + const validateWords = () => { + const allWordsValid = strongWords.every((word) => word.correctBox === boxNames.strong) && + weakWords.every((word) => word.correctBox === boxNames.weak) && + strongWords.length + weakWords.length === props.words.length; + setAllowNext(allWordsValid); + }; + + const handleDropStrong = (item: WordItem) => { + if (item.correctBox === boxNames.strong) { + setStrongWords((prev) => [...prev, item]); + setWordBank((prev) => prev.filter((word) => word.id !== item.id)); + setStrongErrorMessage(null); + } else { + setStrongErrorMessage(`Incorrect`); + } + }; + + const handleDropWeak = (item: WordItem) => { + if (item.correctBox === boxNames.weak) { + setWeakWords((prev) => [...prev, item]); + setWordBank((prev) => prev.filter((word) => word.id !== item.id)); + setWeakErrorMessage(null); + } else { + setWeakErrorMessage(`Incorrect`); + } + }; + + return ( +
+

Drag and Drop the words into the correct categories

+
+ + +
+
+

Word Bank

+
+ {wordBank.map((word) => ( + + ))} +
+
+
+ ); +} diff --git a/frontend/src/components/FixPassword.tsx b/frontend/src/components/FixPassword.tsx new file mode 100644 index 00000000..84a2112f --- /dev/null +++ b/frontend/src/components/FixPassword.tsx @@ -0,0 +1,80 @@ +import { useState, useEffect } from "react"; + +interface FixPasswordProps { + props: { + weakPasswords: string[]; + }; + setAllowNext: React.Dispatch>; +} + +const isStrongPassword = (password: string) => { + const minLength = 8; + const hasUpperCase = /[A-Z]/.test(password); + const hasLowerCase = /[a-z]/.test(password); + const hasNumber = /[0-9]/.test(password); + const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password); + + return ( + password.length >= minLength && + hasUpperCase && + hasLowerCase && + hasNumber && + hasSpecialChar + ); +}; + +const FixPassword = ({ props, setAllowNext }: FixPasswordProps) => { + const { weakPasswords } = props; + const [passwords, setPasswords] = useState(Array(weakPasswords.length).fill("")); + const [isStrong, setIsStrong] = useState(Array(weakPasswords.length).fill(false)); + const [showPasswords, setShowPasswords] = useState(Array(weakPasswords.length).fill(false)); + + useEffect(() => { + setAllowNext(isStrong.every((strength) => strength)); + }, [isStrong, setAllowNext]); + + const handleChange = (index: number, value: string) => { + const updatedPasswords = [...passwords]; + updatedPasswords[index] = value; + setPasswords(updatedPasswords); + + const updatedIsStrong = [...isStrong]; + updatedIsStrong[index] = isStrongPassword(value); + setIsStrong(updatedIsStrong); + }; + + const toggleShowPassword = (index: number) => { + const updatedShowPasswords = [...showPasswords]; + updatedShowPasswords[index] = !updatedShowPasswords[index]; + setShowPasswords(updatedShowPasswords); + }; + + return ( +
+ {weakPasswords.map((weakPassword, index) => ( +
+ {weakPassword} +
+ handleChange(index, e.target.value)} + className="border p-2 mr-2" + /> + + {isStrong[index] && ( + Success! + )} +
+
+ ))} +
+ ); +}; + +export default FixPassword; diff --git a/frontend/src/components/PasswordChecker.tsx b/frontend/src/components/PasswordChecker.tsx new file mode 100644 index 00000000..720dd7a1 --- /dev/null +++ b/frontend/src/components/PasswordChecker.tsx @@ -0,0 +1,99 @@ +import { Dispatch, SetStateAction, useEffect, useState } from "react"; + +export function PasswordChecker({ + props, + setAllowNext, +}: { + props: any; + setAllowNext: Dispatch>; +}) { + const [password, setPassword] = useState(""); + const [isValid, setIsValid] = useState(false); + const [showPassword, setShowPassword] = useState(false); + const [hasMinLength, setHasMinLength] = useState(false); + const [hasUpperCase, setHasUpperCase] = useState(false); + const [hasLowerCase, setHasLowerCase] = useState(false); + const [hasSpecialChar, setHasSpecialChar] = useState(false); + const [hasNumber, setHasNumber] = useState(false); + + useEffect(() => { + validatePassword(password, props); + }, [password, props]); + + const validatePassword = (password: string, requirements: any) => { + const minLength = password.length >= requirements.minLength; + const upperCase = requirements.requireUpperCase ? /[A-Z]/.test(password) : true; + const lowerCase = requirements.requireLowerCase ? /[a-z]/.test(password) : true; + const specialChar = requirements.requireSpecialChar ? /[!@#$%^&*(),.?":{}|<>]/.test(password) : true; + const number = requirements.requireNumber ? /\d/.test(password) : true; + + setHasMinLength(minLength); + setHasUpperCase(upperCase); + setHasLowerCase(lowerCase); + setHasSpecialChar(specialChar); + setHasNumber(number); + + setIsValid(minLength && upperCase && lowerCase && specialChar && number); + }; + + useEffect(() => { + setAllowNext(isValid); + }, [isValid, setAllowNext]); + + const toggleShowPassword = () => { + setShowPassword((prev) => !prev); + }; + + return ( +
+

Type your password below:

+
+ setPassword(e.target.value)} + className={`border p-2 ${isValid ? 'border-green-500' : 'border-red-500'} flex-grow`} + /> + +
+

+ Password must meet the following requirements: + {props.minLength && ( + +
- Minimum {props.minLength} characters long +
+ )} + {props.requireUpperCase && ( + +
- At least one uppercase letter +
+ )} + {props.requireLowerCase && ( + +
- At least one lowercase letter +
+ )} + {props.requireSpecialChar && ( + +
- At least one special character +
+ )} + {props.requireNumber && ( + +
- At least one number +
+ )} +

+ {isValid && ( +

+ Password meets all requirements! +

+ )} +
+ ); +} diff --git a/frontend/src/util/componentEditorDefaults.ts b/frontend/src/util/componentEditorDefaults.ts index e627bf05..18e2c02f 100644 --- a/frontend/src/util/componentEditorDefaults.ts +++ b/frontend/src/util/componentEditorDefaults.ts @@ -1,3 +1,4 @@ + import { Styles } from "../components/Question"; export const editorDefaults: { [key: string]: any } = { @@ -211,4 +212,28 @@ export const editorDefaults: { [key: string]: any } = { }, ], }, + PasswordChecker: { + minLength: 8, + requireUpperCase: true, + requireLowerCase: true, + requireSpecialChar: true, + requireNumber: true, + }, + DragDrop: { + words: [ + { id: 1, text: "Secure", correctBox: "Strong" }, + { id: 2, text: "123456", correctBox: "Weak" }, + { id: 3, text: "Password", correctBox: "Weak" }, + { id: 4, text: "StrongPass1!", correctBox: "Strong" }, + { id: 5, text: "WeakPass", correctBox: "Weak" }, + ], + boxNames: { + strong: "Strong", + weak: "Weak", + }, + }, + FixPassword: { + weakPasswords: ["weakPass1", "123456", "password"], + }, + }; From 7a6d9356a46e4102c03a71b3e3ffdcbdfe4614bb Mon Sep 17 00:00:00 2001 From: rickyang2023F <149544171+rickyang2023F@users.noreply.github.com> Date: Sun, 14 Jul 2024 00:11:56 -0700 Subject: [PATCH 2/6] Update DragDrop.tsx Fixed formatting --- frontend/src/components/DragDrop.tsx | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/DragDrop.tsx b/frontend/src/components/DragDrop.tsx index b198a9ab..a80fad91 100644 --- a/frontend/src/components/DragDrop.tsx +++ b/frontend/src/components/DragDrop.tsx @@ -69,21 +69,27 @@ export function DragDrop({ const [strongWords, setStrongWords] = useState([]); const [weakWords, setWeakWords] = useState([]); const [wordBank, setWordBank] = useState(props.words); - const [strongErrorMessage, setStrongErrorMessage] = useState(null); + const [strongErrorMessage, setStrongErrorMessage] = useState( + null, + ); const [weakErrorMessage, setWeakErrorMessage] = useState(null); const boxNames = props.boxNames || { strong: "Strong", weak: "Weak" }; useEffect(() => { - validateWords(); - }, [strongWords, weakWords]); - - const validateWords = () => { - const allWordsValid = strongWords.every((word) => word.correctBox === boxNames.strong) && - weakWords.every((word) => word.correctBox === boxNames.weak) && - strongWords.length + weakWords.length === props.words.length; + const allWordsValid = + strongWords.every((word) => word.correctBox === boxNames.strong) && + weakWords.every((word) => word.correctBox === boxNames.weak) && + strongWords.length + weakWords.length === props.words.length; setAllowNext(allWordsValid); - }; + }, [ + strongWords, + weakWords, + boxNames.strong, + boxNames.weak, + props.words.length, + setAllowNext, + ]); const handleDropStrong = (item: WordItem) => { if (item.correctBox === boxNames.strong) { @@ -107,7 +113,9 @@ export function DragDrop({ return (
-

Drag and Drop the words into the correct categories

+

+ Drag and Drop the words into the correct categories +

Date: Sun, 14 Jul 2024 00:12:43 -0700 Subject: [PATCH 3/6] Update PasswordChecker.tsx Fixed formatting --- frontend/src/components/PasswordChecker.tsx | 36 ++++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/PasswordChecker.tsx b/frontend/src/components/PasswordChecker.tsx index 720dd7a1..63331224 100644 --- a/frontend/src/components/PasswordChecker.tsx +++ b/frontend/src/components/PasswordChecker.tsx @@ -22,9 +22,15 @@ export function PasswordChecker({ const validatePassword = (password: string, requirements: any) => { const minLength = password.length >= requirements.minLength; - const upperCase = requirements.requireUpperCase ? /[A-Z]/.test(password) : true; - const lowerCase = requirements.requireLowerCase ? /[a-z]/.test(password) : true; - const specialChar = requirements.requireSpecialChar ? /[!@#$%^&*(),.?":{}|<>]/.test(password) : true; + const upperCase = requirements.requireUpperCase + ? /[A-Z]/.test(password) + : true; + const lowerCase = requirements.requireLowerCase + ? /[a-z]/.test(password) + : true; + const specialChar = requirements.requireSpecialChar + ? /[!@#$%^&*(),.?":{}|<>]/.test(password) + : true; const number = requirements.requireNumber ? /\d/.test(password) : true; setHasMinLength(minLength); @@ -52,7 +58,7 @@ export function PasswordChecker({ type={showPassword ? "text" : "password"} value={password} onChange={(e) => setPassword(e.target.value)} - className={`border p-2 ${isValid ? 'border-green-500' : 'border-red-500'} flex-grow`} + className={`border p-2 ${isValid ? "border-green-500" : "border-red-500"} flex-grow`} />
); From bfe6d8cbd72173f1ae03a06ad56e5ab7ca5ea675 Mon Sep 17 00:00:00 2001 From: rickyang2023F <149544171+rickyang2023F@users.noreply.github.com> Date: Sun, 14 Jul 2024 00:13:45 -0700 Subject: [PATCH 4/6] Update BookImage.tsx Fixed formatting --- frontend/src/components/BookImage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/BookImage.tsx b/frontend/src/components/BookImage.tsx index 7764a811..a05d1773 100644 --- a/frontend/src/components/BookImage.tsx +++ b/frontend/src/components/BookImage.tsx @@ -150,7 +150,7 @@ export function BookImage({ )} {image === "FixPassword" && ( - + )}
); From 4125a6487d25a73dcb4ea26610863db379bc745a Mon Sep 17 00:00:00 2001 From: rickyang2023F <149544171+rickyang2023F@users.noreply.github.com> Date: Sun, 14 Jul 2024 00:14:56 -0700 Subject: [PATCH 5/6] Update FixPassword.tsx Fixed formatting --- frontend/src/components/FixPassword.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/FixPassword.tsx b/frontend/src/components/FixPassword.tsx index 84a2112f..46f4d13a 100644 --- a/frontend/src/components/FixPassword.tsx +++ b/frontend/src/components/FixPassword.tsx @@ -25,9 +25,15 @@ const isStrongPassword = (password: string) => { const FixPassword = ({ props, setAllowNext }: FixPasswordProps) => { const { weakPasswords } = props; - const [passwords, setPasswords] = useState(Array(weakPasswords.length).fill("")); - const [isStrong, setIsStrong] = useState(Array(weakPasswords.length).fill(false)); - const [showPasswords, setShowPasswords] = useState(Array(weakPasswords.length).fill(false)); + const [passwords, setPasswords] = useState( + Array(weakPasswords.length).fill(""), + ); + const [isStrong, setIsStrong] = useState( + Array(weakPasswords.length).fill(false), + ); + const [showPasswords, setShowPasswords] = useState( + Array(weakPasswords.length).fill(false), + ); useEffect(() => { setAllowNext(isStrong.every((strength) => strength)); From 450ac507db22668aac64a848f0f030c4b78f355f Mon Sep 17 00:00:00 2001 From: rickyang2023F <149544171+rickyang2023F@users.noreply.github.com> Date: Sun, 14 Jul 2024 00:16:04 -0700 Subject: [PATCH 6/6] Update componentEditorDefaults.ts Fixed formatting --- frontend/src/util/componentEditorDefaults.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/util/componentEditorDefaults.ts b/frontend/src/util/componentEditorDefaults.ts index 18e2c02f..d21c02a4 100644 --- a/frontend/src/util/componentEditorDefaults.ts +++ b/frontend/src/util/componentEditorDefaults.ts @@ -1,4 +1,3 @@ - import { Styles } from "../components/Question"; export const editorDefaults: { [key: string]: any } = { @@ -235,5 +234,4 @@ export const editorDefaults: { [key: string]: any } = { FixPassword: { weakPasswords: ["weakPass1", "123456", "password"], }, - };