diff --git a/src/components/Medium/InviteCard.tsx b/src/components/Medium/InviteCard.tsx new file mode 100644 index 00000000..9b3e0a20 --- /dev/null +++ b/src/components/Medium/InviteCard.tsx @@ -0,0 +1,77 @@ +import { Invite } from "@/interfaces/invite"; +import { User } from "@/interfaces/user"; +import axios from "axios"; +import { useState } from "react"; +import { BsArrowRepeat } from "react-icons/bs"; +import { toast } from "react-toastify"; + +interface Props { + invite: Invite; + users: User[]; + reload: () => 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/dashboards/Student.tsx b/src/dashboards/Student.tsx index 780330f9..ebade14d 100644 --- a/src/dashboards/Student.tsx +++ b/src/dashboards/Student.tsx @@ -1,5 +1,6 @@ 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"; @@ -102,71 +103,6 @@ export default function StudentDashboard({ user }: Props) { }); }; - const InviteCard = (invite: Invite) => { - 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" }, - ); - reloadInvites(); - }) - .catch((e) => { - toast.success(`Something went wrong, please try again later!`, { - toastId: "error", - }); - reloadInvites(); - }) - .finally(() => setIsLoading(false)); - }; - - return ( -
- Invited by {name} -
- - -
-
- ); - }; - return ( <> {corporateUserToShow && ( @@ -356,7 +292,12 @@ export default function StudentDashboard({ user }: Props) { {invites.map((invite) => ( - + ))} diff --git a/src/pages/(status)/PaymentDue.tsx b/src/pages/(status)/PaymentDue.tsx index 2dce4123..f7973726 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/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 && ( + + )} + + ); }