remove reseller

This commit is contained in:
sianida26 2024-02-20 07:11:17 +07:00
parent fa8422849a
commit 7bbf2b8a5e
20 changed files with 0 additions and 1415 deletions

View File

@ -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}`);

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -1,5 +0,0 @@
const resellerOffice365Config = {
activePeriods: ["1 Month", "1 Year", "2 Years"],
} as const;
export default resellerOffice365Config;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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>
);
}

View File

@ -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} />
</>
);
}

View File

@ -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;

View File

@ -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} />
</>
);
}

View File

@ -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;

View File

@ -1,6 +0,0 @@
export default interface RequestLink {
id: string;
requestDate: string,
userCount: number,
status: string
}

View File

@ -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
}[];
}

View File

@ -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
}