feat: workflow to invite an user is working

This commit is contained in:
Pablo Lara
2024-11-13 18:52:06 +01:00
parent 29dfd303db
commit 890bd12e99
8 changed files with 271 additions and 4 deletions
+24
View File
@@ -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
View File
@@ -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
View File
@@ -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>
);
};
+24
View File
@@ -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;