From fd8d34e8bc109024a28a13ed74cdd3c5e55abb18 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 28 Nov 2024 10:39:10 +0100 Subject: [PATCH] feat(ui:profile) add profile card (#5948) --- ui/actions/auth/auth.ts | 31 ++++++ ui/app/(prowler)/profile/page.tsx | 45 +++++---- .../filters/custom-region-selection.tsx | 32 +------ ui/components/users/profile/index.ts | 2 + .../users/profile/skeleton-user-info.tsx | 64 +++++++++++++ ui/components/users/profile/user-info.tsx | 77 +++++++++++++++ ui/lib/helper.ts | 94 +++++++++++++++++++ ui/types/components.ts | 26 +++++ 8 files changed, 322 insertions(+), 49 deletions(-) create mode 100644 ui/components/users/profile/index.ts create mode 100644 ui/components/users/profile/skeleton-user-info.tsx create mode 100644 ui/components/users/profile/user-info.tsx diff --git a/ui/actions/auth/auth.ts b/ui/actions/auth/auth.ts index 1bbc79b17e..19e0c62a38 100644 --- a/ui/actions/auth/auth.ts +++ b/ui/actions/auth/auth.ts @@ -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) => { } }; +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`); diff --git a/ui/app/(prowler)/profile/page.tsx b/ui/app/(prowler)/profile/page.tsx index 0cfb4f8ab2..26d46a4380 100644 --- a/ui/app/(prowler)/profile/page.tsx +++ b/ui/app/(prowler)/profile/page.tsx @@ -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 ( <>
- -
{JSON.stringify(session.user, null, 2)}
-
{JSON.stringify(session.userId, null, 2)}
-
{JSON.stringify(session.tenantId, null, 2)}
-
{JSON.stringify(session, null, 2)}
+
+
+
+
+ }> + + +
+
+
+
); } + +const SSRDataUser = async () => { + const userProfile: UserProfileProps = await getProfileInfo(); + + return ( + <> +

User Info

+ + + ); +}; diff --git a/ui/components/filters/custom-region-selection.tsx b/ui/components/filters/custom-region-selection.tsx index 7fc40fe0ea..f97179a5dd 100644 --- a/ui/components/filters/custom-region-selection.tsx +++ b/ui/components/filters/custom-region-selection.tsx @@ -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(); diff --git a/ui/components/users/profile/index.ts b/ui/components/users/profile/index.ts new file mode 100644 index 0000000000..07aeca9b89 --- /dev/null +++ b/ui/components/users/profile/index.ts @@ -0,0 +1,2 @@ +export * from "./skeleton-user-info"; +export * from "./user-info"; diff --git a/ui/components/users/profile/skeleton-user-info.tsx b/ui/components/users/profile/skeleton-user-info.tsx new file mode 100644 index 0000000000..6bd52c322e --- /dev/null +++ b/ui/components/users/profile/skeleton-user-info.tsx @@ -0,0 +1,64 @@ +import { Card, CardBody, CardHeader, Skeleton } from "@nextui-org/react"; + +export const SkeletonUserInfo = () => { + const rows = 4; + + return ( + + + +
+
+
+ +
+ {/* Header Skeleton */} +
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + {/* Row Skeletons */} + {Array.from({ length: rows }).map((_, index) => ( +
+ {/* Provider Name */} +
+ +
+
+ +
+
+
+ {/* Percent Passing */} + +
+
+ {/* Failing Checks */} + +
+
+ {/* Total Resources */} + +
+
+
+ ))} +
+
+
+ ); +}; diff --git a/ui/components/users/profile/user-info.tsx b/ui/components/users/profile/user-info.tsx new file mode 100644 index 0000000000..ac44770a04 --- /dev/null +++ b/ui/components/users/profile/user-info.tsx @@ -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 ( + + +
+
+

Name:

+ - +
+
+

Email:

+ - +
+
+

Company:

+ - +
+
+

+ Date Joined: +

+ - +
+
+
+ Unable to load user information. +
+ Please check your API connection. +
+
+
+ ); + } + + const { name, email, company_name, date_joined } = user.attributes; + + return ( + + +
+
+

Name:

+ {name} +
+
+

Email:

+ {email} +
+
+

Company:

+ {company_name} +
+
+

+ Date Joined: +

+ + + +
+
+
+
+ ); +}; diff --git a/ui/lib/helper.ts b/ui/lib/helper.ts index 5b6d2ec8f2..9c031b4e78 100644 --- a/ui/lib/helper.ts +++ b/ui/lib/helper.ts @@ -89,3 +89,97 @@ export const getErrorMessage = async (error: unknown): Promise => { } 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)" }, +]; diff --git a/ui/types/components.ts b/ui/types/components.ts index ffe10216e0..0eca498945 100644 --- a/ui/types/components.ts +++ b/ui/types/components.ts @@ -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";