diff --git a/app/(prowler)/error.tsx b/app/(prowler)/error.tsx
new file mode 100644
index 0000000000..fd8468f123
--- /dev/null
+++ b/app/(prowler)/error.tsx
@@ -0,0 +1,36 @@
+"use client";
+
+import Link from "next/link";
+import { useEffect } from "react";
+
+import { Alert, AlertDescription, AlertTitle } from "@/components";
+import { RocketIcon } from "@/components/icons";
+
+export default function Error({
+ error,
+ // reset,
+}: {
+ error: Error;
+ reset: () => void;
+}) {
+ useEffect(() => {
+ // Log the error to an error reporting service
+ /* eslint-disable no-console */
+ console.error(error);
+ }, [error]);
+
+ return (
+
+
+ An unexpected error occurred
+
+ We're sorry for the inconvenience. Please try again or contact support
+ if the problem persists.
+
+
+ {" "}
+ Go to the homepage
+
+
+ );
+}
diff --git a/app/(prowler)/layout.tsx b/app/(prowler)/layout.tsx
index 13319c3540..6ef0e1aaa4 100644
--- a/app/(prowler)/layout.tsx
+++ b/app/(prowler)/layout.tsx
@@ -3,7 +3,7 @@ import "@/styles/globals.css";
import { Metadata, Viewport } from "next";
import React from "react";
-import { SidebarWrap } from "@/components";
+import { SidebarWrap } from "@/components/ui/sidebar";
import { fontSans } from "@/config/fonts";
import { siteConfig } from "@/config/site";
import { cn } from "@/lib/utils";
diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx
index 2301d26738..4301b83e37 100644
--- a/app/(prowler)/providers/page.tsx
+++ b/app/(prowler)/providers/page.tsx
@@ -28,7 +28,10 @@ export default async function Providers() {
openButtonLabel="Add Cloud Accounts"
/>
-
+
>
);
diff --git a/app/error.tsx b/app/error.tsx
deleted file mode 100644
index 9ed5104e8a..0000000000
--- a/app/error.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-"use client";
-
-import { useEffect } from "react";
-
-export default function Error({
- error,
- reset,
-}: {
- error: Error;
- reset: () => void;
-}) {
- useEffect(() => {
- // Log the error to an error reporting service
- /* eslint-disable no-console */
- console.error(error);
- }, [error]);
-
- return (
-
-
Something went wrong!
-
-
- );
-}
diff --git a/components/icons/Icons.tsx b/components/icons/Icons.tsx
index 9b79a7c35d..aca8d6fc4d 100644
--- a/components/icons/Icons.tsx
+++ b/components/icons/Icons.tsx
@@ -270,3 +270,25 @@ export const WifiOffIcon: React.FC = ({
/>
);
+
+export const RocketIcon: React.FC = ({
+ size = 24,
+ width,
+ height,
+ ...props
+}) => {
+ return (
+
+ );
+};
diff --git a/components/index.ts b/components/index.ts
index faa5b19c57..cc6a14b022 100644
--- a/components/index.ts
+++ b/components/index.ts
@@ -3,6 +3,7 @@ export * from "./providers/ProviderInfo";
export * from "./providers/ScanStatus";
export * from "./providers/table/ColumnsProviders";
export * from "./providers/table/DataTable";
+export * from "./ui/alert/Alert";
export * from "./ui/header/Header";
export * from "./ui/modal";
export * from "./ui/sidebar";
diff --git a/components/ui/alert/Alert.tsx b/components/ui/alert/Alert.tsx
new file mode 100644
index 0000000000..7f6fb50705
--- /dev/null
+++ b/components/ui/alert/Alert.tsx
@@ -0,0 +1,59 @@
+import { cva, type VariantProps } from "class-variance-authority";
+import * as React from "react";
+
+import { cn } from "@/lib/utils";
+
+const alertVariants = cva(
+ "relative w-full rounded-lg border border-slate-200 px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-slate-950 [&>svg~*]:pl-7 dark:border-slate-800 dark:[&>svg]:text-slate-50",
+ {
+ variants: {
+ variant: {
+ default: "bg-white text-slate-950 dark:bg-slate-950 dark:text-slate-50",
+ destructive:
+ "border-red-500/50 text-red-500 dark:border-red-500 [&>svg]:text-red-500 dark:border-red-900/50 dark:text-red-900 dark:dark:border-red-900 dark:[&>svg]:text-red-900",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ },
+);
+
+const Alert = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes & VariantProps
+>(({ className, variant, ...props }, ref) => (
+
+));
+Alert.displayName = "Alert";
+
+const AlertTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+AlertTitle.displayName = "AlertTitle";
+
+const AlertDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+AlertDescription.displayName = "AlertDescription";
+
+export { Alert, AlertDescription, AlertTitle };
diff --git a/lib/actions/provider.actions.ts b/lib/actions/provider.actions.ts
index 12ee153dc1..c1973f0917 100644
--- a/lib/actions/provider.actions.ts
+++ b/lib/actions/provider.actions.ts
@@ -4,11 +4,16 @@ import { parseStringify } from "../utils";
export const getProvider = async () => {
const key = process.env.LOCAL_SITE_URL;
+
+ if (!key) return undefined;
+
try {
const providers = await fetch(`${key}/api/providers`);
+
const data = await providers.json();
+
return parseStringify(data);
} catch (error) {
- console.log(error);
+ return undefined;
}
};
diff --git a/lib/utils.ts b/lib/utils.ts
index 261f2865a8..c3e96dd8df 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -5,16 +5,15 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
-export function capitalize(str: string): string {
- return str.charAt(0).toUpperCase() + str.slice(1);
-}
-
export const parseStringify = (value: any) => JSON.parse(JSON.stringify(value));
export const convertFileToUrl = (file: File) => URL.createObjectURL(file);
// FORMAT DATE TIME
-export const formatDateTime = (dateString: Date | string) => {
+export const formatDateTime = (
+ dateString: Date | string,
+ timeZone: string = Intl.DateTimeFormat().resolvedOptions().timeZone,
+) => {
const dateTimeOptions: Intl.DateTimeFormatOptions = {
// weekday: "short", // abbreviated weekday name (e.g., 'Mon')
month: "short", // abbreviated month name (e.g., 'Oct')
@@ -22,7 +21,8 @@ export const formatDateTime = (dateString: Date | string) => {
year: "numeric", // numeric year (e.g., '2023')
hour: "numeric", // numeric hour (e.g., '8')
minute: "numeric", // numeric minute (e.g., '30')
- hour12: true, // use 12-hour clock (true) or 24-hour clock (false)
+ hour12: true, // use 12-hour clock (true) or 24-hour clock (false),
+ timeZone: timeZone, // use the provided timezone
};
const dateDayOptions: Intl.DateTimeFormatOptions = {
@@ -30,18 +30,21 @@ export const formatDateTime = (dateString: Date | string) => {
year: "numeric", // numeric year (e.g., '2023')
month: "2-digit", // abbreviated month name (e.g., 'Oct')
day: "2-digit", // numeric day of the month (e.g., '25')
+ timeZone: timeZone, // use the provided timezone
};
const dateOptions: Intl.DateTimeFormatOptions = {
month: "short", // abbreviated month name (e.g., 'Oct')
year: "numeric", // numeric year (e.g., '2023')
day: "numeric", // numeric day of the month (e.g., '25')
+ timeZone: timeZone, // use the provided timezone
};
const timeOptions: Intl.DateTimeFormatOptions = {
hour: "numeric", // numeric hour (e.g., '8')
minute: "numeric", // numeric minute (e.g., '30')
hour12: true, // use 12-hour clock (true) or 24-hour clock (false)
+ timeZone: timeZone, // use the provided timezone
};
const formattedDateTime: string = new Date(dateString).toLocaleString(
diff --git a/package.json b/package.json
index c0dd46c781..caad2dcdb8 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,8 @@
"@react-aria/ssr": "3.9.4",
"@react-aria/visually-hidden": "3.8.12",
"@tanstack/react-table": "^8.19.3",
+ "add": "^2.0.6",
+ "alert": "^6.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
@@ -17,7 +19,7 @@
"react": "18.3.1",
"react-dom": "18.3.1",
"server-only": "^0.0.1",
- "swr": "^2.2.5",
+ "shadcn-ui": "^0.8.0",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7"
},
@@ -46,7 +48,7 @@
"postcss": "8.4.38",
"tailwind-variants": "0.1.20",
"tailwindcss": "3.4.3",
- "typescript": "5.0.4",
+ "typescript": "^5.5.4",
"usehooks-ts": "^3.1.0"
},
"name": "prowler-next-app",
diff --git a/tailwind.config.js b/tailwind.config.js
index 5a6f937328..ac42eb57ec 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -2,22 +2,41 @@ import { nextui } from "@nextui-org/theme";
/** @type {import('tailwindcss').Config} */
module.exports = {
+ darkMode: ["class"],
content: [
- "./components/**/*.{js,ts,jsx,tsx,mdx}",
- "./app/**/*.{js,ts,jsx,tsx,mdx}",
+ "./components/**/*.{ts,jsx,tsx,mdx}",
+ "./app/**/*.{ts,jsx,tsx,mdx}",
"./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}",
],
+ prefix: "",
theme: {
+ container: {
+ center: true,
+ padding: "2rem",
+ screens: {
+ "2xl": "1400px",
+ },
+ },
extend: {
fontFamily: {
sans: ["var(--font-sans)"],
mono: ["var(--font-geist-mono)"],
},
- height: {
- "dvh-minus-16": "calc(100dvh - 116px)",
+ keyframes: {
+ "accordion-down": {
+ from: { height: "0" },
+ to: { height: "var(--radix-accordion-content-height)" },
+ },
+ "accordion-up": {
+ from: { height: "var(--radix-accordion-content-height)" },
+ to: { height: "0" },
+ },
+ },
+ animation: {
+ "accordion-down": "accordion-down 0.2s ease-out",
+ "accordion-up": "accordion-up 0.2s ease-out",
},
},
},
- darkMode: "class",
- plugins: [nextui()],
+ plugins: [require("tailwindcss-animate"), nextui()],
};