diff --git a/prisma/migrations/20240215193803_add_referral_link_request_and_referral_link/migration.sql b/prisma/migrations/20240215193803_add_referral_link_request_and_referral_link/migration.sql
new file mode 100644
index 0000000..5838fff
--- /dev/null
+++ b/prisma/migrations/20240215193803_add_referral_link_request_and_referral_link/migration.sql
@@ -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;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 05bd2c1..4f73773 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -16,6 +16,7 @@ model User {
photoProfile String?
directPermissions Permission[] @relation("PermissionToUser")
roles Role[] @relation("RoleToUser")
+ linkRequests Office365LinkRequest[]
}
model Role {
@@ -37,3 +38,32 @@ model Permission {
roles Role[] @relation("PermissionToRole")
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
+}
\ No newline at end of file
diff --git a/prisma/seeds/permissionSeed.ts b/prisma/seeds/permissionSeed.ts
index 668aded..2050aeb 100644
--- a/prisma/seeds/permissionSeed.ts
+++ b/prisma/seeds/permissionSeed.ts
@@ -81,6 +81,18 @@ export default async function permissionSeed(prisma: PrismaClient) {
description: "Allows deleting a user",
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(
diff --git a/src/app/dashboard/reseller-office-365/request/page.tsx b/src/app/dashboard/reseller-office-365/request/page.tsx
index 2db2afb..dea4d45 100644
--- a/src/app/dashboard/reseller-office-365/request/page.tsx
+++ b/src/app/dashboard/reseller-office-365/request/page.tsx
@@ -1,6 +1,7 @@
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";
@@ -17,11 +18,19 @@ export default async function RequestLinkPage() {
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 (
Permohonan Link Office 365
-
+
);
diff --git a/src/modules/dashboard/errors/DashboardError.ts b/src/modules/dashboard/errors/DashboardError.ts
index 070d506..af3f052 100644
--- a/src/modules/dashboard/errors/DashboardError.ts
+++ b/src/modules/dashboard/errors/DashboardError.ts
@@ -7,6 +7,7 @@ export const DashboardErrorCodes = [
"INVALID_JWT_TOKEN",
"JWT_SECRET_EMPTY",
"USER_ALREADY_EXISTS",
+ "INVALID_FORM_DATA"
] as const;
interface DashboardErrorOptions {
diff --git a/src/modules/resellerOffice365/actions/createLinkRequest.ts b/src/modules/resellerOffice365/actions/createLinkRequest.ts
new file mode 100644
index 0000000..16fdd6b
--- /dev/null
+++ b/src/modules/resellerOffice365/actions/createLinkRequest.ts
@@ -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 {
+ 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);
+ }
+}
diff --git a/src/modules/resellerOffice365/actions/getLinkRequests.ts b/src/modules/resellerOffice365/actions/getLinkRequests.ts
new file mode 100644
index 0000000..a5f2eeb
--- /dev/null
+++ b/src/modules/resellerOffice365/actions/getLinkRequests.ts
@@ -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
+> {
+ 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);
+ }
+}
diff --git a/src/modules/resellerOffice365/formSchemas/requestLinkFormSchema.ts b/src/modules/resellerOffice365/formSchemas/requestLinkFormSchema.ts
new file mode 100644
index 0000000..2b58b5d
--- /dev/null
+++ b/src/modules/resellerOffice365/formSchemas/requestLinkFormSchema.ts
@@ -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;
diff --git a/src/modules/resellerOffice365/modals/RequestModal.tsx b/src/modules/resellerOffice365/modals/RequestModal.tsx
index 73af93a..99060ad 100644
--- a/src/modules/resellerOffice365/modals/RequestModal.tsx
+++ b/src/modules/resellerOffice365/modals/RequestModal.tsx
@@ -1,4 +1,5 @@
import {
+ Alert,
Button,
Divider,
Fieldset,
@@ -10,8 +11,10 @@ import {
Select,
Stack,
TextInput,
+ Loader,
+ Text,
} from "@mantine/core";
-import { useForm } from "@mantine/form";
+import { useForm, zodResolver } from "@mantine/form";
import React, { useState } from "react";
import {
TbAt,
@@ -22,6 +25,11 @@ import {
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";
export interface ModalProps {
title: string;
@@ -30,25 +38,18 @@ export interface ModalProps {
onClose?: () => void;
}
-interface FormType {
- numberOfLinks: number;
- details: {
- email: string;
- activePeriod: (typeof resellerOffice365Config.activePeriods)[number];
- endUserQty: number;
- }[];
-}
-
export default function RequestModal(props: ModalProps) {
const [formState, setFormState] = useState<
"idle" | "submitting" | "waiting"
>("idle");
+ const [errorMessage, setErrorMessage] = useState("");
+
const closeModal = () => {
props.onClose ? props.onClose() : "";
};
- const form = useForm({
+ const form = useForm({
initialValues: {
numberOfLinks: 1,
details: [
@@ -59,6 +60,7 @@ export default function RequestModal(props: ModalProps) {
},
],
},
+ validate: zodResolver(requestLinkFormSchema),
onValuesChange: (values, prev) => {
// Check if numberOfLinks has changed
if (values.numberOfLinks !== prev.numberOfLinks) {
@@ -90,24 +92,60 @@ export default function RequestModal(props: ModalProps) {
const disableChange = formState !== "idle";
- const handleSubmit = (values: FormType) => {
+ const handleSubmit = (values: RequestLinkForm) => {
const submitableState = ["idle"];
if (!submitableState.includes(formState)) return; //prevent submit
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 (