diff --git a/src/components/Diagnostic.tsx b/src/components/Diagnostic.tsx index f7b448f4..b38215d6 100644 --- a/src/components/Diagnostic.tsx +++ b/src/components/Diagnostic.tsx @@ -14,6 +14,7 @@ import {useEffect, useState} from "react"; import {BsBook, BsChevronDown, BsHeadphones, BsMegaphone, BsPen, BsQuestionSquare} from "react-icons/bs"; import {toast} from "react-toastify"; import Button from "./Low/Button"; +import ModuleLevelSelector from "./Medium/ModuleLevelSelector"; interface Props { user: User; @@ -90,140 +91,44 @@ export default function Diagnostic({onFinish}: Props) { +

What is your current IELTS level?

-
-
-
- - Reading level - - - - - - {levels.reading === -1 ? "Select your reading level" : `Level ${levels.reading}`} - - - - - {Object.values(writingMarking).map((x) => ( - - setLevels((prev) => ({...prev, reading: x}))} - className="w-full py-4 text-center cursor-pointer bg-white hover:bg-mti-gray-platinum transition ease-in-out duration-300"> - Level {x} - - - ))} - - -
-
- - Listening level - - - - - - {levels.listening === -1 ? "Select your listening level" : `Level ${levels.listening}`} - - - - - {Object.values(writingMarking).map((x) => ( - - setLevels((prev) => ({...prev, listening: x}))} - className="w-full py-5 text-center cursor-pointer bg-white hover:bg-mti-gray-platinum transition ease-in-out duration-300"> - Level {x} - - - ))} - - -
-
- - Writing level - - - - - - {levels.writing === -1 ? "Select your writing level" : `Level ${levels.writing}`} - - - - - {Object.values(writingMarking).map((x) => ( - - setLevels((prev) => ({...prev, writing: x}))} - className="w-full py-5 text-center cursor-pointer bg-white hover:bg-mti-gray-platinum transition ease-in-out duration-300"> - Level {x} - - - ))} - - -
-
- - Speaking level - - - - - - {levels.speaking === -1 ? "Select your speaking level" : `Level ${levels.speaking}`} - - - - - {Object.values(writingMarking).map((x) => ( - - setLevels((prev) => ({...prev, speaking: x}))} - className="w-full py-5 text-center cursor-pointer bg-white hover:bg-mti-gray-platinum transition ease-in-out duration-300"> - Level {x} - - - ))} - - -
-
-
-
- -
- - -
+ +
+ +
+

What is your desired IELTS level?

