- ENCOA-3: Added the ability to delete multiple codes at once;
- ENCOA-5 Added a column for the Creator on the code list;
This commit is contained in:
@@ -62,8 +62,8 @@ export default function Button({
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"py-4 px-6 rounded-full transition ease-in-out duration-300 disabled:cursor-not-allowed cursor-pointer",
|
"py-4 px-6 rounded-full transition ease-in-out duration-300 disabled:cursor-not-allowed cursor-pointer",
|
||||||
className,
|
|
||||||
colorClassNames[color][variant],
|
colorClassNames[color][variant],
|
||||||
|
className,
|
||||||
)}
|
)}
|
||||||
disabled={disabled || isLoading}>
|
disabled={disabled || isLoading}>
|
||||||
{!isLoading && children}
|
{!isLoading && children}
|
||||||
|
|||||||
@@ -1,15 +1,69 @@
|
|||||||
|
import Button from "@/components/Low/Button";
|
||||||
|
import Checkbox from "@/components/Low/Checkbox";
|
||||||
import useCodes from "@/hooks/useCodes";
|
import useCodes from "@/hooks/useCodes";
|
||||||
|
import useUser from "@/hooks/useUser";
|
||||||
|
import useUsers from "@/hooks/useUsers";
|
||||||
import {Code, User} from "@/interfaces/user";
|
import {Code, User} from "@/interfaces/user";
|
||||||
|
import {USER_TYPE_LABELS} from "@/resources/user";
|
||||||
import {createColumnHelper, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table";
|
import {createColumnHelper, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
import {BsTrash} from "react-icons/bs";
|
import {BsTrash} from "react-icons/bs";
|
||||||
import {toast} from "react-toastify";
|
import {toast} from "react-toastify";
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<Code>();
|
const columnHelper = createColumnHelper<Code>();
|
||||||
|
|
||||||
|
const CreatorCell = ({id, users}: {id: string; users: User[]}) => {
|
||||||
|
const [creatorUser, setCreatorUser] = useState<User>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(id);
|
||||||
|
setCreatorUser(users.find((x) => x.id === id));
|
||||||
|
}, [id, users]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{(creatorUser?.type === "corporate" ? creatorUser?.corporateInformation?.companyInformation?.name : creatorUser?.name || "N/A") || "N/A"}{" "}
|
||||||
|
{creatorUser && `(${USER_TYPE_LABELS[creatorUser.type]})`}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default function CodeList({user}: {user: User}) {
|
export default function CodeList({user}: {user: User}) {
|
||||||
|
const [selectedCodes, setSelectedCodes] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const {users} = useUsers();
|
||||||
const {codes, reload} = useCodes(user?.type === "corporate" ? user?.id : undefined);
|
const {codes, reload} = useCodes(user?.type === "corporate" ? user?.id : undefined);
|
||||||
|
|
||||||
|
const toggleCode = (id: string) => {
|
||||||
|
setSelectedCodes((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteCodes = async (codes: string[]) => {
|
||||||
|
if (!confirm(`Are you sure you want to delete these ${codes.length} code(s)?`)) return;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
codes.forEach((code) => params.append("code", code));
|
||||||
|
|
||||||
|
axios
|
||||||
|
.delete(`/api/code?${params.toString()}`)
|
||||||
|
.then(() => toast.success(`Deleted the codes!`))
|
||||||
|
.catch((reason) => {
|
||||||
|
if (reason.response.status === 404) {
|
||||||
|
toast.error("Code not found!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reason.response.status === 403) {
|
||||||
|
toast.error("You do not have permission to delete this code!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.error("Something went wrong, please try again later.");
|
||||||
|
})
|
||||||
|
.finally(reload);
|
||||||
|
};
|
||||||
|
|
||||||
const deleteCode = async (code: Code) => {
|
const deleteCode = async (code: Code) => {
|
||||||
if (!confirm(`Are you sure you want to delete this "${code.code}" code?`)) return;
|
if (!confirm(`Are you sure you want to delete this "${code.code}" code?`)) return;
|
||||||
|
|
||||||
@@ -33,6 +87,14 @@ export default function CodeList({user}: {user: User}) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const defaultColumns = [
|
const defaultColumns = [
|
||||||
|
columnHelper.accessor("code", {
|
||||||
|
header: "",
|
||||||
|
cell: (info) => (
|
||||||
|
<Checkbox isChecked={selectedCodes.includes(info.getValue())} onChange={() => toggleCode(info.getValue())}>
|
||||||
|
{""}
|
||||||
|
</Checkbox>
|
||||||
|
),
|
||||||
|
}),
|
||||||
columnHelper.accessor("code", {
|
columnHelper.accessor("code", {
|
||||||
header: "Code",
|
header: "Code",
|
||||||
cell: (info) => info.getValue(),
|
cell: (info) => info.getValue(),
|
||||||
@@ -41,6 +103,10 @@ export default function CodeList({user}: {user: User}) {
|
|||||||
header: "Invited E-mail",
|
header: "Invited E-mail",
|
||||||
cell: (info) => info.getValue() || "N/A",
|
cell: (info) => info.getValue() || "N/A",
|
||||||
}),
|
}),
|
||||||
|
columnHelper.accessor("creator", {
|
||||||
|
header: "Creator",
|
||||||
|
cell: (info) => <CreatorCell id={info.getValue()} users={users} />,
|
||||||
|
}),
|
||||||
columnHelper.accessor("userId", {
|
columnHelper.accessor("userId", {
|
||||||
header: "Availability",
|
header: "Availability",
|
||||||
cell: (info) =>
|
cell: (info) =>
|
||||||
@@ -78,29 +144,42 @@ export default function CodeList({user}: {user: User}) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table className="rounded-xl bg-mti-purple-ultralight/40 w-full">
|
<>
|
||||||
<thead>
|
<div className="flex items-center justify-between pb-4 pt-1 px-4">
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
<span>{selectedCodes.length} code(s) selected</span>
|
||||||
<tr key={headerGroup.id}>
|
<Button
|
||||||
{headerGroup.headers.map((header) => (
|
disabled={selectedCodes.length === 0}
|
||||||
<th className="p-4 text-left" key={header.id}>
|
variant="outline"
|
||||||
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
color="red"
|
||||||
</th>
|
className="!py-1 px-10"
|
||||||
))}
|
onClick={() => deleteCodes(selectedCodes)}>
|
||||||
</tr>
|
Delete
|
||||||
))}
|
</Button>
|
||||||
</thead>
|
</div>
|
||||||
<tbody className="px-2">
|
<table className="rounded-xl bg-mti-purple-ultralight/40 w-full">
|
||||||
{table.getRowModel().rows.map((row) => (
|
<thead>
|
||||||
<tr className="odd:bg-white even:bg-mti-purple-ultralight/40 rounded-lg py-2" key={row.id}>
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
{row.getVisibleCells().map((cell) => (
|
<tr key={headerGroup.id}>
|
||||||
<td className="px-4 py-2" key={cell.id}>
|
{headerGroup.headers.map((header) => (
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
<th className="p-4 text-left" key={header.id}>
|
||||||
</td>
|
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
||||||
))}
|
</th>
|
||||||
</tr>
|
))}
|
||||||
))}
|
</tr>
|
||||||
</tbody>
|
))}
|
||||||
</table>
|
</thead>
|
||||||
|
<tbody className="px-2">
|
||||||
|
{table.getRowModel().rows.map((row) => (
|
||||||
|
<tr className="odd:bg-white even:bg-mti-purple-ultralight/40 rounded-lg py-2" key={row.id}>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<td className="px-4 py-2" key={cell.id}>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
import type {NextApiRequest, NextApiResponse} from "next";
|
import type {NextApiRequest, NextApiResponse} from "next";
|
||||||
import {app} from "@/firebase";
|
import {app} from "@/firebase";
|
||||||
import {getFirestore, setDoc, doc, query, collection, where, getDocs} from "firebase/firestore";
|
import {getFirestore, setDoc, doc, query, collection, where, getDocs, getDoc, deleteDoc} 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 {Type} from "@/interfaces/user";
|
import {Type} from "@/interfaces/user";
|
||||||
@@ -16,6 +16,7 @@ export default withIronSessionApiRoute(handler, sessionOptions);
|
|||||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (req.method === "GET") return get(req, res);
|
if (req.method === "GET") return get(req, res);
|
||||||
if (req.method === "POST") return post(req, res);
|
if (req.method === "POST") return post(req, res);
|
||||||
|
if (req.method === "DELETE") return del(req, res);
|
||||||
|
|
||||||
return res.status(404).json({ok: false});
|
return res.status(404).json({ok: false});
|
||||||
}
|
}
|
||||||
@@ -121,3 +122,21 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
res.status(200).json({ok: true, valid: results.filter((x) => x).length});
|
res.status(200).json({ok: true, valid: results.filter((x) => x).length});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function del(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
if (!req.session.user) {
|
||||||
|
res.status(401).json({ok: false, reason: "You must be logged in to generate a code!"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const codes = req.query.code as string[];
|
||||||
|
|
||||||
|
for (const code of codes) {
|
||||||
|
const snapshot = await getDoc(doc(db, "codes", code as string));
|
||||||
|
if (!snapshot.exists()) continue;
|
||||||
|
|
||||||
|
await deleteDoc(snapshot.ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({codes});
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user