diff --git a/src/components/Solutions/MultipleChoice.tsx b/src/components/Solutions/MultipleChoice.tsx index 5a589336..708c497f 100644 --- a/src/components/Solutions/MultipleChoice.tsx +++ b/src/components/Solutions/MultipleChoice.tsx @@ -1,11 +1,11 @@ /* eslint-disable @next/next/no-img-element */ -import { MultipleChoiceExercise, MultipleChoiceQuestion, ShuffleMap } from "@/interfaces/exam"; +import {MultipleChoiceExercise, MultipleChoiceQuestion, ShuffleMap} from "@/interfaces/exam"; import useExamStore from "@/stores/examStore"; import clsx from "clsx"; import reactStringReplace from "react-string-replace"; -import { CommonProps } from "."; +import {CommonProps} from "."; import Button from "../Low/Button"; -import { v4 } from "uuid"; +import {v4} from "uuid"; function Question({ id, @@ -14,12 +14,12 @@ function Question({ solution, options, userSolution, -}: MultipleChoiceQuestion & { userSolution: string | undefined; onSelectOption?: (option: string) => void; showSolution?: boolean }) { - const { userSolutions } = useExamStore((state) => state); +}: MultipleChoiceQuestion & {userSolution: string | undefined; onSelectOption?: (option: string) => void; showSolution?: boolean}) { + const {userSolutions} = useExamStore((state) => state); const questionShuffleMap = userSolutions.reduce((foundMap, userSolution) => { if (foundMap) return foundMap; - return userSolution.shuffleMaps?.find(map => map.questionID === id) || null; + return userSolution.shuffleMaps?.find((map) => map.questionID === id) || null; }, null as ShuffleMap | null); const newSolution = questionShuffleMap ? questionShuffleMap?.map[solution] : solution; @@ -33,14 +33,14 @@ function Question({ const optionColor = (option: string) => { if (option === newSolution && !userSolution) { - return "!border-mti-gray-davy !text-mti-gray-davy"; + return "!bg-mti-gray-davy !text-white"; } if (option === newSolution) { - return "!border-mti-purple-light !text-mti-purple-light"; + return "!bg-mti-purple-light !text-white"; } - return userSolution === option ? "!border-mti-rose-light !text-mti-rose-light" : ""; + return userSolution === option ? "!bg-mti-rose-light !text-white" : ""; }; return ( @@ -50,7 +50,10 @@ function Question({ ) : ( <> - {id} - {renderPrompt(prompt).filter((x) => x?.toString() !== "")} + {id} -{" "} + + {renderPrompt(prompt).filter((x) => x?.toString() !== "")}{" "} + )} @@ -63,7 +66,9 @@ function Question({ "flex flex-col items-center border border-mti-gray-platinum p-4 px-8 rounded-xl gap-4 cursor-pointer bg-white relative select-none", optionColor(option!.id), )}> - {option?.id} + + {option?.id} + {"src" in option && {`Option} ))} @@ -71,7 +76,10 @@ function Question({ options.map((option) => (
+ className={clsx( + "flex border p-4 rounded-xl gap-2 cursor-pointer bg-white text-base select-none", + optionColor(option!.id), + )}> {option?.id}. {option?.text}
@@ -81,32 +89,30 @@ function Question({ ); } -export default function MultipleChoice({ id, type, prompt, questions, userSolutions, onNext, onBack }: MultipleChoiceExercise & CommonProps) { - const { questionIndex, setQuestionIndex, partIndex, exam } = useExamStore((state) => state); +export default function MultipleChoice({id, type, prompt, questions, userSolutions, onNext, onBack}: MultipleChoiceExercise & CommonProps) { + const {questionIndex, setQuestionIndex, partIndex, exam} = useExamStore((state) => state); const stats = useExamStore((state) => state.userSolutions); const calculateScore = () => { const total = questions.length; const questionShuffleMap = stats.find((x) => x.exercise == id)?.shuffleMaps; - const correct = userSolutions.filter( - (x) => { - if (questionShuffleMap) { - const shuffleMap = questionShuffleMap.find((y) => y.questionID === x.question) - const originalSol = questions.find((y) => y.id.toString() === x.question.toString())?.solution!; - return x.option == shuffleMap?.map[originalSol] - } else { - return questions.find((y) => y.id.toString() === x.question.toString())?.solution === x.option || false - } - }, - ).length; - const missing = total - userSolutions.filter((x) => questions.find((y) => x.question.toString() === y.id.toString())).length; - return { total, correct, missing }; + const correct = userSolutions.filter((x) => { + if (questionShuffleMap) { + const shuffleMap = questionShuffleMap.find((y) => y.questionID === x.question); + const originalSol = questions.find((y) => y.id.toString() === x.question.toString())?.solution!; + return x.option == shuffleMap?.map[originalSol]; + } else { + return questions.find((y) => y.id.toString() === x.question.toString())?.solution === x.option || false; + } + }).length; + const missing = total - userSolutions.filter((x) => questions.find((y) => y.id.toString() === x.question.toString())).length; + return {total, correct, missing}; }; const next = () => { if (questionIndex === questions.length - 1) { - onNext({ exercise: id, solutions: userSolutions, score: calculateScore(), type }); + onNext({exercise: id, solutions: userSolutions, score: calculateScore(), type}); } else { setQuestionIndex(questionIndex + 1); } @@ -114,7 +120,7 @@ export default function MultipleChoice({ id, type, prompt, questions, userSoluti const back = () => { if (questionIndex === 0) { - onBack({ exercise: id, solutions: userSolutions, score: calculateScore(), type }); + onBack({exercise: id, solutions: userSolutions, score: calculateScore(), type}); } else { setQuestionIndex(questionIndex - 1); } @@ -149,11 +155,19 @@ export default function MultipleChoice({ id, type, prompt, questions, userSoluti
- diff --git a/src/pages/(admin)/BatchCodeGenerator.tsx b/src/pages/(admin)/BatchCodeGenerator.tsx index 313264eb..ac6764b4 100644 --- a/src/pages/(admin)/BatchCodeGenerator.tsx +++ b/src/pages/(admin)/BatchCodeGenerator.tsx @@ -55,7 +55,7 @@ const USER_TYPE_PERMISSIONS: { }, }; -export default function BatchCodeGenerator({user}: {user: User}) { +export default function BatchCodeGenerator({user, onFinish}: {user: User; onFinish: () => void}) { const [infos, setInfos] = useState<{email: string; name: string; passport_id: string}[]>([]); const [isLoading, setIsLoading] = useState(false); const [expiryDate, setExpiryDate] = useState( @@ -165,6 +165,8 @@ export default function BatchCodeGenerator({user}: {user: User}) { )} codes and they have been notified by e-mail!`, {toastId: "success"}, ); + + onFinish(); return; } diff --git a/src/pages/(admin)/BatchCreateUser.tsx b/src/pages/(admin)/BatchCreateUser.tsx index 4453bead..bf08962a 100644 --- a/src/pages/(admin)/BatchCreateUser.tsx +++ b/src/pages/(admin)/BatchCreateUser.tsx @@ -61,7 +61,7 @@ const USER_TYPE_PERMISSIONS: { }, }; -export default function BatchCreateUser({user}: {user: User}) { +export default function BatchCreateUser({user, onFinish}: {user: User; onFinish: () => void}) { const [infos, setInfos] = useState< { email: string; @@ -159,6 +159,7 @@ export default function BatchCreateUser({user}: {user: User}) { try { for (const newUser of newUsers) await axios.post("/api/make_user", {...newUser, type, expiryDate}); toast.success(`Successfully added ${newUsers.length} user(s)!`); + onFinish(); } catch { toast.error("Something went wrong, please try again later!"); } finally { diff --git a/src/pages/(admin)/CodeGenerator.tsx b/src/pages/(admin)/CodeGenerator.tsx index c8fbcfe3..cbd1b5fb 100644 --- a/src/pages/(admin)/CodeGenerator.tsx +++ b/src/pages/(admin)/CodeGenerator.tsx @@ -48,7 +48,7 @@ const USER_TYPE_PERMISSIONS: { }, }; -export default function CodeGenerator({user}: {user: User}) { +export default function CodeGenerator({user, onFinish}: {user: User; onFinish: () => void}) { const [generatedCode, setGeneratedCode] = useState(); const [expiryDate, setExpiryDate] = useState( user?.subscriptionExpirationDate ? moment(user.subscriptionExpirationDate).toDate() : null, diff --git a/src/pages/(admin)/UserCreator.tsx b/src/pages/(admin)/UserCreator.tsx index a0b955f5..a5a61db4 100644 --- a/src/pages/(admin)/UserCreator.tsx +++ b/src/pages/(admin)/UserCreator.tsx @@ -54,7 +54,7 @@ const USER_TYPE_PERMISSIONS: { }, }; -export default function UserCreator({user}: {user: User}) { +export default function UserCreator({user, onFinish}: {user: User; onFinish: () => void}) { const [name, setName] = useState(); const [email, setEmail] = useState(); const [phone, setPhone] = useState(); @@ -118,6 +118,7 @@ export default function UserCreator({user}: {user: User}) { .post("/api/make_user", body) .then(() => { toast.success("That user has been created!"); + onFinish(); setName(""); setEmail(""); diff --git a/src/pages/(status)/PaymentDue.tsx b/src/pages/(status)/PaymentDue.tsx index b081db1d..c8d90612 100644 --- a/src/pages/(status)/PaymentDue.tsx +++ b/src/pages/(status)/PaymentDue.tsx @@ -160,49 +160,54 @@ export default function PaymentDue({user, hasExpired = false, reload}: Props) {
)} - {!isIndividual() && user.type === "corporate" && user?.corporateInformation.payment && ( -
- - To add to your use of EnCoach and that of your students and teachers, please pay your designated package below: - -
-
- EnCoach's Logo - EnCoach - {user.corporateInformation?.monthlyDuration} Months -
-
- - {user.corporateInformation.payment.value} {user.corporateInformation.payment.currency} - - { - setIsLoading(false); - setTimeout(reload, 500); - }} - /> -
-
- This includes: -
    -
  • - - Allow a total of {user.corporateInformation.companyInformation.userAmount} students and teachers to - use EnCoach -
  • -
  • - Train their abilities for the IELTS exam
  • -
  • - Gain insights into your students' weaknesses and strengths
  • -
  • - Allow them to correctly prepare for the exam
  • -
+ {!isIndividual() && + (user?.type === "corporate" || user?.type === "mastercorporate") && + user?.corporateInformation.payment && ( +
+ + To add to your use of EnCoach and that of your students and teachers, please pay your designated package + below: + +
+
+ EnCoach's Logo + + EnCoach - {user.corporateInformation?.monthlyDuration} Months + +
+
+ + {user.corporateInformation.payment.value} {user.corporateInformation.payment.currency} + + { + setIsLoading(false); + setTimeout(reload, 500); + }} + /> +
+
+ This includes: +
    +
  • + - Allow a total of {user.corporateInformation.companyInformation.userAmount} students and teachers + to use EnCoach +
  • +
  • - Train their abilities for the IELTS exam
  • +
  • - Gain insights into your students' weaknesses and strengths
  • +
  • - Allow them to correctly prepare for the exam
  • +
+
-
- )} - {!isIndividual() && user.type !== "corporate" && ( + )} + {!isIndividual() && !(user?.type === "corporate" || user?.type === "mastercorporate") && (
You are not the person in charge of your time credits, please contact your administrator about this situation. @@ -213,17 +218,19 @@ export default function PaymentDue({user, hasExpired = false, reload}: Props) {
)} - {!isIndividual() && user.type === "corporate" && !user.corporateInformation.payment && ( -
- - An admin nor your agent have yet set the price intended to your requirements in terms of the amount of users you - desire and your expected monthly duration. - - - Please try again later or contact your agent or an admin, thank you for your patience. - -
- )} + {!isIndividual() && + (user?.type === "corporate" || user?.type === "mastercorporate") && + !user.corporateInformation.payment && ( +
+ + An admin nor your agent have yet set the price intended to your requirements in terms of the amount of users + you desire and your expected monthly duration. + + + Please try again later or contact your agent or an admin, thank you for your patience. + +
+ )}
) : ( diff --git a/src/pages/api/make_user.ts b/src/pages/api/make_user.ts index a8ceab07..1b0fde1f 100644 --- a/src/pages/api/make_user.ts +++ b/src/pages/api/make_user.ts @@ -6,6 +6,7 @@ import {sessionOptions} from "@/lib/session"; import {v4} from "uuid"; import {CorporateUser, Group} from "@/interfaces/user"; import {createUserWithEmailAndPassword, getAuth} from "firebase/auth"; +import ShortUniqueId from "short-unique-id"; const DEFAULT_DESIRED_LEVELS = { reading: 9, @@ -73,7 +74,22 @@ async function post(req: NextApiRequest, res: NextApiResponse) { subscriptionExpirationDate: expiryDate || null, }; + const uid = new ShortUniqueId(); + const code = uid.randomUUID(6); + await setDoc(doc(db, "users", userId), user); + await setDoc(doc(db, "codes", code), { + code, + creator: maker.id, + expiryDate, + type, + creationDate: new Date(), + userId, + email: email.toLowerCase(), + name: req.body.name, + passport_id, + }); + if (type === "corporate") { const defaultTeachersGroup: Group = { admin: userId, @@ -110,6 +126,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) { if (!corporateSnapshot.empty) { const corporateUser = corporateSnapshot.docs[0].data() as CorporateUser; + await setDoc(doc(db, "codes", code), {creator: corporateUser.id}, {merge: true}); const q = query( collection(db, "groups"), @@ -124,7 +141,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) { const participants: string[] = doc.get("participants"); if (!participants.includes(userId)) { - updateDoc(doc.ref, { + await updateDoc(doc.ref, { participants: [...participants, userId], }); } diff --git a/src/pages/settings.tsx b/src/pages/settings.tsx index d1918448..0276d94d 100644 --- a/src/pages/settings.tsx +++ b/src/pages/settings.tsx @@ -72,22 +72,25 @@ export default function Admin() { {user && ( setModalOpen(undefined)}> - + setModalOpen(undefined)} /> setModalOpen(undefined)}> - + setModalOpen(undefined)} /> setModalOpen(undefined)}> - + setModalOpen(undefined)} /> setModalOpen(undefined)}> - + setModalOpen(undefined)} /> setModalOpen(undefined)}> mutate({user: user.id, steps})} + mutate={(steps) => { + mutate({user: user.id, steps}); + setModalOpen(undefined); + }} /> diff --git a/src/utils/propagate.user.changes.ts b/src/utils/propagate.user.changes.ts index c0346741..1ec882fe 100644 --- a/src/utils/propagate.user.changes.ts +++ b/src/utils/propagate.user.changes.ts @@ -13,7 +13,7 @@ export const propagateStatusChange = (userId: string, status: UserStatus) => const user = docUser.data() as User; // only update the status of the user's groups if the user is a corporate user - if (user.type === "corporate") { + if (user.type === "corporate" || user.type === "mastercorporate") { getDocs(query(collection(db, "groups"), where("admin", "==", userId))).then(async (userGroupsRef) => { const userGroups = userGroupsRef.docs.map((x) => x.data()); diff --git a/src/utils/users.be.ts b/src/utils/users.be.ts index 334a5d8e..b50467fb 100644 --- a/src/utils/users.be.ts +++ b/src/utils/users.be.ts @@ -42,7 +42,7 @@ export async function getUserBalance(user: User) { const groups = await getGroupsForUser(user.id); const participants = uniq(groups.flatMap((x) => x.participants)); - if (user.type === "corporate") return participants.length + codes.length; + if (user.type === "corporate") return participants.length + codes.filter((x) => !participants.includes(x.userId || "")).length; const participantUsers = await Promise.all(participants.map(getUser)); const corporateUsers = participantUsers.filter((x) => x.type === "corporate") as CorporateUser[]; @@ -50,6 +50,6 @@ export async function getUserBalance(user: User) { return ( corporateUsers.reduce((acc, curr) => acc + curr.corporateInformation?.companyInformation?.userAmount || 0, 0) + corporateUsers.length + - codes.length + codes.filter((x) => !participants.includes(x.userId || "") && !corporateUsers.map((u) => u.id).includes(x.userId || "")).length ); }