diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
index 82f1cfb..f58e804 100644
--- a/src/app/dashboard/page.tsx
+++ b/src/app/dashboard/page.tsx
@@ -3,7 +3,7 @@ import React from 'react'
export default async function Dashboard() {
return (
-
Dashboard
+ Dashboard
)
}
diff --git a/src/app/dashboard/reseller-office-365/list/page.tsx b/src/app/dashboard/reseller-office-365/list/page.tsx
new file mode 100644
index 0000000..9420636
--- /dev/null
+++ b/src/app/dashboard/reseller-office-365/list/page.tsx
@@ -0,0 +1,39 @@
+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 (
+
+ List Link Office 365
+
+
+
+
+ );
+}
diff --git a/src/app/dashboard/reseller-office-365/request/page.tsx b/src/app/dashboard/reseller-office-365/request/page.tsx
index dea4d45..3c81347 100644
--- a/src/app/dashboard/reseller-office-365/request/page.tsx
+++ b/src/app/dashboard/reseller-office-365/request/page.tsx
@@ -22,7 +22,7 @@ export default async function RequestLinkPage() {
if (!data.success){
//todo: handle error
console.error(data.error)
- throw new Error("Error while fetch permission")
+ throw new Error("Error while fetch data")
}
const tableData = data.data
diff --git a/src/core/db/index.ts b/src/core/db/index.ts
index 0273c94..faf0759 100644
--- a/src/core/db/index.ts
+++ b/src/core/db/index.ts
@@ -8,8 +8,8 @@ declare global {
var prisma: undefined | ReturnType;
}
-const prisma = globalThis.prisma ?? prismaClientSingleton();
+const db = globalThis.prisma ?? prismaClientSingleton();
-export default prisma;
+export default db;
-if (process.env.NODE_ENV !== "production") globalThis.prisma = prisma;
+if (process.env.NODE_ENV !== "production") globalThis.prisma = db;
diff --git a/src/db/index.ts b/src/db/index.ts
index 77d6b4b..7f5046e 100644
--- a/src/db/index.ts
+++ b/src/db/index.ts
@@ -8,7 +8,7 @@ declare global {
var prisma: undefined | ReturnType
}
-const prisma = globalThis.prisma ?? prismaClientSingleton()
+const db = globalThis.prisma ?? prismaClientSingleton()
export default prisma
diff --git a/src/modules/dashboard/data/sidebarMenus.ts b/src/modules/dashboard/data/sidebarMenus.ts
index 6a969ac..44fe8d5 100644
--- a/src/modules/dashboard/data/sidebarMenus.ts
+++ b/src/modules/dashboard/data/sidebarMenus.ts
@@ -31,13 +31,13 @@ const sidebarMenus: SidebarMenu[] = [
allowedPermissions: ["*"],
children: [
{
- label: "Request Link",
+ label: "My Request Links",
link: "/reseller-office-365/request",
allowedRoles: ["*"]
},
{
- label: "Respond Request Link",
- link: "#",
+ label: "Process Request Link",
+ link: "/reseller-office-365/list",
allowedRoles: ["*"]
}
]
diff --git a/src/modules/resellerOffice365/actions/createLinkRequest.ts b/src/modules/resellerOffice365/actions/createLinkRequest.ts
index c54145a..16fdd6b 100644
--- a/src/modules/resellerOffice365/actions/createLinkRequest.ts
+++ b/src/modules/resellerOffice365/actions/createLinkRequest.ts
@@ -10,7 +10,6 @@ import mapObjectToFirstValue from "@/utils/mapObjectToFirstValue";
import db from "@/core/db";
import getCurrentUser from "@/modules/auth/utils/getCurrentUser";
import { revalidatePath } from "next/cache";
-import {server} from "../../../../server/socket";
export default async function createLinkRequest(
formData: RequestLinkForm
@@ -55,8 +54,6 @@ export default async function createLinkRequest(
revalidatePath(".")
- server.publish(`mwrl-${currentUser.id}`, "update")
-
return {
success: true,
message:
diff --git a/src/modules/resellerOffice365/actions/getAllLinkRequests.ts b/src/modules/resellerOffice365/actions/getAllLinkRequests.ts
new file mode 100644
index 0000000..e095902
--- /dev/null
+++ b/src/modules/resellerOffice365/actions/getAllLinkRequests.ts
@@ -0,0 +1,55 @@
+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
+> {
+ try {
+ //TODO: Fix permission check
+ if (!(await checkPermission("authenticated-only")))
+ return unauthorized();
+
+ const requestLinks = await db.office365LinkRequest.findMany({
+ orderBy: [
+ {
+ status: "desc"
+ },
+ {
+ 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);
+ }
+}
diff --git a/src/modules/resellerOffice365/actions/getLinkRequestDataById.ts b/src/modules/resellerOffice365/actions/getLinkRequestDataById.ts
new file mode 100644
index 0000000..9e266bf
--- /dev/null
+++ b/src/modules/resellerOffice365/actions/getLinkRequestDataById.ts
@@ -0,0 +1,70 @@
+"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>>
+ >
+> {
+ 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);
+ }
+}
diff --git a/src/modules/resellerOffice365/modals/RequestModal.tsx b/src/modules/resellerOffice365/modals/RequestModal.tsx
index 99060ad..9810653 100644
--- a/src/modules/resellerOffice365/modals/RequestModal.tsx
+++ b/src/modules/resellerOffice365/modals/RequestModal.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react-hooks/exhaustive-deps */
import {
Alert,
Button,
@@ -13,9 +14,10 @@ import {
TextInput,
Loader,
Text,
+ Skeleton,
} from "@mantine/core";
import { useForm, zodResolver } from "@mantine/form";
-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
import {
TbAt,
TbCalendarTime,
@@ -30,27 +32,82 @@ 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";
export interface ModalProps {
title: string;
opened: boolean;
- readonly: 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"
+ "idle" | "submitting" | "waiting" | "fetching" | "error"
>("idle");
const [errorMessage, setErrorMessage] = useState("");
const closeModal = () => {
+ if (formState === "submitting") return; //prevents closing
+ //reset state
+ setErrorMessage("");
+ 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 "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,
+ })),
+ });
+ })
+ .catch((e) => {
+ if (e instanceof Error) {
+ setErrorMessage(e.message);
+ } else {
+ setErrorMessage("Unkown error occured");
+ }
+ })
+ .finally(() => {
+ setFormState("idle");
+ });
+ }
+ }
+ }, [props]);
+
const form = useForm({
initialValues: {
+ id: undefined,
numberOfLinks: 1,
details: [
{
@@ -90,51 +147,67 @@ export default function RequestModal(props: ModalProps) {
},
});
- const disableChange = formState !== "idle";
-
const handleSubmit = (values: RequestLinkForm) => {
- const submitableState = ["idle"];
+ const submitableState: (typeof formState)[] = ["idle"];
- if (!submitableState.includes(formState)) return; //prevent submit
+ if (!submitableState.includes(formState)) return; //prevent submit when not in subitable state
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);
+ switch (props.type) {
+ case "create": {
+ 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(e.message);
+ setErrorMessage(
+ `Unkown error is occured. Please contact administrator`
+ );
}
- } 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");
- });
+ setFormState("idle");
+ });
+ break;
+ }
+ case "input link": {
+ //TODO: Handle add link
+ }
+ }
};
+ const disableChange = formState !== "idle";
+ const readonly = props.type === "input link";
+ const showSkeleton = formState === "fetching";
+
return (
@@ -146,16 +219,21 @@ export default function RequestModal(props: ModalProps) {
)}
- }
- disabled={disableChange}
- {...form.getInputProps("numberOfLinks")}
- />
+ {errorMessage && {errorMessage}}
+
+
+ }
+ disabled={disableChange}
+ readOnly={readonly}
+ {...form.getInputProps("numberOfLinks")}
+ />
+
{form.values.details.map((item, i) => (
))}
@@ -211,7 +310,7 @@ export default function RequestModal(props: ModalProps) {
>
Close
- {(!props.readonly || formState === "waiting") && (
+ {formState === "waiting" && (
}
diff --git a/src/modules/resellerOffice365/tables/ListOfRequestTable/ListOfRequestTable.tsx b/src/modules/resellerOffice365/tables/ListOfRequestTable/ListOfRequestTable.tsx
new file mode 100644
index 0000000..38765f0
--- /dev/null
+++ b/src/modules/resellerOffice365/tables/ListOfRequestTable/ListOfRequestTable.tsx
@@ -0,0 +1,66 @@
+"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;
+ 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(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) => {props.getValue() as ReactNode},
+ },
+ });
+
+ const openFormModal = (id: string) => {
+ setModalProps({
+ opened: true,
+ title: "Request Detail",
+ type: "input link",
+ detailId: id,
+ });
+ };
+
+ const closeModal = () => {
+ setModalProps(defaultModalProps);
+ };
+
+ return (
+ <>
+
+
+
+ >
+ );
+}
diff --git a/src/modules/resellerOffice365/tables/ListOfRequestTable/columns.tsx b/src/modules/resellerOffice365/tables/ListOfRequestTable/columns.tsx
new file mode 100644
index 0000000..809f83d
--- /dev/null
+++ b/src/modules/resellerOffice365/tables/ListOfRequestTable/columns.tsx
@@ -0,0 +1,95 @@
+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;
+ actions: {
+ detail: (id: string) => void;
+ // edit: (id: string) => void;
+ // delete: (id: string) => void;
+ };
+}
+
+const createColumns = (options: ColumnOptions) => {
+ const columnHelper = createColumnHelper();
+
+ 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 (
+
+
+ {props.cell.getValue().name}
+ {props.cell.getValue().email}
+
+ )
+ }
+ }),
+
+ columnHelper.accessor("userCount", {
+ header: "User Count",
+ }),
+
+ columnHelper.accessor("status", {
+ header: "Status",
+ cell: (props) => {
+ switch (props.row.original.status) {
+ case "WAITING":
+ return WAITING;
+ break;
+ case "ACCEPTED":
+ return ACCEPTED;
+ break;
+ case "CANCELLED":
+ return CANCELLED;
+ break;
+ case "REJECTED":
+ return REJECTED;
+ break;
+ }
+ },
+ }),
+
+ columnHelper.display({
+ id: "Actions",
+ header: "Actions",
+ cell: (props) => (
+
+ {createActionButtons([
+ {
+ label: "Detail",
+ permission: options.permissions.read,
+ action: () =>
+ options.actions.detail(props.row.original.id),
+ color: "green",
+ icon: ,
+ },
+ ])}
+
+ ),
+ }),
+ ];
+
+ return columns;
+};
+
+export default createColumns;
diff --git a/src/modules/resellerOffice365/tables/RequestTable/RequestTable.tsx b/src/modules/resellerOffice365/tables/RequestTable/RequestTable.tsx
index b8f05ea..94b2055 100644
--- a/src/modules/resellerOffice365/tables/RequestTable/RequestTable.tsx
+++ b/src/modules/resellerOffice365/tables/RequestTable/RequestTable.tsx
@@ -14,21 +14,25 @@ interface Props {
tableData: RequestLink[];
}
+const defaultModalProps: ModalProps = {
+ opened: false,
+ title: "",
+ type: "create",
+ detailId: null,
+};
+
export default function RequestTable(props: Props) {
- // const [modalProps, setModalProps] = useState({
- // opened: false,
- // title: "",
- // readonly: false,
- // });
- const [openModal, setOpenModal] = useState(false);
+ const [modalProps, setModalProps] = useState(defaultModalProps);
const table = useReactTable({
data: props.tableData,
columns: createColumns({
permissions: props.permissions,
actions: {
- detail: (id) => {console.log(id)}
- }
+ detail: (id) => {
+ console.log(id);
+ },
+ },
}),
getCoreRowModel: getCoreRowModel(),
defaultColumn: {
@@ -36,33 +40,24 @@ export default function RequestTable(props: Props) {
},
});
- const openFormModall = () => {
- // setModalProps({
- // opened: true,
- // title: "Request new link",
- // readonly: false,
- // });
- // console.log('hai')
- setOpenModal(true);
+ const openCreateModal = () => {
+ setModalProps({
+ opened: true,
+ title: "Create New Office 365 Link Request",
+ detailId: null,
+ type: "create",
+ });
};
const closeModal = () => {
- // setModalProps({
- // opened: false,
- // title: "",
- // readonly: false,
- // });
- setOpenModal(false)
+ setModalProps(defaultModalProps);
};
return (
<>
{
- }
- onClick={() => openFormModall()}
- >
+ } onClick={openCreateModal}>
New Link Request
}
@@ -70,7 +65,7 @@ export default function RequestTable(props: Props) {
-
+
>
);
}
diff --git a/src/modules/resellerOffice365/tables/RequestTable/columns.tsx b/src/modules/resellerOffice365/tables/RequestTable/columns.tsx
index 44fe0d1..fec8b69 100644
--- a/src/modules/resellerOffice365/tables/RequestTable/columns.tsx
+++ b/src/modules/resellerOffice365/tables/RequestTable/columns.tsx
@@ -6,9 +6,9 @@ import createActionButtons from "@/modules/dashboard/utils/createActionButton";
export interface RequestLinkRow {
id: string;
- requestDate: string,
- userCount: number,
- status: string
+ requestDate: string;
+ userCount: number;
+ status: string;
}
interface ColumnOptions {
@@ -34,8 +34,8 @@ const createColumns = (options: ColumnOptions) => {
header: "Request Date",
cell: (props) => {
const date = new Date(props.row.original.requestDate);
- return `${date.toDateString()}; ${date.toLocaleTimeString()}`
- }
+ return `${date.toDateString()}; ${date.toLocaleTimeString()}`;
+ },
}),
columnHelper.accessor("userCount", {
@@ -44,6 +44,22 @@ const createColumns = (options: ColumnOptions) => {
columnHelper.accessor("status", {
header: "Status",
+ cell: (props) => {
+ switch (props.row.original.status) {
+ case "WAITING":
+ return WAITING;
+ break;
+ case "ACCEPTED":
+ return ACCEPTED;
+ break;
+ case "CANCELLED":
+ return CANCELLED;
+ break;
+ case "REJECTED":
+ return REJECTED;
+ break;
+ }
+ },
}),
columnHelper.display({
@@ -68,23 +84,13 @@ const createColumns = (options: ColumnOptions) => {
color: "yellow",
icon: ,
},
- {
- label: "Delete",
- permission: options.permissions.delete,
- // action: () =>
- // options.actions.delete(
- // props.row.original.id
- // ),
- color: "red",
- icon: ,
- },
])}
),
}),
];
- return columns;
+ return columns;
};
export default createColumns;
diff --git a/src/modules/resellerOffice365/types/RequestLinkForm.d.ts b/src/modules/resellerOffice365/types/RequestLinkForm.d.ts
index 30d11de..3745dc3 100644
--- a/src/modules/resellerOffice365/types/RequestLinkForm.d.ts
+++ b/src/modules/resellerOffice365/types/RequestLinkForm.d.ts
@@ -1,4 +1,5 @@
interface RequestLinkForm {
+ id: string | undefined;
numberOfLinks: number;
details: {
email: string;
diff --git a/src/modules/resellerOffice365/types/RequestLinkWithIssuerData.d.ts b/src/modules/resellerOffice365/types/RequestLinkWithIssuerData.d.ts
new file mode 100644
index 0000000..577a312
--- /dev/null
+++ b/src/modules/resellerOffice365/types/RequestLinkWithIssuerData.d.ts
@@ -0,0 +1,14 @@
+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
+}