Updated part of the payment

This commit is contained in:
Tiago Ribeiro
2024-12-30 18:39:02 +00:00
parent 17154be8bf
commit f64b50df9e
11 changed files with 457 additions and 357 deletions

View File

@@ -35,7 +35,7 @@ export default function Layout({
return ( return (
<main className={clsx("w-full min-h-full h-screen flex flex-col bg-mti-gray-smoke relative")}> <main className={clsx("w-full min-h-full h-screen flex flex-col bg-mti-gray-smoke relative")}>
<ToastContainer /> <ToastContainer />
{!hideSidebar && ( {!hideSidebar && user && (
<Navbar <Navbar
path={router.pathname} path={router.pathname}
user={user} user={user}
@@ -45,7 +45,7 @@ export default function Layout({
/> />
)} )}
<div className={clsx("h-full w-full flex gap-2")}> <div className={clsx("h-full w-full flex gap-2")}>
{!hideSidebar && ( {!hideSidebar && user && (
<Sidebar <Sidebar
path={router.pathname} path={router.pathname}
navDisabled={navDisabled} navDisabled={navDisabled}

View File

@@ -13,6 +13,7 @@ interface Props {
disabled?: boolean; disabled?: boolean;
max?: number; max?: number;
min?: number; min?: number;
thin?: boolean
name: string; name: string;
onChange: (value: string) => void; onChange: (value: string) => void;
} }
@@ -29,6 +30,7 @@ export default function Input({
className, className,
roundness = "full", roundness = "full",
disabled = false, disabled = false,
thin = false,
min, min,
onChange, onChange,
}: Props) { }: Props) {
@@ -95,9 +97,10 @@ export default function Input({
min={type === "number" ? (min ?? 0) : undefined} min={type === "number" ? (min ?? 0) : undefined}
placeholder={placeholder} placeholder={placeholder}
className={clsx( className={clsx(
"px-8 py-6 text-sm font-normal bg-white border border-mti-gray-platinum focus:outline-none", "px-8 text-sm font-normal bg-white border border-mti-gray-platinum focus:outline-none",
"placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed", "placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed",
roundness === "full" ? "rounded-full" : "rounded-xl", roundness === "full" ? "rounded-full" : "rounded-xl",
thin ? 'py-4' : 'py-6'
)} )}
required={required} required={required}
defaultValue={defaultValue} defaultValue={defaultValue}

View File

@@ -65,28 +65,28 @@ export default function Navbar({ user, path, navDisabled = false, focusMode = fa
{ {
module: "reading", module: "reading",
icon: () => <BsBook className="h-4 w-4 text-white" />, icon: () => <BsBook className="h-4 w-4 text-white" />,
achieved: user.levels?.reading || 0 >= user.desiredLevels?.reading || 9, achieved: user?.levels?.reading || 0 >= user?.desiredLevels?.reading || 9,
}, },
{ {
module: "listening", module: "listening",
icon: () => <BsHeadphones className="h-4 w-4 text-white" />, icon: () => <BsHeadphones className="h-4 w-4 text-white" />,
achieved: user.levels?.listening || 0 >= user.desiredLevels?.listening || 9, achieved: user?.levels?.listening || 0 >= user?.desiredLevels?.listening || 9,
}, },
{ {
module: "writing", module: "writing",
icon: () => <BsPen className="h-4 w-4 text-white" />, icon: () => <BsPen className="h-4 w-4 text-white" />,
achieved: user.levels?.writing || 0 >= user.desiredLevels?.writing || 9, achieved: user?.levels?.writing || 0 >= user?.desiredLevels?.writing || 9,
}, },
{ {
module: "speaking", module: "speaking",
icon: () => <BsMegaphone className="h-4 w-4 text-white" />, icon: () => <BsMegaphone className="h-4 w-4 text-white" />,
achieved: user.levels?.speaking || 0 >= user.desiredLevels?.speaking || 9, achieved: user?.levels?.speaking || 0 >= user?.desiredLevels?.speaking || 9,
}, },
{ {
module: "level", module: "level",
icon: () => <BsClipboard className="h-4 w-4 text-white" />, icon: () => <BsClipboard className="h-4 w-4 text-white" />,
achieved: user.levels?.level || 0 >= user.desiredLevels?.level || 9, achieved: user?.levels?.level || 0 >= user?.desiredLevels?.level || 9,
}, },
]; ];

View File

@@ -1,3 +1,4 @@
import { Entity } from "@/interfaces/entity";
import { PaymentIntention } from "@/interfaces/paymob"; import { PaymentIntention } from "@/interfaces/paymob";
import { DurationUnit } from "@/interfaces/paypal"; import { DurationUnit } from "@/interfaces/paypal";
import { User } from "@/interfaces/user"; import { User } from "@/interfaces/user";
@@ -10,6 +11,7 @@ import Modal from "./Modal";
interface Props { interface Props {
user: User; user: User;
entity?: Entity
currency: string; currency: string;
price: number; price: number;
setIsPaymentLoading: (v: boolean) => void; setIsPaymentLoading: (v: boolean) => void;
@@ -18,7 +20,7 @@ interface Props {
onSuccess: (duration: number, duration_unit: DurationUnit) => void; onSuccess: (duration: number, duration_unit: DurationUnit) => void;
} }
export default function PaymobPayment({user, price, setIsPaymentLoading, currency, duration, duration_unit, onSuccess}: Props) { export default function PaymobPayment({ user, entity, price, setIsPaymentLoading, currency, duration, duration_unit, onSuccess }: Props) {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const router = useRouter(); const router = useRouter();
@@ -56,6 +58,7 @@ export default function PaymobPayment({user, price, setIsPaymentLoading, currenc
userID: user.id, userID: user.id,
duration, duration,
duration_unit, duration_unit,
entity: entity?.id
}, },
}; };

View File

@@ -28,7 +28,7 @@ interface Customer {
extras: IntentionExtras; extras: IntentionExtras;
} }
type IntentionExtras = {[key: string]: string | number}; type IntentionExtras = { [key: string]: string | number | undefined };
export interface IntentionResult { export interface IntentionResult {
payment_keys: PaymentKeysItem[]; payment_keys: PaymentKeysItem[];

View File

@@ -5,8 +5,8 @@ import usePackages from "@/hooks/usePackages";
import useUsers from "@/hooks/useUsers"; import useUsers from "@/hooks/useUsers";
import { User } from "@/interfaces/user"; import { User } from "@/interfaces/user";
import clsx from "clsx"; import clsx from "clsx";
import { capitalize } from "lodash"; import { capitalize, sortBy } from "lodash";
import { useEffect, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import useInvites from "@/hooks/useInvites"; import useInvites from "@/hooks/useInvites";
import { BsArrowRepeat } from "react-icons/bs"; import { BsArrowRepeat } from "react-icons/bs";
import InviteCard from "@/components/Medium/InviteCard"; import InviteCard from "@/components/Medium/InviteCard";
@@ -16,46 +16,50 @@ import useDiscounts from "@/hooks/useDiscounts";
import PaymobPayment from "@/components/PaymobPayment"; import PaymobPayment from "@/components/PaymobPayment";
import moment from "moment"; import moment from "moment";
import { EntityWithRoles } from "@/interfaces/entity"; import { EntityWithRoles } from "@/interfaces/entity";
import { Discount, Package } from "@/interfaces/paypal";
import { isAdmin } from "@/utils/users";
import { useAllowedEntities } from "@/hooks/useEntityPermissions";
import Select from "@/components/Low/Select";
interface Props { interface Props {
user: User; user: User
discounts: Discount[]
packages: Package[]
entities: EntityWithRoles[] entities: EntityWithRoles[]
hasExpired?: boolean; hasExpired?: boolean;
reload: () => void; reload: () => void;
} }
export default function PaymentDue({ user, hasExpired = false, reload }: Props) { export default function PaymentDue({ user, discounts = [], entities = [], packages = [], hasExpired = false, reload }: Props) {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [appliedDiscount, setAppliedDiscount] = useState(0); const [entity, setEntity] = useState<EntityWithRoles>()
const router = useRouter(); const router = useRouter();
const { packages } = usePackages();
const { discounts } = useDiscounts();
const { users } = useUsers(); const { users } = useUsers();
const { groups } = useGroups({});
const { invites, isLoading: isInvitesLoading, reload: reloadInvites } = useInvites({ to: user?.id }); const { invites, isLoading: isInvitesLoading, reload: reloadInvites } = useInvites({ to: user?.id });
useEffect(() => { const isIndividual = useMemo(() => {
const userDiscounts = discounts.filter((x) => user.email.endsWith(`@${x.domain}`)); if (isAdmin(user)) return false;
if (userDiscounts.length === 0) return;
const biggestDiscount = [...userDiscounts].sort((a, b) => b.percentage - a.percentage).shift();
if (!biggestDiscount || (biggestDiscount.validUntil && moment(biggestDiscount.validUntil).isBefore(moment()))) return;
setAppliedDiscount(biggestDiscount.percentage);
}, [discounts, user]);
const isIndividual = () => {
if (user?.type === "developer") return true;
if (user?.type !== "student") return false; if (user?.type !== "student") return false;
const userGroups = groups.filter((g) => g.participants.includes(user?.id));
if (userGroups.length === 0) return true; return user.entities.length === 0
}, [user])
const userGroupsAdminTypes = userGroups.map((g) => users?.find((u) => u.id === g.admin)?.type).filter((t) => !!t); const appliedDiscount = useMemo(() => {
return userGroupsAdminTypes.every((t) => t !== "corporate"); const biggestDiscount = [...discounts].sort((a, b) => b.percentage - a.percentage).shift();
};
if (!biggestDiscount || (biggestDiscount.validUntil && moment(biggestDiscount.validUntil).isBefore(moment())))
return 0;
return biggestDiscount.percentage
}, [discounts])
const entitiesThatCanBePaid = useAllowedEntities(user, entities, 'pay_entity')
useEffect(() => {
if (entitiesThatCanBePaid.length > 0) setEntity(entitiesThatCanBePaid[0])
}, [entitiesThatCanBePaid])
return ( return (
<> <>
@@ -74,7 +78,6 @@ export default function PaymentDue({ user, hasExpired = false, reload }: Props)
</div> </div>
</div> </div>
)} )}
{user ? (
<Layout user={user} navDisabled={hasExpired}> <Layout user={user} navDisabled={hasExpired}>
{invites.length > 0 && ( {invites.length > 0 && (
<section className="flex flex-col gap-1 md:gap-3"> <section className="flex flex-col gap-1 md:gap-3">
@@ -104,7 +107,7 @@ export default function PaymentDue({ user, hasExpired = false, reload }: Props)
<div className="flex w-full flex-col items-center justify-center gap-4 text-center"> <div className="flex w-full flex-col items-center justify-center gap-4 text-center">
{hasExpired && <span className="text-lg font-bold">You do not have time credits for your account type!</span>} {hasExpired && <span className="text-lg font-bold">You do not have time credits for your account type!</span>}
{isIndividual() && ( {isIndividual && (
<div className="scrollbar-hide flex w-full flex-col items-center gap-12 overflow-x-scroll"> <div className="scrollbar-hide flex w-full flex-col items-center gap-12 overflow-x-scroll">
<span className="max-w-lg"> <span className="max-w-lg">
To add to your use of EnCoach, please purchase one of the time packages available below: To add to your use of EnCoach, please purchase one of the time packages available below:
@@ -162,10 +165,20 @@ export default function PaymentDue({ user, hasExpired = false, reload }: Props)
</div> </div>
</div> </div>
)} )}
{!isIndividual() &&
(user?.type === "corporate" || user?.type === "mastercorporate") && {!isIndividual && entitiesThatCanBePaid.length > 0 &&
user?.corporateInformation.payment && ( entity?.payment && (
<div className="flex flex-col items-center"> <div className="flex flex-col items-center gap-8">
<div className={clsx("flex flex-col items-center gap-4 w-full")}>
<label className="font-normal text-base text-mti-gray-dim">Entity</label>
<Select
defaultValue={{ value: entity?.id, label: entity?.label }}
options={entitiesThatCanBePaid.map((e) => ({ value: e.id, label: e.label, entity: e }))}
onChange={(e) => e?.value ? setEntity(e?.entity) : null}
className="!w-full max-w-[400px] self-center"
/>
</div>
<span className="max-w-lg"> <span className="max-w-lg">
To add to your use of EnCoach and that of your students and teachers, please pay your designated package To add to your use of EnCoach and that of your students and teachers, please pay your designated package
below: below:
@@ -179,13 +192,14 @@ export default function PaymentDue({ user, hasExpired = false, reload }: Props)
</div> </div>
<div className="flex w-full flex-col items-start gap-2"> <div className="flex w-full flex-col items-start gap-2">
<span className="text-2xl"> <span className="text-2xl">
{user.corporateInformation.payment.value} {user.corporateInformation.payment.currency} {entity.payment.price} {entity.payment.currency}
</span> </span>
<PaymobPayment <PaymobPayment
user={user} user={user}
setIsPaymentLoading={setIsLoading} setIsPaymentLoading={setIsLoading}
currency={user.corporateInformation.payment.currency} entity={entity}
price={user.corporateInformation.payment.value} currency={entity.payment.currency}
price={entity.payment.price}
duration={12} duration={12}
duration_unit="months" duration_unit="months"
onSuccess={() => { onSuccess={() => {
@@ -198,7 +212,7 @@ export default function PaymentDue({ user, hasExpired = false, reload }: Props)
<span>This includes:</span> <span>This includes:</span>
<ul className="flex flex-col items-start text-sm"> <ul className="flex flex-col items-start text-sm">
<li> <li>
- Allow a total of 0 students and teachers to use EnCoach - Allow a total of {entity.licenses} students and teachers to use EnCoach
</li> </li>
<li>- Train their abilities for the IELTS exam</li> <li>- Train their abilities for the IELTS exam</li>
<li>- Gain insights into your students&apos; weaknesses and strengths</li> <li>- Gain insights into your students&apos; weaknesses and strengths</li>
@@ -208,7 +222,7 @@ export default function PaymentDue({ user, hasExpired = false, reload }: Props)
</div> </div>
</div> </div>
)} )}
{!isIndividual() && !(user?.type === "corporate" || user?.type === "mastercorporate") && ( {!isIndividual && entitiesThatCanBePaid.length === 0 && (
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<span className="max-w-lg"> <span className="max-w-lg">
You are not the person in charge of your time credits, please contact your administrator about this situation. You are not the person in charge of your time credits, please contact your administrator about this situation.
@@ -219,10 +233,19 @@ export default function PaymentDue({ user, hasExpired = false, reload }: Props)
</span> </span>
</div> </div>
)} )}
{!isIndividual() && {!isIndividual &&
(user?.type === "corporate" || user?.type === "mastercorporate") && entitiesThatCanBePaid.length > 0 &&
!user.corporateInformation.payment && ( !entity?.payment && (
<div className="flex flex-col items-center"> <div className="flex flex-col items-center gap-8">
<div className={clsx("flex flex-col items-center gap-4 w-full")}>
<label className="font-normal text-base text-mti-gray-dim">Entity</label>
<Select
defaultValue={{ value: entity?.id || "", label: entity?.label || "" }}
options={entitiesThatCanBePaid.map((e) => ({ value: e.id, label: e.label, entity: e }))}
onChange={(e) => e?.value ? setEntity(e?.entity) : null}
className="!w-full max-w-[400px] self-center"
/>
</div>
<span className="max-w-lg"> <span className="max-w-lg">
An admin nor your agent have yet set the price intended to your requirements in terms of the amount of users 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. you desire and your expected monthly duration.
@@ -234,9 +257,6 @@ export default function PaymentDue({ user, hasExpired = false, reload }: Props)
)} )}
</div> </div>
</Layout> </Layout>
) : (
<div />
)}
</> </>
); );
} }

View File

@@ -68,6 +68,11 @@ async function patch(req: NextApiRequest, res: NextApiResponse) {
return res.status(200).json({ ok: entity.acknowledged }); return res.status(200).json({ ok: entity.acknowledged });
} }
if (req.body.payment) {
const entity = await db.collection<Entity>("entities").updateOne({ id }, { $set: { payment: req.body.payment } });
return res.status(200).json({ ok: entity.acknowledged });
}
if (req.body.expiryDate !== undefined) { if (req.body.expiryDate !== undefined) {
const entity = await getEntity(id) const entity = await getEntity(id)
const result = await db.collection<Entity>("entities").updateOne({ id }, { $set: { expiryDate: req.body.expiryDate } }); const result = await db.collection<Entity>("entities").updateOne({ id }, { $set: { expiryDate: req.body.expiryDate } });

View File

@@ -10,6 +10,10 @@ import axios from "axios";
import { IntentionResult, PaymentIntention, TransactionResult } from "@/interfaces/paymob"; import { IntentionResult, PaymentIntention, TransactionResult } from "@/interfaces/paymob";
import moment from "moment"; import moment from "moment";
import client from "@/lib/mongodb"; import client from "@/lib/mongodb";
import { getEntity } from "@/utils/entities.be";
import { Entity } from "@/interfaces/entity";
import { getEntityUsers } from "@/utils/users.be";
import { mapBy } from "@/utils";
const db = client.db(process.env.MONGODB_DB); const db = client.db(process.env.MONGODB_DB);
@@ -25,10 +29,11 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
if (!checkTransaction(authToken, transactionResult.transaction.order.id)) return res.status(404).json({ ok: false }); if (!checkTransaction(authToken, transactionResult.transaction.order.id)) return res.status(404).json({ ok: false });
if (!transactionResult.transaction.success) return res.status(400).json({ ok: false }); if (!transactionResult.transaction.success) return res.status(400).json({ ok: false });
const {userID, duration, duration_unit} = transactionResult.intention.extras.creation_extras as { const { userID, duration, duration_unit, entity: entityID } = transactionResult.intention.extras.creation_extras as {
userID: string; userID: string;
duration: number; duration: number;
duration_unit: DurationUnit; duration_unit: DurationUnit;
entity: string
}; };
const user = await db.collection("users").findOne<User>({ id: userID as string }); const user = await db.collection("users").findOne<User>({ id: userID as string });
@@ -60,22 +65,19 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
value: transactionResult.transaction.amount_cents / 1000, value: transactionResult.transaction.amount_cents / 1000,
}); });
if (user.type === "corporate") { if (entityID) {
const groups = await db.collection("groups").find<Group>({ admin: user.id }).toArray(); const entity = await getEntity(entityID)
await db.collection<Entity>("entities").updateOne({ id: entityID }, { $set: { expiryDate: req.body.expiryDate } });
const participants = (await Promise.all( const users = await getEntityUsers(entityID, 0, {
groups.flatMap((x) => x.participants).map(async (x) => ({...(await db.collection("users").findOne({ id: x}))})), subscriptionExpirationDate: entity?.expiryDate,
)) as User[]; $and: [
const sameExpiryDateParticipants = participants.filter( { type: { $ne: "admin" } },
(x) => x.subscriptionExpirationDate === subscriptionExpirationDate && x.status !== "disabled", { type: { $ne: "developer" } },
); ]
})
for (const participant of sameExpiryDateParticipants) { await db.collection<User>("users").updateMany({ id: { $in: mapBy(users, 'id') } }, { $set: { subscriptionExpirationDate: req.body.expiryDate } })
await db.collection("users").updateOne(
{ id: participant.id },
{ $set: {subscriptionExpirationDate: updatedSubscriptionExpirationDate, status: "active"} }
);
}
} }
res.status(200).json({ res.status(200).json({

View File

@@ -2,6 +2,7 @@
import CardList from "@/components/High/CardList"; import CardList from "@/components/High/CardList";
import Layout from "@/components/High/Layout"; import Layout from "@/components/High/Layout";
import Select from "@/components/Low/Select"; import Select from "@/components/Low/Select";
import Input from "@/components/Low/Input";
import Checkbox from "@/components/Low/Checkbox"; import Checkbox from "@/components/Low/Checkbox";
import Tooltip from "@/components/Low/Tooltip"; import Tooltip from "@/components/Low/Tooltip";
import { useEntityPermission } from "@/hooks/useEntityPermissions"; import { useEntityPermission } from "@/hooks/useEntityPermissions";
@@ -29,6 +30,7 @@ import { useRouter } from "next/router";
import { Divider } from "primereact/divider"; import { Divider } from "primereact/divider";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import ReactDatePicker from "react-datepicker"; import ReactDatePicker from "react-datepicker";
import { CURRENCIES } from "@/resources/paypal";
import { import {
BsCheck, BsCheck,
@@ -56,6 +58,11 @@ const expirationDateColor = (date: Date) => {
if (today.add(7, "days").isAfter(momentDate)) return "!bg-mti-orange-ultralight border-mti-orange-light"; if (today.add(7, "days").isAfter(momentDate)) return "!bg-mti-orange-ultralight border-mti-orange-light";
}; };
const CURRENCIES_OPTIONS = CURRENCIES.map(({ label, currency }) => ({
value: currency,
label,
}));
export const getServerSideProps = withIronSessionSsr(async ({ req, params }) => { export const getServerSideProps = withIronSessionSsr(async ({ req, params }) => {
const user = req.session.user as User; const user = req.session.user as User;
@@ -102,6 +109,8 @@ export default function Home({ user, entity, users, linkedUsers }: Props) {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [selectedUsers, setSelectedUsers] = useState<string[]>([]); const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
const [expiryDate, setExpiryDate] = useState(entity?.expiryDate) const [expiryDate, setExpiryDate] = useState(entity?.expiryDate)
const [paymentPrice, setPaymentPrice] = useState(entity?.payment?.price)
const [paymentCurrency, setPaymentCurrency] = useState(entity?.payment?.currency)
const router = useRouter(); const router = useRouter();
@@ -198,6 +207,23 @@ export default function Home({ user, entity, users, linkedUsers }: Props) {
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}; };
const updatePayment = () => {
if (!isAdmin(user)) return;
setIsLoading(true);
axios
.patch(`/api/entities/${entity.id}`, { payment: { price: paymentPrice, currency: paymentCurrency } })
.then(() => {
toast.success("The entity has been updated successfully!");
router.replace(router.asPath);
})
.catch((e) => {
console.error(e);
toast.error("Something went wrong!");
})
.finally(() => setIsLoading(false));
};
const editLicenses = () => { const editLicenses = () => {
if (!isAdmin(user)) return; if (!isAdmin(user)) return;
@@ -430,6 +456,36 @@ export default function Home({ user, entity, users, linkedUsers }: Props) {
<span className="text-xs">Apply Change</span> <span className="text-xs">Apply Change</span>
</button> </button>
</div> </div>
<Divider />
<div className="w-full flex items-center justify-between gap-8">
<div className="w-full max-w-xl flex items-center gap-4">
<Input
name="paymentValue"
onChange={(e) => setPaymentPrice(e ? parseInt(e) : undefined)}
type="number"
defaultValue={entity.payment?.price || 0}
thin
/>
<Select
className={clsx(
"px-4 !py-2 !w-full text-sm font-normal placeholder:text-mti-gray-cool bg-white rounded-full border border-mti-gray-platinum focus:outline-none",
)}
options={CURRENCIES_OPTIONS}
value={CURRENCIES_OPTIONS.find((c) => c.value === paymentCurrency)}
onChange={(value) => setPaymentCurrency(value?.value ?? undefined)}
/>
</div>
<button
onClick={updatePayment}
disabled={!paymentPrice || paymentPrice <= 0 || !paymentCurrency}
className="flex w-fit text-nowrap items-center gap-1 px-2 py-2 border rounded-full border-mti-green bg-mti-green-light text-white hover:bg-mti-green-dark disabled:hover:bg-mti-green-light disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition ease-in-out duration-300">
<BsCheck />
<span className="text-xs">Apply Change</span>
</button>
</div>
</> </>
)} )}

View File

@@ -11,6 +11,10 @@ import { getEntities, getEntitiesWithRoles } from "@/utils/entities.be";
import { isAdmin } from "@/utils/users"; import { isAdmin } from "@/utils/users";
import { EntityWithRoles } from "@/interfaces/entity"; import { EntityWithRoles } from "@/interfaces/entity";
import { User } from "@/interfaces/user"; import { User } from "@/interfaces/user";
import client from "@/lib/mongodb";
import { Discount, Package } from "@/interfaces/paypal";
const db = client.db(process.env.MONGODB_DB);
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => { export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
const user = await requestUser(req, res) const user = await requestUser(req, res)
@@ -19,17 +23,23 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
const entityIDs = mapBy(user.entities, 'id') const entityIDs = mapBy(user.entities, 'id')
const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : entityIDs) const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : entityIDs)
const domain = user.email.split("@").pop()
const discounts = await db.collection<Discount>("discounts").find({ domain }).toArray()
const packages = await db.collection<Package>("packages").find().toArray()
return { return {
props: serialize({ user, entities }), props: serialize({ user, entities, discounts, packages }),
}; };
}, sessionOptions); }, sessionOptions);
interface Props { interface Props {
user: User, user: User,
entities: EntityWithRoles[] entities: EntityWithRoles[]
discounts: Discount[]
packages: Package[]
} }
export default function Home({ user, entities }: Props) { export default function Home(props: Props) {
const router = useRouter(); const router = useRouter();
return ( return (
@@ -43,7 +53,8 @@ export default function Home({ user, entities }: Props) {
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<PaymentDue entities={entities} user={user} reload={router.reload} />
<PaymentDue {...props} reload={router.reload} />
</> </>
); );
} }

View File

@@ -158,7 +158,7 @@ const defaultModuleSettings = (module: Module, minTimer: number): ModuleState =>
examLabel: defaultExamLabel(module), examLabel: defaultExamLabel(module),
minTimer, minTimer,
difficulty: sample(["A1", "A2", "B1", "B2", "C1", "C2"] as Difficulty[])!, difficulty: sample(["A1", "A2", "B1", "B2", "C1", "C2"] as Difficulty[])!,
isPrivate: false, isPrivate: true,
sectionLabels: sectionLabels(module), sectionLabels: sectionLabels(module),
expandedSections: [1], expandedSections: [1],
focusedSection: 1, focusedSection: 1,