Merge branch 'develop' into feature/user-choose-topics
This commit is contained in:
@@ -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",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
35
src/email/templates/ticketStatusCompleted.handlebars
Normal file
35
src/email/templates/ticketStatusCompleted.handlebars
Normal 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>
|
||||||
26
src/hooks/useAcceptedTerms.tsx
Normal file
26
src/hooks/useAcceptedTerms.tsx
Normal 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;
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user