Added actions
This commit is contained in:
parent
f4c384665c
commit
da678fd487
|
|
@ -0,0 +1,30 @@
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `Office365LinkRequest` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`status` ENUM('WAITING', 'ACCEPTED', 'CANCELLED', 'REJECTED') NOT NULL DEFAULT 'WAITING',
|
||||||
|
`requestedAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
`acceptedAt` DATETIME(3) NULL,
|
||||||
|
`cancelledAt` DATETIME(3) NULL,
|
||||||
|
`rejectedAt` DATETIME(3) NULL,
|
||||||
|
`createdBy` VARCHAR(191) NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `Office365ReferralLink` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`email` VARCHAR(191) NOT NULL,
|
||||||
|
`activePeriod` VARCHAR(191) NOT NULL,
|
||||||
|
`numberOfUsers` INTEGER NOT NULL,
|
||||||
|
`link` VARCHAR(191) NULL,
|
||||||
|
`requestId` VARCHAR(191) NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `Office365LinkRequest` ADD CONSTRAINT `Office365LinkRequest_createdBy_fkey` FOREIGN KEY (`createdBy`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `Office365ReferralLink` ADD CONSTRAINT `Office365ReferralLink_requestId_fkey` FOREIGN KEY (`requestId`) REFERENCES `Office365LinkRequest`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
@ -16,6 +16,7 @@ model User {
|
||||||
photoProfile String?
|
photoProfile String?
|
||||||
directPermissions Permission[] @relation("PermissionToUser")
|
directPermissions Permission[] @relation("PermissionToUser")
|
||||||
roles Role[] @relation("RoleToUser")
|
roles Role[] @relation("RoleToUser")
|
||||||
|
linkRequests Office365LinkRequest[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Role {
|
model Role {
|
||||||
|
|
@ -37,3 +38,32 @@ model Permission {
|
||||||
roles Role[] @relation("PermissionToRole")
|
roles Role[] @relation("PermissionToRole")
|
||||||
directUsers User[] @relation("PermissionToUser")
|
directUsers User[] @relation("PermissionToUser")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Office365LinkRequestStatus {
|
||||||
|
WAITING
|
||||||
|
ACCEPTED
|
||||||
|
CANCELLED
|
||||||
|
REJECTED
|
||||||
|
}
|
||||||
|
|
||||||
|
model Office365LinkRequest {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
creator User @relation(fields: [createdBy], references: [id])
|
||||||
|
status Office365LinkRequestStatus @default(WAITING)
|
||||||
|
requestedAt DateTime @default(now())
|
||||||
|
acceptedAt DateTime?
|
||||||
|
cancelledAt DateTime?
|
||||||
|
rejectedAt DateTime?
|
||||||
|
createdBy String
|
||||||
|
links Office365ReferralLink[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Office365ReferralLink {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
request Office365LinkRequest @relation(fields: [requestId], references: [id])
|
||||||
|
email String
|
||||||
|
activePeriod String
|
||||||
|
numberOfUsers Int
|
||||||
|
link String?
|
||||||
|
requestId String
|
||||||
|
}
|
||||||
|
|
@ -81,6 +81,18 @@ export default async function permissionSeed(prisma: PrismaClient) {
|
||||||
description: "Allows deleting a user",
|
description: "Allows deleting a user",
|
||||||
isActive: true,
|
isActive: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
code: "office-365-request.create",
|
||||||
|
name: "Create Office 365 Request",
|
||||||
|
description: "Allows create an Office 365 Reseller Request",
|
||||||
|
isActive: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "office-365-request.getMine",
|
||||||
|
name: "Get my Office 365 Requests",
|
||||||
|
description: "Allows retrieve user's Office 365 Link Requests",
|
||||||
|
isActive: true
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import getUserRoles from "@/modules/auth/utils/getUserRoles";
|
import getUserRoles from "@/modules/auth/utils/getUserRoles";
|
||||||
import checkMultiplePermissions from "@/modules/dashboard/services/checkMultiplePermissions";
|
import checkMultiplePermissions from "@/modules/dashboard/services/checkMultiplePermissions";
|
||||||
import checkPermission from "@/modules/dashboard/services/checkPermission";
|
import checkPermission from "@/modules/dashboard/services/checkPermission";
|
||||||
|
import getLinkRequests from "@/modules/resellerOffice365/actions/getLinkRequests";
|
||||||
import RequestTable from "@/modules/resellerOffice365/tables/RequestTable/RequestTable";
|
import RequestTable from "@/modules/resellerOffice365/tables/RequestTable/RequestTable";
|
||||||
import { Card, Stack, Title } from "@mantine/core";
|
import { Card, Stack, Title } from "@mantine/core";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
|
|
@ -17,11 +18,19 @@ export default async function RequestLinkPage() {
|
||||||
|
|
||||||
if (!permissions.readAll) notFound();
|
if (!permissions.readAll) notFound();
|
||||||
|
|
||||||
|
const data = await getLinkRequests()
|
||||||
|
if (!data.success){
|
||||||
|
//todo: handle error
|
||||||
|
console.error(data.error)
|
||||||
|
throw new Error("Error while fetch permission")
|
||||||
|
}
|
||||||
|
const tableData = data.data
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Title order={1}>Permohonan Link Office 365</Title>
|
<Title order={1}>Permohonan Link Office 365</Title>
|
||||||
<Card>
|
<Card>
|
||||||
<RequestTable permissions={permissions} tableData={[]} />
|
<RequestTable permissions={permissions} tableData={tableData} />
|
||||||
</Card>
|
</Card>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ export const DashboardErrorCodes = [
|
||||||
"INVALID_JWT_TOKEN",
|
"INVALID_JWT_TOKEN",
|
||||||
"JWT_SECRET_EMPTY",
|
"JWT_SECRET_EMPTY",
|
||||||
"USER_ALREADY_EXISTS",
|
"USER_ALREADY_EXISTS",
|
||||||
|
"INVALID_FORM_DATA"
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
interface DashboardErrorOptions {
|
interface DashboardErrorOptions {
|
||||||
|
|
|
||||||
65
src/modules/resellerOffice365/actions/createLinkRequest.ts
Normal file
65
src/modules/resellerOffice365/actions/createLinkRequest.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
"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);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/modules/resellerOffice365/actions/getLinkRequests.ts
Normal file
49
src/modules/resellerOffice365/actions/getLinkRequests.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
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({
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
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,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
Alert,
|
||||||
Button,
|
Button,
|
||||||
Divider,
|
Divider,
|
||||||
Fieldset,
|
Fieldset,
|
||||||
|
|
@ -10,8 +11,10 @@ import {
|
||||||
Select,
|
Select,
|
||||||
Stack,
|
Stack,
|
||||||
TextInput,
|
TextInput,
|
||||||
|
Loader,
|
||||||
|
Text,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useForm } from "@mantine/form";
|
import { useForm, zodResolver } from "@mantine/form";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import {
|
import {
|
||||||
TbAt,
|
TbAt,
|
||||||
|
|
@ -22,6 +25,11 @@ import {
|
||||||
TbUsers,
|
TbUsers,
|
||||||
} from "react-icons/tb";
|
} from "react-icons/tb";
|
||||||
import resellerOffice365Config from "../config";
|
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";
|
||||||
|
|
||||||
export interface ModalProps {
|
export interface ModalProps {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -30,25 +38,18 @@ export interface ModalProps {
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FormType {
|
|
||||||
numberOfLinks: number;
|
|
||||||
details: {
|
|
||||||
email: string;
|
|
||||||
activePeriod: (typeof resellerOffice365Config.activePeriods)[number];
|
|
||||||
endUserQty: number;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function RequestModal(props: ModalProps) {
|
export default function RequestModal(props: ModalProps) {
|
||||||
const [formState, setFormState] = useState<
|
const [formState, setFormState] = useState<
|
||||||
"idle" | "submitting" | "waiting"
|
"idle" | "submitting" | "waiting"
|
||||||
>("idle");
|
>("idle");
|
||||||
|
|
||||||
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
props.onClose ? props.onClose() : "";
|
props.onClose ? props.onClose() : "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const form = useForm<FormType>({
|
const form = useForm<RequestLinkForm>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
numberOfLinks: 1,
|
numberOfLinks: 1,
|
||||||
details: [
|
details: [
|
||||||
|
|
@ -59,6 +60,7 @@ export default function RequestModal(props: ModalProps) {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
validate: zodResolver(requestLinkFormSchema),
|
||||||
onValuesChange: (values, prev) => {
|
onValuesChange: (values, prev) => {
|
||||||
// Check if numberOfLinks has changed
|
// Check if numberOfLinks has changed
|
||||||
if (values.numberOfLinks !== prev.numberOfLinks) {
|
if (values.numberOfLinks !== prev.numberOfLinks) {
|
||||||
|
|
@ -90,24 +92,60 @@ export default function RequestModal(props: ModalProps) {
|
||||||
|
|
||||||
const disableChange = formState !== "idle";
|
const disableChange = formState !== "idle";
|
||||||
|
|
||||||
const handleSubmit = (values: FormType) => {
|
const handleSubmit = (values: RequestLinkForm) => {
|
||||||
const submitableState = ["idle"];
|
const submitableState = ["idle"];
|
||||||
|
|
||||||
if (!submitableState.includes(formState)) return; //prevent submit
|
if (!submitableState.includes(formState)) return; //prevent submit
|
||||||
|
|
||||||
setFormState("submitting");
|
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");
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
size="sm"
|
size="sm"
|
||||||
opened={props.opened}
|
opened={props.opened}
|
||||||
title={props.title}
|
title={formState === "waiting" ? "Link Request Detail" : "Create New Request"}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
scrollAreaComponent={ScrollArea.Autosize}
|
scrollAreaComponent={ScrollArea.Autosize}
|
||||||
>
|
>
|
||||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
|
{formState === "waiting" && (
|
||||||
|
<Alert color="orange">
|
||||||
|
Your request is being processed by administrator
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
<NumberInput
|
<NumberInput
|
||||||
label="Please input the number of links you request"
|
label="Please input the number of links you request"
|
||||||
min={1}
|
min={1}
|
||||||
|
|
@ -178,9 +216,10 @@ export default function RequestModal(props: ModalProps) {
|
||||||
variant="filled"
|
variant="filled"
|
||||||
leftSection={<TbDeviceFloppy size={20} />}
|
leftSection={<TbDeviceFloppy size={20} />}
|
||||||
type="submit"
|
type="submit"
|
||||||
loading={["submitting", "waiting"].includes(
|
disabled={["submitting", "waiting"].includes(
|
||||||
formState
|
formState
|
||||||
)}
|
)}
|
||||||
|
loading={["submitting"].includes(formState)}
|
||||||
>
|
>
|
||||||
Make Request
|
Make Request
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import createActionButtons from "@/modules/dashboard/utils/createActionButton";
|
||||||
|
|
||||||
export interface RequestLinkRow {
|
export interface RequestLinkRow {
|
||||||
id: string;
|
id: string;
|
||||||
requestDate: Date,
|
requestDate: string,
|
||||||
userCount: number,
|
userCount: number,
|
||||||
status: string
|
status: string
|
||||||
}
|
}
|
||||||
|
|
@ -32,6 +32,10 @@ const createColumns = (options: ColumnOptions) => {
|
||||||
|
|
||||||
columnHelper.accessor("requestDate", {
|
columnHelper.accessor("requestDate", {
|
||||||
header: "Request Date",
|
header: "Request Date",
|
||||||
|
cell: (props) => {
|
||||||
|
const date = new Date(props.row.original.requestDate);
|
||||||
|
return `${date.toDateString()}; ${date.toLocaleTimeString()}`
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
columnHelper.accessor("userCount", {
|
columnHelper.accessor("userCount", {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export default interface RequestLink {
|
export default interface RequestLink {
|
||||||
id: string;
|
id: string;
|
||||||
requestDate: Date,
|
requestDate: string,
|
||||||
userCount: number,
|
userCount: number,
|
||||||
status: string
|
status: string
|
||||||
}
|
}
|
||||||
8
src/modules/resellerOffice365/types/RequestLinkForm.d.ts
vendored
Normal file
8
src/modules/resellerOffice365/types/RequestLinkForm.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
interface RequestLinkForm {
|
||||||
|
numberOfLinks: number;
|
||||||
|
details: {
|
||||||
|
email: string;
|
||||||
|
activePeriod: (typeof resellerOffice365Config.activePeriods)[number];
|
||||||
|
endUserQty: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user