Updated the register to only allow to create users if they have a code available
This commit is contained in:
@@ -11,13 +11,20 @@
|
|||||||
<img src="/logo_title.png" class="w-48 h-48 self-center" />
|
<img src="/logo_title.png" class="w-48 h-48 self-center" />
|
||||||
<div>
|
<div>
|
||||||
<span>Hello future {{type}} of <b>EnCoach</b>,</span><br />
|
<span>Hello future {{type}} of <b>EnCoach</b>,</span><br />
|
||||||
<span>You have been invited to register at <a href="https://encoach.com">EnCoach</a> to become a
|
<span>You have been invited to register at <a href="https://encoach.com/register?code={{code}}">EnCoach</a> to
|
||||||
|
become a
|
||||||
{{type}}!</span><br />
|
{{type}}!</span><br />
|
||||||
<span>Please use the following code when registering:</span>
|
<span>Please use the following code when registering:</span>
|
||||||
</div>
|
</div>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<a href="https://encoach.com/register?code={{code}}"></a>
|
||||||
<span class="self-center p-4 px-12 text-lg text-[#]" style="background-color: #D5D9F0; color: #353338">
|
<span class="self-center p-4 px-12 text-lg text-[#]" style="background-color: #D5D9F0; color: #353338">
|
||||||
<b>{{code}}</b>
|
<b>{{code}}</b>
|
||||||
</span>
|
</span>
|
||||||
|
</a>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
<div>
|
<div>
|
||||||
<span>Thanks, <br /> Your EnCoach team</span>
|
<span>Thanks, <br /> Your EnCoach team</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const codePromises = codes.map(async (code, index) => {
|
const codePromises = codes.map(async (code, index) => {
|
||||||
const codeRef = doc(db, "codes", uuidv4());
|
const codeRef = doc(db, "codes", code);
|
||||||
await setDoc(codeRef, {type, code});
|
await setDoc(codeRef, {type, code});
|
||||||
|
|
||||||
if (emails && emails.length > index) {
|
if (emails && emails.length > index) {
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import {createUserWithEmailAndPassword, getAuth, sendPasswordResetEmail} from "f
|
|||||||
import {app} from "@/firebase";
|
import {app} from "@/firebase";
|
||||||
import {sessionOptions} from "@/lib/session";
|
import {sessionOptions} from "@/lib/session";
|
||||||
import {withIronSessionApiRoute} from "iron-session/next";
|
import {withIronSessionApiRoute} from "iron-session/next";
|
||||||
import {getFirestore, getDoc, doc, setDoc} from "firebase/firestore";
|
import {getFirestore, getDoc, doc, setDoc, deleteDoc} from "firebase/firestore";
|
||||||
import {DemographicInformation} from "@/interfaces/user";
|
import {DemographicInformation, Type} from "@/interfaces/user";
|
||||||
|
|
||||||
const auth = getAuth(app);
|
const auth = getAuth(app);
|
||||||
const db = getFirestore(app);
|
const db = getFirestore(app);
|
||||||
@@ -26,7 +26,15 @@ const DEFAULT_LEVELS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function login(req: NextApiRequest, res: NextApiResponse) {
|
async function login(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const {email, password} = req.body as {email: string; password: string; demographicInformation: DemographicInformation};
|
const {email, password, code} = req.body as {email: string; password: string; code: string; demographicInformation: DemographicInformation};
|
||||||
|
|
||||||
|
const codeRef = await getDoc(doc(db, "codes", code));
|
||||||
|
if (!codeRef.exists()) {
|
||||||
|
res.status(400).json({error: "Invalid Code!"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const codeData = codeRef.data() as {code: string; type: Type};
|
||||||
|
|
||||||
createUserWithEmailAndPassword(auth, email, password)
|
createUserWithEmailAndPassword(auth, email, password)
|
||||||
.then(async (userCredentials) => {
|
.then(async (userCredentials) => {
|
||||||
@@ -38,12 +46,13 @@ async function login(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
desiredLevels: DEFAULT_DESIRED_LEVELS,
|
desiredLevels: DEFAULT_DESIRED_LEVELS,
|
||||||
levels: DEFAULT_LEVELS,
|
levels: DEFAULT_LEVELS,
|
||||||
bio: "",
|
bio: "",
|
||||||
isFirstLogin: true,
|
isFirstLogin: codeData.type === "student",
|
||||||
focus: "academic",
|
focus: "academic",
|
||||||
type: "student",
|
type: codeData.type,
|
||||||
};
|
};
|
||||||
|
|
||||||
await setDoc(doc(db, "users", userId), user);
|
await setDoc(doc(db, "users", userId), user);
|
||||||
|
await deleteDoc(codeRef.ref);
|
||||||
|
|
||||||
req.session.user = {...user, id: userId};
|
req.session.user = {...user, id: userId};
|
||||||
await req.session.save();
|
await req.session.save();
|
||||||
|
|||||||
@@ -11,12 +11,23 @@ import axios from "axios";
|
|||||||
import {Divider} from "primereact/divider";
|
import {Divider} from "primereact/divider";
|
||||||
import {useRouter} from "next/router";
|
import {useRouter} from "next/router";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import {NextPageContext} from "next";
|
||||||
|
import {NextRequest, NextResponse} from "next/server";
|
||||||
|
|
||||||
export default function Register() {
|
export const getServerSideProps = (context: any) => {
|
||||||
|
const {code} = context.query;
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {code},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Register({code: queryCode}: {code: string}) {
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [confirmPassword, setConfirmPassword] = useState("");
|
const [confirmPassword, setConfirmPassword] = useState("");
|
||||||
|
const [code, setCode] = useState(queryCode || "");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -40,6 +51,7 @@ export default function Register() {
|
|||||||
name,
|
name,
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
|
code,
|
||||||
profilePicture: "/defaultAvatar.png",
|
profilePicture: "/defaultAvatar.png",
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
@@ -52,6 +64,12 @@ export default function Register() {
|
|||||||
toast.error("There is already a user with that e-mail!");
|
toast.error("There is already a user with that e-mail!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error.response.status === 400) {
|
||||||
|
toast.error("The provided code is invalid!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
toast.error("There was something wrong, please try again!");
|
toast.error("There was something wrong, please try again!");
|
||||||
})
|
})
|
||||||
.finally(() => setIsLoading(false));
|
.finally(() => setIsLoading(false));
|
||||||
@@ -95,10 +113,11 @@ export default function Register() {
|
|||||||
defaultValue={confirmPassword}
|
defaultValue={confirmPassword}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<Input type="text" name="code" onChange={(e) => setCode(e)} placeholder="Enter your registration code" defaultValue={code} required />
|
||||||
<Button
|
<Button
|
||||||
className="lg:mt-8 w-full"
|
className="lg:mt-8 w-full"
|
||||||
color="purple"
|
color="purple"
|
||||||
disabled={isLoading || !email || !name || !password || !confirmPassword || password !== confirmPassword}>
|
disabled={isLoading || !email || !name || !password || !confirmPassword || password !== confirmPassword || !code}>
|
||||||
Create account
|
Create account
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import {Chart} from "react-chartjs-2";
|
|||||||
import useUsers from "@/hooks/useUsers";
|
import useUsers from "@/hooks/useUsers";
|
||||||
import Select from "react-select";
|
import Select from "react-select";
|
||||||
import useGroups from "@/hooks/useGroups";
|
import useGroups from "@/hooks/useGroups";
|
||||||
|
import DatePicker from "react-datepicker";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
ChartJS.register(LinearScale, CategoryScale, PointElement, LineElement, LineController, Legend, Tooltip);
|
ChartJS.register(LinearScale, CategoryScale, PointElement, LineElement, LineController, Legend, Tooltip);
|
||||||
|
|
||||||
@@ -45,6 +47,8 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
|||||||
|
|
||||||
export default function Stats() {
|
export default function Stats() {
|
||||||
const [statsUserId, setStatsUserId] = useState<string>();
|
const [statsUserId, setStatsUserId] = useState<string>();
|
||||||
|
const [startDate, setStartDate] = useState<Date | null>(null);
|
||||||
|
const [endDate, setEndDate] = useState<Date | null>(new Date());
|
||||||
|
|
||||||
const {user} = useUser({redirectTo: "/login"});
|
const {user} = useUser({redirectTo: "/login"});
|
||||||
const {users} = useUsers();
|
const {users} = useUsers();
|
||||||
@@ -56,6 +60,17 @@ export default function Stats() {
|
|||||||
if (user) setStatsUserId(user.id);
|
if (user) setStatsUserId(user.id);
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (stats && stats.length > 0) {
|
||||||
|
const sortedStats = stats.sort((a, b) => a.date - b.date);
|
||||||
|
const firstStat = sortedStats.shift()!;
|
||||||
|
|
||||||
|
setStartDate(moment.unix(firstStat.date).toDate());
|
||||||
|
console.log(stats.filter((x) => moment.unix(x.date).isAfter(startDate)));
|
||||||
|
console.log(stats.filter((x) => moment.unix(x.date).isBefore(endDate)));
|
||||||
|
}
|
||||||
|
}, [stats]);
|
||||||
|
|
||||||
const calculateTotalScorePerSession = () => {
|
const calculateTotalScorePerSession = () => {
|
||||||
const groupedBySession = groupBySession(stats);
|
const groupedBySession = groupBySession(stats);
|
||||||
const sessionAverage = Object.keys(groupedBySession).map((x: string) => {
|
const sessionAverage = Object.keys(groupedBySession).map((x: string) => {
|
||||||
@@ -160,22 +175,53 @@ export default function Stats() {
|
|||||||
</section>
|
</section>
|
||||||
{stats.length > 0 && (
|
{stats.length > 0 && (
|
||||||
<section className="flex flex-col gap-3">
|
<section className="flex flex-col gap-3">
|
||||||
|
<div className="w-full flex justify-between gap-8 items-center">
|
||||||
|
<>
|
||||||
{(user.type === "developer" || user.type === "owner") && (
|
{(user.type === "developer" || user.type === "owner") && (
|
||||||
<Select
|
<Select
|
||||||
|
className="w-full"
|
||||||
options={users.map((x) => ({value: x.id, label: `${x.name} - ${x.email}`}))}
|
options={users.map((x) => ({value: x.id, label: `${x.name} - ${x.email}`}))}
|
||||||
defaultValue={{value: user.id, label: `${user.name} - ${user.email}`}}
|
defaultValue={{value: user.id, label: `${user.name} - ${user.email}`}}
|
||||||
onChange={(value) => setStatsUserId(value?.value)}
|
onChange={(value) => setStatsUserId(value?.value)}
|
||||||
|
styles={{
|
||||||
|
option: (styles, state) => ({
|
||||||
|
...styles,
|
||||||
|
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
|
||||||
|
color: state.isFocused ? "black" : styles.color,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(user.type === "admin" || user.type === "teacher") && groups.length > 0 && (
|
{(user.type === "admin" || user.type === "teacher") && groups.length > 0 && (
|
||||||
<Select
|
<Select
|
||||||
|
className="w-full"
|
||||||
options={users
|
options={users
|
||||||
.filter((x) => groups.flatMap((y) => y.participants).includes(x.id))
|
.filter((x) => groups.flatMap((y) => y.participants).includes(x.id))
|
||||||
.map((x) => ({value: x.id, label: `${x.name} - ${x.email}`}))}
|
.map((x) => ({value: x.id, label: `${x.name} - ${x.email}`}))}
|
||||||
defaultValue={{value: user.id, label: `${user.name} - ${user.email}`}}
|
defaultValue={{value: user.id, label: `${user.name} - ${user.email}`}}
|
||||||
onChange={(value) => setStatsUserId(value?.value)}
|
onChange={(value) => setStatsUserId(value?.value)}
|
||||||
|
styles={{
|
||||||
|
option: (styles, state) => ({
|
||||||
|
...styles,
|
||||||
|
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
|
||||||
|
color: state.isFocused ? "black" : styles.color,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
<DatePicker
|
||||||
|
dateFormat="dd/MM/yyyy"
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
selectsRange
|
||||||
|
filterDate={(date) => !moment(date).isSameOrBefore(moment(startDate))}
|
||||||
|
onChange={([initialDate, finalDate]) => {
|
||||||
|
setStartDate(initialDate);
|
||||||
|
setEndDate(finalDate);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="flex gap-4 flex-wrap">
|
<div className="flex gap-4 flex-wrap">
|
||||||
{/* Exams per module */}
|
{/* Exams per module */}
|
||||||
<div className="flex flex-col gap-12 border w-full h-fit max-w-xs border-mti-gray-platinum p-4 pb-12 rounded-xl">
|
<div className="flex flex-col gap-12 border w-full h-fit max-w-xs border-mti-gray-platinum p-4 pb-12 rounded-xl">
|
||||||
@@ -319,7 +365,13 @@ export default function Stats() {
|
|||||||
<Chart
|
<Chart
|
||||||
type="line"
|
type="line"
|
||||||
data={{
|
data={{
|
||||||
labels: Object.keys(groupBySession(stats)).map((_, index) => index),
|
labels: Object.keys(
|
||||||
|
groupBySession(
|
||||||
|
stats.filter(
|
||||||
|
(x) => moment.unix(x.date).isAfter(startDate) && moment.unix(x.date).isBefore(endDate),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).map((_, index) => index),
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
type: "line",
|
type: "line",
|
||||||
@@ -342,7 +394,13 @@ export default function Stats() {
|
|||||||
<Chart
|
<Chart
|
||||||
type="line"
|
type="line"
|
||||||
data={{
|
data={{
|
||||||
labels: Object.keys(groupBySession(stats)).map((_, index) => index),
|
labels: Object.keys(
|
||||||
|
groupBySession(
|
||||||
|
stats.filter(
|
||||||
|
(x) => moment.unix(x.date).isAfter(startDate) && moment.unix(x.date).isBefore(endDate),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).map((_, index) => index),
|
||||||
datasets: [
|
datasets: [
|
||||||
...MODULE_ARRAY.map((module, index) => ({
|
...MODULE_ARRAY.map((module, index) => ({
|
||||||
type: "line" as const,
|
type: "line" as const,
|
||||||
|
|||||||
Reference in New Issue
Block a user