mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 20:42:02 +00:00
feat(ui:profile) add profile card (#5948)
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { AuthError } from "next-auth";
|
||||
import { z } from "zod";
|
||||
|
||||
import { signIn, signOut } from "@/auth.config";
|
||||
import { auth } from "@/auth.config";
|
||||
import { parseStringify } from "@/lib";
|
||||
import { authFormSchema } from "@/types";
|
||||
|
||||
const formSchemaSignIn = authFormSchema("sign-in");
|
||||
@@ -139,6 +142,34 @@ export const getToken = async (formData: z.infer<typeof formSchemaSignIn>) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getProfileInfo = async () => {
|
||||
const session = await auth();
|
||||
const keyServer = process.env.API_BASE_URL;
|
||||
const url = new URL(`${keyServer}/users/me`);
|
||||
|
||||
try {
|
||||
const response = await fetch(url.toString(), {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/vnd.api+json",
|
||||
Authorization: `Bearer ${session?.accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch user data: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const parsedData = parseStringify(data);
|
||||
revalidatePath("/profile");
|
||||
return parsedData;
|
||||
} catch (error) {
|
||||
console.error("Error fetching profile:", error);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const getUserByMe = async (accessToken: string) => {
|
||||
const keyServer = process.env.API_BASE_URL;
|
||||
const url = new URL(`${keyServer}/users/me`);
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
import { Spacer } from "@nextui-org/react";
|
||||
import { redirect } from "next/navigation";
|
||||
import React from "react";
|
||||
import React, { Suspense } from "react";
|
||||
|
||||
// import { getUserByMe } from "@/actions/auth/auth";
|
||||
import { auth } from "@/auth.config";
|
||||
import { getProfileInfo } from "@/actions/auth";
|
||||
import { Header } from "@/components/ui";
|
||||
import { SkeletonUserInfo } from "@/components/users/profile";
|
||||
import { UserInfo } from "@/components/users/profile/user-info";
|
||||
import { UserProfileProps } from "@/types";
|
||||
|
||||
export default async function Profile() {
|
||||
const session = await auth();
|
||||
|
||||
if (!session?.user) {
|
||||
// redirect("/sign-in?returnTo=/profile");
|
||||
redirect("/sign-in");
|
||||
}
|
||||
|
||||
// const user = await getUserByMe();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header title="User Profile" icon="ci:users" />
|
||||
<Spacer y={4} />
|
||||
<Spacer y={6} />
|
||||
<pre>{JSON.stringify(session.user, null, 2)}</pre>
|
||||
<pre>{JSON.stringify(session.userId, null, 2)}</pre>
|
||||
<pre>{JSON.stringify(session.tenantId, null, 2)}</pre>
|
||||
<pre>{JSON.stringify(session, null, 2)}</pre>
|
||||
<div className="min-h-screen">
|
||||
<div className="container mx-auto space-y-8 px-0 py-6">
|
||||
<div className="grid grid-cols-12 gap-6">
|
||||
<div className="col-span-12 lg:col-span-3">
|
||||
<Suspense fallback={<SkeletonUserInfo />}>
|
||||
<SSRDataUser />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const SSRDataUser = async () => {
|
||||
const userProfile: UserProfileProps = await getProfileInfo();
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="mb-4 text-sm font-bold">User Info</h3>
|
||||
<UserInfo user={userProfile?.data} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,37 +4,7 @@ import { Select, SelectItem } from "@nextui-org/react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
|
||||
const regions = [
|
||||
{ key: "af-south-1", label: "AF South 1" },
|
||||
{ key: "ap-east-1", label: "AP East 1" },
|
||||
{ key: "ap-northeast-1", label: "AP Northeast 1" },
|
||||
{ key: "ap-northeast-2", label: "AP Northeast 2" },
|
||||
{ key: "ap-northeast-3", label: "AP Northeast 3" },
|
||||
{ key: "ap-south-1", label: "AP South 1" },
|
||||
{ key: "ap-south-2", label: "AP South 2" },
|
||||
{ key: "ap-southeast-1", label: "AP Southeast 1" },
|
||||
{ key: "ap-southeast-2", label: "AP Southeast 2" },
|
||||
{ key: "ap-southeast-3", label: "AP Southeast 3" },
|
||||
{ key: "ap-southeast-4", label: "AP Southeast 4" },
|
||||
{ key: "ca-central-1", label: "CA Central 1" },
|
||||
{ key: "ca-west-1", label: "CA West 1" },
|
||||
{ key: "eu-central-1", label: "EU Central 1" },
|
||||
{ key: "eu-central-2", label: "EU Central 2" },
|
||||
{ key: "eu-north-1", label: "EU North 1" },
|
||||
{ key: "eu-south-1", label: "EU South 1" },
|
||||
{ key: "eu-south-2", label: "EU South 2" },
|
||||
{ key: "eu-west-1", label: "EU West 1" },
|
||||
{ key: "eu-west-2", label: "EU West 2" },
|
||||
{ key: "eu-west-3", label: "EU West 3" },
|
||||
{ key: "il-central-1", label: "IL Central 1" },
|
||||
{ key: "me-central-1", label: "ME Central 1" },
|
||||
{ key: "me-south-1", label: "ME South 1" },
|
||||
{ key: "sa-east-1", label: "SA East 1" },
|
||||
{ key: "us-east-1", label: "US East 1" },
|
||||
{ key: "us-east-2", label: "US East 2" },
|
||||
{ key: "us-west-1", label: "US West 1" },
|
||||
{ key: "us-west-2", label: "US West 2" },
|
||||
];
|
||||
import { regions } from "@/lib/helper";
|
||||
|
||||
export const CustomRegionSelection: React.FC = () => {
|
||||
const router = useRouter();
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./skeleton-user-info";
|
||||
export * from "./user-info";
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Card, CardBody, CardHeader, Skeleton } from "@nextui-org/react";
|
||||
|
||||
export const SkeletonUserInfo = () => {
|
||||
const rows = 4;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<Skeleton className="h-6 w-1/3 rounded-lg">
|
||||
<div className="h-6 bg-default-200"></div>
|
||||
</Skeleton>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{/* Header Skeleton */}
|
||||
<div className="grid grid-cols-4 border-b pb-2 text-sm font-semibold">
|
||||
<Skeleton className="h-5 w-full rounded-lg">
|
||||
<div className="h-5 bg-default-200"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="h-5 w-full rounded-lg">
|
||||
<div className="h-5 bg-default-200"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="h-5 w-full rounded-lg">
|
||||
<div className="h-5 bg-default-200"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="h-5 w-full rounded-lg">
|
||||
<div className="h-5 bg-default-200"></div>
|
||||
</Skeleton>
|
||||
</div>
|
||||
|
||||
{/* Row Skeletons */}
|
||||
{Array.from({ length: rows }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="grid grid-cols-4 items-center border-b py-2 text-sm"
|
||||
>
|
||||
{/* Provider Name */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<Skeleton className="h-5 w-5 rounded-lg">
|
||||
<div className="h-5 w-5 bg-default-200"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="h-5 w-1/3 rounded-lg">
|
||||
<div className="h-5 bg-default-200"></div>
|
||||
</Skeleton>
|
||||
</div>
|
||||
{/* Percent Passing */}
|
||||
<Skeleton className="h-5 w-1/4 rounded-lg">
|
||||
<div className="h-5 bg-default-200"></div>
|
||||
</Skeleton>
|
||||
{/* Failing Checks */}
|
||||
<Skeleton className="h-5 w-1/4 rounded-lg">
|
||||
<div className="h-5 bg-default-200"></div>
|
||||
</Skeleton>
|
||||
{/* Total Resources */}
|
||||
<Skeleton className="h-5 w-1/4 rounded-lg">
|
||||
<div className="h-5 bg-default-200"></div>
|
||||
</Skeleton>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
"use client";
|
||||
|
||||
import { Card, CardBody } from "@nextui-org/react";
|
||||
|
||||
import { DateWithTime } from "@/components/ui/entities";
|
||||
import { UserProfileProps } from "@/types";
|
||||
|
||||
export const UserInfo = ({
|
||||
user,
|
||||
}: {
|
||||
user: UserProfileProps["data"] | null;
|
||||
}) => {
|
||||
if (!user || !user.attributes) {
|
||||
return (
|
||||
<Card className="dark:bg-prowler-blue-400">
|
||||
<CardBody>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-semibold text-default-600">Name:</p>
|
||||
<span className="text-sm">-</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-semibold text-default-600">Email:</p>
|
||||
<span className="text-sm">-</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-semibold text-default-600">Company:</p>
|
||||
<span className="text-sm">-</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-semibold text-default-600">
|
||||
Date Joined:
|
||||
</p>
|
||||
<span className="text-sm">-</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 text-center text-sm text-red-600">
|
||||
Unable to load user information.
|
||||
<br />
|
||||
Please check your API connection.
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
const { name, email, company_name, date_joined } = user.attributes;
|
||||
|
||||
return (
|
||||
<Card className="dark:bg-prowler-blue-400">
|
||||
<CardBody>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-semibold text-default-600">Name:</p>
|
||||
<span className="text-sm">{name}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-semibold text-default-600">Email:</p>
|
||||
<span className="text-sm">{email}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-semibold text-default-600">Company:</p>
|
||||
<span className="text-sm">{company_name}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-semibold text-default-600">
|
||||
Date Joined:
|
||||
</p>
|
||||
<span className="text-sm">
|
||||
<DateWithTime inline dateTime={date_joined} />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -89,3 +89,97 @@ export const getErrorMessage = async (error: unknown): Promise<string> => {
|
||||
}
|
||||
return message;
|
||||
};
|
||||
|
||||
export const regions = [
|
||||
// AWS Regions (ordered by usage)
|
||||
{ key: "us-east-1", label: "AWS - US East 1" },
|
||||
{ key: "us-west-1", label: "AWS - US West 1" },
|
||||
{ key: "us-west-2", label: "AWS - US West 2" },
|
||||
{ key: "eu-west-1", label: "AWS - EU West 1" },
|
||||
{ key: "eu-central-1", label: "AWS - EU Central 1" },
|
||||
{ key: "ap-southeast-1", label: "AWS - AP Southeast 1" },
|
||||
{ key: "ap-northeast-1", label: "AWS - AP Northeast 1" },
|
||||
{ key: "ap-southeast-2", label: "AWS - AP Southeast 2" },
|
||||
{ key: "ca-central-1", label: "AWS - CA Central 1" },
|
||||
{ key: "sa-east-1", label: "AWS - SA East 1" },
|
||||
{ key: "af-south-1", label: "AWS - AF South 1" },
|
||||
{ key: "ap-east-1", label: "AWS - AP East 1" },
|
||||
{ key: "ap-northeast-2", label: "AWS - AP Northeast 2" },
|
||||
{ key: "ap-northeast-3", label: "AWS - AP Northeast 3" },
|
||||
{ key: "ap-south-1", label: "AWS - AP South 1" },
|
||||
{ key: "ap-south-2", label: "AWS - AP South 2" },
|
||||
{ key: "ap-southeast-3", label: "AWS - AP Southeast 3" },
|
||||
{ key: "ap-southeast-4", label: "AWS - AP Southeast 4" },
|
||||
{ key: "ca-west-1", label: "AWS - CA West 1" },
|
||||
{ key: "eu-central-2", label: "AWS - EU Central 2" },
|
||||
{ key: "eu-north-1", label: "AWS - EU North 1" },
|
||||
{ key: "eu-south-1", label: "AWS - EU South 1" },
|
||||
{ key: "eu-south-2", label: "AWS - EU South 2" },
|
||||
{ key: "eu-west-2", label: "AWS - EU West 2" },
|
||||
{ key: "eu-west-3", label: "AWS - EU West 3" },
|
||||
{ key: "il-central-1", label: "AWS - IL Central 1" },
|
||||
{ key: "me-central-1", label: "AWS - ME Central 1" },
|
||||
{ key: "me-south-1", label: "AWS - ME South 1" },
|
||||
|
||||
// Azure Regions (ordered by usage)
|
||||
{ key: "eastus", label: "Azure - East US" },
|
||||
{ key: "eastus2", label: "Azure - East US 2" },
|
||||
{ key: "westeurope", label: "Azure - West Europe" },
|
||||
{ key: "southeastasia", label: "Azure - Southeast Asia" },
|
||||
{ key: "uksouth", label: "Azure - UK South" },
|
||||
{ key: "northeurope", label: "Azure - North Europe" },
|
||||
{ key: "centralus", label: "Azure - Central US" },
|
||||
{ key: "westus2", label: "Azure - West US 2" },
|
||||
{ key: "southcentralus", label: "Azure - South Central US" },
|
||||
{ key: "australiaeast", label: "Azure - Australia East" },
|
||||
{ key: "canadacentral", label: "Azure - Canada Central" },
|
||||
{ key: "japaneast", label: "Azure - Japan East" },
|
||||
{ key: "koreacentral", label: "Azure - Korea Central" },
|
||||
{ key: "southafricanorth", label: "Azure - South Africa North" },
|
||||
{ key: "brazilsouth", label: "Azure - Brazil South" },
|
||||
{ key: "francecentral", label: "Azure - France Central" },
|
||||
{ key: "germanywestcentral", label: "Azure - Germany West Central" },
|
||||
{ key: "switzerlandnorth", label: "Azure - Switzerland North" },
|
||||
{ key: "uaenorth", label: "Azure - UAE North" },
|
||||
// Remaining Azure Regions (less frequently used)
|
||||
{ key: "westus", label: "Azure - West US" },
|
||||
{ key: "northcentralus", label: "Azure - North Central US" },
|
||||
{ key: "australiasoutheast", label: "Azure - Australia Southeast" },
|
||||
{ key: "southindia", label: "Azure - South India" },
|
||||
{ key: "westindia", label: "Azure - West India" },
|
||||
{ key: "canadaeast", label: "Azure - Canada East" },
|
||||
{ key: "francesouth", label: "Azure - France South" },
|
||||
{ key: "norwayeast", label: "Azure - Norway East" },
|
||||
{ key: "switzerlandwest", label: "Azure - Switzerland West" },
|
||||
{ key: "ukwest", label: "Azure - UK West" },
|
||||
{ key: "uaecentral", label: "Azure - UAE Central" },
|
||||
{ key: "brazilsoutheast", label: "Azure - Brazil Southeast" },
|
||||
|
||||
// GCP Regions (ordered by usage)
|
||||
{ key: "us-central1", label: "GCP - US Central (Iowa)" },
|
||||
{ key: "us-east1", label: "GCP - US East (South Carolina)" },
|
||||
{ key: "us-west1", label: "GCP - US West (Oregon)" },
|
||||
{ key: "europe-west1", label: "GCP - Europe West (Belgium)" },
|
||||
{ key: "asia-east1", label: "GCP - Asia East (Taiwan)" },
|
||||
{ key: "asia-northeast1", label: "GCP - Asia Northeast (Tokyo)" },
|
||||
{ key: "europe-west2", label: "GCP - Europe West (London)" },
|
||||
{ key: "europe-west3", label: "GCP - Europe West (Frankfurt)" },
|
||||
{ key: "europe-west4", label: "GCP - Europe West (Netherlands)" },
|
||||
{ key: "asia-southeast1", label: "GCP - Asia Southeast (Singapore)" },
|
||||
{ key: "australia-southeast1", label: "GCP - Australia Southeast (Sydney)" },
|
||||
{
|
||||
key: "northamerica-northeast1",
|
||||
label: "GCP - North America Northeast (Montreal)",
|
||||
},
|
||||
// Remaining GCP Regions
|
||||
{ key: "asia-east2", label: "GCP - Asia East (Hong Kong)" },
|
||||
{ key: "asia-northeast2", label: "GCP - Asia Northeast (Osaka)" },
|
||||
{ key: "asia-northeast3", label: "GCP - Asia Northeast (Seoul)" },
|
||||
{ key: "asia-south1", label: "GCP - Asia South (Mumbai)" },
|
||||
{ key: "asia-southeast2", label: "GCP - Asia Southeast (Jakarta)" },
|
||||
{ key: "europe-north1", label: "GCP - Europe North (Finland)" },
|
||||
{ key: "europe-west6", label: "GCP - Europe West (Zurich)" },
|
||||
{ key: "southamerica-east1", label: "GCP - South America East (São Paulo)" },
|
||||
{ key: "us-west2", label: "GCP - US West (Los Angeles)" },
|
||||
{ key: "us-east4", label: "GCP - US East (Northern Virginia)" },
|
||||
];
|
||||
|
||||
@@ -227,6 +227,32 @@ export interface InvitationProps {
|
||||
self: string;
|
||||
};
|
||||
}
|
||||
export interface UserProfileProps {
|
||||
data: {
|
||||
type: "users";
|
||||
id: string;
|
||||
attributes: {
|
||||
name: string;
|
||||
email: string;
|
||||
company_name: string;
|
||||
date_joined: string;
|
||||
};
|
||||
relationships: {
|
||||
memberships: {
|
||||
meta: {
|
||||
count: number;
|
||||
};
|
||||
data: Array<{
|
||||
type: "memberships";
|
||||
id: string;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
};
|
||||
meta: {
|
||||
version: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UserProps {
|
||||
type: "users";
|
||||
|
||||
Reference in New Issue
Block a user