+ +
+ +
+
+
+ +
); diff --git a/src/components/Low/ProgressBar.tsx b/src/components/Low/ProgressBar.tsx index 341a9869..b900d8e0 100644 --- a/src/components/Low/ProgressBar.tsx +++ b/src/components/Low/ProgressBar.tsx @@ -5,12 +5,14 @@ interface Props { label: string; percentage: number; color: "red" | "rose" | "purple" | Module; + mark?: number; + markLabel?: string; useColor?: boolean; className?: string; textClassName?: string; } -export default function ProgressBar({label, percentage, color, useColor = false, className, textClassName}: Props) { +export default function ProgressBar({label, percentage, color, mark, markLabel, useColor = false, className, textClassName}: Props) { const progressColorClass: {[key in typeof color]: string} = { red: "bg-mti-red-light", rose: "bg-mti-rose-light", @@ -30,6 +32,9 @@ export default function ProgressBar({label, percentage, color, useColor = false, !useColor ? "bg-mti-gray-anti-flash" : progressColorClass[color], useColor && "bg-opacity-20", )}> + {mark && ( +
+ )}
void; +} + +export default function InviteCard({ invite, users, reload }: Props) { + const [isLoading, setIsLoading] = useState(false); + + const inviter = users.find((u) => u.id === invite.from); + const name = !inviter + ? null + : inviter.type === "corporate" + ? inviter.corporateInformation?.companyInformation?.name || inviter.name + : inviter.name; + + const decide = (decision: "accept" | "decline") => { + if (!confirm(`Are you sure you want to ${decision} this invite?`)) return; + + setIsLoading(true); + axios + .get(`/api/invites/${decision}/${invite.id}`) + .then(() => { + toast.success( + `Successfully ${decision === "accept" ? "accepted" : "declined"} the invite!`, + { toastId: "success" }, + ); + reload(); + }) + .catch((e) => { + toast.success(`Something went wrong, please try again later!`, { + toastId: "error", + }); + reload(); + }) + .finally(() => setIsLoading(false)); + }; + + return ( +
+ Invited by {name} +
+ + +
+
+ ); +} diff --git a/src/components/Medium/ModuleLevelSelector.tsx b/src/components/Medium/ModuleLevelSelector.tsx new file mode 100644 index 00000000..d45bb76b --- /dev/null +++ b/src/components/Medium/ModuleLevelSelector.tsx @@ -0,0 +1,121 @@ +import {Module} from "@/interfaces"; +import {writingMarking} from "@/utils/score"; +import {Menu} from "@headlessui/react"; +import {Dispatch, SetStateAction} from "react"; +import {BsBook, BsChevronDown, BsHeadphones, BsMegaphone, BsPen} from "react-icons/bs"; + +type Levels = {[key in Module]: number}; + +interface Props { + levels: Levels; + setLevels: Dispatch>; +} + +export default function ModuleLevelSelector({levels, setLevels}: Props) { + return ( +
+
+
+ + Reading level + + + + + + {levels.reading === -1 ? "Select your reading level" : `Level ${levels.reading}`} + + + + + {Object.values(writingMarking).map((x) => ( + + setLevels((prev) => ({...prev, reading: x}))} + className="w-full py-4 text-center cursor-pointer bg-white hover:bg-mti-gray-platinum transition ease-in-out duration-300"> + Level {x} + + + ))} + + +
+
+ + Listening level + + + + + + {levels.listening === -1 ? "Select your listening level" : `Level ${levels.listening}`} + + + + + {Object.values(writingMarking).map((x) => ( + + setLevels((prev) => ({...prev, listening: x}))} + className="w-full py-5 text-center cursor-pointer bg-white hover:bg-mti-gray-platinum transition ease-in-out duration-300"> + Level {x} + + + ))} + + +
+
+ + Writing level + + + + + + {levels.writing === -1 ? "Select your writing level" : `Level ${levels.writing}`} + + + + + {Object.values(writingMarking).map((x) => ( + + setLevels((prev) => ({...prev, writing: x}))} + className="w-full py-5 text-center cursor-pointer bg-white hover:bg-mti-gray-platinum transition ease-in-out duration-300"> + Level {x} + + + ))} + + +
+
+ + Speaking level + + + + + + {levels.speaking === -1 ? "Select your speaking level" : `Level ${levels.speaking}`} + + + + + {Object.values(writingMarking).map((x) => ( + + setLevels((prev) => ({...prev, speaking: x}))} + className="w-full py-5 text-center cursor-pointer bg-white hover:bg-mti-gray-platinum transition ease-in-out duration-300"> + Level {x} + + + ))} + + +
+
+
+ ); +} diff --git a/src/constants/serviceAccountKey.json b/src/constants/serviceAccountKey.json index 7022844b..2e8fe016 100644 --- a/src/constants/serviceAccountKey.json +++ b/src/constants/serviceAccountKey.json @@ -1,13 +1,13 @@ { "type": "service_account", - "project_id": "mti-ielts", - "private_key_id": "22b783a14c760d1215a8d1f5de0fa40a33a840e7", - "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDoNkd7s/izUBRb\nlmJYWl0xk4X9wEVJU4LKA4HPeha8RFDse4T4suVP08oCP9ODSXF5A83+IqXNMs/N\na7PtFABBAx433JrB7I4NsAUrDSjI4LeYEIqh6YzHsQvBU53HAmPChX525S4i0IBy\ncNnyXut0nmlHz5ZwCPXgqg4eN44C+m0f7sxzivcnPth/zLupnMiDAHFZrxQolWO2\n6JfozMWGw0TmCkUxngzeGBMVYmsGiKRIxEi3MWeuwjYjGO4nR1krEUlcpjCbx4UX\nxYXicJb17HOs9LTcSh9bpDWZPHKXR48hxd2cMLr+XQzw7Otwu2p8fEUOJ+CiTyNz\nlkN9p7OhAgMBAAECggEAB5DsMZdGu1X4wdazr+AK4RCG2UKkZ0wbqvgkCMX4O2xo\n7BmmtqFCmEAk+P+KJWEVW81wTu9jUl0tWOrBVzBThUrEF2seVkL+SmshsfpI6cmr\npb5lO/sTgZau1L7kGU3GQRpvKVHUl+EODFyJt2xZFOjL8qFsjAw4sbgsw1aJT6a4\nFilm6Gapi1qSKOPSlXVmi0NJ9DUtNbKaQK8/coqEJRizeXs9MORvzyKQaV8PBmWI\noEnkxahKOD48U2kmI7rT9/YsCuaP2BlGdLxvANXLjAKcrDccVZkYEH82tPtCicED\noow3i956HPdWSXQgUOU65MfGccjOmqGaGa4zUTICyQKBgQD6zLMwL9YS+n9EKZaK\nEbzRybN2d+eKbXyDJzkDi6FnSGVre2ndShsimoOtwZDLmOF/XhN79YOLJVbI124p\npAWO+WxAfe9Xy3iFEBmL4kSREA873Sd8EN5OfYS2DsN7IbjZkoaLuM8QlyXL9ZRS\nBJDVGjx+wFKRjnClcBNbVMMXiQKBgQDtBumKZS0ZCtJuBeuwLGJ1ZJtYECykIrsD\nUtQ7zxwXJzPGqZ2c5JLpHdDm/bb9nllpLsh4SpDRqxFa2H2FF8x5KWaS7JQUsS8e\ner6x5wUt6wAJqV/ZvttVrLZCa8VYn+K7bTANnkPNJZHTqBTJbxkXMDTtkwWXUN2z\nQP3N9lodWQKBgFBHiewYw9ubV3WIImnbt6cne0ymoPUMitioi3V5Epcu81fuTzrI\nZ9sxvoi19xVUwIm2oWICerLlptvvKZImsKjNajtSlHRz6wYc2zCNowkULOwqpGLw\nO1jAkOR94VDewH7UikDbTVywJSceWvXOBFZSaZ7hDQ0OnTw3ndqUTUaRAoGAd2BG\n2PPyDa28o7sJpBYGlJdSAb1LrnLre1YJHAJIZITS99hPUEhykUP6BYx80CkjYO01\n/BeZ7m9Y80cbmJ+O1Or8BT1vqyg90f0B8/mlSyYTQ8pxQupz7ydoN/WtU+BawgjQ\n7drqzPSCCHab2YPBwEMANTMZ2sbYkcJG0aekZSkCgYBbnFJm8kUy57isxHyvrci+\nR30KQl2Y9okPytF8PpLH+yNjLDoduTOHL/hZoFC0M4Gklx4wPKpsEhImIrWmG9VC\n0UrQC6TT1WoY6/S3YehVmTXo/nBPD1XTUcbF/xxUrWDjmMjnt1IlXBbIzUPD3U4P\niRXzHnXb7yi+/iRxSDts2w==\n-----END PRIVATE KEY-----\n", - "client_email": "firebase-adminsdk-dyg6p@mti-ielts.iam.gserviceaccount.com", - "client_id": "104980563453519094431", + "project_id": "storied-phalanx-349916", + "private_key_id": "c9e05f6fe413b1031a71f981160075ff4b044444", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDdgavFB63nMHyb\n38ncwijTrUmqU9UyzNJ8wlZCWAWuoz25Gng988fkKNDXnHY+ap9esHyNYg9IdSA7\nAuZeHpzTZmKiWZzFWq61KWSTgIn1JwKHGHJJdmVhTYfCe9I51cFLa5q2lTFzJ0ce\nbP7/X/7kw53odgva+M8AhDTbe60akpemgZc+LFwO0Abm7erH2HiNyjoNZzNw525L\n933PCaQwhZan04s1u0oRdVlBIBwMk+J0ojgVEpUiJOzF7gkN+UpDXujalLYdlR4q\nhkGgScXQhDYJkECC3GuvOnEo1YXGNjW9D73S6sSH+Lvqta4wW1+sTn0kB6goiQBI\n7cA1G6x3AgMBAAECggEAZPMwAX/adb7XS4LWUNH8IVyccg/63kgSteErxtiu3kRv\nYOj7W+C6fPVNGLap/RBCybjNSvIh3PfkVICh1MtG1eGXmj4VAKyvaskOmVq/hQbe\nVAuEKo7W7V2UPcKIsOsGSQUlYYjlHIIOG4O5Q1HQrRmp4cPK62Txkl6uaEkZPz4u\nbvIK2BJI8aHRwxE3Phw09blwlLqQQQ8nrhK29x5puaN+ft++IlzIOVsLz+n4kTdB\n6qkG/dhenn3K8o3+NkmSN6eNRbdJd36zXTo4Oatbvqb7r0E8vYn/3Llawo2X75zn\nec7jMHrOmcwtiu9H3PsrTWtzdSjxPHy0UtEn1HWK4QKBgQD+c/V8tAvbaUGVoZf6\ntKtDSKF6IHuY2vUO33v950mVdjrTursqOG2d+SLfSnKpc+sjDlj7/S5u4uRP+qUN\ng1rb2U7oIA7tsDa2ZTSkIx6HkPUzS+fBOxELLrbgMoJ2RLzgkiPhS95YgXJ/rYG5\nWQTehzCT5roes0RvtgM0gl3EhQKBgQDe2m7PRIU4g3RJ8HTx92B4ja8W9FVCYDG5\nPOAdZB8WB6Bvu4BJHBDLr8vDi930pKj+vYObRqBDQuILW4t8wZQJ834dnoq6EpUz\nhbVEURVBP4A/nEHrQHfq0Lp+cxThy2rw7obRQOLPETtC7p3WFgSHT6PRTcpGzCCX\n+76a30yrywKBgC/5JNtyBppDaf4QDVtTHMb+tpMT9LmI7pLzR6lDJfhr5gNtPURk\nhyY1hoGaw6t3E2n0lopL3alCVdFObDfz//lbKylQggAGLQqOYjJf/K2KgvA862Df\nBgOZtxjl7PrnUsT0SJd9elotbazsxXxwcB6UVnBMG+MV4V0+b7RCr/MRAoGBAIfp\nTcVIs7roqOZjKN9dEE/VkR/9uXW2tvyS/NfP9Ql5c0ZRYwazgCbJOwsyZRZLyek6\naWYsp5b91mA435QhdwiuoI6t30tmA+qdNBTLIpxdfvjMcoNoGPpzfBmcU/L1HW58\n+mnqGalRiAPlBQvI99ASKQWAXMnaulIWrYNEhj0LAoGBALi+QZ2pp+hDeC59ezWr\nbP1zbbONceHKGgJcevChP2k1OJyIOIqmBYeTuM4cPc5ofZYQNaMC31cs8SVeSRX1\nNTxQZmvCjMyTe/WYWYNFXdgkVz4egFXbeochCGzMYo57HV1PCkPBrARRZO8OfdDD\n8sDu//ohb7nCzceEI0DnWs13\n-----END PRIVATE KEY-----\n", + "client_email": "firebase-adminsdk-3ml0u@storied-phalanx-349916.iam.gserviceaccount.com", + "client_id": "114163760341944984396", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-dyg6p%40mti-ielts.iam.gserviceaccount.com", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-3ml0u%40storied-phalanx-349916.iam.gserviceaccount.com", "universe_domain": "googleapis.com" } diff --git a/src/dashboards/Student.tsx b/src/dashboards/Student.tsx index 780330f9..03544a19 100644 --- a/src/dashboards/Student.tsx +++ b/src/dashboards/Student.tsx @@ -1,417 +1,260 @@ import Button from "@/components/Low/Button"; import ProgressBar from "@/components/Low/ProgressBar"; +import InviteCard from "@/components/Medium/InviteCard"; import PayPalPayment from "@/components/PayPalPayment"; import ProfileSummary from "@/components/ProfileSummary"; import useAssignments from "@/hooks/useAssignments"; import useInvites from "@/hooks/useInvites"; import useStats from "@/hooks/useStats"; import useUsers from "@/hooks/useUsers"; -import { Invite } from "@/interfaces/invite"; -import { Assignment } from "@/interfaces/results"; -import { CorporateUser, User } from "@/interfaces/user"; +import {Invite} from "@/interfaces/invite"; +import {Assignment} from "@/interfaces/results"; +import {CorporateUser, User} from "@/interfaces/user"; import useExamStore from "@/stores/examStore"; -import { getExamById } from "@/utils/exams"; -import { getUserCorporate } from "@/utils/groups"; -import { - MODULE_ARRAY, - sortByModule, - sortByModuleName, -} from "@/utils/moduleUtils"; -import { averageScore, groupBySession } from "@/utils/stats"; -import { - CreateOrderActions, - CreateOrderData, - OnApproveActions, - OnApproveData, - OrderResponseBody, -} from "@paypal/paypal-js"; -import { PayPalButtons } from "@paypal/react-paypal-js"; +import {getExamById} from "@/utils/exams"; +import {getUserCorporate} from "@/utils/groups"; +import {MODULE_ARRAY, sortByModule, sortByModuleName} from "@/utils/moduleUtils"; +import {averageScore, groupBySession} from "@/utils/stats"; +import {CreateOrderActions, CreateOrderData, OnApproveActions, OnApproveData, OrderResponseBody} from "@paypal/paypal-js"; +import {PayPalButtons} from "@paypal/react-paypal-js"; import axios from "axios"; import clsx from "clsx"; -import { capitalize } from "lodash"; +import {capitalize} from "lodash"; import moment from "moment"; import Link from "next/link"; -import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; -import { - BsArrowRepeat, - BsBook, - BsClipboard, - BsFileEarmarkText, - BsHeadphones, - BsMegaphone, - BsPen, - BsPencil, - BsStar, -} from "react-icons/bs"; -import { toast } from "react-toastify"; +import {useRouter} from "next/router"; +import {useEffect, useState} from "react"; +import {BsArrowRepeat, BsBook, BsClipboard, BsFileEarmarkText, BsHeadphones, BsMegaphone, BsPen, BsPencil, BsStar} from "react-icons/bs"; +import {toast} from "react-toastify"; interface Props { - user: User; + user: User; } -export default function StudentDashboard({ user }: Props) { - const [corporateUserToShow, setCorporateUserToShow] = - useState(); +export default function StudentDashboard({user}: Props) { + const [corporateUserToShow, setCorporateUserToShow] = useState(); - const { stats } = useStats(user.id); - const { users } = useUsers(); - const { - assignments, - isLoading: isAssignmentsLoading, - reload: reloadAssignments, - } = useAssignments({ assignees: user?.id }); - const { - invites, - isLoading: isInvitesLoading, - reload: reloadInvites, - } = useInvites({ to: user.id }); + const {stats} = useStats(user.id); + const {users} = useUsers(); + const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({assignees: user?.id}); + const {invites, isLoading: isInvitesLoading, reload: reloadInvites} = useInvites({to: user.id}); - const router = useRouter(); + const router = useRouter(); - const setExams = useExamStore((state) => state.setExams); - const setShowSolutions = useExamStore((state) => state.setShowSolutions); - const setUserSolutions = useExamStore((state) => state.setUserSolutions); - const setSelectedModules = useExamStore((state) => state.setSelectedModules); - const setAssignment = useExamStore((state) => state.setAssignment); + const setExams = useExamStore((state) => state.setExams); + const setShowSolutions = useExamStore((state) => state.setShowSolutions); + const setUserSolutions = useExamStore((state) => state.setUserSolutions); + const setSelectedModules = useExamStore((state) => state.setSelectedModules); + const setAssignment = useExamStore((state) => state.setAssignment); - useEffect(() => { - getUserCorporate(user.id).then(setCorporateUserToShow); - }, [user]); + useEffect(() => { + getUserCorporate(user.id).then(setCorporateUserToShow); + }, [user]); - const startAssignment = (assignment: Assignment) => { - const examPromises = assignment.exams - .filter((e) => e.assignee === user.id) - .map((e) => getExamById(e.module, e.id)); + const startAssignment = (assignment: Assignment) => { + const examPromises = assignment.exams.filter((e) => e.assignee === user.id).map((e) => getExamById(e.module, e.id)); - Promise.all(examPromises).then((exams) => { - if (exams.every((x) => !!x)) { - setUserSolutions([]); - setShowSolutions(false); - setExams(exams.map((x) => x!).sort(sortByModule)); - setSelectedModules( - exams - .map((x) => x!) - .sort(sortByModule) - .map((x) => x!.module), - ); - setAssignment(assignment); + Promise.all(examPromises).then((exams) => { + if (exams.every((x) => !!x)) { + setUserSolutions([]); + setShowSolutions(false); + setExams(exams.map((x) => x!).sort(sortByModule)); + setSelectedModules( + exams + .map((x) => x!) + .sort(sortByModule) + .map((x) => x!.module), + ); + setAssignment(assignment); - router.push("/exercises"); - } - }); - }; + router.push("/exercises"); + } + }); + }; - const InviteCard = (invite: Invite) => { - const [isLoading, setIsLoading] = useState(false); + return ( + <> + {corporateUserToShow && ( +
+ Linked to: {corporateUserToShow?.corporateInformation?.companyInformation.name || corporateUserToShow.name} +
+ )} + , + value: Object.keys(groupBySession(stats)).length, + label: "Exams", + }, + { + icon: , + value: stats.length, + label: "Exercises", + }, + { + icon: , + value: `${stats.length > 0 ? averageScore(stats) : 0}%`, + label: "Average Score", + }, + ]} + /> - const inviter = users.find((u) => u.id === invite.from); - const name = !inviter - ? null - : inviter.type === "corporate" - ? inviter.corporateInformation?.companyInformation?.name || inviter.name - : inviter.name; + {/* Bio */} +
+ Bio + + {user.bio || "Your bio will appear here, you can change it by clicking on your name in the top right corner."} + +
- const decide = (decision: "accept" | "decline") => { - if (!confirm(`Are you sure you want to ${decision} this invite?`)) return; + {/* Assignments */} +
+
+
+ Assignments + +
+
+ + {assignments.filter((a) => moment(a.endDate).isSameOrAfter(moment())).length === 0 && + "Assignments will appear here. It seems that for now there are no assignments for you."} + {assignments + .filter((a) => moment(a.endDate).isSameOrAfter(moment())) + .sort((a, b) => moment(a.startDate).diff(b.startDate)) + .map((assignment) => ( +
r.user).includes(user.id) && "border-mti-green-light", + )} + key={assignment.id}> +
+

{assignment.name}

+ + {moment(assignment.startDate).format("DD/MM/YY, HH:mm")} + - + {moment(assignment.endDate).format("DD/MM/YY, HH:mm")} + +
+
+
+ {assignment.exams + .filter((e) => e.assignee === user.id) + .map((e) => e.module) + .sort(sortByModuleName) + .map((module) => ( +
+ {module === "reading" && } + {module === "listening" && } + {module === "writing" && } + {module === "speaking" && } + {module === "level" && } +
+ ))} +
+ {!assignment.results.map((r) => r.user).includes(user.id) && ( + <> +
+ +
+ + + )} + {assignment.results.map((r) => r.user).includes(user.id) && ( + + )} +
+
+ ))} +
+
- setIsLoading(true); - axios - .get(`/api/invites/${decision}/${invite.id}`) - .then(() => { - toast.success( - `Successfully ${decision === "accept" ? "accepted" : "declined"} the invite!`, - { toastId: "success" }, - ); - reloadInvites(); - }) - .catch((e) => { - toast.success(`Something went wrong, please try again later!`, { - toastId: "error", - }); - reloadInvites(); - }) - .finally(() => setIsLoading(false)); - }; + {/* Invites */} + {invites.length > 0 && ( +
+
+
+ Invites + +
+
+ + {invites.map((invite) => ( + + ))} + +
+ )} - return ( -
- Invited by {name} -
- - -
-
- ); - }; - - return ( - <> - {corporateUserToShow && ( -
- Linked to:{" "} - - {corporateUserToShow?.corporateInformation?.companyInformation - .name || corporateUserToShow.name} - -
- )} - - ), - value: Object.keys(groupBySession(stats)).length, - label: "Exams", - }, - { - icon: ( - - ), - value: stats.length, - label: "Exercises", - }, - { - icon: ( - - ), - value: `${stats.length > 0 ? averageScore(stats) : 0}%`, - label: "Average Score", - }, - ]} - /> - -
- Bio - - {user.bio || - "Your bio will appear here, you can change it by clicking on your name in the top right corner."} - -
- -
-
-
- - Assignments - - -
-
- - {assignments.filter((a) => moment(a.endDate).isSameOrAfter(moment())) - .length === 0 && - "Assignments will appear here. It seems that for now there are no assignments for you."} - {assignments - .filter((a) => moment(a.endDate).isSameOrAfter(moment())) - .sort((a, b) => moment(a.startDate).diff(b.startDate)) - .map((assignment) => ( -
r.user).includes(user.id) && - "border-mti-green-light", - )} - key={assignment.id} - > -
-

