Merge branch 'develop' into feature/user-choose-topics

This commit is contained in:
Tiago Ribeiro
2024-02-13 00:45:56 +00:00
7 changed files with 137 additions and 11 deletions

View File

@@ -1,4 +1,5 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const websiteUrl = process.env.NODE_ENV === 'production' ? "https://encoach.com" : "http://localhost:3000";
const nextConfig = { const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
output: "standalone", output: "standalone",
@@ -8,7 +9,7 @@ const nextConfig = {
source: "/api/packages", source: "/api/packages",
headers: [ headers: [
{key: "Access-Control-Allow-Credentials", value: "false"}, {key: "Access-Control-Allow-Credentials", value: "false"},
{key: "Access-Control-Allow-Origin", value: "https://encoach.com"}, {key: "Access-Control-Allow-Origin", value: websiteUrl},
{ {
key: "Access-Control-Allow-Methods", key: "Access-Control-Allow-Methods",
value: "GET", value: "GET",
@@ -19,6 +20,21 @@ const nextConfig = {
}, },
], ],
}, },
{
source: "/api/tickets",
headers: [
{key: "Access-Control-Allow-Credentials", value: "false"},
{key: "Access-Control-Allow-Origin", value: websiteUrl},
{
key: "Access-Control-Allow-Methods",
value: "POST,OPTIONS",
},
{
key: "Access-Control-Allow-Headers",
value: "Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date",
},
],
},
]; ];
}, },
}; };

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
</head>
<div style="background-color: #ffffff; color: #353338;"
class="h-full min-h-screen w-full flex flex-col p-8 gap-16 text-base">
<img src="/logo_title.png" class="w-48 h-48 self-center" />
<div>
<span>Your ticket has been completed!</span>
<br/>
<span>Here is the ticket's information:</span>
<br/>
<br/>
<span><b>ID:</b> {{id}}</span><br/>
<span><b>Subject:</b> {{subject}}</span><br/>
<span><b>Reporter:</b> {{reporter.name}} - {{reporter.email}}</span><br/>
<span><b>Date:</b> {{date}}</span><br/>
<span><b>Type:</b> {{type}}</span><br/>
<span><b>Page:</b> {{reportedFrom}}</span>
<br/>
<br/>
<span><b>Description:</b> {{description}}</span><br/>
</div>
<br />
<br />
<div>
<span>Thanks, <br /> Your EnCoach team</span>
</div>
</div>
</html>

View File

@@ -0,0 +1,26 @@
import React from "react";
import Link from "next/link";
import Checkbox from "@/components/Low/Checkbox";
const useAcceptedTerms = () => {
const [acceptedTerms, setAcceptedTerms] = React.useState(false);
const renderCheckbox = () => (
<Checkbox isChecked={acceptedTerms} onChange={setAcceptedTerms}>
I agree to the
<Link href={`https://encoach.com/terms`} className="text-mti-purple-light">
{" "}
Terms and Conditions
</Link>{" "}
and
<Link href={`https://encoach.com/privacy-policy`} className="text-mti-purple-light">
{" "}
Privacy Policy
</Link>
</Checkbox>
);
return {acceptedTerms, renderCheckbox};
};
export default useAcceptedTerms;

View File

