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 && }
))}
@@ -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
-
+ exam &&
+ typeof partIndex !== "undefined" &&
+ exam.module === "level" &&
+ typeof exam.parts[0].intro === "string" &&
+ questionIndex === 0 &&
+ partIndex === 0
+ }>
Back
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 - {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 - {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
);
}