@@ -24,6 +24,7 @@ const HomePage = lazy(() => import("./pages/HomePage"));
|
||||
const NotFoundPage = lazy(() => import("./pages/NotFoundPage"));
|
||||
const PeoplesPage = lazy(() => import("./pages/PeoplesPage"));
|
||||
const VendorsPage = lazy(() => import("./pages/VendorsPage"));
|
||||
const FrameworksPage = lazy(() => import("./pages/FrameworksPage"));
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@@ -39,6 +40,7 @@ function App() {
|
||||
<Route index element={<HomePage />} />
|
||||
<Route path="/peoples" element={<PeoplesPage />} />
|
||||
<Route path="/vendors" element={<VendorsPage />} />
|
||||
<Route path="/frameworks" element={<FrameworksPage />} />
|
||||
</Route>
|
||||
<Route path="*" element={<NotFoundPage />} />
|
||||
</Routes>
|
||||
|
||||
@@ -36,6 +36,11 @@ const staticData = {
|
||||
avatar: "/avatars/shadcn.jpg",
|
||||
},
|
||||
navMain: [
|
||||
{
|
||||
title: "Frameworks",
|
||||
url: "/frameworks",
|
||||
icon: BookOpen,
|
||||
},
|
||||
{
|
||||
title: "Peoples",
|
||||
url: "/peoples",
|
||||
|
||||
76
apps/console/src/components/ui/card.tsx
Normal file
76
apps/console/src/components/ui/card.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-xl border bg-card text-card-foreground shadow",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Card.displayName = "Card"
|
||||
|
||||
const CardHeader = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardHeader.displayName = "CardHeader"
|
||||
|
||||
const CardTitle = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("font-semibold leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardTitle.displayName = "CardTitle"
|
||||
|
||||
const CardDescription = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardDescription.displayName = "CardDescription"
|
||||
|
||||
const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
))
|
||||
CardContent.displayName = "CardContent"
|
||||
|
||||
const CardFooter = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex items-center p-6 pt-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardFooter.displayName = "CardFooter"
|
||||
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||
129
apps/console/src/pages/FrameworksPage.tsx
Normal file
129
apps/console/src/pages/FrameworksPage.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import { Suspense, useEffect } from "react";
|
||||
import {
|
||||
graphql,
|
||||
PreloadedQuery,
|
||||
usePreloadedQuery,
|
||||
useQueryLoader,
|
||||
} from "react-relay";
|
||||
import { Globe2, CheckCircle } from "lucide-react";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import type { FrameworksPageQuery as FrameworksPageQueryType } from "./__generated__/FrameworksPageQuery.graphql";
|
||||
|
||||
const FrameworksPageQuery = graphql`
|
||||
query FrameworksPageQuery {
|
||||
node(id: "AZSfP_xAcAC5IAAAAAAltA") {
|
||||
id
|
||||
... on Organization {
|
||||
frameworks {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
description
|
||||
controls {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
function FrameworksPageContent({
|
||||
queryRef,
|
||||
}: {
|
||||
queryRef: PreloadedQuery<FrameworksPageQueryType>;
|
||||
}) {
|
||||
const data = usePreloadedQuery(FrameworksPageQuery, queryRef);
|
||||
const frameworks = data.node.frameworks?.edges.map(edge => edge?.node) ?? [];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background p-6">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-semibold mb-2">Framework</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Compliance frameworks like SOC 2 and ISO 27001 provide guidelines to secure sensitive data, manage risks, and
|
||||
build trust. SOC 2 focuses on customer data protection via five Trust Principles, while ISO 27001 establishes
|
||||
a comprehensive Information Security Management System for global standards.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
{frameworks.map((framework) => (
|
||||
<Card key={framework?.id} className="bg-card/50">
|
||||
<CardContent className="p-6">
|
||||
<div className="relative mb-6">
|
||||
<Badge className="absolute right-0 top-0 bg-green-500/10 text-green-500 hover:bg-green-500/20">
|
||||
Active
|
||||
</Badge>
|
||||
<div className="bg-green-500/10 w-24 h-24 rounded-full flex items-center justify-center mb-4">
|
||||
<Globe2 className="w-12 h-12 text-green-500" />
|
||||
<div className="absolute text-xs font-medium text-green-500">{framework?.name}</div>
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold mb-2">{framework?.name}</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{framework?.description}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center text-muted-foreground text-sm">
|
||||
<div className="flex items-center">
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
{framework?.controls?.edges.length} Controls
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FrameworksPageFallback() {
|
||||
return (
|
||||
<div className="min-h-screen bg-background p-6">
|
||||
<div className="mb-8">
|
||||
<div className="h-8 w-48 bg-muted animate-pulse rounded" />
|
||||
<div className="h-4 w-96 bg-muted animate-pulse rounded mt-2" />
|
||||
</div>
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
{[1, 2].map((i) => (
|
||||
<Card key={i} className="bg-card/50">
|
||||
<CardContent className="p-6">
|
||||
<div className="relative mb-6">
|
||||
<div className="bg-muted w-24 h-24 rounded-full animate-pulse mb-4" />
|
||||
<div className="h-6 w-48 bg-muted animate-pulse rounded mb-2" />
|
||||
<div className="h-20 w-full bg-muted animate-pulse rounded" />
|
||||
</div>
|
||||
<div className="h-4 w-32 bg-muted animate-pulse rounded" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FrameworksPage() {
|
||||
const [queryRef, loadQuery] = useQueryLoader<FrameworksPageQueryType>(FrameworksPageQuery);
|
||||
|
||||
useEffect(() => {
|
||||
loadQuery({});
|
||||
}, [loadQuery]);
|
||||
|
||||
return (
|
||||
<Suspense fallback={<FrameworksPageFallback />}>
|
||||
{queryRef && <FrameworksPageContent queryRef={queryRef} />}
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user