diff --git a/src/components/PayPalPayment.tsx b/src/components/PayPalPayment.tsx index 20cf9787..c1e72ffe 100644 --- a/src/components/PayPalPayment.tsx +++ b/src/components/PayPalPayment.tsx @@ -55,7 +55,14 @@ export default function PayPalPayment({ trackingId, }) .then((response) => response.data) - .then((data) => data.id); + .then((data) => { + setIsLoading(false); + return data.id; + }) + .catch((err) => { + setIsLoading(false); + return err; + }); }; const onApprove = async (data: OnApproveData, actions: OnApproveActions) => { @@ -63,18 +70,26 @@ export default function PayPalPayment({ throw new Error("trackingId is not set"); } - const request = await axios.post<{ ok: boolean; reason?: string }>( - "/api/paypal/approve", - { id: data.orderID, duration, duration_unit, trackingId } - ); + axios + .post<{ ok: boolean; reason?: string }>("/api/paypal/approve", { + id: data.orderID, + duration, + duration_unit, + trackingId, + }) + .then((request) => { + if (request.status !== 200) { + toast.error("Something went wrong, please try again later"); + return; + } - if (request.status !== 200) { - toast.error("Something went wrong, please try again later"); - return; - } - - toast.success("Your account has been credited more time!"); - return onSuccess(duration, duration_unit); + toast.success("Your account has been credited more time!"); + return onSuccess(duration, duration_unit); + }) + .catch((err) => { + console.error(err); + toast.error("Something went wrong, please try again later"); + }); }; const onError = async (data: Record) => { @@ -96,7 +111,6 @@ export default function PayPalPayment({ currency, intent: "capture", commit: true, - vault: true, }} >

Don't forget to do it before its end date!

-

Click here to open the EnCoach Platform!

+

Click here to open the EnCoach Platform!


Thanks,

Your EnCoach team

diff --git a/src/email/templates/main.handlebars b/src/email/templates/main.handlebars index c7ac02a1..77a0f501 100644 --- a/src/email/templates/main.handlebars +++ b/src/email/templates/main.handlebars @@ -11,7 +11,8 @@
Hello future {{type}} of EnCoach,
- You have been invited to register at EnCoach + You have been invited to register at EnCoach to become a {{type}}!
@@ -19,7 +20,7 @@


- + {{code}} diff --git a/src/email/templates/verification.handlebars b/src/email/templates/verification.handlebars index ebf387db..03a35498 100644 --- a/src/email/templates/verification.handlebars +++ b/src/email/templates/verification.handlebars @@ -10,7 +10,8 @@

Hello {{name}},


Follow this link to verify your email address.

- Verify account + Verify + account

If you didn’t ask to verify this address, you can ignore this email.

diff --git a/src/email/templates/verification.handlebars.json b/src/email/templates/verification.handlebars.json index 2fbb1fad..c415aa8f 100644 --- a/src/email/templates/verification.handlebars.json +++ b/src/email/templates/verification.handlebars.json @@ -1,5 +1,6 @@ { "name": "Tiago Ribeiro", "email": "tiago.ribeiro@ecrop.dev", - "code": "123" + "code": "123", + "environment": "platform" } \ No newline at end of file diff --git a/src/pages/(status)/PaymentDue.tsx b/src/pages/(status)/PaymentDue.tsx index 4a2cbc07..e6aab64b 100644 --- a/src/pages/(status)/PaymentDue.tsx +++ b/src/pages/(status)/PaymentDue.tsx @@ -15,6 +15,8 @@ import InviteCard from "@/components/Medium/InviteCard"; import {useRouter} from "next/router"; import {PayPalScriptProvider} from "@paypal/react-paypal-js"; import { usePaypalTracking } from "@/hooks/usePaypalTracking"; +import {ToastContainer} from "react-toastify"; + interface Props { user: User; @@ -47,6 +49,7 @@ export default function PaymentDue({user, hasExpired = false, clientID, reload}: return ( <> + {isLoading && (
@@ -97,7 +100,6 @@ export default function PaymentDue({user, hasExpired = false, clientID, reload}: currency: "USD", intent: "capture", commit: true, - vault: true, }}> {packages.map((p) => (
diff --git a/src/pages/action.tsx b/src/pages/action.tsx index f7564cd5..bee4552a 100644 --- a/src/pages/action.tsx +++ b/src/pages/action.tsx @@ -1,227 +1,170 @@ /* 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, + res, }: { - query: { - oobCode: string; - mode: string; - continueUrl?: string; - }; - res: any; + 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: {}, - }; - } + 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, - ...(query.continueUrl ? { continueUrl: query.continueUrl } : {}), - }, - }; + return { + props: { + code: query.oobCode, + mode: query.mode, + ...(query.continueUrl ? {continueUrl: query.continueUrl} : {}), + }, + }; } -export default function Reset({ - code, - mode, - continueUrl, -}: { - code: string; - mode: 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.push("/"); - }, 1000); - return; - } + useEffect(() => { + if (mode === "signIn") { + axios + .post<{ok: boolean}>("/api/reset/verify", { + email: continueUrl?.replace("https://platform.encoach.com/", "").replace("https://staging.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/assignments/index.ts b/src/pages/api/assignments/index.ts index 471b8376..4d0cc11a 100644 --- a/src/pages/api/assignments/index.ts +++ b/src/pages/api/assignments/index.ts @@ -163,6 +163,7 @@ async function POST(req: NextApiRequest, res: NextApiResponse) { modules: examModulesLabel, assigner: teacher.name, }, + environment: process.env.ENVIRONMENT, }, [assignee.email], "EnCoach - New Assignment!", diff --git a/src/pages/api/invites/accept/[id].ts b/src/pages/api/invites/accept/[id].ts index b12db506..20e8445e 100644 --- a/src/pages/api/invites/accept/[id].ts +++ b/src/pages/api/invites/accept/[id].ts @@ -113,6 +113,7 @@ async function get(req: NextApiRequest, res: NextApiResponse) { corporateName: invitedBy.name, name: req.session.user.name, decision: "accept", + environment: process.env.ENVIRONMENT, }, [invitedBy.email], `${req.session.user.name} has accepted your invite!`, diff --git a/src/pages/api/invites/decline/[id].ts b/src/pages/api/invites/decline/[id].ts index e8ab55a9..4110e70b 100644 --- a/src/pages/api/invites/decline/[id].ts +++ b/src/pages/api/invites/decline/[id].ts @@ -1,72 +1,62 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type { NextApiRequest, NextApiResponse } from "next"; -import { app } from "@/firebase"; -import { - getFirestore, - getDoc, - doc, - deleteDoc, - setDoc, - getDocs, - collection, - where, - query, -} from "firebase/firestore"; -import { withIronSessionApiRoute } from "iron-session/next"; -import { sessionOptions } from "@/lib/session"; -import { Ticket } from "@/interfaces/ticket"; -import { Invite } from "@/interfaces/invite"; -import { Group, User } from "@/interfaces/user"; -import { v4 } from "uuid"; -import { sendEmail } from "@/email"; +import type {NextApiRequest, NextApiResponse} from "next"; +import {app} from "@/firebase"; +import {getFirestore, getDoc, doc, deleteDoc, setDoc, getDocs, collection, where, query} from "firebase/firestore"; +import {withIronSessionApiRoute} from "iron-session/next"; +import {sessionOptions} from "@/lib/session"; +import {Ticket} from "@/interfaces/ticket"; +import {Invite} from "@/interfaces/invite"; +import {Group, User} from "@/interfaces/user"; +import {v4} from "uuid"; +import {sendEmail} from "@/email"; const db = getFirestore(app); export default withIronSessionApiRoute(handler, sessionOptions); async function handler(req: NextApiRequest, res: NextApiResponse) { - if (req.method === "GET") return await get(req, res); + if (req.method === "GET") return await get(req, res); - res.status(404).json(undefined); + res.status(404).json(undefined); } async function get(req: NextApiRequest, res: NextApiResponse) { - if (!req.session.user) { - res.status(401).json({ ok: false }); - return; - } + if (!req.session.user) { + res.status(401).json({ok: false}); + return; + } - const { id } = req.query as { id: string }; - const snapshot = await getDoc(doc(db, "invites", id)); + const {id} = req.query as {id: string}; + const snapshot = await getDoc(doc(db, "invites", id)); - if (snapshot.exists()) { - const invite = { ...snapshot.data(), id: snapshot.id } as Invite; - if (invite.to !== req.session.user.id) - return res.status(403).json({ ok: false }); + if (snapshot.exists()) { + const invite = {...snapshot.data(), id: snapshot.id} as Invite; + if (invite.to !== req.session.user.id) return res.status(403).json({ok: false}); - await deleteDoc(snapshot.ref); - const invitedByRef = await getDoc(doc(db, "users", invite.from)); - if (!invitedByRef.exists()) return res.status(404).json({ ok: false }); + await deleteDoc(snapshot.ref); + const invitedByRef = await getDoc(doc(db, "users", invite.from)); + if (!invitedByRef.exists()) return res.status(404).json({ok: false}); - const invitedBy = { ...invitedByRef.data(), id: invitedByRef.id } as User; + const invitedBy = {...invitedByRef.data(), id: invitedByRef.id} as User; - try { - await sendEmail( - "respondedInvite", - { - corporateName: invitedBy.name, - name: req.session.user.name, - decision: "decline", - }, - [invitedBy.email], - `${req.session.user.name} has declined your invite!`, - ); - } catch (e) { - console.log(e); - } + try { + await sendEmail( + "respondedInvite", + { + corporateName: invitedBy.name, + name: req.session.user.name, + decision: "decline", + environment: process.env.ENVIRONMENT, + }, + [invitedBy.email], + `${req.session.user.name} has declined your invite!`, + ); + } catch (e) { + console.log(e); + } - res.status(200).json({ ok: true }); - } else { - res.status(404).json(undefined); - } + res.status(200).json({ok: true}); + } else { + res.status(404).json(undefined); + } } diff --git a/src/pages/api/invites/index.ts b/src/pages/api/invites/index.ts index 023e6e2d..efc88d72 100644 --- a/src/pages/api/invites/index.ts +++ b/src/pages/api/invites/index.ts @@ -1,20 +1,13 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import { sendEmail } from "@/email"; -import { app } from "@/firebase"; -import { Invite } from "@/interfaces/invite"; -import { Ticket } from "@/interfaces/ticket"; -import { User } from "@/interfaces/user"; -import { sessionOptions } from "@/lib/session"; -import { - collection, - doc, - getDoc, - getDocs, - getFirestore, - setDoc, -} from "firebase/firestore"; -import { withIronSessionApiRoute } from "iron-session/next"; -import type { NextApiRequest, NextApiResponse } from "next"; +import {sendEmail} from "@/email"; +import {app} from "@/firebase"; +import {Invite} from "@/interfaces/invite"; +import {Ticket} from "@/interfaces/ticket"; +import {User} from "@/interfaces/user"; +import {sessionOptions} from "@/lib/session"; +import {collection, doc, getDoc, getDocs, getFirestore, setDoc} from "firebase/firestore"; +import {withIronSessionApiRoute} from "iron-session/next"; +import type {NextApiRequest, NextApiResponse} from "next"; import ShortUniqueId from "short-unique-id"; const db = getFirestore(app); @@ -22,67 +15,60 @@ const db = getFirestore(app); export default withIronSessionApiRoute(handler, sessionOptions); async function handler(req: NextApiRequest, res: NextApiResponse) { - if (!req.session.user) { - res.status(401).json({ ok: false }); - return; - } + if (!req.session.user) { + res.status(401).json({ok: false}); + return; + } - if (req.method === "GET") await get(req, res); - if (req.method === "POST") await post(req, res); + if (req.method === "GET") await get(req, res); + if (req.method === "POST") await post(req, res); } async function get(req: NextApiRequest, res: NextApiResponse) { - const snapshot = await getDocs(collection(db, "invites")); + const snapshot = await getDocs(collection(db, "invites")); - res.status(200).json( - snapshot.docs.map((doc) => ({ - id: doc.id, - ...doc.data(), - })), - ); + res.status(200).json( + snapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data(), + })), + ); } async function post(req: NextApiRequest, res: NextApiResponse) { - const body = req.body as Invite; + const body = req.body as Invite; - const existingInvites = (await getDocs(collection(db, "invites"))).docs.map( - (x) => ({ ...x.data(), id: x.id }), - ) as Invite[]; + const existingInvites = (await getDocs(collection(db, "invites"))).docs.map((x) => ({...x.data(), id: x.id})) as Invite[]; - const invitedRef = await getDoc(doc(db, "users", body.to)); - if (!invitedRef.exists()) return res.status(404).json({ ok: false }); + const invitedRef = await getDoc(doc(db, "users", body.to)); + if (!invitedRef.exists()) return res.status(404).json({ok: false}); - const invitedByRef = await getDoc(doc(db, "users", body.from)); - if (!invitedByRef.exists()) return res.status(404).json({ ok: false }); + const invitedByRef = await getDoc(doc(db, "users", body.from)); + if (!invitedByRef.exists()) return res.status(404).json({ok: false}); - const invited = { ...invitedRef.data(), id: invitedRef.id } as User; - const invitedBy = { ...invitedByRef.data(), id: invitedByRef.id } as User; + const invited = {...invitedRef.data(), id: invitedRef.id} as User; + const invitedBy = {...invitedByRef.data(), id: invitedByRef.id} as User; - try { - await sendEmail( - "receivedInvite", - { - name: invited.name, - corporateName: - invitedBy.type === "corporate" - ? invitedBy.corporateInformation?.companyInformation?.name || - invitedBy.name - : invitedBy.name, - }, - [invited.email], - "You have been invited to a group!", - ); - } catch (e) { - console.log(e); - } + try { + await sendEmail( + "receivedInvite", + { + name: invited.name, + corporateName: + invitedBy.type === "corporate" ? invitedBy.corporateInformation?.companyInformation?.name || invitedBy.name : invitedBy.name, + environment: process.env.ENVIRONMENT, + }, + [invited.email], + "You have been invited to a group!", + ); + } catch (e) { + console.log(e); + } - if ( - existingInvites.filter((i) => i.to === body.to && i.from === body.from) - .length == 0 - ) { - const shortUID = new ShortUniqueId(); - await setDoc(doc(db, "invites", body.id || shortUID.randomUUID(8)), body); - } + if (existingInvites.filter((i) => i.to === body.to && i.from === body.from).length == 0) { + const shortUID = new ShortUniqueId(); + await setDoc(doc(db, "invites", body.id || shortUID.randomUUID(8)), body); + } - res.status(200).json({ ok: true }); + res.status(200).json({ok: true}); } diff --git a/src/pages/api/paypal/approve.ts b/src/pages/api/paypal/approve.ts index 2a176a3b..af9f18f9 100644 --- a/src/pages/api/paypal/approve.ts +++ b/src/pages/api/paypal/approve.ts @@ -42,91 +42,96 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { if (!trackingId) return res.status(401).json({ ok: false, reason: "Missing tracking id!" }); - const request = await axios.post( - `${process.env.PAYPAL_ACCESS_TOKEN_URL}/v2/checkout/orders/${id}/capture`, - {}, - { - headers: { - Authorization: `Bearer ${accessToken}`, - "PayPal-Client-Metadata-Id": trackingId, - }, - } - ); + const url = `${process.env.PAYPAL_ACCESS_TOKEN_URL}/v2/checkout/orders/${id}/capture`; + const headers = { + headers: { + Authorization: `Bearer ${accessToken}`, + "PayPal-Client-Metadata-Id": trackingId, + }, + }; + axios + .post(url, {}, headers) + .then(async (request) => { + if (request.data.status === "COMPLETED") { + const user = req.session.user; + const subscriptionExpirationDate = + user!.subscriptionExpirationDate; + const today = moment(new Date()); + const dateToBeAddedTo = !subscriptionExpirationDate + ? today + : moment(subscriptionExpirationDate).isAfter(today) + ? moment(subscriptionExpirationDate) + : today; - if (request.data.status === "COMPLETED") { - const user = req.session.user; - const subscriptionExpirationDate = - req.session.user.subscriptionExpirationDate; - const today = moment(new Date()); - const dateToBeAddedTo = !subscriptionExpirationDate - ? today - : moment(subscriptionExpirationDate).isAfter(today) - ? moment(subscriptionExpirationDate) - : today; + const updatedExpirationDate = dateToBeAddedTo.add( + duration, + duration_unit + ); + await setDoc( + doc(db, "users", req.session.user!.id), + { + subscriptionExpirationDate: updatedExpirationDate.toISOString(), + status: "active", + }, + { merge: true } + ); - const updatedExpirationDate = dateToBeAddedTo.add(duration, duration_unit); - await setDoc( - doc(db, "users", req.session.user.id), - { - subscriptionExpirationDate: updatedExpirationDate.toISOString(), - status: "active", - }, - { merge: true } - ); - - try { - await setDoc( - doc(db, 'paypalpayments', v4()), - { - orderId: id, - userId: req.session.user.id, - status: request.data.status, - createdAt: new Date().toISOString(), - value: request.data.purchase_units[0].payments.captures[0].amount.value, - currency: request.data.purchase_units[0].payments.captures[0].amount.currency_code, - subscriptionDuration: duration, - subscriptionDurationUnit: duration_unit, - subscriptionExpirationDate: updatedExpirationDate.toISOString(), - } - ); - } catch(err) { - console.error('Failed to insert paypal payment!', err); - } + try { + await setDoc(doc(db, "paypalpayments", v4()), { + orderId: id, + userId: req.session.user!.id, + status: request.data.status, + createdAt: new Date().toISOString(), + value: + request.data.purchase_units[0].payments.captures[0].amount.value, + currency: + request.data.purchase_units[0].payments.captures[0].amount + .currency_code, + subscriptionDuration: duration, + subscriptionDurationUnit: duration_unit, + subscriptionExpirationDate: updatedExpirationDate.toISOString(), + }); + } catch (err) { + console.error("Failed to insert paypal payment!", err); + } - if (user.type === "corporate") { - const snapshot = await getDocs(collection(db, "groups")); - const groups: Group[] = ( - snapshot.docs.map((doc) => ({ - id: doc.id, - ...doc.data(), - })) as Group[] - ).filter((x) => x.admin === user.id); + if (user!.type === "corporate") { + const snapshot = await getDocs(collection(db, "groups")); + const groups: Group[] = ( + snapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data(), + })) as Group[] + ).filter((x) => x.admin === user!.id); - await Promise.all( - groups - .flatMap((x) => x.participants) - .map( - async (x) => - await setDoc( - doc(db, "users", x), - { - subscriptionExpirationDate: - updatedExpirationDate.toISOString(), - status: "active", - }, - { merge: true } + await Promise.all( + groups + .flatMap((x) => x.participants) + .map( + async (x) => + await setDoc( + doc(db, "users", x), + { + subscriptionExpirationDate: + updatedExpirationDate.toISOString(), + status: "active", + }, + { merge: true } + ) ) - ) - ); - } + ); + } - return res.status(200).json({ ok: true }); - } + return res.status(200).json({ ok: true }); + } - res - .status(404) - .json({ - ok: false, - reason: "Order ID not found or purchase was not approved!", + res.status(404).json({ + ok: false, + reason: "Order ID not found or purchase was not approved!", + }); + }) + .catch((err) => { + console.error(err.response.status, err.response.data); + res.status(err.response.status).json(err.response.data); }); } diff --git a/src/pages/api/paypal/index.ts b/src/pages/api/paypal/index.ts index 9a44eac4..9b01efe8 100644 --- a/src/pages/api/paypal/index.ts +++ b/src/pages/api/paypal/index.ts @@ -1,62 +1,99 @@ // 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} from "firebase/firestore"; -import {withIronSessionApiRoute} from "iron-session/next"; -import {sessionOptions} from "@/lib/session"; +import type { NextApiRequest, NextApiResponse } from "next"; +import { app } from "@/firebase"; +import { getFirestore, collection, getDocs } from "firebase/firestore"; +import { withIronSessionApiRoute } from "iron-session/next"; +import { sessionOptions } from "@/lib/session"; import axios from "axios"; -import {v4} from "uuid"; -import {OrderResponseBody} from "@paypal/paypal-js"; -import {getAccessToken} from "@/utils/paypal"; +import { v4 } from "uuid"; +import { OrderResponseBody } from "@paypal/paypal-js"; +import { getAccessToken } from "@/utils/paypal"; const db = getFirestore(app); export default withIronSessionApiRoute(handler, sessionOptions); async function handler(req: NextApiRequest, res: NextApiResponse) { - if (req.method !== "POST") return res.status(404).json({ok: false, reason: "Method not supported!"}); - if (!req.session.user) return res.status(401).json({ok: false}); + if (req.method !== "POST") + return res.status(404).json({ ok: false, reason: "Method not supported!" }); + if (!req.session.user) return res.status(401).json({ ok: false }); - const accessToken = await getAccessToken(); - if (!accessToken) return res.status(401).json({ok: false, reason: "Authorization failed!"}); + const accessToken = await getAccessToken(); + if (!accessToken) + return res.status(401).json({ ok: false, reason: "Authorization failed!" }); - const {currencyCode, price, trackingId} = req.body as {currencyCode: string; price: number, trackingId: string}; + const { currencyCode, price, trackingId } = req.body as { + currencyCode: string; + price: number; + trackingId: string; + }; - if(!trackingId) return res.status(401).json({ok: false, reason: "Missing tracking id!"}); + if (!trackingId) + return res.status(401).json({ ok: false, reason: "Missing tracking id!" }); - const request = await axios.post( - `${process.env.PAYPAL_ACCESS_TOKEN_URL}/v2/checkout/orders`, - { - purchase_units: [ - { - amount: { - currency_code: currencyCode, - value: price.toString(), - }, - reference_id: v4(), - }, - ], - payment_source: { - paypal: { - email_address: req.session.user.email || "", - experience_context: { - payment_method_preference: "IMMEDIATE_PAYMENT_REQUIRED", - locale: "en-US", - landing_page: "LOGIN", - shipping_preference: "NO_SHIPPING", - user_action: "PAY_NOW", + const url = `${process.env.PAYPAL_ACCESS_TOKEN_URL}/v2/checkout/orders`; + const amount = { + currency_code: currencyCode, + value: price.toString(), + }; + + const data = { + purchase_units: [ + { + invoice_id: `INV-${v4()}`, + amount: { + ...amount, + breakdown: { + item_total: amount, }, }, + items: [ + { + name: "Encoach Subscription", + quantity: "1", + category: "DIGITAL_GOODS", + unit_amount: amount, + }, + ], }, - intent: "CAPTURE", - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - 'PayPal-Client-Metadata-Id': trackingId, - }, - }, - ); + ], + payment_source: { + paypal: { + email_address: req.session.user.email || "", + experience_context: { + payment_method_preference: "IMMEDIATE_PAYMENT_REQUIRED", + locale: "en-US", + landing_page: "LOGIN", + shipping_preference: "NO_SHIPPING", + user_action: "PAY_NOW", + brand_name: "Encoach", + }, + }, + }, + intent: "CAPTURE", + }; - res.status(request.status).json(request.data); + const headers = { + headers: { + Authorization: `Bearer ${accessToken}`, + "PayPal-Client-Metadata-Id": trackingId, + }, + }; + console.log( + JSON.stringify({ + url, + data, + headers, + }) + ); + + axios + .post(url, data, headers) + .then((request) => { + res.status(request.status).json(request.data); + }) + .catch((err) => { + console.error(err.response.status, err.response.data); + res.status(err.response.status).json(err.response.data); + }); } diff --git a/src/pages/api/paypal/raas.ts b/src/pages/api/paypal/raas.ts index c4eb90af..1a26d3d1 100644 --- a/src/pages/api/paypal/raas.ts +++ b/src/pages/api/paypal/raas.ts @@ -25,29 +25,35 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { const trackingId = `${req.session.user.id}-${Date.now()}`; - try { - const request = await axios.put( - `${process.env.PAYPAL_ACCESS_TOKEN_URL}/v1/risk/transaction-contexts/${process.env.PAYPAL_MERCHANT_ID}/${trackingId}`, + const url = `${process.env.PAYPAL_ACCESS_TOKEN_URL}/v1/risk/transaction-contexts/${process.env.PAYPAL_MERCHANT_ID}/${trackingId}`; + const data = { + additional_data: [ { - additional_data: [ - { - key: "user_id", - value: req.session.user.id, - }, - ], + key: "user_id", + value: req.session.user.id, }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - } - ); + ], + }; + + const headers = { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }; + console.log(JSON.stringify({ + url, + data, + headers, + })); + try { + const request = await axios.put(url, data, headers); return res.status(request.status).json({ ok: true, trackingId, }); } catch (err) { + console.error(url, err); return res .status(500) .json({ ok: false, reason: "Failed to create tracking ID" }); diff --git a/src/pages/api/reset/sendVerification.ts b/src/pages/api/reset/sendVerification.ts index cb582cb1..1116cc2d 100644 --- a/src/pages/api/reset/sendVerification.ts +++ b/src/pages/api/reset/sendVerification.ts @@ -19,6 +19,7 @@ async function sendVerification(req: NextApiRequest, res: NextApiResponse) { name: req.session.user.name, code: short.randomUUID(6), email: req.session.user.email, + environment: process.env.ENVIRONMENT, }, [req.session.user.email], "EnCoach Verification", diff --git a/src/pages/api/tickets/[id].ts b/src/pages/api/tickets/[id].ts index 29e35e45..f55ca518 100644 --- a/src/pages/api/tickets/[id].ts +++ b/src/pages/api/tickets/[id].ts @@ -1,109 +1,104 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type { NextApiRequest, NextApiResponse } from "next"; -import { app } from "@/firebase"; -import { - getFirestore, - getDoc, - doc, - deleteDoc, - setDoc, -} from "firebase/firestore"; -import { withIronSessionApiRoute } from "iron-session/next"; -import { sessionOptions } from "@/lib/session"; -import { Ticket, TicketTypeLabel, TicketStatusLabel } from "@/interfaces/ticket"; +import type {NextApiRequest, NextApiResponse} from "next"; +import {app} from "@/firebase"; +import {getFirestore, getDoc, doc, deleteDoc, setDoc} from "firebase/firestore"; +import {withIronSessionApiRoute} from "iron-session/next"; +import {sessionOptions} from "@/lib/session"; +import {Ticket, TicketTypeLabel, TicketStatusLabel} from "@/interfaces/ticket"; import moment from "moment"; -import { sendEmail } from "@/email"; +import {sendEmail} from "@/email"; const db = getFirestore(app); export default withIronSessionApiRoute(handler, sessionOptions); async function handler(req: NextApiRequest, res: NextApiResponse) { - if (req.method === "GET") return await get(req, res); - if (req.method === "DELETE") return await del(req, res); - if (req.method === "PATCH") return await patch(req, res); + if (req.method === "GET") return await get(req, res); + if (req.method === "DELETE") return await del(req, res); + if (req.method === "PATCH") return await patch(req, res); - res.status(404).json(undefined); + res.status(404).json(undefined); } async function get(req: NextApiRequest, res: NextApiResponse) { - if (!req.session.user) { - res.status(401).json({ ok: false }); - return; - } + if (!req.session.user) { + res.status(401).json({ok: false}); + return; + } - const { id } = req.query as { id: string }; + const {id} = req.query as {id: string}; - const snapshot = await getDoc(doc(db, "tickets", id)); + const snapshot = await getDoc(doc(db, "tickets", id)); - if (snapshot.exists()) { - res.status(200).json({ ...snapshot.data(), id: snapshot.id }); - } else { - res.status(404).json(undefined); - } + if (snapshot.exists()) { + res.status(200).json({...snapshot.data(), id: snapshot.id}); + } else { + res.status(404).json(undefined); + } } async function del(req: NextApiRequest, res: NextApiResponse) { - if (!req.session.user) { - res.status(401).json({ ok: false }); - return; - } + if (!req.session.user) { + res.status(401).json({ok: false}); + return; + } - const { id } = req.query as { id: string }; + const {id} = req.query as {id: string}; - const snapshot = await getDoc(doc(db, "tickets", id)); - const data = snapshot.data() as Ticket; + const snapshot = await getDoc(doc(db, "tickets", id)); + const data = snapshot.data() as Ticket; - const user = req.session.user; - if (user.type === "admin" || user.type === "developer") { - await deleteDoc(snapshot.ref); - res.status(200).json({ ok: true }); - return; - } + const user = req.session.user; + if (user.type === "admin" || user.type === "developer") { + await deleteDoc(snapshot.ref); + res.status(200).json({ok: true}); + return; + } - res.status(403).json({ ok: false }); + res.status(403).json({ok: false}); } async function patch(req: NextApiRequest, res: NextApiResponse) { - if (!req.session.user) { - res.status(401).json({ ok: false }); - return; - } + if (!req.session.user) { + res.status(401).json({ok: false}); + return; + } - const { id } = req.query as { id: string }; - const body = req.body as Ticket; + const {id} = req.query as {id: string}; + const body = req.body as Ticket; - const snapshot = await getDoc(doc(db, "tickets", id)); + const snapshot = await getDoc(doc(db, "tickets", id)); - const user = req.session.user; - if (user.type === "admin" || user.type === "developer") { - const data = snapshot.data() as Ticket; - await setDoc(snapshot.ref, body, { merge: true }); - try { - // send email if the status actually changed to completed - if(data.status !== req.body.status && req.body.status === 'completed') { - await sendEmail( - "ticketStatusCompleted", - { - id, - subject: body.subject, - reporter: body.reporter, - date: moment(body.date).format("DD/MM/YYYY - HH:mm"), - type: TicketTypeLabel[body.type], - reportedFrom: body.reportedFrom, - description: body.description, - }, - [data.reporter.email], - `Ticket ${id}: ${data.subject}`, - ); - } - } catch(err) { - console.error(err); - // doesnt matter if the email fails - } - res.status(200).json({ ok: true }); - return; - } + const user = req.session.user; + if (user.type === "admin" || user.type === "developer") { + const data = snapshot.data() as Ticket; + await setDoc(snapshot.ref, body, {merge: true}); + try { + // send email if the status actually changed to completed + if (data.status !== req.body.status && req.body.status === "completed") { + await sendEmail( + "ticketStatusCompleted", + { + id, + subject: body.subject, + reporter: body.reporter, + date: moment(body.date).format("DD/MM/YYYY - HH:mm"), + type: TicketTypeLabel[body.type], + reportedFrom: body.reportedFrom, + description: body.description, + environment: process.env.ENVIRONMENT, + }, + [data.reporter.email], + `Ticket ${id}: ${data.subject}`, + ); + } + } catch (err) { + console.error(err); + // doesnt matter if the email fails + } + res.status(200).json({ok: true}); + return; + } - res.status(403).json({ ok: false }); + res.status(403).json({ok: false}); } diff --git a/src/pages/api/tickets/index.ts b/src/pages/api/tickets/index.ts index d7daa365..d99b9fee 100644 --- a/src/pages/api/tickets/index.ts +++ b/src/pages/api/tickets/index.ts @@ -1,110 +1,103 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import { sendEmail } from "@/email"; -import { app } from "@/firebase"; -import { Ticket, TicketTypeLabel, TicketWithCorporate } from "@/interfaces/ticket"; -import { sessionOptions } from "@/lib/session"; -import { - collection, - doc, - getDocs, - getFirestore, - setDoc, - where, - query, -} from "firebase/firestore"; -import { withIronSessionApiRoute } from "iron-session/next"; +import {sendEmail} from "@/email"; +import {app} from "@/firebase"; +import {Ticket, TicketTypeLabel, TicketWithCorporate} from "@/interfaces/ticket"; +import {sessionOptions} from "@/lib/session"; +import {collection, doc, getDocs, getFirestore, setDoc, where, query} from "firebase/firestore"; +import {withIronSessionApiRoute} from "iron-session/next"; import moment from "moment"; -import type { NextApiRequest, NextApiResponse } from "next"; +import type {NextApiRequest, NextApiResponse} from "next"; import ShortUniqueId from "short-unique-id"; -import { Group, CorporateUser } from "@/interfaces/user"; +import {Group, CorporateUser} from "@/interfaces/user"; const db = getFirestore(app); export default withIronSessionApiRoute(handler, sessionOptions); async function handler(req: NextApiRequest, res: NextApiResponse) { - // due to integration with the homepage the POST request should be public - if (req.method === "POST") { - await post(req, res); - return; - } + // due to integration with the homepage the POST request should be public + if (req.method === "POST") { + await post(req, res); + return; + } - // specific logic for the preflight request - if (req.method === "OPTIONS") { - res.status(200).end(); - return; - } - if (!req.session.user) { - res.status(401).json({ ok: false }); - return; - } + // specific logic for the preflight request + if (req.method === "OPTIONS") { + res.status(200).end(); + return; + } + if (!req.session.user) { + res.status(401).json({ok: false}); + return; + } - if (req.method === "GET") { - await get(req, res); - } + if (req.method === "GET") { + await get(req, res); + } } async function get(req: NextApiRequest, res: NextApiResponse) { - const snapshot = await getDocs(collection(db, "tickets")); + const snapshot = await getDocs(collection(db, "tickets")); - const docs = snapshot.docs.map((doc) => ({ - id: doc.id, - ...doc.data(), - })) as Ticket[]; + const docs = snapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data(), + })) as Ticket[]; - // fetch all groups for these users + // fetch all groups for these users - const reporters = [...new Set(docs.map((d) => d.reporter.id).filter((id) => id))]; + const reporters = [...new Set(docs.map((d) => d.reporter.id).filter((id) => id))]; - const groupsSnapshot = await getDocs(query(collection(db, "groups"), where("participants", "array-contains-any", reporters))); - const groups = groupsSnapshot.docs.map((doc) => doc.data()) as Group[]; + const groupsSnapshot = await getDocs(query(collection(db, "groups"), where("participants", "array-contains-any", reporters))); + const groups = groupsSnapshot.docs.map((doc) => doc.data()) as Group[]; - // based on the admin of each group, verify if it exists and it's of type corporate - const groupsAdmins = [...new Set(groups.map((g) => g.admin).filter((id) => id))]; - const adminsSnapshot = await getDocs(query(collection(db, "users"), where("id", "in", groupsAdmins), where("type", "==", "corporate"))); - const admins = adminsSnapshot.docs.map((doc) => doc.data()); + // based on the admin of each group, verify if it exists and it's of type corporate + const groupsAdmins = [...new Set(groups.map((g) => g.admin).filter((id) => id))]; + const adminsSnapshot = await getDocs(query(collection(db, "users"), where("id", "in", groupsAdmins), where("type", "==", "corporate"))); + const admins = adminsSnapshot.docs.map((doc) => doc.data()); - const docsWithAdmins = docs.map((d) => { - const group = groups.find((g) => g.participants.includes(d.reporter.id)); - const admin = admins.find((a) => a.id === group?.admin) as CorporateUser; + const docsWithAdmins = docs.map((d) => { + const group = groups.find((g) => g.participants.includes(d.reporter.id)); + const admin = admins.find((a) => a.id === group?.admin) as CorporateUser; - if(admin) { - return { - ...d, - corporate: admin.corporateInformation?.companyInformation?.name, - }; - } + if (admin) { + return { + ...d, + corporate: admin.corporateInformation?.companyInformation?.name, + }; + } - return d; - }) as TicketWithCorporate[]; + return d; + }) as TicketWithCorporate[]; - res.status(200).json(docsWithAdmins); + res.status(200).json(docsWithAdmins); } async function post(req: NextApiRequest, res: NextApiResponse) { - const body = req.body as Ticket; + const body = req.body as Ticket; - const shortUID = new ShortUniqueId(); - const id = body.id || shortUID.randomUUID(8); - await setDoc(doc(db, "tickets", id), body); - res.status(200).json({ ok: true }); + const shortUID = new ShortUniqueId(); + const id = body.id || shortUID.randomUUID(8); + await setDoc(doc(db, "tickets", id), body); + res.status(200).json({ok: true}); - try { - await sendEmail( - "submittedFeedback", - { - id, - subject: body.subject, - reporter: body.reporter, - date: moment(body.date).format("DD/MM/YYYY - HH:mm"), - type: TicketTypeLabel[body.type], - reportedFrom: body.reportedFrom, - description: body.description, - }, - [body.reporter.email], - `Ticket ${id}: ${body.subject}` - ); - } catch (e) { - console.log(e); - } + try { + await sendEmail( + "submittedFeedback", + { + id, + subject: body.subject, + reporter: body.reporter, + date: moment(body.date).format("DD/MM/YYYY - HH:mm"), + type: TicketTypeLabel[body.type], + reportedFrom: body.reportedFrom, + description: body.description, + environment: process.env.ENVIRONMENT, + }, + [body.reporter.email], + `Ticket ${id}: ${body.subject}`, + ); + } catch (e) { + console.log(e); + } }