mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 04:21:52 +00:00
feat: workflow to invite an user is working
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import { auth } from "@/auth.config";
|
||||
import { getErrorMessage, parseStringify } from "@/lib";
|
||||
|
||||
export const sendInvite = async (formData: FormData) => {
|
||||
const session = await auth();
|
||||
const keyServer = process.env.API_BASE_URL;
|
||||
@@ -38,3 +39,26 @@ export const sendInvite = async (formData: FormData) => {
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const getInvitationInfoById = async (invitationId: string) => {
|
||||
const session = await auth();
|
||||
const keyServer = process.env.API_BASE_URL;
|
||||
const url = new URL(`${keyServer}/tenants/invitations/${invitationId}`);
|
||||
|
||||
try {
|
||||
const response = await fetch(url.toString(), {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/vnd.api+json",
|
||||
Authorization: `Bearer ${session?.accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
return parseStringify(data);
|
||||
} catch (error) {
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,3 +1,43 @@
|
||||
export default function CheckDetailsPage() {
|
||||
return <div>CheckDetailsPage</div>;
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { getInvitationInfoById } from "@/actions/invitations/invitation";
|
||||
import { InvitationDetails } from "@/components/invitations";
|
||||
import { SkeletonInvitationInfo } from "@/components/invitations/workflow";
|
||||
import { SearchParamsProps } from "@/types";
|
||||
|
||||
export default async function CheckDetailsPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: SearchParamsProps;
|
||||
}) {
|
||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
||||
|
||||
return (
|
||||
<Suspense key={searchParamsKey} fallback={<SkeletonInvitationInfo />}>
|
||||
<SSRDataInvitation searchParams={searchParams} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
const SSRDataInvitation = async ({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: SearchParamsProps;
|
||||
}) => {
|
||||
const invitationId = searchParams.id;
|
||||
|
||||
if (!invitationId) {
|
||||
return <div>Invalid invitation ID</div>;
|
||||
}
|
||||
|
||||
const invitationData = (await getInvitationInfoById(invitationId as string))
|
||||
.data;
|
||||
|
||||
if (!invitationData) {
|
||||
return <div>Invitation not found</div>;
|
||||
}
|
||||
|
||||
const { attributes, links } = invitationData;
|
||||
|
||||
return <InvitationDetails attributes={attributes} selfLink={links.self} />;
|
||||
};
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./invitation-details";
|
||||
export * from "./send-invitation-button";
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
"use client";
|
||||
|
||||
import { Card, CardBody, Divider, Snippet } from "@nextui-org/react";
|
||||
|
||||
import { AddIcon } from "../icons";
|
||||
import { CustomButton } from "../ui/custom";
|
||||
import { DateWithTime } from "../ui/entities";
|
||||
|
||||
interface InvitationDetailsProps {
|
||||
attributes: {
|
||||
email: string;
|
||||
state: string;
|
||||
token: string;
|
||||
expires_at: string;
|
||||
inserted_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
relationships?: {
|
||||
inviter: {
|
||||
data: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
selfLink: string;
|
||||
}
|
||||
|
||||
export const InvitationDetails = ({
|
||||
attributes,
|
||||
selfLink,
|
||||
}: InvitationDetailsProps) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-x-4 gap-y-8">
|
||||
<Card
|
||||
isBlurred
|
||||
className="border-none bg-background/60 dark:bg-default-100/50"
|
||||
shadow="sm"
|
||||
>
|
||||
<CardBody>
|
||||
<h2 className="text-2xl font-bold text-foreground/90">
|
||||
Invitation Details
|
||||
</h2>
|
||||
<Divider className="my-4" />
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<strong className="text-default-500">Email:</strong>
|
||||
<span>{attributes.email}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<strong className="text-default-500">State:</strong>
|
||||
<span className="capitalize">{attributes.state}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<strong className="text-default-500">Token:</strong>
|
||||
<span>{attributes.token}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<strong className="text-default-500">Expires At:</strong>
|
||||
<DateWithTime dateTime={attributes.expires_at} />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<strong className="text-default-500">Inserted At:</strong>
|
||||
<DateWithTime dateTime={attributes.inserted_at} />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<strong className="text-default-500">Updated At:</strong>
|
||||
<DateWithTime dateTime={attributes.updated_at} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider className="my-4" />
|
||||
<h3 className="pb-2 text-xl font-bold text-foreground/90">
|
||||
Share this link with the user:
|
||||
</h3>
|
||||
|
||||
<div className="flex flex-col items-start justify-between">
|
||||
<Snippet
|
||||
classNames={{
|
||||
base: "mx-auto",
|
||||
}}
|
||||
hideSymbol
|
||||
variant="bordered"
|
||||
className="overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
<p className="no-scrollbar w-96 overflow-hidden overflow-x-scroll text-ellipsis whitespace-nowrap text-sm">
|
||||
{selfLink}
|
||||
</p>
|
||||
</Snippet>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
<div className="flex w-full items-center justify-end">
|
||||
<CustomButton
|
||||
asLink="/invitations/"
|
||||
ariaLabel="Send Invitation"
|
||||
variant="solid"
|
||||
color="action"
|
||||
size="md"
|
||||
endContent={<AddIcon size={20} />}
|
||||
>
|
||||
Back to Invitations
|
||||
</CustomButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -57,8 +57,8 @@ export const SendInvitationForm = () => {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const token = data?.data?.attributes.token || "";
|
||||
router.push(`invitations/check-details/?invitation_token=${token}`);
|
||||
const invitationId = data?.data?.id || "";
|
||||
router.push(`/invitations/check-details/?id=${invitationId}`);
|
||||
}
|
||||
} catch (error) {
|
||||
toast({
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./skeleton-invitation-info";
|
||||
export * from "./vertical-steps";
|
||||
export * from "./workflow-send-invite";
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Card, Skeleton } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
|
||||
export const SkeletonInvitationInfo = () => {
|
||||
return (
|
||||
<Card className="h-full w-full space-y-5 p-4" radius="sm">
|
||||
{/* Table headers */}
|
||||
<div className="hidden justify-between md:flex">
|
||||
<Skeleton className="w-1/12 rounded-lg">
|
||||
<div className="h-8 bg-default-200"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="w-2/12 rounded-lg">
|
||||
<div className="h-8 bg-default-200"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="w-2/12 rounded-lg">
|
||||
<div className="h-8 bg-default-200"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="w-2/12 rounded-lg">
|
||||
<div className="h-8 bg-default-200"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="w-2/12 rounded-lg">
|
||||
<div className="h-8 bg-default-200"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="w-1/12 rounded-lg">
|
||||
<div className="h-8 bg-default-200"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="w-1/12 rounded-lg">
|
||||
<div className="h-8 bg-default-200"></div>
|
||||
</Skeleton>
|
||||
</div>
|
||||
|
||||
{/* Table body */}
|
||||
<div className="space-y-3">
|
||||
{[...Array(3)].map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col items-center justify-between space-x-0 md:flex-row md:space-x-4"
|
||||
>
|
||||
<Skeleton className="mb-2 w-full rounded-lg md:mb-0 md:w-1/12">
|
||||
<div className="h-12 bg-default-300"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="mb-2 w-full rounded-lg md:mb-0 md:w-2/12">
|
||||
<div className="h-12 bg-default-300"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="mb-2 hidden rounded-lg sm:flex md:mb-0 md:w-2/12">
|
||||
<div className="h-12 bg-default-300"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="mb-2 hidden rounded-lg sm:flex md:mb-0 md:w-2/12">
|
||||
<div className="h-12 bg-default-300"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="mb-2 hidden rounded-lg sm:flex md:mb-0 md:w-2/12">
|
||||
<div className="h-12 bg-default-300"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="mb-2 hidden rounded-lg sm:flex md:mb-0 md:w-1/12">
|
||||
<div className="h-12 bg-default-300"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="mb-2 hidden rounded-lg sm:flex md:mb-0 md:w-1/12">
|
||||
<div className="h-12 bg-default-300"></div>
|
||||
</Skeleton>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -101,6 +101,30 @@ export interface ApiError {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface InvitationProps {
|
||||
type: "Invitation";
|
||||
id: string;
|
||||
attributes: {
|
||||
inserted_at: string;
|
||||
updated_at: string;
|
||||
email: string;
|
||||
state: string;
|
||||
token: string;
|
||||
expires_at: string;
|
||||
};
|
||||
relationships: {
|
||||
inviter: {
|
||||
data: {
|
||||
type: "User";
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
links: {
|
||||
self: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UserProps {
|
||||
type: "User";
|
||||
id: string;
|
||||
|
||||
Reference in New Issue
Block a user