- Updated the colors of the application;
- Added the ability for a user to partially update their profile
This commit is contained in:
@@ -40,10 +40,10 @@ export default function BlankQuestionsModal({isOpen, onClose}: Props) {
|
|||||||
Are you sure you want to continue without completing those questions?
|
Are you sure you want to continue without completing those questions?
|
||||||
</span>
|
</span>
|
||||||
<div className="w-full flex justify-between mt-8">
|
<div className="w-full flex justify-between mt-8">
|
||||||
<Button color="green" onClick={() => onClose(false)} variant="outline" className="max-w-[200px] self-end w-full">
|
<Button color="purple" onClick={() => onClose(false)} variant="outline" className="max-w-[200px] self-end w-full">
|
||||||
Go Back
|
Go Back
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="green" onClick={() => onClose(true)} className="max-w-[200px] self-end w-full">
|
<Button color="purple" onClick={() => onClose(true)} className="max-w-[200px] self-end w-full">
|
||||||
Continue
|
Continue
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -51,10 +51,10 @@ function WordsDrawer({words, isOpen, blankId, previouslySelectedWord, onCancel,
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between w-full">
|
<div className="flex justify-between w-full">
|
||||||
<Button color="green" variant="outline" className="max-w-[200px] w-full" onClick={onCancel}>
|
<Button color="purple" variant="outline" className="max-w-[200px] w-full" onClick={onCancel}>
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="green" className="max-w-[200px] w-full" onClick={() => onAnswer(selectedWord!)} disabled={!selectedWord}>
|
<Button color="purple" className="max-w-[200px] w-full" onClick={() => onAnswer(selectedWord!)} disabled={!selectedWord}>
|
||||||
Confirm
|
Confirm
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -94,11 +94,11 @@ export default function MultipleChoice({id, prompt, type, questions, userSolutio
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
<Button color="green" variant="outline" onClick={back} className="max-w-[200px] w-full">
|
<Button color="purple" variant="outline" onClick={back} className="max-w-[200px] w-full">
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button color="green" onClick={next} className="max-w-[200px] self-end w-full">
|
<Button color="purple" onClick={next} className="max-w-[200px] self-end w-full">
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,26 +3,26 @@ import {ReactNode} from "react";
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
color?: "orange" | "green" | "blue";
|
color?: "rose" | "purple" | "red";
|
||||||
variant?: "outline" | "solid";
|
variant?: "outline" | "solid";
|
||||||
className?: string;
|
className?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Button({color = "green", variant = "solid", disabled = false, className, children, onClick}: Props) {
|
export default function Button({color = "purple", variant = "solid", disabled = false, className, children, onClick}: Props) {
|
||||||
const colorClassNames: {[key in typeof color]: {[key in typeof variant]: string}} = {
|
const colorClassNames: {[key in typeof color]: {[key in typeof variant]: string}} = {
|
||||||
green: {
|
purple: {
|
||||||
solid: "bg-mti-purple-light text-white hover:bg-mti-purple disabled:text-mti-purple disabled:bg-mti-purple-ultralight selection:bg-mti-purple-dark",
|
solid: "bg-mti-purple-light text-white hover:bg-mti-purple disabled:text-mti-purple disabled:bg-mti-purple-ultralight selection:bg-mti-purple-dark",
|
||||||
outline:
|
outline:
|
||||||
"bg-transparent text-mti-purple-light border border-mti-purple-light hover:bg-mti-purple-light disabled:text-mti-purple disabled:bg-mti-purple-ultralight selection:bg-mti-purple-dark hover:text-white selection:text-white",
|
"bg-transparent text-mti-purple-light border border-mti-purple-light hover:bg-mti-purple-light disabled:text-mti-purple disabled:bg-mti-purple-ultralight selection:bg-mti-purple-dark hover:text-white selection:text-white",
|
||||||
},
|
},
|
||||||
blue: {
|
red: {
|
||||||
solid: "bg-mti-red-light text-white hover:bg-mti-red disabled:text-mti-red disabled:bg-mti-red-ultralight selection:bg-mti-red-dark",
|
solid: "bg-mti-red-light text-white hover:bg-mti-red disabled:text-mti-red disabled:bg-mti-red-ultralight selection:bg-mti-red-dark",
|
||||||
outline:
|
outline:
|
||||||
"bg-transparent text-mti-red-light border border-mti-red-light hover:bg-mti-red-light disabled:text-mti-red disabled:bg-mti-red-ultralight selection:bg-mti-red-dark hover:text-white selection:text-white",
|
"bg-transparent text-mti-red-light border border-mti-red-light hover:bg-mti-red-light disabled:text-mti-red disabled:bg-mti-red-ultralight selection:bg-mti-red-dark hover:text-white selection:text-white",
|
||||||
},
|
},
|
||||||
orange: {
|
rose: {
|
||||||
solid: "bg-mti-orange-light text-white hover:bg-mti-orange disabled:text-mti-orange disabled:bg-mti-orange-ultralight selection:bg-mti-orange-dark",
|
solid: "bg-mti-orange-light text-white hover:bg-mti-orange disabled:text-mti-orange disabled:bg-mti-orange-ultralight selection:bg-mti-orange-dark",
|
||||||
outline:
|
outline:
|
||||||
"bg-transparent text-mti-orange-light border border-mti-orange-light hover:bg-mti-orange-light disabled:text-mti-orange disabled:bg-mti-orange-ultralight selection:bg-mti-orange-dark hover:text-white selection:text-white",
|
"bg-transparent text-mti-orange-light border border-mti-orange-light hover:bg-mti-orange-light disabled:text-mti-orange disabled:bg-mti-orange-ultralight selection:bg-mti-orange-dark hover:text-white selection:text-white",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ interface Props {
|
|||||||
export default function Navbar({user}: Props) {
|
export default function Navbar({user}: Props) {
|
||||||
return (
|
return (
|
||||||
<header className="w-full bg-transparent py-4 gap-2 flex items-center">
|
<header className="w-full bg-transparent py-4 gap-2 flex items-center">
|
||||||
<h1 className="font-bold text-2xl w-1/6 px-8">eCrop</h1>
|
<h1 className="font-bold text-2xl w-1/6 px-8">EnCoach</h1>
|
||||||
<div className="flex justify-between w-5/6 mr-8">
|
<div className="flex justify-between w-5/6 mr-8">
|
||||||
<input type="text" placeholder="Search..." className="rounded-full py-4 px-6 border border-mti-gray-platinum outline-none" />
|
<input type="text" placeholder="Search..." className="rounded-full py-4 px-6 border border-mti-gray-platinum outline-none" />
|
||||||
<Link href="/profile" className="flex gap-3 items-center justify-end">
|
<Link href="/profile" className="flex gap-3 items-center justify-end">
|
||||||
|
|||||||
@@ -99,11 +99,11 @@ export default function FillBlanksSolutions({prompt, solutions, text, userSoluti
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
<Button color="green" variant="outline" onClick={onBack} className="max-w-[200px] w-full">
|
<Button color="purple" variant="outline" onClick={onBack} className="max-w-[200px] w-full">
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button color="green" onClick={() => onNext()} className="max-w-[200px] self-end w-full">
|
<Button color="purple" onClick={() => onNext()} className="max-w-[200px] self-end w-full">
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -85,11 +85,11 @@ export default function MatchSentencesSolutions({options, prompt, sentences, use
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
<Button color="green" variant="outline" onClick={() => onBack()} className="max-w-[200px] w-full">
|
<Button color="purple" variant="outline" onClick={() => onBack()} className="max-w-[200px] w-full">
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button color="green" onClick={() => onNext()} className="max-w-[200px] self-end w-full">
|
<Button color="purple" onClick={() => onNext()} className="max-w-[200px] self-end w-full">
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -102,11 +102,11 @@ export default function MultipleChoice({prompt, questions, userSolutions, onNext
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
<Button color="green" variant="outline" onClick={back} className="max-w-[200px] w-full">
|
<Button color="purple" variant="outline" onClick={back} className="max-w-[200px] w-full">
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button color="green" onClick={next} className="max-w-[200px] self-end w-full">
|
<Button color="purple" onClick={next} className="max-w-[200px] self-end w-full">
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -71,11 +71,11 @@ export default function Speaking({title, text, prompts, userSolutions, onNext, o
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
<Button color="green" variant="outline" onClick={onBack} className="max-w-[200px] w-full">
|
<Button color="purple" variant="outline" onClick={onBack} className="max-w-[200px] w-full">
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button color="green" onClick={() => onNext()} className="max-w-[200px] self-end w-full">
|
<Button color="purple" onClick={() => onNext()} className="max-w-[200px] self-end w-full">
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -127,11 +127,11 @@ export default function WriteBlanksSolutions({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
<Button color="green" variant="outline" onClick={() => onBack()} className="max-w-[200px] w-full">
|
<Button color="purple" variant="outline" onClick={() => onBack()} className="max-w-[200px] w-full">
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button color="green" onClick={() => onNext()} className="max-w-[200px] self-end w-full">
|
<Button color="purple" onClick={() => onNext()} className="max-w-[200px] self-end w-full">
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -96,11 +96,11 @@ export default function Writing({id, prompt, info, attachment, userSolutions, on
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
<Button color="green" variant="outline" onClick={onBack} className="max-w-[200px] w-full">
|
<Button color="purple" variant="outline" onClick={onBack} className="max-w-[200px] w-full">
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button color="green" onClick={() => onNext()} className="max-w-[200px] self-end w-full">
|
<Button color="purple" onClick={() => onNext()} className="max-w-[200px] self-end w-full">
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
10
src/constants/errors.ts
Normal file
10
src/constants/errors.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export type Error = "E001" | "E002";
|
||||||
|
export interface ErrorMessage {
|
||||||
|
error: Error;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const errorMessages: {[key in Error]: string} = {
|
||||||
|
E001: "Wrong password!",
|
||||||
|
E002: "Invalid e-mail",
|
||||||
|
};
|
||||||
@@ -191,7 +191,7 @@ export default function Finish({user, scores, modules, isLoading, onViewResults}
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Link href="/" className="max-w-[200px] w-full self-end">
|
<Link href="/" className="max-w-[200px] w-full self-end">
|
||||||
<Button color="green" className="max-w-[200px] self-end w-full">
|
<Button color="purple" className="max-w-[200px] self-end w-full">
|
||||||
Dashboard
|
Dashboard
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export default function Listening({exam, showSolutions = false, onFinish}: Props
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{exerciseIndex === -1 && (
|
{exerciseIndex === -1 && (
|
||||||
<Button color="green" onClick={() => nextExercise()} className="max-w-[200px] self-end w-full justify-self-end">
|
<Button color="purple" onClick={() => nextExercise()} className="max-w-[200px] self-end w-full justify-self-end">
|
||||||
Start now
|
Start now
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ function TextModal({isOpen, title, content, onClose}: {isOpen: boolean; title: s
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="absolute bottom-8 right-8 max-w-[200px] self-end w-full">
|
<div className="absolute bottom-8 right-8 max-w-[200px] self-end w-full">
|
||||||
<Button color="green" variant="outline" className="max-w-[200px] self-end w-full" onClick={onClose}>
|
<Button color="purple" variant="outline" className="max-w-[200px] self-end w-full" onClick={onClose}>
|
||||||
Close
|
Close
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -185,7 +185,7 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props)
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{exerciseIndex === -1 && (
|
{exerciseIndex === -1 && (
|
||||||
<Button color="green" onClick={() => nextExercise()} className="max-w-[200px] self-end w-full">
|
<Button color="purple" onClick={() => nextExercise()} className="max-w-[200px] self-end w-full">
|
||||||
Start now
|
Start now
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -5,8 +5,13 @@ import {getFirestore, collection, getDocs, getDoc, doc, setDoc} from "firebase/f
|
|||||||
import {withIronSessionApiRoute} from "iron-session/next";
|
import {withIronSessionApiRoute} from "iron-session/next";
|
||||||
import {sessionOptions} from "@/lib/session";
|
import {sessionOptions} from "@/lib/session";
|
||||||
import {User} from "@/interfaces/user";
|
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";
|
||||||
|
|
||||||
const db = getFirestore(app);
|
const db = getFirestore(app);
|
||||||
|
const storage = getStorage(app);
|
||||||
|
const auth = getAuth(app);
|
||||||
|
|
||||||
export default withIronSessionApiRoute(handler, sessionOptions);
|
export default withIronSessionApiRoute(handler, sessionOptions);
|
||||||
|
|
||||||
@@ -17,7 +22,45 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const userRef = doc(db, "users", req.session.user.id);
|
const userRef = doc(db, "users", req.session.user.id);
|
||||||
await setDoc(userRef, req.body, {merge: true});
|
const updatedUser = req.body as User & {password?: string; newPassword?: string};
|
||||||
|
|
||||||
|
if (updatedUser.profilePicture && updatedUser.profilePicture !== req.session.user.profilePicture) {
|
||||||
|
const profilePictureFiletype = updatedUser.profilePicture.split(";")[0].split("/")[1];
|
||||||
|
const profilePictureRef = ref(storage, `profile_pictures/${req.session.user.id}.${profilePictureFiletype}`);
|
||||||
|
|
||||||
|
const pictureBytes = Buffer.from(updatedUser.profilePicture, "base64url");
|
||||||
|
const pictureSnapshot = await uploadBytes(profilePictureRef, pictureBytes);
|
||||||
|
|
||||||
|
const pictureReference = ref(storage, pictureSnapshot.metadata.fullPath);
|
||||||
|
updatedUser.profilePicture = await getDownloadURL(pictureReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updatedUser.newPassword && updatedUser.password) {
|
||||||
|
try {
|
||||||
|
const credential = await signInWithEmailAndPassword(auth, req.session.user.email, updatedUser.password);
|
||||||
|
await updatePassword(credential.user, updatedUser.newPassword);
|
||||||
|
} catch {
|
||||||
|
res.status(400).json({error: "E001", message: errorMessages.E001});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updatedUser.email !== req.session.user.email && updatedUser.password) {
|
||||||
|
try {
|
||||||
|
const credential = await signInWithEmailAndPassword(auth, req.session.user.email, updatedUser.password);
|
||||||
|
await updateEmail(credential.user, updatedUser.email);
|
||||||
|
} catch {
|
||||||
|
res.status(400).json({error: "E002", message: errorMessages.E002});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete updatedUser.password;
|
||||||
|
delete updatedUser.newPassword;
|
||||||
|
|
||||||
|
await setDoc(userRef, updatedUser, {merge: true});
|
||||||
|
req.session.user = {...updatedUser, id: req.session.user.id};
|
||||||
|
await req.session.save();
|
||||||
|
|
||||||
res.status(200).json({ok: true});
|
res.status(200).json({ok: true});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,13 +133,7 @@ export default function Home() {
|
|||||||
</section>
|
</section>
|
||||||
<section className="flex flex-col gap-3">
|
<section className="flex flex-col gap-3">
|
||||||
<span className="font-bold text-lg">Bio</span>
|
<span className="font-bold text-lg">Bio</span>
|
||||||
<span className="text-mti-gray-taupe">
|
<span className="text-mti-gray-taupe">{user.bio || "Your bio will appear here..."}</span>
|
||||||
Patricia Smith is a dedicated and enthusiastic student. Her passion for knowledge drives her to constantly seek new
|
|
||||||
academic challenges. She is recognized for her exemplary work ethic, active participation in the classroom, and commitment
|
|
||||||
to helping her peers. Her insatiable curiosity has led her to explore a wide range of areas of study, making her a
|
|
||||||
versatile and adaptable learner. Patricia is a true academic leader, inspiring other students to pursue their own
|
|
||||||
educational goals.
|
|
||||||
</span>
|
|
||||||
</section>
|
</section>
|
||||||
<section className="flex flex-col gap-3">
|
<section className="flex flex-col gap-3">
|
||||||
<span className="font-bold text-lg">Score History</span>
|
<span className="font-bold text-lg">Score History</span>
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export default function Login() {
|
|||||||
Forgot Password?
|
Forgot Password?
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<Button className="mt-8 w-full" color="green" disabled={isLoading}>
|
<Button className="mt-8 w-full" color="purple" disabled={isLoading}>
|
||||||
{!isLoading && "Login"}
|
{!isLoading && "Login"}
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="flex items-center justify-center">
|
<div className="flex items-center justify-center">
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import Navbar from "@/components/Navbar";
|
|||||||
import {BsFileEarmarkText, BsPencil, BsStar, BsBook, BsHeadphones, BsPen, BsMegaphone, BsArrowRepeat} from "react-icons/bs";
|
import {BsFileEarmarkText, BsPencil, BsStar, BsBook, BsHeadphones, BsPen, BsMegaphone, BsArrowRepeat} from "react-icons/bs";
|
||||||
import {withIronSessionSsr} from "iron-session/next";
|
import {withIronSessionSsr} from "iron-session/next";
|
||||||
import {sessionOptions} from "@/lib/session";
|
import {sessionOptions} from "@/lib/session";
|
||||||
import {useEffect, useState} from "react";
|
import {ChangeEvent, useEffect, useRef, useState} from "react";
|
||||||
import useStats from "@/hooks/useStats";
|
import useStats from "@/hooks/useStats";
|
||||||
import {averageScore, totalExams} from "@/utils/stats";
|
import {averageScore, totalExams} from "@/utils/stats";
|
||||||
import useUser from "@/hooks/useUser";
|
import useUser from "@/hooks/useUser";
|
||||||
import Sidebar from "@/components/Sidebar";
|
import Sidebar from "@/components/Sidebar";
|
||||||
import Diagnostic from "@/components/Diagnostic";
|
import Diagnostic from "@/components/Diagnostic";
|
||||||
import {ToastContainer} from "react-toastify";
|
import {toast, ToastContainer} from "react-toastify";
|
||||||
import {capitalize} from "lodash";
|
import {capitalize} from "lodash";
|
||||||
import {Module} from "@/interfaces";
|
import {Module} from "@/interfaces";
|
||||||
import ProgressBar from "@/components/Low/ProgressBar";
|
import ProgressBar from "@/components/Low/ProgressBar";
|
||||||
@@ -20,6 +20,8 @@ import Input from "@/components/Low/Input";
|
|||||||
import Button from "@/components/Low/Button";
|
import Button from "@/components/Low/Button";
|
||||||
import {useRouter} from "next/router";
|
import {useRouter} from "next/router";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import axios from "axios";
|
||||||
|
import {ErrorMessage} from "@/constants/errors";
|
||||||
|
|
||||||
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||||
const user = req.session.user;
|
const user = req.session.user;
|
||||||
@@ -45,8 +47,12 @@ export default function Home() {
|
|||||||
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 [newPassword, setNewPassword] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [profilePicture, setProfilePicture] = useState("");
|
||||||
|
|
||||||
|
const profilePictureInput = useRef(null);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const {user} = useUser({redirectTo: "/login"});
|
const {user} = useUser({redirectTo: "/login"});
|
||||||
|
|
||||||
@@ -55,9 +61,56 @@ export default function Home() {
|
|||||||
setName(user.name);
|
setName(user.name);
|
||||||
setEmail(user.email);
|
setEmail(user.email);
|
||||||
setBio(user.bio);
|
setBio(user.bio);
|
||||||
|
setProfilePicture(user.profilePicture);
|
||||||
}
|
}
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
|
const convertBase64 = (file: File) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const fileReader = new FileReader();
|
||||||
|
fileReader.readAsDataURL(file);
|
||||||
|
fileReader.onload = () => {
|
||||||
|
resolve(fileReader.result);
|
||||||
|
};
|
||||||
|
fileReader.onerror = (error) => {
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadProfilePicture = async (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (event.target.files && event.target.files[0]) {
|
||||||
|
const picture = event.target.files[0];
|
||||||
|
const base64 = await convertBase64(picture);
|
||||||
|
setProfilePicture(base64 as string);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateUser = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
if (email !== user?.email && !password) {
|
||||||
|
toast.error("To update your e-mail you need to input your password!");
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPassword && !password) {
|
||||||
|
toast.error("To update your password you need to input your current one!");
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = await axios.post("/api/users/update", {bio, name, email, password, newPassword});
|
||||||
|
if (request.status === 200) {
|
||||||
|
toast.success("Your profile has been updated!");
|
||||||
|
setTimeout(() => router.reload(), 800);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.error((request.data as ErrorMessage).message);
|
||||||
|
setIsLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
@@ -106,20 +159,20 @@ export default function Home() {
|
|||||||
<Input
|
<Input
|
||||||
label="New Password"
|
label="New Password"
|
||||||
type="password"
|
type="password"
|
||||||
name="confirmPassword"
|
name="newPassword"
|
||||||
onChange={(e) => setConfirmPassword(e)}
|
onChange={(e) => setNewPassword(e)}
|
||||||
placeholder="Confirm your password"
|
placeholder="Enter your new password (optional)"
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-3 items-center w-48">
|
<div className="flex flex-col gap-3 items-center w-48">
|
||||||
<img
|
<img src={profilePicture} alt={user.name} className="aspect-square h-48 w-48 rounded-full drop-shadow-xl self-end" />
|
||||||
src={user.profilePicture}
|
<input type="file" className="hidden" onChange={uploadProfilePicture} accept="image/*" ref={profilePictureInput} />
|
||||||
alt={user.name}
|
<span
|
||||||
className="aspect-square h-48 w-48 rounded-full drop-shadow-xl self-end"
|
onClick={() => (profilePictureInput.current as any)?.click()}
|
||||||
/>
|
className="cursor-pointer text-mti-purple-light text-sm">
|
||||||
<span className="cursor-pointer text-mti-purple-light text-sm">Change picture</span>
|
Change picture
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4 mt-8 mb-20">
|
<div className="flex flex-col gap-4 mt-8 mb-20">
|
||||||
@@ -134,11 +187,11 @@ export default function Home() {
|
|||||||
|
|
||||||
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||||
<Link href="/" className="max-w-[200px] self-end w-full">
|
<Link href="/" className="max-w-[200px] self-end w-full">
|
||||||
<Button color="green" variant="outline" className="max-w-[200px] self-end w-full">
|
<Button color="purple" variant="outline" className="max-w-[200px] self-end w-full">
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Button color="green" className="max-w-[200px] self-end w-full">
|
<Button color="purple" className="max-w-[200px] self-end w-full" onClick={updateUser} disabled={isLoading}>
|
||||||
Save Changes
|
Save Changes
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user