From 4cbd045502feede9bd9674860e881d122c60e627 Mon Sep 17 00:00:00 2001 From: Joao Ramos Date: Tue, 12 Dec 2023 22:44:33 +0000 Subject: [PATCH 1/3] Added comission to user card and updated user update to insert a new payment entry --- src/components/UserCard.tsx | 87 ++++++++++++++++++++--------------- src/interfaces/user.ts | 1 + src/pages/api/users/update.ts | 56 +++++++++++++++++++++- 3 files changed, 106 insertions(+), 38 deletions(-) diff --git a/src/components/UserCard.tsx b/src/components/UserCard.tsx index 98db4a77..c703d99c 100644 --- a/src/components/UserCard.tsx +++ b/src/components/UserCard.tsx @@ -60,7 +60,7 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers, const [paymentValue, setPaymentValue] = useState(user.type === "corporate" ? user.corporateInformation?.payment?.value : undefined); const [paymentCurrency, setPaymentCurrency] = useState(user.type === "corporate" ? user.corporateInformation?.payment?.currency : "EUR"); const [monthlyDuration, setMonthlyDuration] = useState(user.type === "corporate" ? user.corporateInformation?.monthlyDuration : undefined); - + const [commissionValue, setCommission] = useState(user.type === "corporate" ? user.corporateInformation?.payment?.commission : undefined); const {stats} = useStats(user.id); const {users} = useUsers(); @@ -106,6 +106,7 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers, payment: { value: paymentValue, currency: paymentCurrency, + ...referralAgent === '' ? {} : { commission: commissionValue } }, } : undefined, @@ -194,41 +195,6 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers, placeholder="Enter monthly duration" defaultValue={monthlyDuration} /> - -
- - {referralAgentLabel && ( - u.type === "agent").map((x) => ({value: x.id, label: `${x.name} - ${x.email}`})), + ]} + defaultValue={{ + value: referralAgent, + label: referralAgentLabel, + }} + onChange={(value) => setReferralAgent(value?.value)} + styles={{ + control: (styles) => ({ + ...styles, + paddingLeft: "4px", + border: "none", + outline: "none", + ":focus": { + outline: "none", + }, + }), + option: (styles, state) => ({ + ...styles, + backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white", + color: state.isFocused ? "black" : styles.color, + }), + }} + /> + )} +
+
+ {referralAgent !== '' ? ( + <> + + setCommission(e ? parseInt(e) : undefined)} + type="number" + defaultValue={commissionValue || 0} + className="col-span-3" + /> + + ) :
} +
+
)} diff --git a/src/interfaces/user.ts b/src/interfaces/user.ts index 5594f273..cd337ac5 100644 --- a/src/interfaces/user.ts +++ b/src/interfaces/user.ts @@ -57,6 +57,7 @@ export interface CorporateInformation { payment?: { value: number; currency: string; + commission: number; }; referralAgent?: string; } diff --git a/src/pages/api/users/update.ts b/src/pages/api/users/update.ts index 65bf3aa9..2f4a3723 100644 --- a/src/pages/api/users/update.ts +++ b/src/pages/api/users/update.ts @@ -1,13 +1,16 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction import type {NextApiRequest, NextApiResponse} from "next"; import {app} from "@/firebase"; -import {getFirestore, collection, getDocs, getDoc, doc, setDoc} from "firebase/firestore"; +import {getFirestore, collection, getDocs, getDoc, doc, setDoc, query, where} from "firebase/firestore"; import {withIronSessionApiRoute} from "iron-session/next"; import {sessionOptions} from "@/lib/session"; import {User} from "@/interfaces/user"; import {getDownloadURL, getStorage, ref, uploadBytes} from "firebase/storage"; import {getAuth, signInWithEmailAndPassword, updateEmail, updatePassword} from "firebase/auth"; import {errorMessages} from "@/constants/errors"; +import moment from "moment"; +import ShortUniqueId from "short-unique-id"; +import {Payment} from "@/interfaces/paypal"; const db = getFirestore(app); const storage = getStorage(app); @@ -15,6 +18,52 @@ const auth = getAuth(app); export default withIronSessionApiRoute(handler, sessionOptions); +const addPaymentRecord = async (data: Payment) => { + const shortUID = new ShortUniqueId(); + await setDoc(doc(db, "payments", shortUID.randomUUID(8)), data); +} +const managePaymentRecords = async (user: User, userId: string | undefined): Promise => { + try { + if(user.type === 'corporate' && userId) { + const data = { + corporate: userId, + agent: user.corporateInformation.referralAgent, + agentCommission: user.corporateInformation.payment!.commission, + agentValue: (user.corporateInformation.payment!.commission / 100) * user.corporateInformation.payment!.value, + currency: user.corporateInformation.payment!.currency, + value: user.corporateInformation.payment!.value, + isPaid: false, + date: new Date(), + } as Payment; + + const corporatePayments = await getDocs(query(collection(db, "payments"), where("corporate", "==", userId))); + if(corporatePayments.docs.length === 0) { + await addPaymentRecord(data); + return true; + } + + const hasPaymentPaidAndExpiring = corporatePayments.docs.filter((doc) => { + const data = doc.data(); + return data.isPaid + && moment().isAfter(moment(user.subscriptionExpirationDate).subtract(30, "days")) + && moment().isBefore(moment(user.subscriptionExpirationDate)); + }); + + if(hasPaymentPaidAndExpiring.length > 0) { + await addPaymentRecord(data); + return true; + } + } + + return false; + } catch(e) { + // if this process fails it should not stop the rest of the process + console.log(e); + return false; + } + +} + async function handler(req: NextApiRequest, res: NextApiResponse) { if (!req.session.user) { res.status(401).json({ok: false}); @@ -25,7 +74,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { const updatedUser = req.body as User & {password?: string; newPassword?: string}; if (!!req.query.id) { - await setDoc(userRef, updatedUser, {merge: true}); + const user = await setDoc(userRef, updatedUser, {merge: true}); + await managePaymentRecords(updatedUser, updatedUser.id); res.status(200).json({ok: true}); return; } @@ -74,6 +124,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { await req.session.save(); } + await managePaymentRecords(user, req.query.id); + res.status(200).json({user}); } From 1cff6fe2424c1890a114d54540c747421d66827c Mon Sep 17 00:00:00 2001 From: Joao Ramos Date: Tue, 12 Dec 2023 22:48:38 +0000 Subject: [PATCH 2/3] Temporary fix on date/data payment --- src/pages/api/users/update.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/api/users/update.ts b/src/pages/api/users/update.ts index 2f4a3723..c75ae980 100644 --- a/src/pages/api/users/update.ts +++ b/src/pages/api/users/update.ts @@ -18,7 +18,10 @@ const auth = getAuth(app); export default withIronSessionApiRoute(handler, sessionOptions); -const addPaymentRecord = async (data: Payment) => { +// TODO: Data is set as any as data cannot be parsed to Payment +// because the id is not a par of the hash and payment expects date to be of type Date +// but if it is not inserted as a string, some UI components will not work (Invalid Date) +const addPaymentRecord = async (data: any) => { const shortUID = new ShortUniqueId(); await setDoc(doc(db, "payments", shortUID.randomUUID(8)), data); } @@ -33,8 +36,8 @@ const managePaymentRecords = async (user: User, userId: string | undefined): Pro currency: user.corporateInformation.payment!.currency, value: user.corporateInformation.payment!.value, isPaid: false, - date: new Date(), - } as Payment; + date: new Date().toISOString(), + }; const corporatePayments = await getDocs(query(collection(db, "payments"), where("corporate", "==", userId))); if(corporatePayments.docs.length === 0) { From 15947f942cde5902d01f89e8f574a232f8f1f025 Mon Sep 17 00:00:00 2001 From: Joao Ramos Date: Wed, 13 Dec 2023 17:08:26 +0000 Subject: [PATCH 3/3] Fixed issue with payment records on update --- src/interfaces/paypal.ts | 2 +- src/pages/api/users/update.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/interfaces/paypal.ts b/src/interfaces/paypal.ts index 4305b960..2278e284 100644 --- a/src/interfaces/paypal.ts +++ b/src/interfaces/paypal.ts @@ -31,5 +31,5 @@ export interface Payment { currency: string; value: number; isPaid: boolean; - date: Date; + date: Date | string; } diff --git a/src/pages/api/users/update.ts b/src/pages/api/users/update.ts index c75ae980..7d3de267 100644 --- a/src/pages/api/users/update.ts +++ b/src/pages/api/users/update.ts @@ -22,13 +22,14 @@ export default withIronSessionApiRoute(handler, sessionOptions); // because the id is not a par of the hash and payment expects date to be of type Date // but if it is not inserted as a string, some UI components will not work (Invalid Date) const addPaymentRecord = async (data: any) => { - const shortUID = new ShortUniqueId(); - await setDoc(doc(db, "payments", shortUID.randomUUID(8)), data); + await setDoc(doc(db, "payments", data.id), data); } const managePaymentRecords = async (user: User, userId: string | undefined): Promise => { try { if(user.type === 'corporate' && userId) { - const data = { + const shortUID = new ShortUniqueId(); + const data: Payment = { + id: shortUID.randomUUID(8), corporate: userId, agent: user.corporateInformation.referralAgent, agentCommission: user.corporateInformation.payment!.commission,