Created a new page for ticket handling as well as submission
This commit is contained in:
253
src/components/High/TicketDisplay.tsx
Normal file
253
src/components/High/TicketDisplay.tsx
Normal file
@@ -0,0 +1,253 @@
|
||||
import useUsers from "@/hooks/useUsers";
|
||||
import {
|
||||
Ticket,
|
||||
TicketStatus,
|
||||
TicketStatusLabel,
|
||||
TicketType,
|
||||
TicketTypeLabel,
|
||||
} from "@/interfaces/ticket";
|
||||
import { User } from "@/interfaces/user";
|
||||
import { USER_TYPE_LABELS } from "@/resources/user";
|
||||
import axios from "axios";
|
||||
import moment from "moment";
|
||||
import { useState } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import Button from "../Low/Button";
|
||||
import Input from "../Low/Input";
|
||||
import Select from "../Low/Select";
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
ticket: Ticket;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function TicketDisplay({ user, ticket, onClose }: Props) {
|
||||
const [subject] = useState(ticket.subject);
|
||||
const [type, setType] = useState<TicketType>(ticket.type);
|
||||
const [description] = useState(ticket.description);
|
||||
const [reporter] = useState(ticket.reporter);
|
||||
const [reportedFrom] = useState(ticket.reportedFrom);
|
||||
const [status, setStatus] = useState(ticket.status);
|
||||
const [assignedTo, setAssignedTo] = useState<string | null>(
|
||||
ticket.assignedTo || null,
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { users } = useUsers();
|
||||
|
||||
const submit = () => {
|
||||
if (!type)
|
||||
return toast.error("Please choose a type!", { toastId: "missing-type" });
|
||||
|
||||
setIsLoading(true);
|
||||
axios
|
||||
.patch(`/api/tickets/${ticket.id}`, {
|
||||
subject,
|
||||
type,
|
||||
description,
|
||||
reporter,
|
||||
reportedFrom,
|
||||
status,
|
||||
assignedTo,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success(`The ticket has been updated!`, { toastId: "submitted" });
|
||||
onClose();
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
toast.error("Something went wrong, please try again later!", {
|
||||
toastId: "error",
|
||||
});
|
||||
})
|
||||
.finally(() => setIsLoading(false));
|
||||
};
|
||||
|
||||
const del = () => {
|
||||
if (!confirm("Are you sure you want to delete this ticket?")) return;
|
||||
|
||||
setIsLoading(true);
|
||||
axios
|
||||
.delete(`/api/tickets/${ticket.id}`)
|
||||
.then(() => {
|
||||
toast.success(`The ticket has been deleted!`, { toastId: "submitted" });
|
||||
onClose();
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
toast.error("Something went wrong, please try again later!", {
|
||||
toastId: "error",
|
||||
});
|
||||
})
|
||||
.finally(() => setIsLoading(false));
|
||||
};
|
||||
|
||||
return (
|
||||
<form className="flex flex-col gap-4 pt-8">
|
||||
<Input
|
||||
label="Subject"
|
||||
type="text"
|
||||
name="subject"
|
||||
placeholder="Subject..."
|
||||
value={subject}
|
||||
onChange={(e) => null}
|
||||
disabled
|
||||
/>
|
||||
|
||||
<div className="-md:flex-col flex w-full items-center gap-4">
|
||||
<div className="flex w-full flex-col gap-3">
|
||||
<label className="text-mti-gray-dim text-base font-normal">
|
||||
Status
|
||||
</label>
|
||||
<Select
|
||||
options={Object.keys(TicketStatusLabel).map((x) => ({
|
||||
value: x,
|
||||
label: TicketStatusLabel[x as keyof typeof TicketStatusLabel],
|
||||
}))}
|
||||
value={{ value: status, label: TicketStatusLabel[status] }}
|
||||
onChange={(value) =>
|
||||
setStatus((value?.value as TicketStatus) ?? undefined)
|
||||
}
|
||||
placeholder="Status..."
|
||||
/>
|
||||
</div>
|
||||
<div className="flex w-full flex-col gap-3">
|
||||
<label className="text-mti-gray-dim text-base font-normal">
|
||||
Type
|
||||
</label>
|
||||
<Select
|
||||
options={Object.keys(TicketTypeLabel).map((x) => ({
|
||||
value: x,
|
||||
label: TicketTypeLabel[x as keyof typeof TicketTypeLabel],
|
||||
}))}
|
||||
value={{ value: type, label: TicketTypeLabel[type] }}
|
||||
onChange={(value) => setType(value!.value as TicketType)}
|
||||
placeholder="Type..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full flex-col gap-3">
|
||||
<label className="text-mti-gray-dim text-base font-normal">
|
||||
Assignee
|
||||
</label>
|
||||
<Select
|
||||
options={[
|
||||
{ value: "me", label: "Assign to me" },
|
||||
...users
|
||||
.filter((x) => ["admin", "developer"].includes(x.type))
|
||||
.map((u) => ({
|
||||
value: u.id,
|
||||
label: `${u.name} - ${u.email}`,
|
||||
})),
|
||||
]}
|
||||
value={
|
||||
assignedTo
|
||||
? {
|
||||
value: assignedTo,
|
||||
label: `${users.find((u) => u.id === assignedTo)?.name} - ${users.find((u) => u.id === assignedTo)?.email}`,
|
||||
}
|
||||
: null
|
||||
}
|
||||
onChange={(value) =>
|
||||
value
|
||||
? setAssignedTo(value.value === "me" ? user.id : value.value)
|
||||
: setAssignedTo(null)
|
||||
}
|
||||
placeholder="Assignee..."
|
||||
isClearable
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="-md:flex-col flex w-full items-center gap-4">
|
||||
<Input
|
||||
label="Reported From"
|
||||
type="text"
|
||||
name="reportedFrom"
|
||||
onChange={() => null}
|
||||
value={reportedFrom}
|
||||
disabled
|
||||
/>
|
||||
<Input
|
||||
label="Date"
|
||||
type="text"
|
||||
name="date"
|
||||
onChange={() => null}
|
||||
value={moment(ticket.date).format("DD/MM/YYYY - HH:mm")}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="-md:flex-col flex w-full items-center gap-4">
|
||||
<Input
|
||||
label="Reporter's Name"
|
||||
type="text"
|
||||
name="reporter"
|
||||
onChange={() => null}
|
||||
value={reporter.name}
|
||||
disabled
|
||||
/>
|
||||
<Input
|
||||
label="Reporter's E-mail"
|
||||
type="text"
|
||||
name="reporter"
|
||||
onChange={() => null}
|
||||
value={reporter.email}
|
||||
disabled
|
||||
/>
|
||||
<Input
|
||||
label="Reporter's Type"
|
||||
type="text"
|
||||
name="reporterType"
|
||||
onChange={() => null}
|
||||
value={USER_TYPE_LABELS[reporter.type]}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
className="input border-mti-gray-platinum h-full min-h-[300px] w-full cursor-text rounded-3xl border bg-white px-7 py-8"
|
||||
placeholder="Write your ticket's description here..."
|
||||
contentEditable={false}
|
||||
value={description}
|
||||
spellCheck
|
||||
/>
|
||||
|
||||
<div className="-md:flex-col-reverse mt-2 flex w-full items-center justify-between gap-4">
|
||||
<Button
|
||||
type="button"
|
||||
color="red"
|
||||
className="w-full md:max-w-[200px]"
|
||||
variant="outline"
|
||||
onClick={del}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
|
||||
<div className="-md:flex-col-reverse flex w-full items-center justify-end gap-4">
|
||||
<Button
|
||||
type="button"
|
||||
color="red"
|
||||
className="w-full md:max-w-[200px]"
|
||||
variant="outline"
|
||||
onClick={onClose}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
className="w-full md:max-w-[200px]"
|
||||
isLoading={isLoading}
|
||||
onClick={submit}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
134
src/components/High/TicketSubmission.tsx
Normal file
134
src/components/High/TicketSubmission.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { Ticket, TicketType, TicketTypeLabel } from "@/interfaces/ticket";
|
||||
import { User } from "@/interfaces/user";
|
||||
import axios from "axios";
|
||||
import { useState } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import Button from "../Low/Button";
|
||||
import Input from "../Low/Input";
|
||||
import Select from "../Low/Select";
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
page: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function TicketSubmission({ user, page, onClose }: Props) {
|
||||
const [subject, setSubject] = useState("");
|
||||
const [type, setType] = useState<TicketType>();
|
||||
const [description, setDescription] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const submit = () => {
|
||||
if (!type)
|
||||
return toast.error("Please choose a type!", { toastId: "missing-type" });
|
||||
if (subject.trim() === "")
|
||||
return toast.error("Please input a subject!", {
|
||||
toastId: "missing-subject",
|
||||
});
|
||||
if (description.trim() === "")
|
||||
return toast.error("Please describe your ticket!", {
|
||||
toastId: "missing-desc",
|
||||
});
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const shortUID = new ShortUniqueId();
|
||||
const ticket: Ticket = {
|
||||
id: shortUID.randomUUID(8),
|
||||
date: new Date().toISOString(),
|
||||
reporter: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
type: user.type,
|
||||
},
|
||||
status: "submitted",
|
||||
subject,
|
||||
type,
|
||||
reportedFrom: page,
|
||||
description,
|
||||
};
|
||||
|
||||
axios
|
||||
.post(`/api/tickets`, ticket)
|
||||
.then(() => {
|
||||
toast.success(
|
||||
`Your ticket has been submitted! You will be contacted by e-mail for further discussion.`,
|
||||
{ toastId: "submitted" },
|
||||
);
|
||||
onClose();
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
toast.error("Something went wrong, please try again later!", {
|
||||
toastId: "error",
|
||||
});
|
||||
})
|
||||
.finally(() => setIsLoading(false));
|
||||
};
|
||||
|
||||
return (
|
||||
<form className="flex flex-col gap-4 pt-8">
|
||||
<Input
|
||||
label="Subject"
|
||||
type="text"
|
||||
name="subject"
|
||||
placeholder="Subject..."
|
||||
onChange={(e) => setSubject(e)}
|
||||
/>
|
||||
<div className="-md:flex-col flex w-full items-center gap-4">
|
||||
<div className="flex w-full flex-col gap-3">
|
||||
<label className="text-mti-gray-dim text-base font-normal">
|
||||
Type
|
||||
</label>
|
||||
<Select
|
||||
options={Object.keys(TicketTypeLabel).map((x) => ({
|
||||
value: x,
|
||||
label: TicketTypeLabel[x as keyof typeof TicketTypeLabel],
|
||||
}))}
|
||||
onChange={(value) =>
|
||||
setType((value?.value as TicketType) ?? undefined)
|
||||
}
|
||||
placeholder="Type..."
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
label="Reporter"
|
||||
type="text"
|
||||
name="reporter"
|
||||
onChange={() => null}
|
||||
value={`${user.name} - ${user.email}`}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<textarea
|
||||
className="input border-mti-gray-platinum h-full min-h-[300px] w-full cursor-text rounded-3xl border bg-white px-7 py-8"
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="Write your ticket's description here..."
|
||||
spellCheck
|
||||
/>
|
||||
<div className="mt-2 flex w-full items-center justify-end gap-4">
|
||||
<Button
|
||||
type="button"
|
||||
color="red"
|
||||
className="w-full max-w-[200px]"
|
||||
variant="outline"
|
||||
onClick={onClose}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
className="w-full max-w-[200px]"
|
||||
isLoading={isLoading}
|
||||
onClick={submit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
68
src/components/Low/Select.tsx
Normal file
68
src/components/Low/Select.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import clsx from "clsx";
|
||||
import { ComponentProps } from "react";
|
||||
import ReactSelect from "react-select";
|
||||
|
||||
interface Option {
|
||||
[key: string]: any;
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
defaultValue?: Option;
|
||||
value?: Option | null;
|
||||
options: Option[];
|
||||
disabled?: boolean;
|
||||
placeholder?: string;
|
||||
onChange: (value: Option | null) => void;
|
||||
isClearable?: boolean;
|
||||
}
|
||||
|
||||
export default function Select({
|
||||
value,
|
||||
defaultValue,
|
||||
options,
|
||||
placeholder,
|
||||
disabled,
|
||||
onChange,
|
||||
isClearable,
|
||||
}: Props) {
|
||||
return (
|
||||
<ReactSelect
|
||||
className={clsx(
|
||||
"placeholder:text-mti-gray-cool border-mti-gray-platinum w-full rounded-full border bg-white px-4 py-4 text-sm font-normal focus:outline-none",
|
||||
disabled &&
|
||||
"!bg-mti-gray-platinum/40 !text-mti-gray-dim cursor-not-allowed",
|
||||
)}
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
menuPortalTarget={document?.body}
|
||||
defaultValue={defaultValue}
|
||||
styles={{
|
||||
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
||||
control: (styles) => ({
|
||||
...styles,
|
||||
paddingLeft: "4px",
|
||||
border: "none",
|
||||
outline: "none",
|
||||
":focus": {
|
||||
outline: "none",
|
||||
},
|
||||
}),
|
||||
option: (styles, state) => ({
|
||||
...styles,
|
||||
backgroundColor: state.isFocused
|
||||
? "#D5D9F0"
|
||||
: state.isSelected
|
||||
? "#7872BF"
|
||||
: "white",
|
||||
color: state.isFocused ? "black" : styles.color,
|
||||
}),
|
||||
}}
|
||||
isDisabled={disabled}
|
||||
isClearable={isClearable}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,149 +1,189 @@
|
||||
import {User} from "@/interfaces/user";
|
||||
import {Dialog, Transition} from "@headlessui/react";
|
||||
import { User } from "@/interfaces/user";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import axios from "axios";
|
||||
import clsx from "clsx";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import {useRouter} from "next/router";
|
||||
import {Fragment} from "react";
|
||||
import {BsShield, BsShieldFill, BsXLg} from "react-icons/bs";
|
||||
import { useRouter } from "next/router";
|
||||
import { Fragment } from "react";
|
||||
import { BsShield, BsShieldFill, BsXLg } from "react-icons/bs";
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
path: string;
|
||||
user: User;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
path: string;
|
||||
user: User;
|
||||
}
|
||||
|
||||
export default function MobileMenu({isOpen, onClose, path, user}: Props) {
|
||||
const router = useRouter();
|
||||
export default function MobileMenu({ isOpen, onClose, path, user }: Props) {
|
||||
const router = useRouter();
|
||||
|
||||
const logout = async () => {
|
||||
axios.post("/api/logout").finally(() => {
|
||||
setTimeout(() => router.reload(), 500);
|
||||
});
|
||||
};
|
||||
const logout = async () => {
|
||||
axios.post("/api/logout").finally(() => {
|
||||
setTimeout(() => router.reload(), 500);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={onClose}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0">
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
return (
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={onClose}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95">
|
||||
<Dialog.Panel className="w-full h-screen transform overflow-hidden bg-white text-left align-middle shadow-xl transition-all text-black flex flex-col gap-8">
|
||||
<Dialog.Title as="header" className="w-full px-8 py-2 -md:flex justify-between items-center md:hidden shadow-sm">
|
||||
<Link href="/">
|
||||
<Image src="/logo_title.png" alt="EnCoach logo" width={69} height={69} />
|
||||
</Link>
|
||||
<div className="cursor-pointer" onClick={onClose} tabIndex={0}>
|
||||
<BsXLg className="text-2xl text-mti-purple-light" onClick={onClose} />
|
||||
</div>
|
||||
</Dialog.Title>
|
||||
<div className="flex flex-col h-full gap-6 px-8 text-lg">
|
||||
<Link
|
||||
href="/"
|
||||
className={clsx(
|
||||
"transition ease-in-out duration-300 w-fit",
|
||||
path === "/" && "text-mti-purple-light font-semibold border-b-2 border-b-mti-purple-light ",
|
||||
)}>
|
||||
Dashboard
|
||||
</Link>
|
||||
{(user.type === "student" || user.type === "teacher" || user.type === "developer") && (
|
||||
<>
|
||||
<Link
|
||||
href="/exam"
|
||||
className={clsx(
|
||||
"transition ease-in-out duration-300 w-fit",
|
||||
path === "/exam" && "text-mti-purple-light font-semibold border-b-2 border-b-mti-purple-light ",
|
||||
)}>
|
||||
Exams
|
||||
</Link>
|
||||
<Link
|
||||
href="/exercises"
|
||||
className={clsx(
|
||||
"transition ease-in-out duration-300 w-fit",
|
||||
path === "/exercises" &&
|
||||
"text-mti-purple-light font-semibold border-b-2 border-b-mti-purple-light ",
|
||||
)}>
|
||||
Exercises
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
<Link
|
||||
href="/stats"
|
||||
className={clsx(
|
||||
"transition ease-in-out duration-300 w-fit",
|
||||
path === "/stats" && "text-mti-purple-light font-semibold border-b-2 border-b-mti-purple-light ",
|
||||
)}>
|
||||
Stats
|
||||
</Link>
|
||||
<Link
|
||||
href="/record"
|
||||
className={clsx(
|
||||
"transition ease-in-out duration-300 w-fit",
|
||||
path === "/record" && "text-mti-purple-light font-semibold border-b-2 border-b-mti-purple-light ",
|
||||
)}>
|
||||
Record
|
||||
</Link>
|
||||
{["admin", "developer", "agent", "corporate"].includes(user.type) && (
|
||||
<Link
|
||||
href="/payment-record"
|
||||
className={clsx(
|
||||
"transition ease-in-out duration-300 w-fit",
|
||||
path === "/payment-record" &&
|
||||
"text-mti-purple-light font-semibold border-b-2 border-b-mti-purple-light ",
|
||||
)}>
|
||||
Payment Record
|
||||
</Link>
|
||||
)}
|
||||
{["admin", "developer", "corporate", "teacher"].includes(user.type) && (
|
||||
<Link
|
||||
href="/settings"
|
||||
className={clsx(
|
||||
"transition ease-in-out duration-300 w-fit",
|
||||
path === "/settings" && "text-mti-purple-light font-semibold border-b-2 border-b-mti-purple-light ",
|
||||
)}>
|
||||
Settings
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
href="/profile"
|
||||
className={clsx(
|
||||
"transition ease-in-out duration-300 w-fit",
|
||||
path === "/profile" && "text-mti-purple-light font-semibold border-b-2 border-b-mti-purple-light ",
|
||||
)}>
|
||||
Profile
|
||||
</Link>
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="flex h-screen w-full transform flex-col gap-8 overflow-hidden bg-white text-left align-middle text-black shadow-xl transition-all">
|
||||
<Dialog.Title
|
||||
as="header"
|
||||
className="-md:flex w-full items-center justify-between px-8 py-2 shadow-sm md:hidden"
|
||||
>
|
||||
<Link href="/">
|
||||
<Image
|
||||
src="/logo_title.png"
|
||||
alt="EnCoach logo"
|
||||
width={69}
|
||||
height={69}
|
||||
/>
|
||||
</Link>
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
onClick={onClose}
|
||||
tabIndex={0}
|
||||
>
|
||||
<BsXLg
|
||||
className="text-mti-purple-light text-2xl"
|
||||
onClick={onClose}
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Title>
|
||||
<div className="flex h-full flex-col gap-6 px-8 text-lg">
|
||||
<Link
|
||||
href="/"
|
||||
className={clsx(
|
||||
"w-fit transition duration-300 ease-in-out",
|
||||
path === "/" &&
|
||||
"text-mti-purple-light border-b-mti-purple-light border-b-2 font-semibold ",
|
||||
)}
|
||||
>
|
||||
Dashboard
|
||||
</Link>
|
||||
{(user.type === "student" ||
|
||||
user.type === "teacher" ||
|
||||
user.type === "developer") && (
|
||||
<>
|
||||
<Link
|
||||
href="/exam"
|
||||
className={clsx(
|
||||
"w-fit transition duration-300 ease-in-out",
|
||||
path === "/exam" &&
|
||||
"text-mti-purple-light border-b-mti-purple-light border-b-2 font-semibold ",
|
||||
)}
|
||||
>
|
||||
Exams
|
||||
</Link>
|
||||
<Link
|
||||
href="/exercises"
|
||||
className={clsx(
|
||||
"w-fit transition duration-300 ease-in-out",
|
||||
path === "/exercises" &&
|
||||
"text-mti-purple-light border-b-mti-purple-light border-b-2 font-semibold ",
|
||||
)}
|
||||
>
|
||||
Exercises
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
<Link
|
||||
href="/stats"
|
||||
className={clsx(
|
||||
"w-fit transition duration-300 ease-in-out",
|
||||
path === "/stats" &&
|
||||
"text-mti-purple-light border-b-mti-purple-light border-b-2 font-semibold ",
|
||||
)}
|
||||
>
|
||||
Stats
|
||||
</Link>
|
||||
<Link
|
||||
href="/record"
|
||||
className={clsx(
|
||||
"w-fit transition duration-300 ease-in-out",
|
||||
path === "/record" &&
|
||||
"text-mti-purple-light border-b-mti-purple-light border-b-2 font-semibold ",
|
||||
)}
|
||||
>
|
||||
Record
|
||||
</Link>
|
||||
{["admin", "developer", "agent", "corporate"].includes(
|
||||
user.type,
|
||||
) && (
|
||||
<Link
|
||||
href="/payment-record"
|
||||
className={clsx(
|
||||
"w-fit transition duration-300 ease-in-out",
|
||||
path === "/payment-record" &&
|
||||
"text-mti-purple-light border-b-mti-purple-light border-b-2 font-semibold ",
|
||||
)}
|
||||
>
|
||||
Payment Record
|
||||
</Link>
|
||||
)}
|
||||
{["admin", "developer", "corporate", "teacher"].includes(
|
||||
user.type,
|
||||
) && (
|
||||
<Link
|
||||
href="/settings"
|
||||
className={clsx(
|
||||
"w-fit transition duration-300 ease-in-out",
|
||||
path === "/settings" &&
|
||||
"text-mti-purple-light border-b-mti-purple-light border-b-2 font-semibold ",
|
||||
)}
|
||||
>
|
||||
Settings
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
href="/profile"
|
||||
className={clsx(
|
||||
"w-fit transition duration-300 ease-in-out",
|
||||
path === "/profile" &&
|
||||
"text-mti-purple-light border-b-mti-purple-light border-b-2 font-semibold ",
|
||||
)}
|
||||
>
|
||||
Profile
|
||||
</Link>
|
||||
|
||||
<span
|
||||
className={clsx("transition ease-in-out duration-300 w-fit justify-self-end cursor-pointer")}
|
||||
onClick={logout}>
|
||||
Logout
|
||||
</span>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
<span
|
||||
className={clsx(
|
||||
"w-fit cursor-pointer justify-self-end transition duration-300 ease-in-out",
|
||||
)}
|
||||
onClick={logout}
|
||||
>
|
||||
Logout
|
||||
</span>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,95 +1,161 @@
|
||||
import {User} from "@/interfaces/user";
|
||||
import { User } from "@/interfaces/user";
|
||||
import Link from "next/link";
|
||||
import FocusLayer from "@/components/FocusLayer";
|
||||
import {preventNavigation} from "@/utils/navigation.disabled";
|
||||
import {useRouter} from "next/router";
|
||||
import {BsList} from "react-icons/bs";
|
||||
import { preventNavigation } from "@/utils/navigation.disabled";
|
||||
import { useRouter } from "next/router";
|
||||
import { BsList, BsQuestionCircle, BsQuestionCircleFill } from "react-icons/bs";
|
||||
import clsx from "clsx";
|
||||
import moment from "moment";
|
||||
import MobileMenu from "./MobileMenu";
|
||||
import {useEffect, useState} from "react";
|
||||
import {Type} from "@/interfaces/user";
|
||||
import {USER_TYPE_LABELS} from "@/resources/user";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Type } from "@/interfaces/user";
|
||||
import { USER_TYPE_LABELS } from "@/resources/user";
|
||||
import useGroups from "@/hooks/useGroups";
|
||||
import {isUserFromCorporate} from "@/utils/groups";
|
||||
import { isUserFromCorporate } from "@/utils/groups";
|
||||
import Button from "./Low/Button";
|
||||
import Modal from "./Modal";
|
||||
import Input from "./Low/Input";
|
||||
import TicketSubmission from "./High/TicketSubmission";
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
navDisabled?: boolean;
|
||||
focusMode?: boolean;
|
||||
onFocusLayerMouseEnter?: () => void;
|
||||
path: string;
|
||||
user: User;
|
||||
navDisabled?: boolean;
|
||||
focusMode?: boolean;
|
||||
onFocusLayerMouseEnter?: () => void;
|
||||
path: string;
|
||||
}
|
||||
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
export default function Navbar({user, path, navDisabled = false, focusMode = false, onFocusLayerMouseEnter}: Props) {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [disablePaymentPage, setDisablePaymentPage] = useState(true);
|
||||
export default function Navbar({
|
||||
user,
|
||||
path,
|
||||
navDisabled = false,
|
||||
focusMode = false,
|
||||
onFocusLayerMouseEnter,
|
||||
}: Props) {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [disablePaymentPage, setDisablePaymentPage] = useState(true);
|
||||
const [isTicketOpen, setIsTicketOpen] = useState(false);
|
||||
|
||||
const disableNavigation = preventNavigation(navDisabled, focusMode);
|
||||
const router = useRouter();
|
||||
const disableNavigation = preventNavigation(navDisabled, focusMode);
|
||||
|
||||
const expirationDateColor = (date: Date) => {
|
||||
const momentDate = moment(date);
|
||||
const today = moment(new Date());
|
||||
const expirationDateColor = (date: Date) => {
|
||||
const momentDate = moment(date);
|
||||
const today = moment(new Date());
|
||||
|
||||
if (today.add(1, "days").isAfter(momentDate)) return "!bg-mti-red-ultralight border-mti-red-light";
|
||||
if (today.add(3, "days").isAfter(momentDate)) return "!bg-mti-rose-ultralight border-mti-rose-light";
|
||||
if (today.add(7, "days").isAfter(momentDate)) return "!bg-mti-orange-ultralight border-mti-orange-light";
|
||||
};
|
||||
if (today.add(1, "days").isAfter(momentDate))
|
||||
return "!bg-mti-red-ultralight border-mti-red-light";
|
||||
if (today.add(3, "days").isAfter(momentDate))
|
||||
return "!bg-mti-rose-ultralight border-mti-rose-light";
|
||||
if (today.add(7, "days").isAfter(momentDate))
|
||||
return "!bg-mti-orange-ultralight border-mti-orange-light";
|
||||
};
|
||||
|
||||
const showExpirationDate = () => {
|
||||
if (!user.subscriptionExpirationDate) return false;
|
||||
const showExpirationDate = () => {
|
||||
if (!user.subscriptionExpirationDate) return false;
|
||||
|
||||
const momentDate = moment(user.subscriptionExpirationDate);
|
||||
const today = moment(new Date());
|
||||
const momentDate = moment(user.subscriptionExpirationDate);
|
||||
const today = moment(new Date());
|
||||
|
||||
return today.add(7, "days").isAfter(momentDate);
|
||||
};
|
||||
return today.add(7, "days").isAfter(momentDate);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (user.type !== "student" && user.type !== "teacher") setDisablePaymentPage(false);
|
||||
isUserFromCorporate(user.id).then((result) => setDisablePaymentPage(result));
|
||||
}, [user]);
|
||||
useEffect(() => {
|
||||
if (user.type !== "student" && user.type !== "teacher")
|
||||
setDisablePaymentPage(false);
|
||||
isUserFromCorporate(user.id).then((result) =>
|
||||
setDisablePaymentPage(result),
|
||||
);
|
||||
}, [user]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{user && <MobileMenu path={path} isOpen={isMenuOpen} onClose={() => setIsMenuOpen(false)} user={user} />}
|
||||
<header className="w-full bg-transparent py-2 md:py-4 -md:justify-between md:gap-12 flex items-center relative -md:px-4">
|
||||
<Link href={disableNavigation ? "" : "/"} className=" md:px-8 flex gap-8 items-center">
|
||||
<img src="/logo.png" alt="EnCoach's Logo" className="w-8 md:w-12" />
|
||||
<h1 className="font-bold text-2xl w-1/6 -md:hidden">EnCoach</h1>
|
||||
</Link>
|
||||
<div className="flex justify-end -md:items-center gap-4 md:w-5/6 md:mr-8">
|
||||
{showExpirationDate() && (
|
||||
<Link
|
||||
href={disablePaymentPage ? "/payment" : ""}
|
||||
data-tip="Expiry date"
|
||||
className={clsx(
|
||||
"py-2 px-6 w-fit flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer",
|
||||
"transition duration-300 ease-in-out tooltip tooltip-bottom",
|
||||
!user.subscriptionExpirationDate
|
||||
? "bg-mti-green-ultralight border-mti-green-light"
|
||||
: expirationDateColor(user.subscriptionExpirationDate),
|
||||
"bg-white border-mti-gray-platinum",
|
||||
)}>
|
||||
{!user.subscriptionExpirationDate && "Unlimited"}
|
||||
{user.subscriptionExpirationDate && moment(user.subscriptionExpirationDate).format("DD/MM/YYYY")}
|
||||
</Link>
|
||||
)}
|
||||
<Link href={disableNavigation ? "" : "/profile"} className="flex gap-6 items-center justify-end -md:hidden">
|
||||
<img src={user.profilePicture} alt={user.name} className="w-10 h-10 rounded-full object-cover" />
|
||||
<span className="text-right -md:hidden">
|
||||
{user.type === "corporate" ? `${user.corporateInformation?.companyInformation.name} |` : ""} {user.name} |{" "}
|
||||
{USER_TYPE_LABELS[user.type]}
|
||||
</span>
|
||||
</Link>
|
||||
<div className="cursor-pointer md:hidden" onClick={() => setIsMenuOpen(true)}>
|
||||
<BsList className="text-mti-purple-light w-8 h-8" />
|
||||
</div>
|
||||
</div>
|
||||
{focusMode && <FocusLayer onFocusLayerMouseEnter={onFocusLayerMouseEnter} />}
|
||||
</header>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
isOpen={isTicketOpen}
|
||||
onClose={() => setIsTicketOpen(false)}
|
||||
title="Submit a ticket"
|
||||
>
|
||||
<TicketSubmission
|
||||
user={user}
|
||||
page={window.location.href}
|
||||
onClose={() => setIsTicketOpen(false)}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
{user && (
|
||||
<MobileMenu
|
||||
path={path}
|
||||
isOpen={isMenuOpen}
|
||||
onClose={() => setIsMenuOpen(false)}
|
||||
user={user}
|
||||
/>
|
||||
)}
|
||||
<header className="-md:justify-between -md:px-4 relative flex w-full items-center bg-transparent py-2 md:gap-12 md:py-4">
|
||||
<Link
|
||||
href={disableNavigation ? "" : "/"}
|
||||
className=" flex items-center gap-8 md:px-8"
|
||||
>
|
||||
<img src="/logo.png" alt="EnCoach's Logo" className="w-8 md:w-12" />
|
||||
<h1 className="-md:hidden w-1/6 text-2xl font-bold">EnCoach</h1>
|
||||
</Link>
|
||||
<div className="flex items-center justify-end gap-4 md:mr-8 md:w-5/6">
|
||||
{/* OPEN TICKET SYSTEM */}
|
||||
<button
|
||||
className={clsx(
|
||||
"border-mti-purple-light tooltip tooltip-bottom flex h-8 w-8 flex-col items-center justify-center rounded-full border p-1",
|
||||
"hover:bg-mti-purple-light transition duration-300 ease-in-out hover:text-white",
|
||||
)}
|
||||
data-tip="Submit a help/feedback ticket"
|
||||
onClick={() => setIsTicketOpen(true)}
|
||||
>
|
||||
<BsQuestionCircleFill />
|
||||
</button>
|
||||
|
||||
{showExpirationDate() && (
|
||||
<Link
|
||||
href={disablePaymentPage ? "/payment" : ""}
|
||||
data-tip="Expiry date"
|
||||
className={clsx(
|
||||
"flex w-fit cursor-pointer justify-center rounded-full border px-6 py-2 text-sm font-normal focus:outline-none",
|
||||
"tooltip tooltip-bottom transition duration-300 ease-in-out",
|
||||
!user.subscriptionExpirationDate
|
||||
? "bg-mti-green-ultralight border-mti-green-light"
|
||||
: expirationDateColor(user.subscriptionExpirationDate),
|
||||
"border-mti-gray-platinum bg-white",
|
||||
)}
|
||||
>
|
||||
{!user.subscriptionExpirationDate && "Unlimited"}
|
||||
{user.subscriptionExpirationDate &&
|
||||
moment(user.subscriptionExpirationDate).format("DD/MM/YYYY")}
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
href={disableNavigation ? "" : "/profile"}
|
||||
className="-md:hidden flex items-center justify-end gap-6"
|
||||
>
|
||||
<img
|
||||
src={user.profilePicture}
|
||||
alt={user.name}
|
||||
className="h-10 w-10 rounded-full object-cover"
|
||||
/>
|
||||
<span className="-md:hidden text-right">
|
||||
{user.type === "corporate"
|
||||
? `${user.corporateInformation?.companyInformation.name} |`
|
||||
: ""}{" "}
|
||||
{user.name} | {USER_TYPE_LABELS[user.type]}
|
||||
</span>
|
||||
</Link>
|
||||
<div
|
||||
className="cursor-pointer md:hidden"
|
||||
onClick={() => setIsMenuOpen(true)}
|
||||
>
|
||||
<BsList className="text-mti-purple-light h-8 w-8" />
|
||||
</div>
|
||||
</div>
|
||||
{focusMode && (
|
||||
<FocusLayer onFocusLayerMouseEnter={onFocusLayerMouseEnter} />
|
||||
)}
|
||||
</header>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,175 +1,295 @@
|
||||
import clsx from "clsx";
|
||||
import {IconType} from "react-icons";
|
||||
import {MdSpaceDashboard} from "react-icons/md";
|
||||
import { IconType } from "react-icons";
|
||||
import { MdSpaceDashboard } from "react-icons/md";
|
||||
import {
|
||||
BsFileEarmarkText,
|
||||
BsClockHistory,
|
||||
BsPencil,
|
||||
BsGraphUp,
|
||||
BsChevronBarRight,
|
||||
BsChevronBarLeft,
|
||||
BsShieldFill,
|
||||
BsCloudFill,
|
||||
BsCurrencyDollar,
|
||||
BsFileEarmarkText,
|
||||
BsClockHistory,
|
||||
BsPencil,
|
||||
BsGraphUp,
|
||||
BsChevronBarRight,
|
||||
BsChevronBarLeft,
|
||||
BsShieldFill,
|
||||
BsCloudFill,
|
||||
BsCurrencyDollar,
|
||||
BsClipboardData,
|
||||
} from "react-icons/bs";
|
||||
import {RiLogoutBoxFill} from "react-icons/ri";
|
||||
import {SlPencil} from "react-icons/sl";
|
||||
import {FaAward} from "react-icons/fa";
|
||||
import { RiLogoutBoxFill } from "react-icons/ri";
|
||||
import { SlPencil } from "react-icons/sl";
|
||||
import { FaAward } from "react-icons/fa";
|
||||
import Link from "next/link";
|
||||
import {useRouter} from "next/router";
|
||||
import { useRouter } from "next/router";
|
||||
import axios from "axios";
|
||||
import FocusLayer from "@/components/FocusLayer";
|
||||
import {preventNavigation} from "@/utils/navigation.disabled";
|
||||
import {useState} from "react";
|
||||
import { preventNavigation } from "@/utils/navigation.disabled";
|
||||
import { useState } from "react";
|
||||
import usePreferencesStore from "@/stores/preferencesStore";
|
||||
import {Type} from "@/interfaces/user";
|
||||
import { Type } from "@/interfaces/user";
|
||||
interface Props {
|
||||
path: string;
|
||||
navDisabled?: boolean;
|
||||
focusMode?: boolean;
|
||||
onFocusLayerMouseEnter?: () => void;
|
||||
className?: string;
|
||||
userType?: Type;
|
||||
path: string;
|
||||
navDisabled?: boolean;
|
||||
focusMode?: boolean;
|
||||
onFocusLayerMouseEnter?: () => void;
|
||||
className?: string;
|
||||
userType?: Type;
|
||||
}
|
||||
|
||||
interface NavProps {
|
||||
Icon: IconType;
|
||||
label: string;
|
||||
path: string;
|
||||
keyPath: string;
|
||||
disabled?: boolean;
|
||||
isMinimized?: boolean;
|
||||
Icon: IconType;
|
||||
label: string;
|
||||
path: string;
|
||||
keyPath: string;
|
||||
disabled?: boolean;
|
||||
isMinimized?: boolean;
|
||||
}
|
||||
|
||||
const Nav = ({Icon, label, path, keyPath, disabled = false, isMinimized = false}: NavProps) => (
|
||||
<Link
|
||||
href={!disabled ? keyPath : ""}
|
||||
className={clsx(
|
||||
"p-4 rounded-full flex gap-4 items-center text-gray-500 hover:text-white",
|
||||
"transition-all duration-300 ease-in-out",
|
||||
disabled ? "hover:bg-mti-gray-dim cursor-not-allowed" : "hover:bg-mti-purple-light cursor-pointer",
|
||||
path === keyPath && "bg-mti-purple-light text-white",
|
||||
isMinimized ? "w-fit" : "w-full min-w-[200px] 2xl:min-w-[220px] px-8",
|
||||
)}>
|
||||
<Icon size={24} />
|
||||
{!isMinimized && <span className="text-lg font-semibold">{label}</span>}
|
||||
</Link>
|
||||
const Nav = ({
|
||||
Icon,
|
||||
label,
|
||||
path,
|
||||
keyPath,
|
||||
disabled = false,
|
||||
isMinimized = false,
|
||||
}: NavProps) => (
|
||||
<Link
|
||||
href={!disabled ? keyPath : ""}
|
||||
className={clsx(
|
||||
"flex items-center gap-4 rounded-full p-4 text-gray-500 hover:text-white",
|
||||
"transition-all duration-300 ease-in-out",
|
||||
disabled
|
||||
? "hover:bg-mti-gray-dim cursor-not-allowed"
|
||||
: "hover:bg-mti-purple-light cursor-pointer",
|
||||
path === keyPath && "bg-mti-purple-light text-white",
|
||||
isMinimized ? "w-fit" : "w-full min-w-[200px] px-8 2xl:min-w-[220px]",
|
||||
)}
|
||||
>
|
||||
<Icon size={24} />
|
||||
{!isMinimized && <span className="text-lg font-semibold">{label}</span>}
|
||||
</Link>
|
||||
);
|
||||
|
||||
export default function Sidebar({path, navDisabled = false, focusMode = false, userType, onFocusLayerMouseEnter, className}: Props) {
|
||||
const router = useRouter();
|
||||
export default function Sidebar({
|
||||
path,
|
||||
navDisabled = false,
|
||||
focusMode = false,
|
||||
userType,
|
||||
onFocusLayerMouseEnter,
|
||||
className,
|
||||
}: Props) {
|
||||
const router = useRouter();
|
||||
|
||||
const [isMinimized, toggleMinimize] = usePreferencesStore((state) => [state.isSidebarMinimized, state.toggleSidebarMinimized]);
|
||||
const [isMinimized, toggleMinimize] = usePreferencesStore((state) => [
|
||||
state.isSidebarMinimized,
|
||||
state.toggleSidebarMinimized,
|
||||
]);
|
||||
|
||||
const logout = async () => {
|
||||
axios.post("/api/logout").finally(() => {
|
||||
setTimeout(() => router.reload(), 500);
|
||||
});
|
||||
};
|
||||
const logout = async () => {
|
||||
axios.post("/api/logout").finally(() => {
|
||||
setTimeout(() => router.reload(), 500);
|
||||
});
|
||||
};
|
||||
|
||||
const disableNavigation = preventNavigation(navDisabled, focusMode);
|
||||
const disableNavigation = preventNavigation(navDisabled, focusMode);
|
||||
|
||||
return (
|
||||
<section
|
||||
className={clsx(
|
||||
"h-full flex bg-transparent flex-col justify-between px-4 py-4 pb-8 relative",
|
||||
isMinimized ? "w-fit" : "w-1/6 -xl:w-fit",
|
||||
className,
|
||||
)}>
|
||||
<div className="xl:flex -xl:hidden flex-col gap-3">
|
||||
<Nav disabled={disableNavigation} Icon={MdSpaceDashboard} label="Dashboard" path={path} keyPath="/" isMinimized={isMinimized} />
|
||||
{(userType === "student" || userType === "teacher" || userType === "developer") && (
|
||||
<>
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsFileEarmarkText}
|
||||
label="Exams"
|
||||
path={path}
|
||||
keyPath="/exam"
|
||||
isMinimized={isMinimized}
|
||||
/>
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsPencil}
|
||||
label="Exercises"
|
||||
path={path}
|
||||
keyPath="/exercises"
|
||||
isMinimized={isMinimized}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Nav disabled={disableNavigation} Icon={BsGraphUp} label="Stats" path={path} keyPath="/stats" isMinimized={isMinimized} />
|
||||
<Nav disabled={disableNavigation} Icon={BsClockHistory} label="Record" path={path} keyPath="/record" isMinimized={isMinimized} />
|
||||
{["admin", "developer", "agent", "corporate"].includes(userType || "") && (
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsCurrencyDollar}
|
||||
label="Payment Record"
|
||||
path={path}
|
||||
keyPath="/payment-record"
|
||||
isMinimized={isMinimized}
|
||||
/>
|
||||
)}
|
||||
{["admin", "developer", "corporate", "teacher"].includes(userType || "") && (
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsShieldFill}
|
||||
label="Settings"
|
||||
path={path}
|
||||
keyPath="/settings"
|
||||
isMinimized={isMinimized}
|
||||
/>
|
||||
)}
|
||||
{userType === "developer" && (
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsCloudFill}
|
||||
label="Generation"
|
||||
path={path}
|
||||
keyPath="/generation"
|
||||
isMinimized={isMinimized}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="xl:hidden -xl:flex flex-col gap-3">
|
||||
<Nav disabled={disableNavigation} Icon={MdSpaceDashboard} label="Dashboard" path={path} keyPath="/" isMinimized={true} />
|
||||
<Nav disabled={disableNavigation} Icon={BsFileEarmarkText} label="Exams" path={path} keyPath="/exam" isMinimized={true} />
|
||||
<Nav disabled={disableNavigation} Icon={BsPencil} label="Exercises" path={path} keyPath="/exercises" isMinimized={true} />
|
||||
<Nav disabled={disableNavigation} Icon={BsGraphUp} label="Stats" path={path} keyPath="/stats" isMinimized={true} />
|
||||
<Nav disabled={disableNavigation} Icon={BsClockHistory} label="Record" path={path} keyPath="/record" isMinimized={true} />
|
||||
{userType !== "student" && (
|
||||
<Nav disabled={disableNavigation} Icon={BsShieldFill} label="Settings" path={path} keyPath="/settings" isMinimized={true} />
|
||||
)}
|
||||
{userType === "developer" && (
|
||||
<Nav disabled={disableNavigation} Icon={BsCloudFill} label="Generation" path={path} keyPath="/generation" isMinimized={true} />
|
||||
)}
|
||||
</div>
|
||||
return (
|
||||
<section
|
||||
className={clsx(
|
||||
"relative flex h-full flex-col justify-between bg-transparent px-4 py-4 pb-8",
|
||||
isMinimized ? "w-fit" : "-xl:w-fit w-1/6",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="-xl:hidden flex-col gap-3 xl:flex">
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={MdSpaceDashboard}
|
||||
label="Dashboard"
|
||||
path={path}
|
||||
keyPath="/"
|
||||
isMinimized={isMinimized}
|
||||
/>
|
||||
{(userType === "student" ||
|
||||
userType === "teacher" ||
|
||||
userType === "developer") && (
|
||||
<>
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsFileEarmarkText}
|
||||
label="Exams"
|
||||
path={path}
|
||||
keyPath="/exam"
|
||||
isMinimized={isMinimized}
|
||||
/>
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsPencil}
|
||||
label="Exercises"
|
||||
path={path}
|
||||
keyPath="/exercises"
|
||||
isMinimized={isMinimized}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsGraphUp}
|
||||
label="Stats"
|
||||
path={path}
|
||||
keyPath="/stats"
|
||||
isMinimized={isMinimized}
|
||||
/>
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsClockHistory}
|
||||
label="Record"
|
||||
path={path}
|
||||
keyPath="/record"
|
||||
isMinimized={isMinimized}
|
||||
/>
|
||||
{["admin", "developer", "agent", "corporate"].includes(
|
||||
userType || "",
|
||||
) && (
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsCurrencyDollar}
|
||||
label="Payment Record"
|
||||
path={path}
|
||||
keyPath="/payment-record"
|
||||
isMinimized={isMinimized}
|
||||
/>
|
||||
)}
|
||||
{["admin", "developer", "corporate", "teacher"].includes(
|
||||
userType || "",
|
||||
) && (
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsShieldFill}
|
||||
label="Settings"
|
||||
path={path}
|
||||
keyPath="/settings"
|
||||
isMinimized={isMinimized}
|
||||
/>
|
||||
)}
|
||||
{["admin", "developer"].includes(userType || "") && (
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsClipboardData}
|
||||
label="Tickets"
|
||||
path={path}
|
||||
keyPath="/tickets"
|
||||
isMinimized={isMinimized}
|
||||
/>
|
||||
)}
|
||||
{userType === "developer" && (
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsCloudFill}
|
||||
label="Generation"
|
||||
path={path}
|
||||
keyPath="/generation"
|
||||
isMinimized={isMinimized}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="-xl:flex flex-col gap-3 xl:hidden">
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={MdSpaceDashboard}
|
||||
label="Dashboard"
|
||||
path={path}
|
||||
keyPath="/"
|
||||
isMinimized={true}
|
||||
/>
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsFileEarmarkText}
|
||||
label="Exams"
|
||||
path={path}
|
||||
keyPath="/exam"
|
||||
isMinimized={true}
|
||||
/>
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsPencil}
|
||||
label="Exercises"
|
||||
path={path}
|
||||
keyPath="/exercises"
|
||||
isMinimized={true}
|
||||
/>
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsGraphUp}
|
||||
label="Stats"
|
||||
path={path}
|
||||
keyPath="/stats"
|
||||
isMinimized={true}
|
||||
/>
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsClockHistory}
|
||||
label="Record"
|
||||
path={path}
|
||||
keyPath="/record"
|
||||
isMinimized={true}
|
||||
/>
|
||||
{userType !== "student" && (
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsShieldFill}
|
||||
label="Settings"
|
||||
path={path}
|
||||
keyPath="/settings"
|
||||
isMinimized={true}
|
||||
/>
|
||||
)}
|
||||
{userType === "developer" && (
|
||||
<Nav
|
||||
disabled={disableNavigation}
|
||||
Icon={BsCloudFill}
|
||||
label="Generation"
|
||||
path={path}
|
||||
keyPath="/generation"
|
||||
isMinimized={true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-0 bottom-12 fixed">
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={1}
|
||||
onClick={toggleMinimize}
|
||||
className={clsx(
|
||||
"p-4 rounded-full flex gap-4 items-center cursor-pointer text-black hover:text-mti-rose -xl:hidden transition duration-300 ease-in-out",
|
||||
isMinimized ? "w-fit" : "w-full min-w-[250px] px-8",
|
||||
)}>
|
||||
{isMinimized ? <BsChevronBarRight size={24} /> : <BsChevronBarLeft size={24} />}
|
||||
{!isMinimized && <span className="text-lg font-medium">Minimize</span>}
|
||||
</div>
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={1}
|
||||
onClick={focusMode ? () => {} : logout}
|
||||
className={clsx(
|
||||
"p-4 rounded-full flex gap-4 items-center cursor-pointer text-black hover:text-mti-rose transition duration-300 ease-in-out",
|
||||
isMinimized ? "w-fit" : "w-full min-w-[250px] px-8",
|
||||
)}>
|
||||
<RiLogoutBoxFill size={24} />
|
||||
{!isMinimized && <span className="text-lg font-medium -xl:hidden">Log Out</span>}
|
||||
</div>
|
||||
</div>
|
||||
{focusMode && <FocusLayer onFocusLayerMouseEnter={onFocusLayerMouseEnter} />}
|
||||
</section>
|
||||
);
|
||||
<div className="fixed bottom-12 flex flex-col gap-0">
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={1}
|
||||
onClick={toggleMinimize}
|
||||
className={clsx(
|
||||
"hover:text-mti-rose -xl:hidden flex cursor-pointer items-center gap-4 rounded-full p-4 text-black transition duration-300 ease-in-out",
|
||||
isMinimized ? "w-fit" : "w-full min-w-[250px] px-8",
|
||||
)}
|
||||
>
|
||||
{isMinimized ? (
|
||||
<BsChevronBarRight size={24} />
|
||||
) : (
|
||||
<BsChevronBarLeft size={24} />
|
||||
)}
|
||||
{!isMinimized && (
|
||||
<span className="text-lg font-medium">Minimize</span>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={1}
|
||||
onClick={focusMode ? () => {} : logout}
|
||||
className={clsx(
|
||||
"hover:text-mti-rose flex cursor-pointer items-center gap-4 rounded-full p-4 text-black transition duration-300 ease-in-out",
|
||||
isMinimized ? "w-fit" : "w-full min-w-[250px] px-8",
|
||||
)}
|
||||
>
|
||||
<RiLogoutBoxFill size={24} />
|
||||
{!isMinimized && (
|
||||
<span className="-xl:hidden text-lg font-medium">Log Out</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{focusMode && (
|
||||
<FocusLayer onFocusLayerMouseEnter={onFocusLayerMouseEnter} />
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user