- {assignment.name} -

- - - {moment(assignment.startDate).format("DD/MM/YY, HH:mm")} - - - - - {moment(assignment.endDate).format("DD/MM/YY, HH:mm")} - - -
-
-
- {assignment.exams - .filter((e) => e.assignee === user.id) - .map((e) => e.module) - .sort(sortByModuleName) - .map((module) => ( -
- {module === "reading" && ( - - )} - {module === "listening" && ( - - )} - {module === "writing" && ( - - )} - {module === "speaking" && ( - - )} - {module === "level" && ( - - )} -
- ))} -
- {!assignment.results.map((r) => r.user).includes(user.id) && ( - <> -
- -
- - - )} - {assignment.results.map((r) => r.user).includes(user.id) && ( - - )} -
-
- ))} -
-
- - {invites.length > 0 && ( -
-
-
- Invites - -
-
- - {invites.map((invite) => ( - - ))} - -
- )} - -
- Score History -
- {MODULE_ARRAY.map((module) => ( -
-
-
- {module === "reading" && ( - - )} - {module === "listening" && ( - - )} - {module === "writing" && ( - - )} - {module === "speaking" && ( - - )} - {module === "level" && ( - - )} -
-
- - {capitalize(module)} - - - Level {user.levels[module] || 0} / Level{" "} - {user.desiredLevels[module] || 9} - -
-
-
- -
-
- ))} -
-
- - ); + {/* Score History */} +
+ Score History +
+ {MODULE_ARRAY.map((module) => ( +
+
+
+ {module === "reading" && } + {module === "listening" && } + {module === "writing" && } + {module === "speaking" && } + {module === "level" && } +
+
+ {capitalize(module)} + + Level {user.levels[module] || 0} / Level 9 (Desired Level: {user.desiredLevels[module] || 9}) + +
+
+
+ +
+
+ ))} +
+
+ + ); } diff --git a/src/exams/Finish.tsx b/src/exams/Finish.tsx index 9be2f239..a8c0e810 100644 --- a/src/exams/Finish.tsx +++ b/src/exams/Finish.tsx @@ -102,8 +102,7 @@ export default function Finish({ const [levelStr, grade] = getLevelScore(level); return (
- {levelStr} - {grade} + {grade}
); } diff --git a/src/exams/Selection.tsx b/src/exams/Selection.tsx index 02dcb681..74e623b0 100644 --- a/src/exams/Selection.tsx +++ b/src/exams/Selection.tsx @@ -34,33 +34,33 @@ export default function Selection({user, page, onStart, disableSelection = false return ( <> -
+
{user && ( , + icon: , label: "Reading", value: totalExamsByModule(stats, "reading"), }, { - icon: , + icon: , label: "Listening", value: totalExamsByModule(stats, "listening"), }, { - icon: , + icon: , label: "Writing", value: totalExamsByModule(stats, "writing"), }, { - icon: , + icon: , label: "Speaking", value: totalExamsByModule(stats, "speaking"), }, { - icon: , + icon: , label: "Level", value: totalExamsByModule(stats, "level"), }, @@ -69,7 +69,7 @@ export default function Selection({user, page, onStart, disableSelection = false )}
- About {capitalize(page)} + About {capitalize(page)} {page === "exercises" && ( <> @@ -94,150 +94,150 @@ export default function Selection({user, page, onStart, disableSelection = false )}
-
+
toggleModule("reading") : undefined} className={clsx( - "relative w-64 max-w-xs flex flex-col items-center bg-mti-white-alt transition duration-300 ease-in-out border p-5 rounded-xl gap-2 pt-12 cursor-pointer", + "bg-mti-white-alt relative flex w-64 max-w-xs cursor-pointer flex-col items-center gap-2 rounded-xl border p-5 pt-12 transition duration-300 ease-in-out", selectedModules.includes("reading") || disableSelection ? "border-mti-purple-light" : "border-mti-gray-platinum", )}> -
- +
+
Reading: -

+

Expand your vocabulary, improve your reading comprehension and improve your ability to interpret texts in English.

{!selectedModules.includes("reading") && !selectedModules.includes("level") && !disableSelection && ( -
+
)} {(selectedModules.includes("reading") || disableSelection) && ( - + )} - {selectedModules.includes("level") && } + {selectedModules.includes("level") && }
toggleModule("listening") : undefined} className={clsx( - "relative w-64 max-w-xs flex flex-col items-center bg-mti-white-alt transition duration-300 ease-in-out border p-5 rounded-xl gap-2 pt-12 cursor-pointer", + "bg-mti-white-alt relative flex w-64 max-w-xs cursor-pointer flex-col items-center gap-2 rounded-xl border p-5 pt-12 transition duration-300 ease-in-out", selectedModules.includes("listening") || disableSelection ? "border-mti-purple-light" : "border-mti-gray-platinum", )}> -
- +
+
Listening: -

+

Improve your ability to follow conversations in English and your ability to understand different accents and intonations.

{!selectedModules.includes("listening") && !selectedModules.includes("level") && !disableSelection && ( -
+
)} {(selectedModules.includes("listening") || disableSelection) && ( - + )} - {selectedModules.includes("level") && } + {selectedModules.includes("level") && }
toggleModule("writing") : undefined} className={clsx( - "relative w-64 max-w-xs flex flex-col items-center bg-mti-white-alt transition duration-300 ease-in-out border p-5 rounded-xl gap-2 pt-12 cursor-pointer", + "bg-mti-white-alt relative flex w-64 max-w-xs cursor-pointer flex-col items-center gap-2 rounded-xl border p-5 pt-12 transition duration-300 ease-in-out", selectedModules.includes("writing") || disableSelection ? "border-mti-purple-light" : "border-mti-gray-platinum", )}> -
- +
+
Writing: -

+

Allow you to practice writing in a variety of formats, from simple paragraphs to complex essays.

{!selectedModules.includes("writing") && !selectedModules.includes("level") && !disableSelection && ( -
+
)} {(selectedModules.includes("writing") || disableSelection) && ( - + )} - {selectedModules.includes("level") && } + {selectedModules.includes("level") && }
toggleModule("speaking") : undefined} className={clsx( - "relative w-64 max-w-xs flex flex-col items-center bg-mti-white-alt transition duration-300 ease-in-out border p-5 rounded-xl gap-2 pt-12 cursor-pointer", + "bg-mti-white-alt relative flex w-64 max-w-xs cursor-pointer flex-col items-center gap-2 rounded-xl border p-5 pt-12 transition duration-300 ease-in-out", selectedModules.includes("speaking") || disableSelection ? "border-mti-purple-light" : "border-mti-gray-platinum", )}> -
- +
+
Speaking: -

+

You'll have access to interactive dialogs, pronunciation exercises and speech recordings.

{!selectedModules.includes("speaking") && !selectedModules.includes("level") && !disableSelection && ( -
+
)} {(selectedModules.includes("speaking") || disableSelection) && ( - + )} - {selectedModules.includes("level") && } + {selectedModules.includes("level") && }
{!disableSelection && (
toggleModule("level") : undefined} className={clsx( - "relative w-64 max-w-xs flex flex-col items-center bg-mti-white-alt transition duration-300 ease-in-out border p-5 rounded-xl gap-2 pt-12 cursor-pointer", + "bg-mti-white-alt relative flex w-64 max-w-xs cursor-pointer flex-col items-center gap-2 rounded-xl border p-5 pt-12 transition duration-300 ease-in-out", selectedModules.includes("level") || disableSelection ? "border-mti-purple-light" : "border-mti-gray-platinum", )}> -
- +
+
Level: -

You'll be able to test your english level with multiple choice questions.

+

You'll be able to test your english level with multiple choice questions.

{!selectedModules.includes("level") && selectedModules.length === 0 && !disableSelection && ( -
+
)} {(selectedModules.includes("level") || disableSelection) && ( - + )} {!selectedModules.includes("level") && selectedModules.length > 0 && ( - + )}
)}
-
-
+
+
setAvoidRepeatedExams((prev) => !prev)}>
- +
Avoid Repeated Questions
setVariant((prev) => (prev === "full" ? "partial" : "full"))}>
- +
Full length exams
-
@@ -250,7 +250,7 @@ export default function Selection({user, page, onStart, disableSelection = false ) } color="purple" - className="px-12 w-full max-w-xs md:self-end -md:hidden" + className="-md:hidden w-full max-w-xs px-12 md:self-end" disabled={selectedModules.length === 0 && !disableSelection}> Start Exam diff --git a/src/pages/(admin)/BatchCodeGenerator.tsx b/src/pages/(admin)/BatchCodeGenerator.tsx index ec3e6426..45adfff6 100644 --- a/src/pages/(admin)/BatchCodeGenerator.tsx +++ b/src/pages/(admin)/BatchCodeGenerator.tsx @@ -51,6 +51,7 @@ export default function BatchCodeGenerator({ user }: { user: User }) { useEffect(() => { if (user && (user.type === "corporate" || user.type === "teacher")) { setExpiryDate(user.subscriptionExpirationDate || null); + setIsExpiryDateEnabled(!!user.subscriptionExpirationDate); } }, [user]); diff --git a/src/pages/(status)/PaymentDue.tsx b/src/pages/(status)/PaymentDue.tsx index 2dce4123..c44dbaa2 100644 --- a/src/pages/(status)/PaymentDue.tsx +++ b/src/pages/(status)/PaymentDue.tsx @@ -4,167 +4,272 @@ import PayPalPayment from "@/components/PayPalPayment"; import useGroups from "@/hooks/useGroups"; import usePackages from "@/hooks/usePackages"; import useUsers from "@/hooks/useUsers"; -import {User} from "@/interfaces/user"; +import { User } from "@/interfaces/user"; import clsx from "clsx"; -import {capitalize} from "lodash"; -import {useState} from "react"; +import { capitalize } from "lodash"; +import { useState } from "react"; import getSymbolFromCurrency from "currency-symbol-map"; +import useInvites from "@/hooks/useInvites"; +import { BsArrowRepeat } from "react-icons/bs"; +import InviteCard from "@/components/Medium/InviteCard"; +import { useRouter } from "next/router"; interface Props { - user: User; - hasExpired?: boolean; - clientID: string; - reload: () => void; + user: User; + hasExpired?: boolean; + clientID: string; + reload: () => void; } -export default function PaymentDue({user, hasExpired = false, clientID, reload}: Props) { - const [isLoading, setIsLoading] = useState(false); +export default function PaymentDue({ + user, + hasExpired = false, + clientID, + reload, +}: Props) { + const [isLoading, setIsLoading] = useState(false); - const {packages} = usePackages(); - const {users} = useUsers(); - const {groups} = useGroups(); + const router = useRouter(); - const isIndividual = () => { - if (user?.type === "developer") return true; - if (user?.type !== "student") return false; - const userGroups = groups.filter((g) => g.participants.includes(user?.id)); + const { packages } = usePackages(); + const { users } = useUsers(); + const { groups } = useGroups(); + const { + invites, + isLoading: isInvitesLoading, + reload: reloadInvites, + } = useInvites({ to: user?.id }); - if (userGroups.length === 0) return true; + const isIndividual = () => { + if (user?.type === "developer") return true; + if (user?.type !== "student") return false; + const userGroups = groups.filter((g) => g.participants.includes(user?.id)); - const userGroupsAdminTypes = userGroups.map((g) => users?.find((u) => u.id === g.admin)?.type).filter((t) => !!t); - return userGroupsAdminTypes.every((t) => t !== "corporate"); - }; + if (userGroups.length === 0) return true; - return ( - <> - {isLoading && ( -
-
- - Completing your payment... -
-
- )} - {user ? ( - -
- {hasExpired && You do not have time credits for your account type!} - {isIndividual() && ( -
- - To add to your use of EnCoach, please purchase one of the time packages available below: - -
- {packages.map((p) => ( -
-
- EnCoach's Logo - - EnCoach - {p.duration}{" "} - {capitalize( - p.duration === 1 ? p.duration_unit.slice(0, p.duration_unit.length - 1) : p.duration_unit, - )} - -
-
- - {p.price} - {getSymbolFromCurrency(p.currency)} - - { - setTimeout(reload, 500); - }} - /> -
-
- This includes: -
    -
  • - Train your abilities for the IELTS exam
  • -
  • - Gain insights into your weaknesses and strengths
  • -
  • - Allow yourself to correctly prepare for the exam
  • -
-
-
- ))} -
-
- )} - {!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} - {getSymbolFromCurrency(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" && ( -
- - You are not the person in charge of your time credits, please contact your administrator about this situation. - - - If you believe this to be a mistake, please contact the platform's administration, thank you for your - patience. - -
- )} - {!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. - -
- )} -
-
- ) : ( -
- )} - - ); + const userGroupsAdminTypes = userGroups + .map((g) => users?.find((u) => u.id === g.admin)?.type) + .filter((t) => !!t); + return userGroupsAdminTypes.every((t) => t !== "corporate"); + }; + + return ( + <> + {isLoading && ( +
+
+ + + Completing your payment... + +
+
+ )} + {user ? ( + + {invites.length > 0 && ( +
+
+
+ + Invites + + +
+
+ + {invites.map((invite) => ( + { + reloadInvites(); + router.reload(); + }} + /> + ))} + +
+ )} + +
+ {hasExpired && ( + + You do not have time credits for your account type! + + )} + {isIndividual() && ( +
+ + To add to your use of EnCoach, please purchase one of the time + packages available below: + +
+ {packages.map((p) => ( +
+
+ EnCoach's Logo + + EnCoach - {p.duration}{" "} + {capitalize( + p.duration === 1 + ? p.duration_unit.slice( + 0, + p.duration_unit.length - 1, + ) + : p.duration_unit, + )} + +
+
+ + {p.price} + {getSymbolFromCurrency(p.currency)} + + { + setTimeout(reload, 500); + }} + /> +
+
+ This includes: +
    +
  • - Train your abilities for the IELTS exam
  • +
  • + - Gain insights into your weaknesses and strengths +
  • +
  • + - Allow yourself to correctly prepare for the exam +
  • +
+
+
+ ))} +
+
+ )} + {!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} + {getSymbolFromCurrency( + 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" && ( +
+ + You are not the person in charge of your time credits, please + contact your administrator about this situation. + + + If you believe this to be a mistake, please contact the + platform's administration, thank you for your patience. + +
+ )} + {!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. + +
+ )} +
+
+ ) : ( +
+ )} + + ); } diff --git a/src/pages/action.tsx b/src/pages/action.tsx index ea1d9687..f7564cd5 100644 --- a/src/pages/action.tsx +++ b/src/pages/action.tsx @@ -1,157 +1,227 @@ /* eslint-disable @next/next/no-img-element */ -import {toast, ToastContainer} from "react-toastify"; +import { toast, ToastContainer } from "react-toastify"; import axios from "axios"; -import {FormEvent, useEffect, useState} from "react"; +import { FormEvent, useEffect, useState } from "react"; import Head from "next/head"; import useUser from "@/hooks/useUser"; -import {Divider} from "primereact/divider"; +import { Divider } from "primereact/divider"; import Button from "@/components/Low/Button"; -import {BsArrowRepeat} from "react-icons/bs"; +import { BsArrowRepeat } from "react-icons/bs"; import Link from "next/link"; import Input from "@/components/Low/Input"; -import {useRouter} from "next/router"; +import { useRouter } from "next/router"; -export function getServerSideProps({query, res}: {query: {oobCode: string; mode: string; apiKey?: string; continueUrl?: string}; res: any}) { - if (!query || !query.oobCode || !query.mode) { - res.setHeader("location", "/login"); - res.statusCode = 302; - res.end(); - return { - props: {}, - }; - } +export function getServerSideProps({ + query, + res, +}: { + query: { + oobCode: string; + mode: string; + continueUrl?: string; + }; + res: any; +}) { + if (!query || !query.oobCode || !query.mode) { + res.setHeader("location", "/login"); + res.statusCode = 302; + res.end(); + return { + props: {}, + }; + } - return { - props: { - code: query.oobCode, - mode: query.mode, - apiKey: query.apiKey, - ...query.continueUrl ? { continueUrl: query.continueUrl } : {}, - }, - }; + return { + props: { + code: query.oobCode, + mode: query.mode, + ...(query.continueUrl ? { continueUrl: query.continueUrl } : {}), + }, + }; } -export default function Reset({code, mode, apiKey, continueUrl}: {code: string; mode: string; apiKey?: string; continueUrl?: string}) { - const [password, setPassword] = useState(""); - const [isLoading, setIsLoading] = useState(false); +export default function Reset({ + code, + mode, + continueUrl, +}: { + code: string; + mode: string; + continueUrl?: string; +}) { + const [password, setPassword] = useState(""); + const [isLoading, setIsLoading] = useState(false); - const router = useRouter(); + const router = useRouter(); - useUser({ - redirectTo: "/", - redirectIfFound: true, - }); + useUser({ + redirectTo: "/", + redirectIfFound: true, + }); - useEffect(() => { - if (mode === "signIn") { - axios - .post<{ok: boolean}>("/api/reset/verify", { - email: continueUrl?.replace("https://platform.encoach.com/", ""), - }) - .then((response) => { - if (response.data.ok) { - toast.success("Your account has been verified!", {toastId: "verify-successful"}); - setTimeout(() => { - router.reload(); - }, 1000); - return; - } + useEffect(() => { + if (mode === "signIn") { + axios + .post<{ ok: boolean }>("/api/reset/verify", { + email: continueUrl?.replace("https://platform.encoach.com/", ""), + }) + .then((response) => { + if (response.data.ok) { + toast.success("Your account has been verified!", { + toastId: "verify-successful", + }); + setTimeout(() => { + router.push("/"); + }, 1000); + return; + } - toast.error("Something went wrong! Please make sure to click the link in your e-mail again and input the correct e-mail!", { - toastId: "verify-error", - }); - }) - .catch(() => { - toast.error("Something went wrong! Please make sure to click the link in your e-mail again and input the correct e-mail!", { - toastId: "verify-error", - }); - setIsLoading(false); - }); - } - }); + toast.error( + "Something went wrong! Please make sure to click the link in your e-mail again and input the correct e-mail!", + { + toastId: "verify-error", + }, + ); + }) + .catch(() => { + toast.error( + "Something went wrong! Please make sure to click the link in your e-mail again and input the correct e-mail!", + { + toastId: "verify-error", + }, + ); + setIsLoading(false); + }); + } + }); - const login = (e: FormEvent) => { - e.preventDefault(); + const login = (e: FormEvent) => { + e.preventDefault(); - setIsLoading(true); - axios - .post<{ok: boolean}>("/api/reset/confirm", {code, password}) - .then((response) => { - if (response.data.ok) { - toast.success("Your password has been reset!", {toastId: "reset-successful"}); - setTimeout(() => { - router.push("/login"); - }, 1000); - return; - } + setIsLoading(true); + axios + .post<{ ok: boolean }>("/api/reset/confirm", { code, password }) + .then((response) => { + if (response.data.ok) { + toast.success("Your password has been reset!", { + toastId: "reset-successful", + }); + setTimeout(() => { + router.push("/login"); + }, 1000); + return; + } - toast.error("Something went wrong! Please make sure to click the link in your e-mail again!", {toastId: "reset-error"}); - }) - .catch(() => { - toast.error("Something went wrong! Please make sure to click the link in your e-mail again!", {toastId: "reset-error"}); - }) - .finally(() => setIsLoading(false)); - }; + toast.error( + "Something went wrong! Please make sure to click the link in your e-mail again!", + { toastId: "reset-error" }, + ); + }) + .catch(() => { + toast.error( + "Something went wrong! Please make sure to click the link in your e-mail again!", + { toastId: "reset-error" }, + ); + }) + .finally(() => setIsLoading(false)); + }; - return ( - <> - - Reset | EnCoach - - - - -
- -
-
- People smiling looking at a tablet -
- {mode === "resetPassword" && ( -
-
- EnCoach's Logo -

Reset your password

-

to your registered Email Address

-
- -
- setPassword(e)} placeholder="Password" /> + return ( + <> + + Reset | EnCoach + + + + +
+ +
+
+ People smiling looking at a tablet +
+ {mode === "resetPassword" && ( +
+
+ EnCoach's Logo +

+ Reset your password +

+

+ to your registered Email Address +

+
+ + + setPassword(e)} + placeholder="Password" + /> - - - - Don't have an account?{" "} - - Sign up - - -
- )} - {mode === "signIn" && ( -
-
- EnCoach's Logo -

Confirm your account

-

to your registered Email Address

-
- -
- - Your e-mail is currently being verified, please wait a second.

- Once it has been verified, you will be redirected to the home page. -
-
-
- )} -
- - ); + + + + Don't have an account?{" "} + + Sign up + + +
+ )} + {mode === "signIn" && ( +
+
+ EnCoach's Logo +

+ Confirm your account +

+

+ to your registered Email Address +

+
+ +
+ + Your e-mail is currently being verified, please wait a second.{" "} +

+ Once it has been verified, you will be redirected to the home + page. +
+
+
+ )} +
+ + ); } diff --git a/src/pages/api/invites/accept/[id].ts b/src/pages/api/invites/accept/[id].ts index 41ca356d..ed2bb136 100644 --- a/src/pages/api/invites/accept/[id].ts +++ b/src/pages/api/invites/accept/[id].ts @@ -19,6 +19,7 @@ import { Invite } from "@/interfaces/invite"; import { Group, User } from "@/interfaces/user"; import { v4 } from "uuid"; import { sendEmail } from "@/email"; +import { updateExpiryDateOnGroup } from "@/utils/groups.be"; const db = getFirestore(app); @@ -48,6 +49,8 @@ async function get(req: NextApiRequest, res: NextApiResponse) { const invitedByRef = await getDoc(doc(db, "users", invite.from)); if (!invitedByRef.exists()) return res.status(404).json({ ok: false }); + await updateExpiryDateOnGroup(invite.to, invite.from); + const invitedBy = { ...invitedByRef.data(), id: invitedByRef.id } as User; const invitedByGroupsRef = await getDocs( query(collection(db, "groups"), where("admin", "==", invitedBy.id)), diff --git a/src/pages/payment.tsx b/src/pages/payment.tsx index c851d389..bb68b16c 100644 --- a/src/pages/payment.tsx +++ b/src/pages/payment.tsx @@ -1,61 +1,65 @@ /* eslint-disable @next/next/no-img-element */ import Head from "next/head"; -import {withIronSessionSsr} from "iron-session/next"; -import {sessionOptions} from "@/lib/session"; +import { withIronSessionSsr } from "iron-session/next"; +import { sessionOptions } from "@/lib/session"; import useUser from "@/hooks/useUser"; import PaymentDue from "./(status)/PaymentDue"; -import {useRouter} from "next/router"; +import { useRouter } from "next/router"; -export const getServerSideProps = withIronSessionSsr(({req, res}) => { - const user = req.session.user; +export const getServerSideProps = withIronSessionSsr(({ req, res }) => { + const user = req.session.user; - const envVariables: {[key: string]: string} = {}; - Object.keys(process.env) - .filter((x) => x.startsWith("NEXT_PUBLIC")) - .forEach((x: string) => { - envVariables[x] = process.env[x]!; - }); + const envVariables: { [key: string]: string } = {}; + Object.keys(process.env) + .filter((x) => x.startsWith("NEXT_PUBLIC")) + .forEach((x: string) => { + envVariables[x] = process.env[x]!; + }); - if (!user || !user.isVerified) { - res.setHeader("location", "/login"); - res.statusCode = 302; - res.end(); - return { - props: { - user: null, - envVariables, - }, - }; - } + if (!user || !user.isVerified) { + res.setHeader("location", "/login"); + res.statusCode = 302; + res.end(); + return { + props: { + user: null, + envVariables, + }, + }; + } - return { - props: {user: req.session.user, envVariables}, - }; + return { + props: { user: req.session.user, envVariables }, + }; }, sessionOptions); -export default function Home({envVariables}: {envVariables: {[key: string]: string}}) { - const {user, mutateUser} = useUser({redirectTo: "/login"}); - const router = useRouter(); +export default function Home({ + envVariables, +}: { + envVariables: { [key: string]: string }; +}) { + const { user } = useUser({ redirectTo: "/login" }); + const router = useRouter(); - return ( - <> - - EnCoach - - - - - {user && ( - - )} - - ); + return ( + <> + + EnCoach + + + + + {user && ( + + )} + + ); } diff --git a/src/pages/profile.tsx b/src/pages/profile.tsx index 8273741b..4bdcd1b2 100644 --- a/src/pages/profile.tsx +++ b/src/pages/profile.tsx @@ -2,7 +2,7 @@ import Head from "next/head"; import {withIronSessionSsr} from "iron-session/next"; import {sessionOptions} from "@/lib/session"; -import {ChangeEvent, ReactNode, useEffect, useRef, useState} from "react"; +import {ChangeEvent, Dispatch, ReactNode, SetStateAction, useEffect, useRef, useState} from "react"; import useUser from "@/hooks/useUser"; import {toast, ToastContainer} from "react-toastify"; import Layout from "@/components/High/Layout"; @@ -25,6 +25,9 @@ import {Divider} from "primereact/divider"; import GenderInput from "@/components/High/GenderInput"; import EmploymentStatusInput from "@/components/High/EmploymentStatusInput"; import TimezoneSelect from "@/components/Low/TImezoneSelect"; +import Modal from "@/components/Modal"; +import {Module} from "@/interfaces"; +import ModuleLevelSelector from "@/components/Medium/ModuleLevelSelector"; export const getServerSideProps = withIronSessionSsr(({req, res}) => { const user = req.session.user; @@ -69,6 +72,10 @@ function UserProfile({user, mutateUser}: Props) { const [isLoading, setIsLoading] = useState(false); const [profilePicture, setProfilePicture] = useState(user.profilePicture); + const [desiredLevels, setDesiredLevels] = useState<{[key in Module]: number} | undefined>( + ["developer", "student"].includes(user.type) ? user.desiredLevels : undefined, + ); + const [country, setCountry] = useState(user.demographicInformation?.country || ""); const [phone, setPhone] = useState(user.demographicInformation?.phone || ""); const [gender, setGender] = useState(user.demographicInformation?.gender || undefined); @@ -138,6 +145,7 @@ function UserProfile({user, mutateUser}: Props) { password, newPassword, profilePicture, + desiredLevels, demographicInformation: { phone, country, @@ -319,6 +327,18 @@ function UserProfile({user, mutateUser}: Props) { + {desiredLevels && ["developer", "student"].includes(user.type) && ( +
+ + >} + /> +
+ )} + + + {user.type === "corporate" && ( <>