@@ -24,6 +24,7 @@ const HomePage = lazy(() => import("./pages/HomePage"));
|
|||||||
const NotFoundPage = lazy(() => import("./pages/NotFoundPage"));
|
const NotFoundPage = lazy(() => import("./pages/NotFoundPage"));
|
||||||
const PeoplesPage = lazy(() => import("./pages/PeoplesPage"));
|
const PeoplesPage = lazy(() => import("./pages/PeoplesPage"));
|
||||||
const VendorsPage = lazy(() => import("./pages/VendorsPage"));
|
const VendorsPage = lazy(() => import("./pages/VendorsPage"));
|
||||||
|
const FrameworksPage = lazy(() => import("./pages/FrameworksPage"));
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -39,6 +40,7 @@ function App() {
|
|||||||
<Route index element={<HomePage />} />
|
<Route index element={<HomePage />} />
|
||||||
<Route path="/peoples" element={<PeoplesPage />} />
|
<Route path="/peoples" element={<PeoplesPage />} />
|
||||||
<Route path="/vendors" element={<VendorsPage />} />
|
<Route path="/vendors" element={<VendorsPage />} />
|
||||||
|
<Route path="/frameworks" element={<FrameworksPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="*" element={<NotFoundPage />} />
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ const staticData = {
|
|||||||
avatar: "/avatars/shadcn.jpg",
|
avatar: "/avatars/shadcn.jpg",
|
||||||
},
|
},
|
||||||
navMain: [
|
navMain: [
|
||||||
|
{
|
||||||
|
title: "Frameworks",
|
||||||
|
url: "/frameworks",
|
||||||
|
icon: BookOpen,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Peoples",
|
title: "Peoples",
|
||||||
url: "/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