diff --git a/apps/app/public/onboarding/cloud-management.webp b/apps/app/public/onboarding/cloud-management.webp
new file mode 100644
index 0000000000..76cccee542
Binary files /dev/null and b/apps/app/public/onboarding/cloud-management.webp differ
diff --git a/apps/app/public/onboarding/cloud-tests.png b/apps/app/public/onboarding/cloud-tests.png
deleted file mode 100644
index 9bf69f8789..0000000000
Binary files a/apps/app/public/onboarding/cloud-tests.png and /dev/null differ
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/(overview)/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/(overview)/layout.tsx
deleted file mode 100644
index 0562083cc4..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/(overview)/layout.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { getI18n } from "@/locales/server";
-import { SecondaryMenu } from "@comp/ui/secondary-menu";
-import { auth } from "@comp/auth";
-import { headers } from "next/headers";
-import { redirect } from "next/navigation";
-
-export default async function Layout({
- children,
-}: {
- children: React.ReactNode;
-}) {
- const t = await getI18n();
-
- const organization = await auth.api.getFullOrganization({
- headers: await headers(),
- });
-
- if (!organization) {
- redirect("/");
- }
-
- const organizationId = organization.id;
-
- return (
-
-
-
- {children}
-
- );
-}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/(overview)/loading.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/(overview)/loading.tsx
new file mode 100644
index 0000000000..9e020d05a9
--- /dev/null
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/(overview)/loading.tsx
@@ -0,0 +1,63 @@
+import { Button } from "@comp/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@comp/ui/card";
+import { Input } from "@comp/ui/input";
+import { getI18n } from "@/locales/server";
+
+export default async function Loading() {
+ const t = await getI18n();
+
+ return (
+
+ );
+}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/(overview)/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/(overview)/page.tsx
index 20a1b02893..6b569d8ea7 100644
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/(overview)/page.tsx
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/(overview)/page.tsx
@@ -1,123 +1,49 @@
-import { cache } from "react";
-import { auth } from "@comp/auth";
-import { TestsSeverity } from "@/components/tests/charts/tests-severity";
-import { TestsByAssignee } from "@/components/tests/charts/tests-by-assignee";
+import { AppOnboarding } from "@/components/app-onboarding";
import { getI18n } from "@/locales/server";
-import { db } from "@comp/db";
-import type { Metadata } from "next";
import { setStaticParamsLocale } from "next-international/server";
-import { redirect } from "next/navigation";
-import { headers } from "next/headers";
-export default async function TestsOverview({
- params,
+export default async function CloudTests({
+ params,
}: {
- params: Promise<{ locale: string }>;
+ params: Promise<{ locale: string }>;
}) {
- const { locale } = await params;
- setStaticParamsLocale(locale);
-
- const session = await auth.api.getSession({
- headers: await headers(),
- });
-
- if (!session?.session?.activeOrganizationId) {
- redirect("/onboarding");
- }
-
- const overview = await getTestsOverview(session.session.activeOrganizationId);
-
- if (overview?.totalTests === 0) {
- redirect(`/${session.session.activeOrganizationId}/tests/all`);
- }
-
- return (
-
- );
+ const { locale } = await params;
+ const t = await getI18n();
+ setStaticParamsLocale(locale);
+
+ return (
+
+ );
}
-const getTestsOverview = cache(async (organizationId: string) => {
- return await db.$transaction(async (tx) => {
- const [
- totalTests,
- infoSeverityTests,
- lowSeverityTests,
- mediumSeverityTests,
- highSeverityTests,
- criticalSeverityTests,
- ] = await Promise.all([
- tx.integrationResult.count({
- where: {
- organizationId,
- },
- }),
- tx.integrationResult.count({
- where: {
- organizationId,
- severity: "INFO",
- },
- }),
- tx.integrationResult.count({
- where: {
- organizationId,
- severity: "LOW",
- },
- }),
- tx.integrationResult.count({
- where: {
- organizationId,
- severity: "MEDIUM",
- },
- }),
- tx.integrationResult.count({
- where: {
- organizationId,
- severity: "HIGH",
- },
- }),
- tx.integrationResult.count({
- where: {
- organizationId,
- severity: "CRITICAL",
- },
- }),
- ]);
- return {
- totalTests,
- infoSeverityTests,
- lowSeverityTests,
- mediumSeverityTests,
- highSeverityTests,
- criticalSeverityTests,
- };
- });
-});
-
-export async function generateMetadata({
- params,
-}: {
- params: Promise<{ locale: string }>;
-}): Promise {
- const { locale } = await params;
- setStaticParamsLocale(locale);
- const t = await getI18n();
-
- return {
- title: t("sidebar.tests"),
- };
-}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/assignTest.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/assignTest.ts
deleted file mode 100644
index b3c26eac18..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/assignTest.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-"use server";
-
-import { authActionClient } from "@/actions/safe-action";
-import { db } from "@comp/db";
-import { Role } from "@prisma/client";
-import { z } from "zod";
-
-export const assignTest = authActionClient
- .schema(
- z.object({
- id: z.string(),
- assigneeId: z.string().nullable(),
- }),
- )
- .metadata({
- name: "assignTest",
- track: {
- event: "assign-test",
- channel: "server",
- },
- })
- .action(async ({ ctx, parsedInput }) => {
- const { session } = ctx;
- const { id, assigneeId } = parsedInput;
-
- if (!session.activeOrganizationId) {
- return {
- success: false,
- error: "Not authorized - no organization found",
- };
- }
-
- try {
- // Verify the evidence exists and belongs to the organization
- const test = await db.integrationResult.findFirst({
- where: {
- id,
- organizationId: session.activeOrganizationId,
- },
- });
-
- if (!test) {
- return {
- success: false,
- error: "test not found",
- };
- }
-
- // If assigneeId is provided, verify the user exists and has admin privileges
- if (assigneeId) {
- const adminMember = await db.member.findFirst({
- where: {
- userId: assigneeId,
- organizationId: session.activeOrganizationId,
- role: {
- in: [Role.admin],
- },
- },
- });
-
- if (!adminMember) {
- return {
- success: false,
- error: "User not found or does not have admin privileges",
- };
- }
- }
-
- // Update the evidence with the new assignee
- const updatedTest = await db.integrationResult.update({
- where: {
- id,
- },
- data: {
- assignedUserId: assigneeId,
- },
- });
-
- return {
- success: true,
- data: updatedTest,
- };
- } catch (error) {
- console.error("Error assigning test:", error);
- return {
- success: false,
- error: "Failed to assign Cloud Test",
- };
- }
- });
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/getTest.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/getTest.ts
deleted file mode 100644
index 3d43b1a569..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/getTest.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-"use server";
-
-import { auth } from "@comp/auth";
-import { db } from "@comp/db";
-import { appErrors, type ActionResponse } from "./types";
-
-import type { Test } from "../../types";
-import { headers } from "next/headers";
-
-export async function getTest(input: {
- testId: string;
-}): Promise> {
- const { testId } = input;
-
- const session = await auth.api.getSession({
- headers: await headers(),
- });
-
- const organizationId = session?.session.activeOrganizationId;
-
- if (!organizationId) {
- throw new Error("Organization ID not found");
- }
-
- try {
- const results = await db.integrationResult.findUnique({
- where: {
- id: testId,
- organizationId: organizationId,
- },
- include: {
- integration: true,
- },
- });
-
- if (!results) {
- return {
- success: false,
- error: appErrors.NOT_FOUND,
- };
- }
-
- const integrationResult = results;
-
- // Format the result to match the expected Test structure
- const result: Test = {
- id: integrationResult.id,
- title: integrationResult.title || "",
- description: integrationResult.description || "",
- remediation: integrationResult.remediation || "",
- provider: integrationResult.integration.name,
- status: integrationResult.status || "",
- resultDetails: integrationResult.resultDetails,
- severity: integrationResult.severity || "",
- assignedUserId: integrationResult.assignedUserId || "",
- organizationId: organizationId,
- completedAt: integrationResult.completedAt || new Date(),
- organizationIntegrationId: integrationResult.integrationId || "",
- };
-
- return {
- success: true,
- data: result,
- };
- } catch (error) {
- console.error("Error fetching integration result details:", error);
- return {
- success: false,
- error: appErrors.UNEXPECTED_ERROR,
- };
- }
-}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/types.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/types.ts
deleted file mode 100644
index 19e19008dc..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/types.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { z } from "zod";
-
-export const createTestCommentSchema = z.object({
- testId: z.string().min(1, {
- message: "Test ID is required",
- }),
- content: z
- .string()
- .min(1, {
- message: "Comment content is required",
- })
- .max(1000, {
- message: "Comment content should be at most 1000 characters",
- }),
-});
-
-// Define the app errors
-export const appErrors = {
- NOT_FOUND: { message: "Cloud test not found" },
- UNEXPECTED_ERROR: { message: "An unexpected error occurred" }
-};
-
-export type AppError = typeof appErrors[keyof typeof appErrors];
-
-// Define the input schema
-export const cloudTestDetailsInputSchema = z.object({
- testId: z.string()
-});
-
-// Type-safe action response
-export type ActionResponse = Promise<
- { success: true; data: T } | { success: false; error: AppError }
->;
\ No newline at end of file
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/components/AssigneeSection.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/components/AssigneeSection.tsx
deleted file mode 100644
index ea8468651d..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/components/AssigneeSection.tsx
+++ /dev/null
@@ -1,139 +0,0 @@
-"use client";
-
-import { useState, useEffect } from "react";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@comp/ui/select";
-import { Avatar, AvatarFallback, AvatarImage } from "@comp/ui/avatar";
-import { useAction } from "next-safe-action/hooks";
-import { toast } from "sonner";
-import { assignTest } from "../actions/assignTest";
-import {
- type Admin,
- useOrganizationAdmins,
-} from "@/app/[locale]/(app)/(dashboard)/[orgId]/hooks/useOrganizationAdmins";
-
-interface AssigneeSectionProps {
- testId: string;
- currentAssigneeId: string | null | undefined;
- onSuccess: () => Promise;
-}
-
-export function AssigneeSection({
- testId,
- currentAssigneeId,
- onSuccess,
-}: AssigneeSectionProps) {
- const [assigneeId, setAssigneeId] = useState(
- currentAssigneeId || null,
- );
- const { data: admins, isLoading, error } = useOrganizationAdmins();
- const [selectedAdmin, setSelectedAdmin] = useState(null);
-
- const { execute: updateAssignee, isExecuting } = useAction(assignTest, {
- onSuccess: async () => {
- toast.success("Assignee updated successfully");
- await onSuccess();
- },
- onError: (error) => {
- console.log("error", error);
- toast.error("Failed to update Assignee");
- },
- });
-
- useEffect(() => {
- setAssigneeId(currentAssigneeId || null);
- }, [currentAssigneeId]);
-
- useEffect(() => {
- if (admins && assigneeId) {
- const admin = admins.find((a) => a.id === assigneeId);
- setSelectedAdmin(admin || null);
- } else {
- setSelectedAdmin(null);
- }
- }, [admins, assigneeId]);
-
- const handleAssigneeChange = (value: string) => {
- const newAssigneeId = value === "none" ? null : value;
- setAssigneeId(newAssigneeId);
-
- if (newAssigneeId && admins) {
- const admin = admins.find((a) => a.id === newAssigneeId);
- setSelectedAdmin(admin || null);
- } else {
- setSelectedAdmin(null);
- }
-
- updateAssignee({ id: testId, assigneeId: newAssigneeId });
- };
-
- if (isLoading) {
- return
;
- }
-
- if (error || !admins) {
- return Failed to load
;
- }
-
- return (
-
-
-
- {selectedAdmin ? (
-
-
-
- {selectedAdmin.name.charAt(0)}
-
-
{selectedAdmin.name}
-
- ) : (
-
- )}
-
-
-
-
- None
-
-
- {admins.map((admin) => (
-
-
-
-
- {admin.name.charAt(0)}
-
-
{admin.name}
-
-
- ))}
-
-
-
- );
-}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/components/TestDetails.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/components/TestDetails.tsx
deleted file mode 100644
index 5c5e054ba9..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/components/TestDetails.tsx
+++ /dev/null
@@ -1,241 +0,0 @@
-"use client";
-
-import { useI18n } from "@/locales/client";
-import type { User } from "@comp/db/types";
-import { Alert, AlertDescription, AlertTitle } from "@comp/ui/alert";
-import { Badge } from "@comp/ui/badge";
-import { Card, CardContent, CardHeader, CardTitle } from "@comp/ui/card";
-import { Label } from "@comp/ui/label";
-import { Skeleton } from "@comp/ui/skeleton";
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from "@comp/ui/table";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@comp/ui/tabs";
-import { AlertCircle, User as UserIcon } from "lucide-react";
-import { useTest } from "../../hooks/useTest";
-import { AssigneeSection } from "./AssigneeSection";
-
-interface CloudTestDetailsProps {
- testId: string;
- users: User[];
-}
-
-export function TestDetails({ testId, users }: CloudTestDetailsProps) {
- const t = useI18n();
- const { cloudTest, isLoading, error, mutate } = useTest(testId);
-
- if (error) {
- return (
-
-
-
- Error
-
- {error.message || "An unexpected error occurred"}
-
-
-
- );
- }
-
- if (isLoading) {
- return (
-
- );
- }
-
- if (!cloudTest) return null;
- // Format the test provider for display
- const providerLabel =
- cloudTest.provider === "aws"
- ? "Amazon Web Services"
- : cloudTest.provider === "azure"
- ? "Microsoft Azure"
- : "Google Cloud Platform";
-
- // Format the test status for display with appropriate badge color
- const getStatusBadge = (status: string) => {
- switch (status.toUpperCase()) {
- case "PASSED":
- return {status} ;
- case "IN_PROGRESS":
- return {status} ;
- case "FAILED":
- return {status} ;
- default:
- return {status} ;
- }
- };
-
- // Check if resources exist and have items
- const hasResources =
- cloudTest.resultDetails?.Resources &&
- cloudTest.resultDetails.Resources.length > 0;
- // Set default tab based on resources availability
- const defaultTab = hasResources ? "resources" : "raw-log";
-
- // // Helper function to get the appropriate icon for test run status
- // const getRunStatusIcon = (status: string, result: string | null) => {
- // if (status === "COMPLETED") {
- // if (result === "PASS") {
- // return ;
- // } else if (result === "FAIL") {
- // return ;
- // } else {
- // return ;
- // }
- // } else if (status === "IN_PROGRESS") {
- // return ;
- // } else if (status === "PENDING") {
- // return ;
- // } else {
- // return ;
- // }
- // };
-
- return (
-
-
-
- {cloudTest.title} {getStatusBadge(cloudTest.status)}
-
-
-
- {providerLabel}
-
-
-
-
- {cloudTest.description && (
-
{cloudTest.description}
- )}
-
-
-
- Concerns
-
-
- {cloudTest.description}
-
-
-
- ASSIGNEE
-
-
- mutate() as Promise}
- />
-
-
-
-
-
- Remediation
-
-
- {cloudTest.remediation}
-
-
-
-
-
- {hasResources && (
- Resources
- )}
- Raw Log
-
-
- {hasResources && (
-
-
-
- Resources
-
-
-
-
-
- Id
- Type
- Region
- Partition
-
-
-
- {cloudTest.resultDetails?.Resources.map((resource: any) => (
-
-
-
- {resource.Id}
-
-
-
-
- {resource.Type}
-
-
-
-
- {resource.Region}
-
-
-
-
- {resource.Partition}
-
-
-
- ))}
-
-
-
-
-
- )}
-
-
-
-
- Test Results
-
-
-
-
-
Provider Results
-
- {JSON.stringify(cloudTest.resultDetails, null, 2)}
-
-
-
-
-
-
-
-
- );
-}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/page.tsx
deleted file mode 100644
index e91993e7c1..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/page.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import { auth } from "@comp/auth";
-import { getI18n } from "@/locales/server";
-import { db } from "@comp/db";
-import type { Metadata } from "next";
-import { setStaticParamsLocale } from "next-international/server";
-import { redirect } from "next/navigation";
-import { TestDetails } from "./components/TestDetails";
-import { headers } from "next/headers";
-
-export default async function TestDetailsPage({
- params,
-}: {
- params: Promise<{ locale: string; testId: string }>;
-}) {
- const { locale, testId } = await params;
- setStaticParamsLocale(locale);
-
- const session = await auth.api.getSession({
- headers: await headers(),
- });
- const organizationId = session?.session.activeOrganizationId;
-
- if (!organizationId) {
- redirect("/");
- }
-
- const users = await getUsers(organizationId);
-
- return ;
-}
-
-export async function generateMetadata({
- params,
-}: {
- params: Promise<{ locale: string; testId: string }>;
-}): Promise {
- const { locale } = await params;
-
- setStaticParamsLocale(locale);
- const t = await getI18n();
-
- return {
- title: t("tests.test_details"),
- };
-}
-
-const getUsers = async (organizationId: string) => {
- const users = await db.user.findMany({
- where: { members: { some: { organizationId } } },
- });
-
- return users;
-};
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/actions/getTests.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/actions/getTests.ts
deleted file mode 100644
index f945c3935d..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/actions/getTests.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-"use server";
-
-import { db } from "@comp/db";
-import { authActionClient } from "@/actions/safe-action";
-import { testsInputSchema } from "../types";
-
-export const getTests = authActionClient
- .schema(testsInputSchema)
- .metadata({
- name: "get-tests",
- track: {
- event: "get-tests",
- channel: "server",
- },
- })
- .action(async ({ parsedInput, ctx }) => {
- const { search, severity, status, page = 1, pageSize = 10 } = parsedInput;
- const { session } = ctx;
-
- console.log("--------------------------------");
- console.log("search", search);
- console.log("severity", severity);
- console.log("status", status);
- console.log("page", page);
- console.log("pageSize", pageSize);
- console.log("--------------------------------");
-
- if (!session.activeOrganizationId) {
- return {
- success: false,
- error: "You are not authorized to view tests",
- };
- }
-
- try {
- const skip = (page - 1) * pageSize;
-
- // Use the prisma client with correct model
- const [integrationResults, total] = await Promise.all([
- db.integrationResult.findMany({
- where: {
- organizationId: session.activeOrganizationId,
- ...(search
- ? {
- OR: [
- {
- integration: {
- name: {
- contains: search,
- mode: "insensitive",
- },
- },
- },
- {
- resultDetails: {
- path: ["Title"],
- string_contains: search,
- },
- },
- ],
- }
- : {}),
- ...(status
- ? { status: { equals: status, mode: "insensitive" } }
- : {}),
- ...(severity
- ? { severity: { equals: severity, mode: "insensitive" } }
- : {}),
- },
- include: {
- integration: {
- select: {
- id: true,
- name: true,
- integrationId: true,
- },
- },
- assignedUser: {
- select: {
- id: true,
- name: true,
- email: true,
- image: true,
- },
- },
- },
- skip,
- take: pageSize,
- orderBy: { completedAt: "desc" },
- }),
- db.integrationResult.count({
- where: {
- organizationId: session.activeOrganizationId,
- ...(search
- ? {
- OR: [
- {
- integration: {
- name: {
- contains: search,
- mode: "insensitive",
- },
- },
- },
- {
- resultDetails: {
- path: ["Title"],
- string_contains: search,
- },
- },
- ],
- }
- : {}),
- ...(status
- ? { status: { equals: status, mode: "insensitive" } }
- : {}),
- ...(severity
- ? { severity: { equals: severity, mode: "insensitive" } }
- : {}),
- },
- }),
- ]);
-
- // Transform the data to include integration info
- const transformedTests = integrationResults.map((result: any) => {
- return {
- id: result.id,
- severity: result.severity,
- result: result.status,
- title: result.title || result.integration.name,
- provider: result.integration.integrationId,
- createdAt: result.completedAt || new Date(),
- // The executedBy information is no longer available in the new schema
- assignedUser: result.assignedUser,
- };
- });
-
- return {
- success: true,
- data: { tests: transformedTests, total },
- };
- } catch (error) {
- console.error("Error fetching integration results:", error);
- return {
- success: false,
- error: "An unexpected error occurred",
- };
- }
- });
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/actions/refreshTests.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/actions/refreshTests.ts
deleted file mode 100644
index 6d27e56645..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/actions/refreshTests.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-"use server";
-
-import { authActionClient } from "@/actions/safe-action";
-import { decrypt } from "@comp/app/src/lib/encryption";
-import { db } from "@comp/db";
-import type { DecryptFunction } from "@comp/integrations";
-import { getIntegrationHandler } from "@comp/integrations";
-
-export const refreshTestsAction = authActionClient
- .metadata({
- name: "refresh-tests",
- track: {
- event: "refresh-tests",
- channel: "server",
- },
- })
- .action(async ({ parsedInput, ctx }) => {
- const { session } = ctx;
-
- if (!session?.activeOrganizationId || !session.activeOrganizationId) {
- throw new Error("Invalid user input");
- }
-
- const integrationsTable = await db.integration.findMany({
- where: {
- organizationId: session.activeOrganizationId,
- },
- });
-
- for (const integration of integrationsTable) {
- // Get the integration handler with proper typing
- const integrationHandler = getIntegrationHandler(
- integration.integrationId,
- );
-
- // Skip if no handler is found for this integration type
- if (!integrationHandler) {
- console.log(
- `No handler found for integration type: ${integration.integrationId}`,
- );
- continue;
- }
-
- try {
- // Process credentials using the integration handler
- const userSettings = integration.userSettings as unknown as Record<
- string,
- unknown
- >;
-
- // Pass the decrypt function to the processCredentials method
- const typedCredentials = await integrationHandler.processCredentials(
- userSettings,
- // Cast decrypt to match the expected DecryptFunction type
- decrypt as unknown as DecryptFunction,
- );
-
- // Fetch results using properly typed credentials
- const results = await integrationHandler.fetch(typedCredentials);
-
- // Store the integration results using model name that matches the database
- for (const result of results) {
- // First verify the integration exists
- const existingIntegration = await db.integration.findUnique({
- where: { id: integration.id },
- });
-
- if (!existingIntegration) {
- console.log(`Integration with ID ${integration.id} not found`);
- continue;
- }
-
- // Check if a result with the same title already exists
- const existingResult = await db.integrationResult.findFirst({
- where: {
- title: result.title,
- integrationId: existingIntegration.id,
- },
- });
-
- if (existingResult) {
- // Update the existing result instead of creating a new one
- await db.integrationResult.update({
- where: { id: existingResult.id },
- data: {
- title: result.title,
- description: result.description,
- remediation: result.remediation,
- status: result.status,
- severity: result.severity,
- resultDetails: result.resultDetails,
- },
- });
- continue;
- }
-
- await db.integrationResult.create({
- data: {
- title: result.title,
- description: result.description,
- remediation: result.remediation,
- status: result.status,
- severity: result.severity,
- resultDetails: result.resultDetails,
- integrationId: existingIntegration.id,
- organizationId: integration.organizationId,
- },
- });
- }
- } catch (error) {
- console.error(
- `Error processing ${integration.integrationId} integration:`,
- error,
- );
- }
- }
-
- console.log("Refreshing tests");
-
- return { success: true };
- });
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/TestsList.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/TestsList.tsx
deleted file mode 100644
index bf2135cf09..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/TestsList.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-"use client";
-
-import { NoTests } from "./table/empty-states";
-import { Loading } from "./table/loading";
-import { useTests } from "../hooks/useTests";
-import { TestsTable } from "./table/TestsTable";
-import { TestsTableProvider } from "../hooks/useTestsTableContext";
-
-export function TestsList() {
- const { tests, isLoading, error } = useTests('');
-
- if (isLoading) {
- return ;
- }
-
- if (error) {
- return (
-
-
Tests
-
- Error loading tests: {error.message}
-
-
- );
- }
-
- if (tests.length === 0) {
- return (
-
-
-
-
- );
- }
-
- return (
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/TestsTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/TestsTable.tsx
deleted file mode 100644
index d9e00bf6ff..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/TestsTable.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-"use client";
-
-import { DataTable } from "@/components/ui/data-table";
-import { useParams, useRouter } from "next/navigation";
-import { getFilterCategories } from "./filterCategories";
-import { getColumns } from "./columns";
-import { useTestsTable } from "../../hooks/useTestsTableContext";
-import { RefreshCcw } from "lucide-react";
-import { refreshTestsAction } from "../../actions/refreshTests";
-import { useAction } from "next-safe-action/hooks";
-import { toast } from "sonner";
-import { useI18n } from "@/locales/client";
-
-export function TestsTable() {
- const router = useRouter();
- const { orgId } = useParams<{ orgId: string }>();
- const t = useI18n();
-
- const {
- page,
- setPage,
- pageSize,
- setPageSize,
- tests,
- total,
- search,
- setSearch,
- status,
- setStatus,
- severity,
- setSeverity,
- hasActiveFilters,
- clearFilters,
- isLoading,
- isSearching,
- } = useTestsTable();
-
- const refreshTests = useAction(refreshTestsAction, {
- onSuccess: () => {
- toast.success(t("tests.actions.refresh_success"));
- window.location.reload();
- },
- onError: () => {
- toast.error(t("tests.actions.refresh_error"));
- },
- });
-
- const handleRowClick = (testId: string) => {
- router.replace(`/${orgId}/tests/all/${testId}`);
- };
-
- const activeFilterCount = [status, severity].filter(Boolean).length;
-
- const filterCategories = getFilterCategories({
- status,
- setStatus,
- severity: severity,
- setSeverity: setSeverity,
- setPage,
- });
-
- // Calculate pagination values only when total is defined
- const pagination =
- total !== undefined
- ? {
- page: Number(page),
- pageSize: Number(pageSize),
- totalCount: total,
- totalPages: Math.ceil(total / Number(pageSize)),
- hasNextPage: Number(page) * Number(pageSize) < total,
- hasPreviousPage: Number(page) > 1,
- }
- : undefined;
-
- return (
- handleRowClick(row.id)}
- emptyMessage="No tests found."
- isLoading={isLoading || isSearching}
- pagination={pagination}
- onPageChange={(page) => setPage(page.toString())}
- onPageSizeChange={(pageSize) => setPageSize(pageSize.toString())}
- search={{
- value: search || "",
- onChange: setSearch,
- placeholder: "Search tests...",
- }}
- filters={{
- categories: filterCategories,
- hasActiveFilters,
- onClearFilters: clearFilters,
- activeFilterCount,
- }}
- ctaButton={{
- label: "Refresh Tests",
- onClick: () => refreshTests.execute(),
- icon: ,
- }}
- />
- );
-}
\ No newline at end of file
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/columns.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/columns.tsx
deleted file mode 100644
index 9704290fea..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/columns.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-"use client";
-
-import type { ColumnDef } from "@tanstack/react-table";
-import type { TestRow, TestResult, TestSeverity } from "../../types";
-import { Badge } from "@comp/ui/badge";
-import Image from "next/image";
-import { integrations } from "@comp/integrations";
-import { AssignedUser } from "@/components/assigned-user";
-
-const getSeverityBadge = (severity: TestSeverity) => {
- if (!severity) return Unknown ;
-
- switch (severity) {
- case "INFO":
- return {severity} ;
- case "LOW":
- return {severity} ;
- case "MEDIUM":
- return {severity} ;
- case "HIGH":
- return {severity} ;
- case "CRITICAL":
- return {severity} ;
- default:
- return {severity} ;
- }
-};
-
-const getResultsBadge = (result: TestResult) => {
- switch (result) {
- case "PASSED":
- return {result} ;
- case "IN_PROGRESS":
- return {result} ;
- case "FAILED":
- return {result} ;
- default:
- return {result} ;
- }
-};
-
-const getProviderLogo = (provider: string): string => {
- const integration = integrations.find((i) => i.id === provider);
- return typeof integration?.logo === "string" ? integration.logo : "";
-};
-
-export function getColumns(
- handleRowClick: (testId: string) => void,
-): ColumnDef[] {
- return [
- {
- id: "severity",
- header: "Severity",
- accessorKey: "severity",
- cell: ({ row }) => {
- return getSeverityBadge(row.original.severity);
- },
- },
- {
- id: "result",
- header: "Result",
- accessorKey: "result",
- cell: ({ row }) => {
- return getResultsBadge(row.original.result);
- },
- },
- {
- id: "title",
- header: "Title",
- accessorKey: "title",
- cell: ({ row }) => {
- const title = row.original.title;
- return (
-
- handleRowClick(row.original.id)}
- >
- {title}
-
-
- );
- },
- },
- {
- id: "provider",
- header: "Provider",
- accessorKey: "provider",
- cell: ({ row }) => {
- const provider = row.original.provider;
- const logo = getProviderLogo(provider);
-
- return (
-
- {logo && (
-
- )}
- {provider}
-
- );
- },
- },
- {
- id: "createdAt",
- header: "Created",
- accessorKey: "createdAt",
- cell: ({ row }) => {
- const date = new Date(row.original.createdAt);
- return (
-
- {date.toLocaleDateString("en-US", {
- year: "numeric",
- month: "short",
- day: "numeric",
- })}
-
- );
- },
- },
- {
- id: "assignedUser",
- header: "Assigned To",
- accessorKey: "assignedUser",
- cell: ({ row }) => {
- const assignedUser = row.original.assignedUser;
- if (!assignedUser) {
- return (
- Not assigned
- );
- }
- return (
-
- );
- },
- },
- ];
-}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/data-table-header.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/data-table-header.tsx
deleted file mode 100644
index c95ff06dfa..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/data-table-header.tsx
+++ /dev/null
@@ -1,165 +0,0 @@
-"use client";
-
-import { useI18n } from "@/locales/client";
-import { Button } from "@comp/ui/button";
-import { TableHead, TableHeader, TableRow } from "@comp/ui/table";
-import { ArrowDown, ArrowUp } from "lucide-react";
-import { usePathname, useRouter, useSearchParams } from "next/navigation";
-import { useCallback } from "react";
-
-type Props = {
- table?: {
- getIsAllPageRowsSelected: () => boolean;
- getIsSomePageRowsSelected: () => boolean;
- getAllLeafColumns: () => {
- id: string;
- getIsVisible: () => boolean;
- }[];
- toggleAllPageRowsSelected: (value: boolean) => void;
- };
- loading?: boolean;
- isEmpty?: boolean;
-};
-
-export function DataTableHeader({ table, loading }: Props) {
- const searchParams = useSearchParams();
- const pathname = usePathname();
- const router = useRouter();
- const t = useI18n();
-
- const sortParam = searchParams.get("sort");
- const [column, value] = sortParam ? sortParam.split(":") : [];
-
- const createSortQuery = useCallback(
- (name: string) => {
- const params = new URLSearchParams(searchParams);
- const prevSort = params.get("sort");
-
- if (`${name}:asc` === prevSort) {
- params.set("sort", `${name}:desc`);
- } else if (`${name}:desc` === prevSort) {
- params.delete("sort");
- } else {
- params.set("sort", `${name}:asc`);
- }
-
- router.replace(`${pathname}?${params.toString()}`);
- },
- [searchParams, router, pathname],
- );
-
- const isVisible = (id: string) =>
- loading ||
- table
- ?.getAllLeafColumns()
- .find((col) => col.id === id)
- ?.getIsVisible();
-
- return (
-
-
- {isVisible("severity") && (
-
- createSortQuery("severity")}
- >
- {t("tests.table.severity")}
- {"severity" === column && value === "asc" && (
-
- )}
- {"severity" === column && value === "desc" && (
-
- )}
-
-
- )}
-
- {isVisible("result") && (
-
- createSortQuery("result")}
- >
- {t("tests.table.result")}
- {"result" === column && value === "asc" && (
-
- )}
- {"result" === column && value === "desc" && }
-
-
- )}
-
- {isVisible("title") && (
-
- createSortQuery("title")}
- >
- {t("tests.table.title")}
- {"title" === column && value === "asc" && }
- {"title" === column && value === "desc" && }
-
-
- )}
-
- {isVisible("provider") && (
-
- createSortQuery("provider")}
- >
- {t("tests.table.provider")}
- {"provider" === column && value === "asc" && (
-
- )}
- {"provider" === column && value === "desc" && (
-
- )}
-
-
- )}
-
- {isVisible("createdAt") && (
-
- createSortQuery("createdAt")}
- >
- {t("tests.table.createdAt")}
- {"createdAt" === column && value === "asc" && (
-
- )}
- {"createdAt" === column && value === "desc" && (
-
- )}
-
-
- )}
-
- {isVisible("assignedUser") && (
-
- createSortQuery("assignedUser")}
- >
- {t("tests.table.assignedUser")}
- {"assignedUser" === column && value === "asc" && (
-
- )}
- {"assignedUser" === column && value === "desc" && (
-
- )}
-
-
- )}
-
-
- );
-}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/empty-states.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/empty-states.tsx
deleted file mode 100644
index eda40ef3aa..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/empty-states.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-"use client";
-
-import { useI18n } from "@/locales/client";
-import { Button } from "@comp/ui/button";
-import { CloudOff } from "lucide-react";
-import { useParams, useRouter } from "next/navigation";
-import { refreshTestsAction } from "@/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/actions/refreshTests";
-import { useAction } from "next-safe-action/hooks";
-import { toast } from "sonner";
-
-export function NoTests() {
- const t = useI18n();
-
- const refreshTests = useAction(refreshTestsAction, {
- onSuccess: () => {
- toast.success(t("tests.actions.refresh_success"));
- window.location.reload();
- },
- onError: () => {
- toast.error(t("tests.actions.refresh_error"));
- },
- });
-
- const refreshTestsClick = () => {
- refreshTests.execute();
- };
-
- return (
-
-
-
-
- {t("tests.empty.no_tests.title")}
-
-
- {t("tests.empty.no_tests.description")}
-
-
- {t("tests.actions.refresh")}
-
-
-
- );
-}
-
-export function NoResults({ hasFilters }: { hasFilters: boolean }) {
- const router = useRouter();
- const t = useI18n();
- const { orgId } = useParams<{ orgId: string }>();
-
- return (
-
-
-
-
-
- {t("tests.empty.no_results.title")}
-
-
- {hasFilters
- ? t("tests.empty.no_results.description_with_filters")
- : t("tests.empty.no_results.description")}
-
-
-
- {hasFilters && (
-
router.push(`${orgId}/tests`)}
- >
- {t("tests.actions.clear")}
-
- )}
-
-
- );
-}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/filter-toolbar.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/filter-toolbar.tsx
deleted file mode 100644
index b0fb13b082..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/filter-toolbar.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-"use client";
-
-import { useI18n } from "@/locales/client";
-import { Button } from "@comp/ui/button";
-import { Input } from "@comp/ui/input";
-import { Search, X } from "lucide-react";
-import { usePathname, useRouter, useSearchParams } from "next/navigation";
-import { useCallback, useEffect, useState, useTransition } from "react";
-import { useDebounce } from "use-debounce";
-import { refreshTestsAction } from "@/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/actions/refreshTests";
-import { useAction } from "next-safe-action/hooks";
-import { toast } from "sonner";
-
-interface FilterToolbarProps {
- isEmpty?: boolean;
-}
-
-export function FilterToolbar({ isEmpty = false }: FilterToolbarProps) {
- const router = useRouter();
- const pathname = usePathname();
- const searchParams = useSearchParams();
- const t = useI18n();
- const [isPending, startTransition] = useTransition();
- const [inputValue, setInputValue] = useState(
- searchParams?.get("search") ?? "",
- );
-
- const refreshTests = useAction(refreshTestsAction, {
- onSuccess: () => {
- toast.success(t("tests.actions.refresh_success"));
- window.location.reload();
- },
- onError: () => {
- toast.error(t("tests.actions.refresh_error"));
- },
- });
-
- const refreshTestsClick = () => {
- refreshTests.execute();
- };
-
- const createQueryString = useCallback(
- (params: Record) => {
- const newSearchParams = new URLSearchParams(searchParams?.toString());
-
- for (const [key, value] of Object.entries(params)) {
- if (value === null) {
- newSearchParams.delete(key);
- } else {
- newSearchParams.set(key, value);
- }
- }
-
- return newSearchParams.toString();
- },
- [searchParams],
- );
-
- const [debouncedValue] = useDebounce(inputValue, 300);
-
- useEffect(() => {
- startTransition(() => {
- router.push(
- `${pathname}?${createQueryString({
- search: debouncedValue || null,
- page: null,
- })}`,
- );
- });
- }, [debouncedValue, createQueryString, pathname, router]);
-
- return (
-
-
- {!isEmpty && (
-
-
- setInputValue(e.target.value)}
- />
-
- )}
-
-
-
- {inputValue && (
- {
- setInputValue("");
- router.push(pathname);
- }}
- disabled={isPending}
- >
-
- {t("common.actions.clear")}
-
- )}
-
- {t("tests.actions.refresh")}
-
-
-
- );
-}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/filterCategories.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/filterCategories.tsx
deleted file mode 100644
index 8644b36808..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/filterCategories.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-"use client";
-
-import { STATUS_FILTERS, SEVERITY_FILTERS } from "./filterConfigs";
-
-interface FilterCategoriesProps {
- status: string | null;
- setStatus: (status: string | null) => void;
- severity: string | null;
- setSeverity: (severity: string | null) => void;
- setPage: (page: string) => void;
-}
-
-export function getFilterCategories({
- status,
- setStatus,
- severity,
- setSeverity,
- setPage,
-}: FilterCategoriesProps) {
- return [
- {
- label: "Filter by Status",
- items: STATUS_FILTERS.map((filter) => ({
- ...filter,
- checked: status === filter.value,
- onChange: (checked: boolean) => {
- setStatus(checked ? filter.value : null);
- setPage("1");
- },
- })),
- },
- {
- label: "Filter by Severity",
- items: SEVERITY_FILTERS.map((filter) => ({
- ...filter,
- checked: severity === filter.value,
- onChange: (checked: boolean) => {
- setSeverity(checked ? filter.value : null);
- setPage("1");
- },
- })),
- },
- ];
-}
\ No newline at end of file
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/filterConfigs.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/filterConfigs.tsx
deleted file mode 100644
index 89dac81f09..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/filterConfigs.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { CheckCircle2, XCircle, Clock } from "lucide-react";
-
-export const STATUS_FILTERS = [
- {
- label: "Passed",
- value: "PASSED",
- icon: ,
- },
- {
- label: "Failed",
- value: "FAILED",
- icon: ,
- },
- {
- label: "In Progress",
- value: "IN_PROGRESS",
- icon: ,
- },
-] as const;
-
-export const SEVERITY_FILTERS = [
- {
- label: "Critical",
- value: "CRITICAL",
- icon:
,
- },
- {
- label: "High",
- value: "HIGH",
- icon:
,
- },
- {
- label: "Medium",
- value: "MEDIUM",
- icon:
,
- },
- {
- label: "Low",
- value: "LOW",
- icon:
,
- },
- {
- label: "Info",
- value: "INFO",
- icon:
,
- },
-] as const;
\ No newline at end of file
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/loading.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/loading.tsx
deleted file mode 100644
index e5eb104310..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/components/table/loading.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-"use client";
-
-import { cn } from "@comp/ui/cn";
-import { Skeleton } from "@comp/ui/skeleton";
-import { Table, TableBody, TableCell, TableRow } from "@comp/ui/table";
-import { Suspense } from "react";
-import { DataTableHeader } from "./data-table-header";
-
-const data = [...Array(10)].map((_, i) => ({ id: i.toString() }));
-
-export function Loading({ isEmpty }: { isEmpty: boolean }) {
- return (
- }>
-
-
-
-
-
- {data?.map((row) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ))}
-
-
-
-
- );
-}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/hooks/useTest.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/hooks/useTest.ts
deleted file mode 100644
index bb0b6a0ff6..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/hooks/useTest.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-"use client";
-import useSWR from "swr";
-import { getTest } from "@/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/[testId]/actions/getTest";
-import type {
- AppError,
- Test,
-} from "@/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/types";
-
-async function fetchTest(testId: string): Promise {
- try {
- const response = await getTest({ testId });
-
- if (response.success) {
- return response.data;
- }
-
- throw response.error;
- } catch (error) {
- if (error && typeof error === "object" && "message" in error) {
- throw error as AppError;
- }
- throw { message: "An unexpected error occurred" };
- }
-}
-
-export function useTest(testId: string) {
- const { data, error, isLoading, mutate } = useSWR(
- testId ? ["cloud-test-details", testId] : null,
- () => fetchTest(testId),
- {
- revalidateOnFocus: false,
- revalidateOnReconnect: false,
- }
- );
-
- return {
- cloudTest: data,
- isLoading,
- error,
- mutate,
- };
-}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/hooks/useTests.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/hooks/useTests.ts
deleted file mode 100644
index 829acd7f07..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/hooks/useTests.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-"use client";
-import useSWR from "swr";
-import { useSearchParams } from "next/navigation";
-
-import { getTests } from "../actions/getTests";
-import type { TestsResponse, TestsInput, AppError } from "../types";
-
-/** Fetcher function for tests */
-async function fetchTests(input: TestsInput): Promise {
- const result = await getTests(input);
-
- if (!result) {
- const error: AppError = {
- code: "UNEXPECTED_ERROR",
- message: "An unexpected error occurred",
- };
- throw error;
- }
-
- if (result.serverError) {
- const error: AppError = {
- code: "UNEXPECTED_ERROR",
- message: result.serverError || "An unexpected error occurred",
- };
- throw error;
- }
-
- if (!result.data) {
- const error: AppError = {
- code: "UNEXPECTED_ERROR",
- message: "No data returned from server",
- };
- throw error;
- }
-
- return result.data.data as TestsResponse;
-}
-
-export function useTests(search: string | undefined) {
- const searchParams = useSearchParams();
- const severity = searchParams.get("severity") || undefined;
- const status = searchParams.get("status") || undefined;
- const page = Number(searchParams.get("page")) || 1;
- const pageSize = Number(searchParams.get("pageSize")) || 10;
-
- /** SWR for fetching tests */
- const {
- data,
- error,
- isLoading,
- mutate: revalidateTests,
- } = useSWR(
- ["tests", { search, severity, status, page, pageSize }],
- () => fetchTests({ search, severity, status, page, pageSize }),
- {
- revalidateOnFocus: false,
- revalidateOnReconnect: false,
- }
- );
-
- // Format the tests to match the TestRow type
- const formattedTests = data?.tests.map(test => ({
- ...test,
- // Convert Date to string for use in components if needed
- createdAt: test.createdAt
- })) || [];
-
- return {
- tests: formattedTests,
- total: data?.total ?? 0,
- isLoading,
- error,
- revalidateTests
- };
-}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/hooks/useTestsTableContext.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/hooks/useTestsTableContext.tsx
deleted file mode 100644
index 181651a1bd..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/hooks/useTestsTableContext.tsx
+++ /dev/null
@@ -1,156 +0,0 @@
-"use client";
-
-import {
- createContext,
- useContext,
- useMemo,
- useState,
- useRef,
- useEffect,
- type ReactNode,
-} from "react";
-import { useQueryState } from "nuqs";
-import { useTests } from "./useTests";
-
-interface TestsTableContextType {
- // State
- search: string;
- severity: string | null;
- status: string | null;
- page: string;
- pageSize: string;
-
- // Setters
- setSearch: (value: string) => void;
- setSeverity: (value: string | null) => void;
- setStatus: (value: string | null) => void;
- setPage: (value: string) => void;
- setPageSize: (value: string) => void;
-
- // Data
- tests: any[] | undefined;
- total: number | undefined;
- isLoading: boolean;
- isSearching: boolean;
-
- // Derived data
- hasActiveFilters: boolean;
-
- // Actions
- clearFilters: () => void;
-}
-
-const TestsTableContext = createContext<
- TestsTableContextType | undefined
->(undefined);
-
-export function TestsTableProvider({ children }: { children: ReactNode }) {
- // Local state for search
- const [search, setSearch] = useState("");
-
- // Query state for other filters
- const [severity, setSeverity] = useQueryState("severity");
- const [status, setStatus] = useQueryState("status");
- const [page, setPage] = useQueryState("page", { defaultValue: "1" });
- const [pageSize, setPageSize] = useQueryState("pageSize", {
- defaultValue: "10",
- });
-
- // Track if this is initial load or a search/filter update
- const initialLoadCompleted = useRef(false);
- const [isSearching, setIsSearching] = useState(false);
-
- const currentPage = Number.parseInt(page, 10);
- const currentPageSize = Number.parseInt(pageSize, 10);
-
- // Fetch data
- const { tests, total, isLoading } = useTests(search);
-
- // Track when search params change
- useEffect(() => {
- if (initialLoadCompleted.current) {
- setIsSearching(true);
- }
- }, [search, severity, status, page, pageSize]);
-
- // Track when loading changes
- useEffect(() => {
- if (isLoading === false) {
- // Small delay to ensure UI transitions properly
- setTimeout(() => {
- initialLoadCompleted.current = true;
- setIsSearching(false);
- }, 50);
- }
- }, [isLoading]);
-
- // Additional safety reset for isSearching when data changes
- useEffect(() => {
- if (tests && isSearching) {
- // If we have data, ensure isSearching is eventually set to false
- const timer = setTimeout(() => {
- setIsSearching(false);
- }, 100);
- return () => clearTimeout(timer);
- }
- }, [tests, isSearching]);
-
- // Check if any filters are active
- const hasActiveFilters = useMemo(() => {
- return severity !== null || status !== null;
- }, [severity, status]);
-
- // Clear all filters
- const clearFilters = () => {
- setSeverity(null);
- setStatus(null);
- setPage("1"); // Reset to first page when clearing filters
- setSearch(""); // Clear search
- };
-
- const contextValue: TestsTableContextType = {
- // State
- search,
- severity,
- status,
- page,
- pageSize,
-
- // Setters
- setSearch,
- setSeverity,
- setStatus,
- setPage,
- setPageSize,
-
- // Data
- tests,
- total,
- isLoading,
- isSearching,
-
- // Derived data
- hasActiveFilters,
-
- // Actions
- clearFilters,
- };
-
- return (
-
- {children}
-
- );
-}
-
-export function useTestsTable() {
- const context = useContext(TestsTableContext);
-
- if (context === undefined) {
- throw new Error(
- "useTestsTable must be used within a TestsTableProvider"
- );
- }
-
- return context;
-}
\ No newline at end of file
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/layout.tsx
deleted file mode 100644
index 4261e1dd2a..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/layout.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import { auth } from "@comp/auth";
-import { getI18n } from "@/locales/server";
-import { SecondaryMenu } from "@comp/ui/secondary-menu";
-import { headers } from "next/headers";
-import { AppOnboarding } from "@/components/app-onboarding";
-import { db } from "@comp/db";
-import { Suspense, cache } from "react";
-
-export default async function Layout({
- children,
-}: {
- children: React.ReactNode;
-}) {
- const t = await getI18n();
-
- const session = await auth.api.getSession({
- headers: await headers(),
- });
-
- const organizationId = session?.session?.activeOrganizationId;
-
- const tests = await getTestsOverview();
- console.log(tests);
-
- if (!tests.length) {
- return (
-
- Loading...
}>
-
-
-
- );
- }
-
- return (
-
-
- {children}
-
- );
-}
-
-const getTestsOverview = cache(async () => {
- const session = await auth.api.getSession({
- headers: await headers(),
- });
- const orgId = session?.session?.activeOrganizationId;
-
- if (!orgId) return [];
-
- const tests = await db.integrationResult.findMany({
- where: {
- organizationId: orgId,
- },
- });
-
- return tests;
-});
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/page.tsx
deleted file mode 100644
index c8e01dcc54..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/page.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { auth } from "@comp/auth";
-import { getI18n } from "@/locales/server";
-import type { Metadata } from "next";
-import { setStaticParamsLocale } from "next-international/server";
-import { redirect } from "next/navigation";
-import { TestsList } from "./components/TestsList";
-import { headers } from "next/headers";
-
-export default async function TestsPage({
- params,
-}: {
- params: Promise<{ locale: string }>;
-}) {
- const { locale } = await params;
- setStaticParamsLocale(locale);
-
- const session = await auth.api.getSession({
- headers: await headers(),
- });
-
- const organizationId = session?.session.activeOrganizationId;
-
- if (!organizationId) {
- return redirect("/");
- }
-
- return ;
-}
-
-export async function generateMetadata({
- params,
-}: {
- params: Promise<{ locale: string }>;
-}): Promise {
- const { locale } = await params;
-
- setStaticParamsLocale(locale);
- const t = await getI18n();
-
- return {
- title: t("sidebar.tests"),
- };
-}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/types/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/types/index.ts
deleted file mode 100644
index 3170a36438..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/types/index.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import { z } from "zod";
-import type { User } from "next-auth";
-import type { ReactNode } from "react";
-
-export const testSchema = z.object({
- id: z.string(),
- title: z.string(),
- description: z.string().nullable(),
- remediation: z.string().nullable(),
- provider: z.string(),
- status: z.string(),
- resultDetails: z.any(),
- severity: z.string().nullable(),
- completedAt: z.date(),
- organizationId: z.string(),
- assignedUserId: z.string(),
- organizationIntegrationId: z.string(),
-});
-
-export const testsInputSchema = z.object({
- search: z.string().optional(),
- severity: z.string().optional(),
- status: z.string().optional(),
- page: z.number().optional(),
- pageSize: z.number().optional(),
-});
-
-export type Test = z.infer;
-export type TestsInput = z.infer;
-
-export interface TestsResponse {
- tests: {
- id: string;
- severity: string | null;
- result: string;
- title: string;
- provider: string;
- createdAt: Date;
- assignedUser: null;
- }[];
- total: number;
-}
-
-export interface AppError {
- code: string;
- message: string;
-}
-
-export const appErrors = {
- UNAUTHORIZED: {
- code: "UNAUTHORIZED" as const,
- message: "You are not authorized to view employees",
- },
- UNEXPECTED_ERROR: {
- code: "UNEXPECTED_ERROR" as const,
- message: "An unexpected error occurred",
- },
-} as const;
-
-export type TestResult = "PASSED" | "FAILED" | "IN_PROGRESS";
-export type TestSeverity =
- | "INFO"
- | "LOW"
- | "MEDIUM"
- | "HIGH"
- | "CRITICAL"
- | null;
-
-export interface TestRow {
- id: string;
- severity: TestSeverity;
- result: TestResult;
- title: string;
- provider: string;
- createdAt: string | Date; // Allow both string and Date to handle different data formats
- assignedUser: {
- id: string;
- name: string | null;
- image: string | null;
- } | null;
-}
-
-export interface TestsTableProps {
- users: User[];
- ctaButton?: {
- label: string;
- onClick: () => void;
- icon?: ReactNode;
- };
-}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/types/search-params.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/types/search-params.ts
deleted file mode 100644
index 2654a2e0ee..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/all/types/search-params.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import {
- createSearchParamsCache,
- parseAsInteger,
- parseAsString,
-} from "nuqs/server";
-
-export const searchParamsCache = createSearchParamsCache({
- q: parseAsString,
- page: parseAsInteger.withDefault(0),
- start: parseAsString,
- end: parseAsString,
-});
diff --git a/apps/app/src/components/app-onboarding.tsx b/apps/app/src/components/app-onboarding.tsx
index 6edf9fff8f..46493db92a 100644
--- a/apps/app/src/components/app-onboarding.tsx
+++ b/apps/app/src/components/app-onboarding.tsx
@@ -1,18 +1,18 @@
"use client";
import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
} from "@comp/ui/card";
import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
} from "@comp/ui/accordion";
import Image from "next/image";
import { Button } from "@comp/ui/button";
@@ -21,94 +21,96 @@ import { useI18n } from "@/locales/client";
import { useQueryState } from "nuqs";
interface FAQ {
- questionKey: string;
- answerKey: string;
+ questionKey: string;
+ answerKey: string;
}
type Props = {
- title: string;
- description: string;
- cta: string;
- imageSrc: string;
- imageAlt: string;
- faqs?: FAQ[];
- sheetName: string;
+ title: string;
+ description: string;
+ cta?: string;
+ imageSrc: string;
+ imageAlt: string;
+ faqs?: FAQ[];
+ sheetName: string;
};
export function AppOnboarding({
- title,
- description,
- cta,
- imageSrc,
- imageAlt,
- faqs,
- sheetName,
+ title,
+ description,
+ cta,
+ imageSrc,
+ imageAlt,
+ faqs,
+ sheetName,
}: Props) {
- const t = useI18n();
- const [open, setOpen] = useQueryState(sheetName);
- const isOpen = Boolean(open);
+ const t = useI18n();
+ const [open, setOpen] = useQueryState(sheetName);
+ const isOpen = Boolean(open);
- return (
-
-
-
-
-
- {title}
-
- {description}
-
-
-
-
-
-
-
- {t("app_onboarding.risk_management.learn_more")}
-
- {faqs && faqs.length > 0 && (
-
- {faqs.map((faq) => (
-
-
- {faq.questionKey}
-
- {faq.answerKey}
-
- ))}
-
- )}
-
-
-
-
-
-
-
-
-
-
- {cta && (
- setOpen("true")}
- >
-
- {cta}
-
- )}
-
-
-
- );
+ return (
+
+
+
+
+
+ {title}
+
+ {description}
+
+
+
+
+
+
+
+ {t("app_onboarding.risk_management.learn_more")}
+
+ {faqs && faqs.length > 0 && (
+
+ {faqs.map((faq) => (
+
+
+ {faq.questionKey}
+
+ {faq.answerKey}
+
+ ))}
+
+ )}
+
+
+
+
+
+
+
+
+
+ {cta && (
+
+ {cta && (
+ setOpen("true")}
+ >
+
+ {cta}
+
+ )}
+
+ )}
+
+
+ );
}
diff --git a/apps/app/src/components/main-menu.tsx b/apps/app/src/components/main-menu.tsx
index 7cc8a36808..3f858ef3b1 100644
--- a/apps/app/src/components/main-menu.tsx
+++ b/apps/app/src/components/main-menu.tsx
@@ -4,263 +4,263 @@ import { useI18n } from "@/locales/client";
import { cn } from "@comp/ui/cn";
import { Icons } from "@comp/ui/icons";
import {
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
} from "@comp/ui/tooltip";
import Link from "next/link";
import { usePathname } from "next/navigation";
import {
- Blocks,
- FileText,
- FlaskConical,
- Gauge,
- ListCheck,
- NotebookText,
- ShieldEllipsis,
- ShieldPlus,
- Store,
- Users,
+ Blocks,
+ FileText,
+ FlaskConical,
+ Gauge,
+ ListCheck,
+ NotebookText,
+ ShieldEllipsis,
+ ShieldPlus,
+ Store,
+ Users,
} from "lucide-react";
// Define menu item types with icon component
type MenuItem = {
- id: string;
- path: string;
- name: string;
- disabled: boolean;
- icon: React.FC<{ size?: number }>;
- protected: boolean;
+ id: string;
+ path: string;
+ name: string;
+ disabled: boolean;
+ icon: React.FC<{ size?: number }>;
+ protected: boolean;
};
interface ItemProps {
- item: MenuItem;
- isActive: boolean;
- disabled: boolean;
- organizationId: string;
- isCollapsed?: boolean;
+ item: MenuItem;
+ isActive: boolean;
+ disabled: boolean;
+ organizationId: string;
+ isCollapsed?: boolean;
}
const Item = ({
- item,
- isActive,
- disabled,
- organizationId,
- isCollapsed = false,
+ item,
+ isActive,
+ disabled,
+ organizationId,
+ isCollapsed = false,
}: ItemProps) => {
- const Icon = item.icon;
- const linkDisabled = disabled || item.disabled;
+ const Icon = item.icon;
+ const linkDisabled = disabled || item.disabled;
- // Replace the organizationId placeholder in the path
- const itemPath = item.path.replace(":organizationId", organizationId);
+ // Replace the organizationId placeholder in the path
+ const itemPath = item.path.replace(":organizationId", organizationId);
- return (
-
- {linkDisabled ? (
-
- Coming
-
- ) : (
-
-
-
-
-
- {Icon && }
- {!isCollapsed && (
-
- {item.name}
-
- )}
-
-
-
-
- {item.name}
-
-
-
- )}
-
- );
+ return (
+
+ {linkDisabled ? (
+
+ Coming
+
+ ) : (
+
+
+
+
+
+ {Icon && }
+ {!isCollapsed && (
+
+ {item.name}
+
+ )}
+
+
+
+
+ {item.name}
+
+
+
+ )}
+
+ );
};
type Props = {
- organizationId: string;
- //userIsAdmin: boolean;
- isCollapsed?: boolean;
+ organizationId: string;
+ //userIsAdmin: boolean;
+ isCollapsed?: boolean;
};
export function MainMenu({
- organizationId,
- //userIsAdmin,
- isCollapsed = false,
+ organizationId,
+ //userIsAdmin,
+ isCollapsed = false,
}: Props) {
- const t = useI18n();
- const pathname = usePathname();
+ const t = useI18n();
+ const pathname = usePathname();
- const items: MenuItem[] = [
- {
- id: "frameworks",
- path: "/:organizationId/frameworks",
- name: t("sidebar.frameworks"),
- disabled: false,
- icon: Gauge,
- protected: false,
- },
- {
- id: "controls",
- path: "/:organizationId/controls",
- name: t("sidebar.controls"),
- disabled: false,
- icon: ShieldEllipsis,
- protected: false,
- },
- {
- id: "policies",
- path: "/:organizationId/policies",
- name: t("sidebar.policies"),
- disabled: false,
- icon: NotebookText,
- protected: false,
- },
- {
- id: "evidence",
- path: "/:organizationId/evidence",
- name: t("sidebar.evidence"),
- disabled: false,
- icon: ListCheck,
- protected: false,
- },
- {
- id: "employees",
- path: "/:organizationId/employees",
- name: t("sidebar.employees"),
- disabled: false,
- icon: Users,
- protected: false,
- },
- {
- id: "risk",
- path: "/:organizationId/risk",
- name: t("sidebar.risk"),
- disabled: false,
- icon: Icons.Risk,
- protected: false,
- },
- {
- id: "vendors",
- path: "/:organizationId/vendors",
- name: t("sidebar.vendors"),
- disabled: false,
- icon: Store,
- protected: false,
- },
- {
- id: "integrations",
- path: "/:organizationId/integrations",
- name: t("sidebar.integrations"),
- disabled: false,
- icon: Blocks,
- protected: true,
- },
- {
- id: "tests",
- path: "/:organizationId/tests",
- name: t("sidebar.tests"),
- disabled: false,
- icon: FlaskConical,
- protected: false,
- },
- {
- id: "settings",
- path: "/:organizationId/settings",
- name: t("sidebar.settings"),
- disabled: false,
- icon: Icons.Settings,
- protected: true,
- },
- ];
+ const items: MenuItem[] = [
+ {
+ id: "frameworks",
+ path: "/:organizationId/frameworks",
+ name: t("sidebar.frameworks"),
+ disabled: false,
+ icon: Gauge,
+ protected: false,
+ },
+ {
+ id: "controls",
+ path: "/:organizationId/controls",
+ name: t("sidebar.controls"),
+ disabled: false,
+ icon: ShieldEllipsis,
+ protected: false,
+ },
+ {
+ id: "policies",
+ path: "/:organizationId/policies",
+ name: t("sidebar.policies"),
+ disabled: false,
+ icon: NotebookText,
+ protected: false,
+ },
+ {
+ id: "evidence",
+ path: "/:organizationId/evidence",
+ name: t("sidebar.evidence"),
+ disabled: false,
+ icon: ListCheck,
+ protected: false,
+ },
+ {
+ id: "employees",
+ path: "/:organizationId/employees",
+ name: t("sidebar.employees"),
+ disabled: false,
+ icon: Users,
+ protected: false,
+ },
+ {
+ id: "risk",
+ path: "/:organizationId/risk",
+ name: t("sidebar.risk"),
+ disabled: false,
+ icon: Icons.Risk,
+ protected: false,
+ },
+ {
+ id: "vendors",
+ path: "/:organizationId/vendors",
+ name: t("sidebar.vendors"),
+ disabled: false,
+ icon: Store,
+ protected: false,
+ },
+ {
+ id: "tests",
+ path: "/:organizationId/tests",
+ name: t("sidebar.tests"),
+ disabled: false,
+ icon: FlaskConical,
+ protected: false,
+ },
+ {
+ id: "integrations",
+ path: "/:organizationId/integrations",
+ name: t("sidebar.integrations"),
+ disabled: false,
+ icon: Blocks,
+ protected: true,
+ },
+ {
+ id: "settings",
+ path: "/:organizationId/settings",
+ name: t("sidebar.settings"),
+ disabled: false,
+ icon: Icons.Settings,
+ protected: true,
+ },
+ ];
- // Helper function to check if a path is active
- const isPathActive = (itemPath: string) => {
- const normalizedItemPath = itemPath.replace(
- ":organizationId",
- organizationId,
- );
+ // Helper function to check if a path is active
+ const isPathActive = (itemPath: string) => {
+ const normalizedItemPath = itemPath.replace(
+ ":organizationId",
+ organizationId,
+ );
- // Extract the base path from the menu item (first two segments after normalization)
- const itemPathParts = normalizedItemPath.split("/").filter(Boolean);
- const itemBaseSegment = itemPathParts.length > 1 ? itemPathParts[1] : "";
+ // Extract the base path from the menu item (first two segments after normalization)
+ const itemPathParts = normalizedItemPath.split("/").filter(Boolean);
+ const itemBaseSegment = itemPathParts.length > 1 ? itemPathParts[1] : "";
- // Extract the current path parts
- const currentPathParts = pathname.split("/").filter(Boolean);
- const currentBaseSegment =
- currentPathParts.length > 1 ? currentPathParts[1] : "";
+ // Extract the current path parts
+ const currentPathParts = pathname.split("/").filter(Boolean);
+ const currentBaseSegment =
+ currentPathParts.length > 1 ? currentPathParts[1] : "";
- // Special case for root organization path
- if (
- normalizedItemPath === `/${organizationId}` ||
- normalizedItemPath === `/${organizationId}/frameworks`
- ) {
- return (
- pathname === `/${organizationId}` ||
- pathname?.startsWith(`/${organizationId}/frameworks`)
- );
- }
+ // Special case for root organization path
+ if (
+ normalizedItemPath === `/${organizationId}` ||
+ normalizedItemPath === `/${organizationId}/frameworks`
+ ) {
+ return (
+ pathname === `/${organizationId}` ||
+ pathname?.startsWith(`/${organizationId}/frameworks`)
+ );
+ }
- // Compare the base segments (usually the feature section like "evidence", "settings", etc.)
- return itemBaseSegment === currentBaseSegment;
- };
+ // Compare the base segments (usually the feature section like "evidence", "settings", etc.)
+ return itemBaseSegment === currentBaseSegment;
+ };
- return (
-
-
-
- {items
- .filter((item) => !item.disabled)
- .map((item) => {
- const isActive = isPathActive(item.path);
+ return (
+
+
+
+ {items
+ .filter((item) => !item.disabled)
+ .map((item) => {
+ const isActive = isPathActive(item.path);
- //if (item.protected && !userIsAdmin) {
- // return null;
- //}
+ //if (item.protected && !userIsAdmin) {
+ // return null;
+ //}
- return (
-
- );
- })}
-
-
-
- );
+ return (
+
+ );
+ })}
+
+
+
+ );
}
diff --git a/apps/app/src/locales/onboarding/app-onboarding.ts b/apps/app/src/locales/onboarding/app-onboarding.ts
index 723f5ddac3..9f6096bf71 100644
--- a/apps/app/src/locales/onboarding/app-onboarding.ts
+++ b/apps/app/src/locales/onboarding/app-onboarding.ts
@@ -55,17 +55,21 @@ export const app_onboarding = {
},
},
cloud_tests: {
- title: "Cloud Security Testing",
- description: "Test and validate your cloud infrastructure security.",
+ title: "Cloud Compliance - Coming Soon",
+ description:
+ "Test and validate your cloud infrastructure security with automated tests and reports.",
cta: "Create your first cloud test",
learn_more: "Learn more",
faqs: {
question_1: "What are cloud security tests?",
- answer_1: "Cloud security tests are automated assessments that evaluate your cloud infrastructure for security vulnerabilities, misconfigurations, and compliance with security best practices.",
+ answer_1:
+ "Cloud security tests are automated assessments that evaluate your cloud infrastructure for security vulnerabilities, misconfigurations, and compliance with security best practices.",
question_2: "Why are cloud security tests important?",
- answer_2: "Cloud security tests help identify potential security risks in your cloud environment, ensure compliance with security standards, and provide evidence of your security controls for auditors. They're essential for maintaining a strong security posture in cloud environments.",
+ answer_2:
+ "Cloud security tests help identify potential security risks in your cloud environment, ensure compliance with security standards, and provide evidence of your security controls for auditors. They're essential for maintaining a strong security posture in cloud environments.",
question_3: "What types of cloud tests are available?",
- answer_3: "Cloud security tests can include infrastructure scanning, configuration analysis, vulnerability assessments, and compliance checks. These tests help ensure your cloud resources are properly secured and configured according to best practices.",
+ answer_3:
+ "Cloud security tests can include infrastructure scanning, configuration analysis, vulnerability assessments, and compliance checks. These tests help ensure your cloud resources are properly secured and configured according to best practices.",
},
},
} as const;