remove reseller
This commit is contained in:
parent
fa8422849a
commit
7bbf2b8a5e
101
server/socket.ts
101
server/socket.ts
|
|
@ -1,101 +0,0 @@
|
||||||
import getUserFromToken from "@/modules/auth/utils/getUserFromToken";
|
|
||||||
import { User } from "@prisma/client";
|
|
||||||
import prisma from "@/core/db";
|
|
||||||
import Bun from "bun";
|
|
||||||
|
|
||||||
const intents = {
|
|
||||||
listenMyWaitingLinkRequest: "listenMyWaitingLinkRequest",
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const waitingLinkRequestConnections: Map<
|
|
||||||
string,
|
|
||||||
Bun.ServerWebSocket<{
|
|
||||||
channel: string;
|
|
||||||
user: User;
|
|
||||||
intent: string;
|
|
||||||
}>
|
|
||||||
> = new Map();
|
|
||||||
|
|
||||||
export const server = Bun.serve<{
|
|
||||||
channel: string;
|
|
||||||
user: User;
|
|
||||||
intent: string;
|
|
||||||
}>({
|
|
||||||
async fetch(req, server) {
|
|
||||||
const url = new URL(req.url);
|
|
||||||
req.headers.getSetCookie();
|
|
||||||
const pathname = url.pathname;
|
|
||||||
|
|
||||||
const cookies = req.headers.get("Cookie");
|
|
||||||
|
|
||||||
// Extract the Authorization header
|
|
||||||
const authHeader = req.headers.get("Authorization");
|
|
||||||
const token = authHeader?.startsWith("Bearer ")
|
|
||||||
? authHeader.substring(7, authHeader.length)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const user = token ? await getUserFromToken(token) : null;
|
|
||||||
|
|
||||||
const intent = pathname.substring(1);
|
|
||||||
|
|
||||||
switch (intent) {
|
|
||||||
case `/${intents.listenMyWaitingLinkRequest}`: {
|
|
||||||
if (!user) {
|
|
||||||
return new Response("Unauthorized", { status: 401 });
|
|
||||||
}
|
|
||||||
const channel = `mwrl-${user.id}`;
|
|
||||||
const success = server.upgrade(req, {
|
|
||||||
data: { user, channel, intent },
|
|
||||||
});
|
|
||||||
if (success) return undefined;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return new Response("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
websocket: {
|
|
||||||
async open(ws) {
|
|
||||||
switch (ws.data.intent) {
|
|
||||||
case intents.listenMyWaitingLinkRequest: {
|
|
||||||
ws.subscribe(ws.data.channel);
|
|
||||||
|
|
||||||
//retrieve user's link requests with status of waiting
|
|
||||||
const result = await prisma.office365LinkRequest.findMany({
|
|
||||||
where: {
|
|
||||||
createdBy: ws.data.user.id,
|
|
||||||
status: "WAITING",
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
server.publish(ws.data.channel, JSON.stringify(result));
|
|
||||||
|
|
||||||
waitingLinkRequestConnections.set(ws.data.channel, ws);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
message(ws, message) {
|
|
||||||
// the server re-broadcasts incoming messages to everyone
|
|
||||||
// server.publish("the-group-chat", `: ${message}`);
|
|
||||||
},
|
|
||||||
close(ws) {
|
|
||||||
// const msg = ` has left the chat`;
|
|
||||||
// server.publish("the-group-chat", msg);
|
|
||||||
// ws.unsubscribe("the-group-chat");
|
|
||||||
switch (ws.data.intent) {
|
|
||||||
case intents.listenMyWaitingLinkRequest: {
|
|
||||||
ws.unsubscribe(ws.data.channel);
|
|
||||||
waitingLinkRequestConnections.delete(ws.data.channel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
port: 3001,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Listening on ${server.hostname}:${server.port}`);
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
import getUserRoles from "@/modules/auth/utils/getUserRoles";
|
|
||||||
import checkMultiplePermissions from "@/modules/dashboard/services/checkMultiplePermissions";
|
|
||||||
import checkPermission from "@/modules/dashboard/services/checkPermission";
|
|
||||||
import getAllLinkRequests from "@/modules/resellerOffice365/actions/getAllLinkRequests";
|
|
||||||
import getLinkRequests from "@/modules/resellerOffice365/actions/getLinkRequests";
|
|
||||||
import ListOfRequestTable from "@/modules/resellerOffice365/tables/ListOfRequestTable/ListOfRequestTable";
|
|
||||||
import RequestTable from "@/modules/resellerOffice365/tables/RequestTable/RequestTable";
|
|
||||||
import { Card, Stack, Title } from "@mantine/core";
|
|
||||||
import { notFound } from "next/navigation";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
export default async function RequestLinkPage() {
|
|
||||||
const permissions = await checkMultiplePermissions({
|
|
||||||
create: "office-365-link.create",
|
|
||||||
readAll: "office-365-link.readAll",
|
|
||||||
read: "office-365-link.read",
|
|
||||||
update: "office-365-link.update",
|
|
||||||
delete: "office-365-link.delete",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!permissions.readAll) notFound();
|
|
||||||
|
|
||||||
const data = await getAllLinkRequests();
|
|
||||||
if (!data.success) {
|
|
||||||
//todo: handle error
|
|
||||||
console.error(data.error);
|
|
||||||
throw new Error("Error while fetch data");
|
|
||||||
}
|
|
||||||
const tableData = data.data;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack>
|
|
||||||
<Title order={1}>List Link Office 365</Title>
|
|
||||||
<Card>
|
|
||||||
<ListOfRequestTable permissions={permissions} tableData={tableData} />
|
|
||||||
</Card>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
import getUserRoles from "@/modules/auth/utils/getUserRoles";
|
|
||||||
import checkMultiplePermissions from "@/modules/dashboard/services/checkMultiplePermissions";
|
|
||||||
import checkPermission from "@/modules/dashboard/services/checkPermission";
|
|
||||||
import getLinkRequests from "@/modules/resellerOffice365/actions/getLinkRequests";
|
|
||||||
import RequestTable from "@/modules/resellerOffice365/tables/RequestTable/RequestTable";
|
|
||||||
import { Card, Stack, Title } from "@mantine/core";
|
|
||||||
import { notFound } from "next/navigation";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
export default async function RequestLinkPage() {
|
|
||||||
const permissions = await checkMultiplePermissions({
|
|
||||||
create: "office-365-link.create",
|
|
||||||
readAll: "office-365-link.readAll",
|
|
||||||
read: "office-365-link.read",
|
|
||||||
update: "office-365-link.update",
|
|
||||||
delete: "office-365-link.delete",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!permissions.readAll) notFound();
|
|
||||||
|
|
||||||
const data = await getLinkRequests()
|
|
||||||
if (!data.success){
|
|
||||||
//todo: handle error
|
|
||||||
console.error(data.error)
|
|
||||||
throw new Error("Error while fetch data")
|
|
||||||
}
|
|
||||||
const tableData = data.data
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack>
|
|
||||||
<Title order={1}>Permohonan Link Office 365</Title>
|
|
||||||
<Card>
|
|
||||||
<RequestTable permissions={permissions} tableData={tableData} />
|
|
||||||
</Card>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
"use server";
|
|
||||||
|
|
||||||
import db from "@/core/db";
|
|
||||||
import checkPermission from "@/modules/dashboard/services/checkPermission";
|
|
||||||
import handleCatch from "@/modules/dashboard/utils/handleCatch";
|
|
||||||
import notFound from "@/modules/dashboard/utils/notFound";
|
|
||||||
import unauthorized from "@/modules/dashboard/utils/unauthorized";
|
|
||||||
import ResellerOffice365Error from "../errors/ResellerOffice365Error";
|
|
||||||
import getCurrentUser from "@/modules/auth/utils/getCurrentUser";
|
|
||||||
import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
|
|
||||||
import { revalidatePath } from "next/cache";
|
|
||||||
import "server-only"
|
|
||||||
|
|
||||||
export default async function cancelRequest(id: string): Promise<ServerResponseAction> {
|
|
||||||
try {
|
|
||||||
//TODO: Fix permission
|
|
||||||
if (!(await checkPermission("authenticated-only"))) return unauthorized();
|
|
||||||
|
|
||||||
const data = await db.office365LinkRequest.findFirst({
|
|
||||||
where: { id },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!data) return notFound({message: "The Provided ID does not match any"})
|
|
||||||
|
|
||||||
if (data.status !== "WAITING") throw new ResellerOffice365Error({
|
|
||||||
errorCode: "REQUEST_IS_NOT_IN_WAITING_STATE",
|
|
||||||
message: "This item is not in \"waiting\" state to perform cancellation. This might be due to the request has been accepted"
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.createdBy !== (await getCurrentUser())?.id) return unauthorized();
|
|
||||||
|
|
||||||
await db.office365LinkRequest.update({
|
|
||||||
where: {id},
|
|
||||||
data: {
|
|
||||||
status: "CANCELLED",
|
|
||||||
cancelledAt: new Date()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
revalidatePath(".")
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return handleCatch(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
"use server";
|
|
||||||
import checkPermission from "@/modules/dashboard/services/checkPermission";
|
|
||||||
import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
|
|
||||||
import handleCatch from "@/modules/dashboard/utils/handleCatch";
|
|
||||||
import unauthorized from "@/modules/dashboard/utils/unauthorized";
|
|
||||||
import "server-only";
|
|
||||||
import requestLinkFormSchema from "../formSchemas/requestLinkFormSchema";
|
|
||||||
import DashboardError from "@/modules/dashboard/errors/DashboardError";
|
|
||||||
import mapObjectToFirstValue from "@/utils/mapObjectToFirstValue";
|
|
||||||
import db from "@/core/db";
|
|
||||||
import getCurrentUser from "@/modules/auth/utils/getCurrentUser";
|
|
||||||
import { revalidatePath } from "next/cache";
|
|
||||||
|
|
||||||
export default async function createLinkRequest(
|
|
||||||
formData: RequestLinkForm
|
|
||||||
): Promise<ServerResponseAction> {
|
|
||||||
try {
|
|
||||||
if (!(await checkPermission("office-365-request.create")))
|
|
||||||
unauthorized();
|
|
||||||
|
|
||||||
const currentUser = await getCurrentUser();
|
|
||||||
if (!currentUser) return unauthorized();
|
|
||||||
|
|
||||||
const validatedFields = requestLinkFormSchema.safeParse(formData);
|
|
||||||
if (!validatedFields.success) {
|
|
||||||
throw new DashboardError({
|
|
||||||
errorCode: "INVALID_FORM_DATA",
|
|
||||||
formErrors: mapObjectToFirstValue(
|
|
||||||
validatedFields.error.flatten().fieldErrors
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//database operations
|
|
||||||
await db.office365LinkRequest.create({
|
|
||||||
data: {
|
|
||||||
creator: {
|
|
||||||
connect: {
|
|
||||||
id: currentUser.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
status: "WAITING",
|
|
||||||
links: {
|
|
||||||
createMany: {
|
|
||||||
data: validatedFields.data.details.map((detail) => ({
|
|
||||||
numberOfUsers: detail.endUserQty,
|
|
||||||
activePeriod: detail.activePeriod,
|
|
||||||
email: detail.email,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
revalidatePath(".")
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message:
|
|
||||||
"Your request has been made. Please wait while our admin processing your request",
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
return handleCatch(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
import checkPermission from "@/modules/dashboard/services/checkPermission";
|
|
||||||
import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
|
|
||||||
import handleCatch from "@/modules/dashboard/utils/handleCatch";
|
|
||||||
import unauthorized from "@/modules/dashboard/utils/unauthorized";
|
|
||||||
import db from "@/core/db";
|
|
||||||
import RequestLinkWithIssuerData from "../types/RequestLinkWithIssuerData";
|
|
||||||
|
|
||||||
export default async function getAllLinkRequests(): Promise<
|
|
||||||
ServerResponseAction<RequestLinkWithIssuerData[]>
|
|
||||||
> {
|
|
||||||
try {
|
|
||||||
//TODO: Fix permission check
|
|
||||||
if (!(await checkPermission("authenticated-only")))
|
|
||||||
return unauthorized();
|
|
||||||
|
|
||||||
const requestLinks = await db.office365LinkRequest.findMany({
|
|
||||||
orderBy: [
|
|
||||||
{
|
|
||||||
status: "asc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
requestedAt: "desc"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
creator: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
photoProfile: true,
|
|
||||||
email: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
status: true,
|
|
||||||
requestedAt: true,
|
|
||||||
_count: {
|
|
||||||
select: {
|
|
||||||
links: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: requestLinks.map((item) => ({
|
|
||||||
...item,
|
|
||||||
userCount: item._count.links,
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
return handleCatch(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
"use server"
|
|
||||||
import checkPermission from "@/modules/dashboard/services/checkPermission";
|
|
||||||
import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
|
|
||||||
import handleCatch from "@/modules/dashboard/utils/handleCatch";
|
|
||||||
import unauthorized from "@/modules/dashboard/utils/unauthorized";
|
|
||||||
import RequestLinkWithIssuerData from "../types/RequestLinkWithIssuerData";
|
|
||||||
import db from "@/core/db";
|
|
||||||
import { string } from "zod";
|
|
||||||
import notFound from "@/modules/dashboard/utils/notFound";
|
|
||||||
|
|
||||||
async function getOffice365LinkRequestData(id: string) {
|
|
||||||
const data = await db.office365LinkRequest.findFirst({
|
|
||||||
where: { id },
|
|
||||||
select: {
|
|
||||||
acceptedAt: true,
|
|
||||||
cancelledAt: true,
|
|
||||||
creator: {
|
|
||||||
select: {
|
|
||||||
name: true,
|
|
||||||
id: true,
|
|
||||||
email: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
id: true,
|
|
||||||
links: {
|
|
||||||
select: {
|
|
||||||
activePeriod: true,
|
|
||||||
email: true,
|
|
||||||
id: true,
|
|
||||||
link: true,
|
|
||||||
numberOfUsers: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rejectedAt: true,
|
|
||||||
requestedAt: true,
|
|
||||||
status: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function getLinkRequestDataById(
|
|
||||||
id: string
|
|
||||||
): Promise<
|
|
||||||
ServerResponseAction<
|
|
||||||
NonNullable<Awaited<ReturnType<typeof getOffice365LinkRequestData>>>
|
|
||||||
>
|
|
||||||
> {
|
|
||||||
try {
|
|
||||||
//TODO: Adjust permission
|
|
||||||
if (!(await checkPermission("authenticated-only")))
|
|
||||||
return unauthorized();
|
|
||||||
|
|
||||||
const data = await getOffice365LinkRequestData(id);
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return notFound({
|
|
||||||
message: "The requested link request item is not found",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
return handleCatch(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
|
|
||||||
import RequestLink from "../types/RequestLink";
|
|
||||||
import handleCatch from "@/modules/dashboard/utils/handleCatch";
|
|
||||||
import checkPermission from "@/modules/dashboard/services/checkPermission";
|
|
||||||
import unauthorized from "@/modules/dashboard/utils/unauthorized";
|
|
||||||
import getCurrentUser from "@/modules/auth/utils/getCurrentUser";
|
|
||||||
import db from "@/core/db";
|
|
||||||
|
|
||||||
export default async function getLinkRequests(): Promise<
|
|
||||||
ServerResponseAction<RequestLink[]>
|
|
||||||
> {
|
|
||||||
try {
|
|
||||||
if (!(await checkPermission("office-365-request.getMine")))
|
|
||||||
unauthorized();
|
|
||||||
|
|
||||||
const user = await getCurrentUser();
|
|
||||||
|
|
||||||
if (!user) return unauthorized();
|
|
||||||
|
|
||||||
const requests = await db.office365LinkRequest.findMany({
|
|
||||||
orderBy: [
|
|
||||||
{
|
|
||||||
status: "asc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
requestedAt: "desc"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
where: {
|
|
||||||
creator: { id: user.id },
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
requestedAt: true,
|
|
||||||
status: true,
|
|
||||||
links: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const result: RequestLink[] = requests.map((request) => ({
|
|
||||||
id: request.id,
|
|
||||||
requestDate: request.requestedAt.toISOString(),
|
|
||||||
status: request.status,
|
|
||||||
userCount: request.links.reduce(
|
|
||||||
(prev, curr) => prev + curr.numberOfUsers,
|
|
||||||
0
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: result,
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
return handleCatch(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
"use server";
|
|
||||||
import db from "@/core/db";
|
|
||||||
import checkPermission from "@/modules/dashboard/services/checkPermission";
|
|
||||||
import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
|
|
||||||
import handleCatch from "@/modules/dashboard/utils/handleCatch";
|
|
||||||
import notFound from "@/modules/dashboard/utils/notFound";
|
|
||||||
import unauthorized from "@/modules/dashboard/utils/unauthorized";
|
|
||||||
import "server-only";
|
|
||||||
import ResellerOffice365Error from "../errors/ResellerOffice365Error";
|
|
||||||
import { revalidatePath } from "next/cache";
|
|
||||||
|
|
||||||
interface Args {
|
|
||||||
id: string;
|
|
||||||
data: {
|
|
||||||
linkId: string;
|
|
||||||
link: string;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function inputLink(
|
|
||||||
args: Args
|
|
||||||
): Promise<ServerResponseAction> {
|
|
||||||
try {
|
|
||||||
//TODO: Implement permission
|
|
||||||
if (!(await checkPermission("authenticated-only")))
|
|
||||||
return unauthorized();
|
|
||||||
|
|
||||||
const data = await db.office365LinkRequest.findFirst({
|
|
||||||
where: {
|
|
||||||
id: args.id,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
links: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!data)
|
|
||||||
return notFound({
|
|
||||||
message:
|
|
||||||
"The requested link is not found. It might seems has been deleted. Please contact administrator",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.status !== "WAITING")
|
|
||||||
throw new ResellerOffice365Error({
|
|
||||||
errorCode: "REQUEST_IS_NOT_IN_WAITING_STATE",
|
|
||||||
message: `This request has been ${
|
|
||||||
data.acceptedAt
|
|
||||||
? "completed"
|
|
||||||
: data.rejectedAt
|
|
||||||
? "rejected"
|
|
||||||
: "cancelled"
|
|
||||||
} thus cannot be updated. Please contact administrator`,
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.office365LinkRequest.update({
|
|
||||||
where: {
|
|
||||||
id: args.id,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
acceptedAt: new Date(),
|
|
||||||
status: "ACCEPTED",
|
|
||||||
links: {
|
|
||||||
updateMany: args.data.map((linkItem) => ({
|
|
||||||
where: { id: linkItem.linkId },
|
|
||||||
data: {
|
|
||||||
link: linkItem.link,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
revalidatePath(".")
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
return handleCatch(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
const resellerOffice365Config = {
|
|
||||||
activePeriods: ["1 Month", "1 Year", "2 Years"],
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export default resellerOffice365Config;
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import DashboardError from "@/modules/dashboard/errors/DashboardError";
|
|
||||||
|
|
||||||
export const ResellerOffice365ErrorCodes = [
|
|
||||||
"REQUEST_IS_NOT_IN_WAITING_STATE"
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
interface ResellerOffice365ErrorOptions {
|
|
||||||
message?: string;
|
|
||||||
errorCode: (typeof ResellerOffice365ErrorCodes)[number] | (string & {});
|
|
||||||
formErrors?: Record<string, string>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class ResellerOffice365Error extends DashboardError {
|
|
||||||
public readonly errorCode: ResellerOffice365ErrorOptions['errorCode'];
|
|
||||||
public readonly formErrors?: ResellerOffice365ErrorOptions['formErrors']
|
|
||||||
|
|
||||||
constructor(options: ResellerOffice365ErrorOptions) {
|
|
||||||
super({
|
|
||||||
errorCode: options.errorCode,
|
|
||||||
message: options.message,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.errorCode = options.errorCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import { z } from "zod";
|
|
||||||
import resellerOffice365Config from "../config";
|
|
||||||
|
|
||||||
const requestLinkFormSchema = z.object({
|
|
||||||
numberOfLinks: z.number().min(1), // Assuming you need at least one link
|
|
||||||
details: z.array(
|
|
||||||
z.object({
|
|
||||||
email: z.string().email(), // Validate string as an email
|
|
||||||
activePeriod: z.enum(resellerOffice365Config.activePeriods), // Validate against the specific allowed values
|
|
||||||
endUserQty: z.number().min(1), // Assuming you need at least one end user
|
|
||||||
})
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default requestLinkFormSchema;
|
|
||||||
|
|
@ -1,449 +0,0 @@
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
import {
|
|
||||||
Alert,
|
|
||||||
Button,
|
|
||||||
Divider,
|
|
||||||
Fieldset,
|
|
||||||
Flex,
|
|
||||||
Group,
|
|
||||||
Modal,
|
|
||||||
NumberInput,
|
|
||||||
ScrollArea,
|
|
||||||
Select,
|
|
||||||
Stack,
|
|
||||||
TextInput,
|
|
||||||
Loader,
|
|
||||||
Text,
|
|
||||||
Skeleton,
|
|
||||||
} from "@mantine/core";
|
|
||||||
import { useForm, zodResolver } from "@mantine/form";
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import {
|
|
||||||
TbAt,
|
|
||||||
TbCalendarTime,
|
|
||||||
TbDeviceFloppy,
|
|
||||||
TbLink,
|
|
||||||
TbUser,
|
|
||||||
TbUsers,
|
|
||||||
} from "react-icons/tb";
|
|
||||||
import resellerOffice365Config from "../config";
|
|
||||||
import requestLinkFormSchema from "../formSchemas/requestLinkFormSchema";
|
|
||||||
import withServerAction from "@/modules/dashboard/utils/withServerAction";
|
|
||||||
import createLinkRequest from "../actions/createLinkRequest";
|
|
||||||
import { notifications } from "@mantine/notifications";
|
|
||||||
import DashboardError from "@/modules/dashboard/errors/DashboardError";
|
|
||||||
import getLinkRequestDataById from "../actions/getLinkRequestDataById";
|
|
||||||
import { isPagesAPIRouteMatch } from "next/dist/server/future/route-matches/pages-api-route-match";
|
|
||||||
import inputLink from "../actions/inputLinks";
|
|
||||||
import { Office365LinkRequestStatus } from "@prisma/client";
|
|
||||||
import cancelRequest from "../actions/cancelRequest";
|
|
||||||
|
|
||||||
export interface ModalProps {
|
|
||||||
title: string;
|
|
||||||
opened: boolean;
|
|
||||||
onClose?: () => void;
|
|
||||||
type: "create" | "detail" | "waiting" | "input link";
|
|
||||||
detailId: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function RequestModal(props: ModalProps) {
|
|
||||||
const [formState, setFormState] = useState<
|
|
||||||
| "idle"
|
|
||||||
| "submitting"
|
|
||||||
| "waiting"
|
|
||||||
| "fetching"
|
|
||||||
| "error"
|
|
||||||
| "confirming cancel"
|
|
||||||
| "cancelling"
|
|
||||||
>("idle");
|
|
||||||
|
|
||||||
const [errorMessage, setErrorMessage] = useState("");
|
|
||||||
const [requestStatus, setRequestStatus] = useState<
|
|
||||||
Office365LinkRequestStatus | "CREATING"
|
|
||||||
>("CREATING");
|
|
||||||
|
|
||||||
const closeModal = (force?: boolean) => {
|
|
||||||
if (!force){
|
|
||||||
if (["submitting, cancelling"].includes(formState)) return; //prevents closing
|
|
||||||
}
|
|
||||||
//reset state
|
|
||||||
setErrorMessage("");
|
|
||||||
setRequestStatus("CREATING");
|
|
||||||
setFormState("idle");
|
|
||||||
form.reset();
|
|
||||||
props.onClose ? props.onClose() : "";
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchDataById = async (id: string) => {
|
|
||||||
const { data } = await withServerAction(getLinkRequestDataById, id);
|
|
||||||
if (!props.opened) return;
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (props.type) {
|
|
||||||
case "detail":
|
|
||||||
case "input link": {
|
|
||||||
if (!props.detailId || !props.opened) return;
|
|
||||||
setFormState("fetching");
|
|
||||||
fetchDataById(props.detailId)
|
|
||||||
.then((data) => {
|
|
||||||
if (!data) {
|
|
||||||
closeModal();
|
|
||||||
notifications.show({
|
|
||||||
message:
|
|
||||||
"The returned data from server is empty. Please try again",
|
|
||||||
color: "red",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
form.setValues({
|
|
||||||
numberOfLinks: data.links.length,
|
|
||||||
id: data.id,
|
|
||||||
details: data.links.map((item) => ({
|
|
||||||
activePeriod: item.activePeriod,
|
|
||||||
email: item.email,
|
|
||||||
endUserQty: item.numberOfUsers,
|
|
||||||
id: item.id,
|
|
||||||
link: item.link ?? "",
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
setRequestStatus(data.status);
|
|
||||||
if (
|
|
||||||
data.status === "WAITING" &&
|
|
||||||
props.type === "detail"
|
|
||||||
) {
|
|
||||||
setFormState("waiting");
|
|
||||||
} else {
|
|
||||||
setFormState("idle");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
setErrorMessage(e.message);
|
|
||||||
} else {
|
|
||||||
setErrorMessage("Unkown error occured");
|
|
||||||
}
|
|
||||||
setFormState("idle");
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [props]);
|
|
||||||
|
|
||||||
const form = useForm<RequestLinkForm>({
|
|
||||||
initialValues: {
|
|
||||||
id: undefined,
|
|
||||||
numberOfLinks: 1,
|
|
||||||
details: [
|
|
||||||
{
|
|
||||||
email: "",
|
|
||||||
activePeriod: "2 Years",
|
|
||||||
endUserQty: 1,
|
|
||||||
link: "",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
validate: zodResolver(requestLinkFormSchema),
|
|
||||||
onValuesChange: (values, prev) => {
|
|
||||||
// Check if numberOfLinks has changed
|
|
||||||
if (values.numberOfLinks !== prev.numberOfLinks) {
|
|
||||||
const currentDetails = values.details;
|
|
||||||
const targetLength = values.numberOfLinks;
|
|
||||||
|
|
||||||
// Add new detail objects if numberOfLinks has increased
|
|
||||||
while (currentDetails.length < targetLength) {
|
|
||||||
currentDetails.push({
|
|
||||||
email: "",
|
|
||||||
activePeriod: "2 Years",
|
|
||||||
endUserQty: 1,
|
|
||||||
link: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove extra detail objects if numberOfLinks has decreased
|
|
||||||
if (currentDetails.length > targetLength) {
|
|
||||||
currentDetails.length = targetLength; // Adjusts the array length
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the form values with the adjusted details array
|
|
||||||
form.setValues({
|
|
||||||
...values,
|
|
||||||
details: currentDetails,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleSubmit = (values: RequestLinkForm) => {
|
|
||||||
const submitableState: (typeof formState)[] = ["idle"];
|
|
||||||
|
|
||||||
if (!submitableState.includes(formState)) return; //prevent submit when not in subitable state
|
|
||||||
|
|
||||||
switch (props.type) {
|
|
||||||
case "create": {
|
|
||||||
setFormState("submitting");
|
|
||||||
withServerAction(createLinkRequest, values)
|
|
||||||
.then((response) => {
|
|
||||||
notifications.show({
|
|
||||||
message: response.message,
|
|
||||||
color: "green",
|
|
||||||
});
|
|
||||||
setFormState("waiting");
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
if (e instanceof DashboardError) {
|
|
||||||
if (e.errorCode === "INVALID_FORM_DATA") {
|
|
||||||
if (e.formErrors) {
|
|
||||||
form.setErrors(e.formErrors);
|
|
||||||
} else {
|
|
||||||
setErrorMessage(e.message);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setErrorMessage(
|
|
||||||
`ERROR: ${e.message} (${e.errorCode})`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (e instanceof Error) {
|
|
||||||
setErrorMessage(`ERROR: ${e.message}`);
|
|
||||||
} else {
|
|
||||||
setErrorMessage(
|
|
||||||
`Unkown error is occured. Please contact administrator`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
setFormState("idle");
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "input link": {
|
|
||||||
if (!props.detailId) return;
|
|
||||||
setFormState("submitting");
|
|
||||||
withServerAction(inputLink, {
|
|
||||||
id: props.detailId,
|
|
||||||
data: form.values.details.map((item) => ({
|
|
||||||
link: item.link,
|
|
||||||
linkId: item.id ?? "",
|
|
||||||
})),
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
setFormState("idle");
|
|
||||||
notifications.show({
|
|
||||||
message: "Data has been updated",
|
|
||||||
color: "green",
|
|
||||||
});
|
|
||||||
closeModal();
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
setErrorMessage(e.message);
|
|
||||||
} else {
|
|
||||||
setErrorMessage("An unkown error occured");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setFormState("idle");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
|
||||||
if (!props.detailId) return setErrorMessage("Cannot request cancellation. Cause: the ID is empty. Please contact your administrator")
|
|
||||||
setFormState("cancelling")
|
|
||||||
withServerAction(cancelRequest, props.detailId)
|
|
||||||
.then(() => {
|
|
||||||
notifications.show({
|
|
||||||
message: "The request has been cancelled",
|
|
||||||
color: "green"
|
|
||||||
})
|
|
||||||
setFormState("idle")
|
|
||||||
closeModal(true)
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
if (e instanceof Error){
|
|
||||||
setErrorMessage(e.message)
|
|
||||||
}
|
|
||||||
setFormState("idle")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderActionButtons = () => (
|
|
||||||
<Flex justify="flex-end" align="center" gap="lg" mt="lg">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => closeModal()}
|
|
||||||
disabled={["submitting"].includes(formState)}
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
{showCancelButton && (
|
|
||||||
<Button variant="outline" color="red" type="button" onClick={() => setFormState("confirming cancel")}>
|
|
||||||
Cancel Request
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{showSubmitButton && (
|
|
||||||
<Button
|
|
||||||
variant="filled"
|
|
||||||
leftSection={<TbDeviceFloppy size={20} />}
|
|
||||||
type="submit"
|
|
||||||
disabled={["submitting", "waiting"].includes(formState)}
|
|
||||||
loading={["submitting"].includes(formState)}
|
|
||||||
>
|
|
||||||
{props.type === "create" ? "Make Request" : "Save"}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderCancelConfirmation = () => (
|
|
||||||
<Stack mt="lg" gap={0}>
|
|
||||||
<Text>Are you sure to cancel this link request?</Text>
|
|
||||||
<Flex justify="flex-end" align="center" gap="lg" mt="lg">
|
|
||||||
<Button type="button" variant="outline" onClick={() => setFormState("waiting")}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button type="button" variant="transparent" color="red" onClick={handleCancel}>
|
|
||||||
Yes, I am sure
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
|
|
||||||
const disableChange = formState !== "idle";
|
|
||||||
const readonly = ["input link", "detail"].includes(props.type);
|
|
||||||
const showSkeleton = formState === "fetching";
|
|
||||||
const showActivationLink = ["input link", "detail"].includes(props.type);
|
|
||||||
const enableInputActivationLink = props.type === "input link";
|
|
||||||
const showSubmitButton = ["create", "input link"].includes(props.type);
|
|
||||||
const showCancelButton =
|
|
||||||
["detail"].includes(props.type) && formState === "waiting";
|
|
||||||
const showCancelRequestConfirmation = [
|
|
||||||
"confirming cancel",
|
|
||||||
"cancelling",
|
|
||||||
].includes(formState);
|
|
||||||
const showWaitingAlert = (["waiting", "confirming cancel"] as typeof formState[]).includes(formState)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
size="sm"
|
|
||||||
opened={props.opened}
|
|
||||||
title={
|
|
||||||
formState === "waiting"
|
|
||||||
? "Link Request Detail"
|
|
||||||
: "Create New Request"
|
|
||||||
}
|
|
||||||
onClose={closeModal}
|
|
||||||
scrollAreaComponent={ScrollArea.Autosize}
|
|
||||||
>
|
|
||||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
|
||||||
<Stack gap="md">
|
|
||||||
{showWaitingAlert && (
|
|
||||||
<Alert color="orange">
|
|
||||||
Your request is being processed by administrator
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{errorMessage && <Alert color="red">{errorMessage}</Alert>}
|
|
||||||
|
|
||||||
<Skeleton visible={showSkeleton}>
|
|
||||||
<NumberInput
|
|
||||||
label="Please input the number of links you request"
|
|
||||||
min={1}
|
|
||||||
max={3}
|
|
||||||
allowDecimal={false}
|
|
||||||
clampBehavior="strict"
|
|
||||||
leftSection={<TbLink />}
|
|
||||||
disabled={disableChange}
|
|
||||||
readOnly={readonly}
|
|
||||||
{...form.getInputProps("numberOfLinks")}
|
|
||||||
/>
|
|
||||||
</Skeleton>
|
|
||||||
|
|
||||||
<Divider
|
|
||||||
label="End User Information"
|
|
||||||
labelPosition="center"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Stack>
|
|
||||||
{form.values.details.map((item, i) => (
|
|
||||||
<Fieldset key={i} legend={`Information ${i + 1}`}>
|
|
||||||
<Stack gap="xs">
|
|
||||||
<Skeleton visible={showSkeleton}>
|
|
||||||
<TextInput
|
|
||||||
leftSection={<TbAt />}
|
|
||||||
label="Email"
|
|
||||||
readOnly={readonly}
|
|
||||||
disabled={disableChange}
|
|
||||||
{...form.getInputProps(
|
|
||||||
`details.${i}.email`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Skeleton>
|
|
||||||
<Flex gap="md">
|
|
||||||
<Skeleton visible={showSkeleton}>
|
|
||||||
<Select
|
|
||||||
data={
|
|
||||||
resellerOffice365Config.activePeriods
|
|
||||||
}
|
|
||||||
label="Active Period"
|
|
||||||
disabled={disableChange}
|
|
||||||
readOnly={readonly}
|
|
||||||
leftSection={<TbCalendarTime />}
|
|
||||||
{...form.getInputProps(
|
|
||||||
`details.${i}.activePeriod`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Skeleton>
|
|
||||||
<Skeleton visible={showSkeleton}>
|
|
||||||
<NumberInput
|
|
||||||
label="End User Quantity"
|
|
||||||
leftSection={<TbUsers />}
|
|
||||||
min={1}
|
|
||||||
max={5}
|
|
||||||
disabled={disableChange}
|
|
||||||
allowDecimal={false}
|
|
||||||
readOnly={readonly}
|
|
||||||
clampBehavior="strict"
|
|
||||||
{...form.getInputProps(
|
|
||||||
`details.${i}.endUserQty`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Skeleton>
|
|
||||||
</Flex>
|
|
||||||
{showActivationLink && (
|
|
||||||
<Skeleton visible={showSkeleton}>
|
|
||||||
<TextInput
|
|
||||||
label="Activation Link"
|
|
||||||
required={
|
|
||||||
enableInputActivationLink
|
|
||||||
}
|
|
||||||
disabled={disableChange}
|
|
||||||
readOnly={
|
|
||||||
!enableInputActivationLink
|
|
||||||
}
|
|
||||||
{...form.getInputProps(
|
|
||||||
`details.${i}.link`
|
|
||||||
)}
|
|
||||||
placeholder={
|
|
||||||
enableInputActivationLink
|
|
||||||
? "Enter link here"
|
|
||||||
: "No link provided"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Skeleton>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Fieldset>
|
|
||||||
))}
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
{/* Buttons */}
|
|
||||||
{showCancelRequestConfirmation
|
|
||||||
? renderCancelConfirmation()
|
|
||||||
: renderActionButtons()}
|
|
||||||
</Stack>
|
|
||||||
</form>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
"use client";
|
|
||||||
import DashboardTable from "@/modules/dashboard/components/DashboardTable";
|
|
||||||
import { Button, Flex, Text } from "@mantine/core";
|
|
||||||
import { getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
|
||||||
import React, { ReactNode, useState } from "react";
|
|
||||||
import { TbPlus } from "react-icons/tb";
|
|
||||||
import createColumns from "./columns";
|
|
||||||
import CrudPermissions from "@/modules/dashboard/types/CrudPermissions";
|
|
||||||
import RequestLink from "../../types/RequestLink";
|
|
||||||
import RequestModal, { ModalProps } from "../../modals/RequestModal";
|
|
||||||
import RequestLinkWithIssuerData from "../../types/RequestLinkWithIssuerData";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
permissions: Partial<CrudPermissions>;
|
|
||||||
tableData: RequestLinkWithIssuerData[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultModalProps: ModalProps = {
|
|
||||||
opened: false,
|
|
||||||
title: "Create new Link",
|
|
||||||
type: "create",
|
|
||||||
detailId: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ListOfRequestTable(props: Props) {
|
|
||||||
const [modalProps, setModalProps] = useState<ModalProps>(defaultModalProps);
|
|
||||||
// const [openModal, setOpenModal] = useState(false);
|
|
||||||
|
|
||||||
const table = useReactTable({
|
|
||||||
data: props.tableData,
|
|
||||||
columns: createColumns({
|
|
||||||
permissions: props.permissions,
|
|
||||||
actions: {
|
|
||||||
detail: (id) => {
|
|
||||||
openFormModal(id);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
defaultColumn: {
|
|
||||||
cell: (props) => <Text>{props.getValue() as ReactNode}</Text>,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const openFormModal = (id: string) => {
|
|
||||||
setModalProps({
|
|
||||||
opened: true,
|
|
||||||
title: "Request Detail",
|
|
||||||
type: "input link",
|
|
||||||
detailId: id,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeModal = () => {
|
|
||||||
setModalProps(defaultModalProps);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<DashboardTable table={table} />
|
|
||||||
|
|
||||||
<RequestModal {...modalProps} onClose={closeModal} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
import { createColumnHelper } from "@tanstack/react-table";
|
|
||||||
import { Avatar, Badge, Flex, Stack, Text } from "@mantine/core";
|
|
||||||
import { TbEye, TbPencil, TbTrash } from "react-icons/tb";
|
|
||||||
import CrudPermissions from "@/modules/dashboard/types/CrudPermissions";
|
|
||||||
import createActionButtons from "@/modules/dashboard/utils/createActionButton";
|
|
||||||
import RequestLinkWithIssuerData from "../../types/RequestLinkWithIssuerData";
|
|
||||||
|
|
||||||
interface ColumnOptions {
|
|
||||||
permissions: Partial<CrudPermissions>;
|
|
||||||
actions: {
|
|
||||||
detail: (id: string) => void;
|
|
||||||
// edit: (id: string) => void;
|
|
||||||
// delete: (id: string) => void;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const createColumns = (options: ColumnOptions) => {
|
|
||||||
const columnHelper = createColumnHelper<RequestLinkWithIssuerData>();
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
columnHelper.accessor("id", {
|
|
||||||
id: "sequence",
|
|
||||||
header: "#",
|
|
||||||
cell: (props) => props.row.index + 1,
|
|
||||||
}),
|
|
||||||
|
|
||||||
columnHelper.accessor("requestedAt", {
|
|
||||||
header: "Request Date",
|
|
||||||
cell: (props) => {
|
|
||||||
const date = new Date(props.row.original.requestedAt);
|
|
||||||
return `${date.toDateString()}; ${date.toLocaleTimeString()}`;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
columnHelper.accessor("creator", {
|
|
||||||
header: "Issuer",
|
|
||||||
cell: (props) => {
|
|
||||||
return (<Flex gap="sm" align="center">
|
|
||||||
<Avatar src={props.cell.getValue().photoProfile} />
|
|
||||||
<div>
|
|
||||||
<Text>{props.cell.getValue().name}</Text>
|
|
||||||
<Text size="xs" c="gray">{props.cell.getValue().email}</Text>
|
|
||||||
</div>
|
|
||||||
</Flex>)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
columnHelper.accessor("userCount", {
|
|
||||||
header: "User Count",
|
|
||||||
}),
|
|
||||||
|
|
||||||
columnHelper.accessor("status", {
|
|
||||||
header: "Status",
|
|
||||||
cell: (props) => {
|
|
||||||
switch (props.row.original.status) {
|
|
||||||
case "WAITING":
|
|
||||||
return <Badge color="cyan">WAITING</Badge>;
|
|
||||||
break;
|
|
||||||
case "ACCEPTED":
|
|
||||||
return <Badge color="green">ACCEPTED</Badge>;
|
|
||||||
break;
|
|
||||||
case "CANCELLED":
|
|
||||||
return <Badge color="gray">CANCELLED</Badge>;
|
|
||||||
break;
|
|
||||||
case "REJECTED":
|
|
||||||
return <Badge color="red">REJECTED</Badge>;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
columnHelper.display({
|
|
||||||
id: "Actions",
|
|
||||||
header: "Actions",
|
|
||||||
cell: (props) => (
|
|
||||||
<Flex gap="xs">
|
|
||||||
{createActionButtons([
|
|
||||||
{
|
|
||||||
label: "Detail",
|
|
||||||
permission: options.permissions.read,
|
|
||||||
action: () =>
|
|
||||||
options.actions.detail(props.row.original.id),
|
|
||||||
color: "green",
|
|
||||||
icon: <TbEye />,
|
|
||||||
},
|
|
||||||
])}
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
return columns;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default createColumns;
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
"use client";
|
|
||||||
import DashboardTable from "@/modules/dashboard/components/DashboardTable";
|
|
||||||
import { Button, Flex, Text } from "@mantine/core";
|
|
||||||
import { getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
|
||||||
import React, { ReactNode, useState } from "react";
|
|
||||||
import { TbPlus } from "react-icons/tb";
|
|
||||||
import createColumns from "./columns";
|
|
||||||
import CrudPermissions from "@/modules/dashboard/types/CrudPermissions";
|
|
||||||
import RequestLink from "../../types/RequestLink";
|
|
||||||
import RequestModal, { ModalProps } from "../../modals/RequestModal";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
permissions: Partial<CrudPermissions>;
|
|
||||||
tableData: RequestLink[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultModalProps: ModalProps = {
|
|
||||||
opened: false,
|
|
||||||
title: "",
|
|
||||||
type: "create",
|
|
||||||
detailId: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RequestTable(props: Props) {
|
|
||||||
const [modalProps, setModalProps] = useState<ModalProps>(defaultModalProps);
|
|
||||||
|
|
||||||
const table = useReactTable({
|
|
||||||
data: props.tableData,
|
|
||||||
columns: createColumns({
|
|
||||||
permissions: props.permissions,
|
|
||||||
actions: {
|
|
||||||
detail: (id) => {
|
|
||||||
openDetailModal(id);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
defaultColumn: {
|
|
||||||
cell: (props) => <Text>{props.getValue() as ReactNode}</Text>,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const openCreateModal = () => {
|
|
||||||
setModalProps({
|
|
||||||
opened: true,
|
|
||||||
title: "Create New Office 365 Link Request",
|
|
||||||
detailId: null,
|
|
||||||
type: "create",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const openDetailModal = (id: string) => {
|
|
||||||
setModalProps({
|
|
||||||
opened: true,
|
|
||||||
title: "Office 365 Link Request Detail",
|
|
||||||
detailId: id,
|
|
||||||
type: "detail",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeModal = () => {
|
|
||||||
setModalProps(defaultModalProps);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Flex justify="flex-end">
|
|
||||||
{
|
|
||||||
<Button leftSection={<TbPlus />} onClick={openCreateModal}>
|
|
||||||
New Link Request
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<DashboardTable table={table} />
|
|
||||||
|
|
||||||
<RequestModal {...modalProps} onClose={closeModal} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
import { createColumnHelper } from "@tanstack/react-table";
|
|
||||||
import { Badge, Flex } from "@mantine/core";
|
|
||||||
import { TbEye, TbPencil, TbTrash } from "react-icons/tb";
|
|
||||||
import CrudPermissions from "@/modules/dashboard/types/CrudPermissions";
|
|
||||||
import createActionButtons from "@/modules/dashboard/utils/createActionButton";
|
|
||||||
|
|
||||||
export interface RequestLinkRow {
|
|
||||||
id: string;
|
|
||||||
requestDate: string;
|
|
||||||
userCount: number;
|
|
||||||
status: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ColumnOptions {
|
|
||||||
permissions: Partial<CrudPermissions>;
|
|
||||||
actions: {
|
|
||||||
detail: (id: string) => void;
|
|
||||||
// edit: (id: string) => void;
|
|
||||||
// delete: (id: string) => void;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const createColumns = (options: ColumnOptions) => {
|
|
||||||
const columnHelper = createColumnHelper<RequestLinkRow>();
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
columnHelper.accessor("id", {
|
|
||||||
id: "sequence",
|
|
||||||
header: "#",
|
|
||||||
cell: (props) => props.row.index + 1,
|
|
||||||
}),
|
|
||||||
|
|
||||||
columnHelper.accessor("requestDate", {
|
|
||||||
header: "Request Date",
|
|
||||||
cell: (props) => {
|
|
||||||
const date = new Date(props.row.original.requestDate);
|
|
||||||
return `${date.toDateString()}; ${date.toLocaleTimeString()}`;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
columnHelper.accessor("userCount", {
|
|
||||||
header: "User Count",
|
|
||||||
}),
|
|
||||||
|
|
||||||
columnHelper.accessor("status", {
|
|
||||||
header: "Status",
|
|
||||||
cell: (props) => {
|
|
||||||
switch (props.row.original.status) {
|
|
||||||
case "WAITING":
|
|
||||||
return <Badge color="cyan">WAITING</Badge>;
|
|
||||||
break;
|
|
||||||
case "ACCEPTED":
|
|
||||||
return <Badge color="green">ACCEPTED</Badge>;
|
|
||||||
break;
|
|
||||||
case "CANCELLED":
|
|
||||||
return <Badge color="gray">CANCELLED</Badge>;
|
|
||||||
break;
|
|
||||||
case "REJECTED":
|
|
||||||
return <Badge color="red">REJECTED</Badge>;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
columnHelper.display({
|
|
||||||
id: "Actions",
|
|
||||||
header: "Actions",
|
|
||||||
cell: (props) => (
|
|
||||||
<Flex gap="xs">
|
|
||||||
{createActionButtons([
|
|
||||||
{
|
|
||||||
label: "Detail",
|
|
||||||
permission: options.permissions.read,
|
|
||||||
action: () =>
|
|
||||||
options.actions.detail(props.row.original.id),
|
|
||||||
color: "green",
|
|
||||||
icon: <TbEye />,
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// label: "Edit",
|
|
||||||
// permission: options.permissions.update,
|
|
||||||
// // action: () =>
|
|
||||||
// // options.actions.edit(props.row.original.id),
|
|
||||||
// color: "yellow",
|
|
||||||
// icon: <TbPencil />,
|
|
||||||
// },
|
|
||||||
])}
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
return columns;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default createColumns;
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
export default interface RequestLink {
|
|
||||||
id: string;
|
|
||||||
requestDate: string,
|
|
||||||
userCount: number,
|
|
||||||
status: string
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
interface RequestLinkForm {
|
|
||||||
id: string | undefined;
|
|
||||||
numberOfLinks: number;
|
|
||||||
details: {
|
|
||||||
id?: string;
|
|
||||||
email: string;
|
|
||||||
activePeriod: (typeof resellerOffice365Config.activePeriods)[number];
|
|
||||||
endUserQty: number;
|
|
||||||
link: string
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
import { Office365LinkRequestStatus, Prisma } from "@prisma/client";
|
|
||||||
|
|
||||||
export default interface RequestLinkWithIssuerData {
|
|
||||||
id: string;
|
|
||||||
status: Office365LinkRequestStatus;
|
|
||||||
requestedAt: Date;
|
|
||||||
creator: {
|
|
||||||
id: string;
|
|
||||||
name: string | null;
|
|
||||||
email: string | null;
|
|
||||||
photoProfile: string | null;
|
|
||||||
};
|
|
||||||
userCount: number
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user