@@ -10,6 +10,7 @@ import { toast } from "react-toastify";
import { KeyedMutator } from "swr"; import { KeyedMutator } from "swr";
import Select from "react-select"; import Select from "react-select";
import moment from "moment"; import moment from "moment";
import useAcceptedTerms from "@/hooks/useAcceptedTerms";
interface Props { interface Props {
isLoading: boolean; isLoading: boolean;
@@ -40,6 +41,7 @@ export default function RegisterCorporate({
const [companyName, setCompanyName] = useState(""); const [companyName, setCompanyName] = useState("");
const [companyUsers, setCompanyUsers] = useState(0); const [companyUsers, setCompanyUsers] = useState(0);
const [subscriptionDuration, setSubscriptionDuration] = useState(1); const [subscriptionDuration, setSubscriptionDuration] = useState(1);
const {acceptedTerms, renderCheckbox} = useAcceptedTerms();
const { users } = useUsers(); const { users } = useUsers();
@@ -257,7 +259,9 @@ export default function RegisterCorporate({
/> />
</div> </div>
</div> </div>
<div className="flex w-full flex-col items-start gap-4">
{renderCheckbox()}
</div>
<Button <Button
className="w-full lg:mt-8" className="w-full lg:mt-8"
color="purple" color="purple"

View File

@@ -4,9 +4,10 @@ import Input from "@/components/Low/Input";
import { User } from "@/interfaces/user"; import { User } from "@/interfaces/user";
import { sendEmailVerification } from "@/utils/email"; import { sendEmailVerification } from "@/utils/email";
import axios from "axios"; import axios from "axios";
import { useEffect, useState } from "react"; import { useState } from "react";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import { KeyedMutator } from "swr"; import { KeyedMutator } from "swr";
import useAcceptedTerms from "@/hooks/useAcceptedTerms";
interface Props { interface Props {
queryCode?: string; queryCode?: string;
@@ -35,6 +36,7 @@ export default function RegisterIndividual({
const [confirmPassword, setConfirmPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState("");
const [code, setCode] = useState(queryCode || ""); const [code, setCode] = useState(queryCode || "");
const [hasCode, setHasCode] = useState<boolean>(!!queryCode); const [hasCode, setHasCode] = useState<boolean>(!!queryCode);
const {acceptedTerms, renderCheckbox} = useAcceptedTerms();
const onSuccess = () => const onSuccess = () =>
toast.success( toast.success(
@@ -146,7 +148,9 @@ export default function RegisterIndividual({
/> />
)} )}
</div> </div>
<div className="flex w-full flex-col items-start gap-4">
{renderCheckbox()}
</div>
<Button <Button
className="w-full lg:mt-8" className="w-full lg:mt-8"
color="purple" color="purple"
@@ -156,6 +160,7 @@ export default function RegisterIndividual({
!name || !name ||
!password || !password ||
!confirmPassword || !confirmPassword ||
!acceptedTerms ||
password !== confirmPassword || password !== confirmPassword ||
(hasCode ? !code : false) (hasCode ? !code : false)
} }

View File

@@ -10,7 +10,9 @@ import {
} from "firebase/firestore"; } from "firebase/firestore";
import { withIronSessionApiRoute } from "iron-session/next"; import { withIronSessionApiRoute } from "iron-session/next";
import { sessionOptions } from "@/lib/session"; import { sessionOptions } from "@/lib/session";
import { Ticket } from "@/interfaces/ticket"; import { Ticket, TicketTypeLabel, TicketStatusLabel } from "@/interfaces/ticket";
import moment from "moment";
import { sendEmail } from "@/email";
const db = getFirestore(app); const db = getFirestore(app);
@@ -69,12 +71,38 @@ async function patch(req: NextApiRequest, res: NextApiResponse) {
} }
const { id } = req.query as { id: string }; 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; const user = req.session.user;
if (user.type === "admin" || user.type === "developer") { if (user.type === "admin" || user.type === "developer") {
await setDoc(snapshot.ref, req.body, { merge: true }); const data = snapshot.data() as Ticket;
return res.status(200).json({ ok: true }); 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;
} }
res.status(403).json({ ok: false }); res.status(403).json({ ok: false });

View File

@@ -20,13 +20,25 @@ const db = getFirestore(app);
export default withIronSessionApiRoute(handler, sessionOptions); export default withIronSessionApiRoute(handler, sessionOptions);
async function handler(req: NextApiRequest, res: NextApiResponse) { 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;
}
// specific logic for the preflight request
if (req.method === "OPTIONS") {
res.status(200).end();
return;
}
if (!req.session.user) { if (!req.session.user) {
res.status(401).json({ ok: false }); res.status(401).json({ ok: false });
return; return;
} }
if (req.method === "GET") await get(req, res); if (req.method === "GET") {
if (req.method === "POST") await post(req, res); await get(req, res);
}
} }
async function get(req: NextApiRequest, res: NextApiResponse) { async function get(req: NextApiRequest, res: NextApiResponse) {
@@ -36,7 +48,7 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
snapshot.docs.map((doc) => ({ snapshot.docs.map((doc) => ({
id: doc.id, id: doc.id,
...doc.data(), ...doc.data(),
})), }))
); );
} }
@@ -61,7 +73,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
description: body.description, description: body.description,
}, },
[body.reporter.email], [body.reporter.email],
`Ticket ${id}: ${body.subject}`, `Ticket ${id}: ${body.subject}`
); );
} catch (e) { } catch (e) {
console.log(e); console.log(e);