Add all missing page skeletons and use them as fallback
Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
@@ -13,6 +13,23 @@ import AuthLayout from "./layouts/AuthLayout";
|
||||
import { RelayEnvironment } from "./RelayEnvironment";
|
||||
import VisitorErrorBoundary from "./components/VisitorErrorBoundary";
|
||||
import { FrameworkListPageSkeleton } from "./pages/FrameworkListPage";
|
||||
import { ControlOverviewPageSkeleton } from "./pages/ControlOverviewPage";
|
||||
import { CreateControlPageSkeleton } from "./pages/CreateControlPage";
|
||||
import { UpdateFrameworkPageSkeleton } from "./pages/UpdateFrameworkPage";
|
||||
import { UpdateControlPageSkeleton } from "./pages/UpdateControlPage";
|
||||
import { CreateFrameworkPageSkeleton } from "./pages/CreateFrameworkPage";
|
||||
import { CreateOrganizationPageSkeleton } from "./pages/CreateOrganizationPage";
|
||||
import { CreatePeoplePageSkeleton } from "./pages/CreatePeoplePage";
|
||||
import { CreatePolicyPageSkeleton } from "./pages/CreatePolicyPage";
|
||||
import { FrameworkOverviewPageSkeleton } from "./pages/FrameworkOverviewPage";
|
||||
import { PeopleListPageSkeleton } from "./pages/PeopleListPage";
|
||||
import { PeopleOverviewPageSkeleton } from "./pages/PeopleOverviewPage";
|
||||
import { PolicyListPageSkeleton } from "./pages/PolicyListPage";
|
||||
import { PolicyOverviewPageSkeleton } from "./pages/PolicyOverviewPage";
|
||||
import { UpdatePolicyPageSkeleton } from "./pages/UpdatePolicyPage";
|
||||
import { VendorListPageSkeleton } from "./pages/VendorListPage";
|
||||
import { VendorOverviewPageSkeleton } from "./pages/VendorOverviewPage";
|
||||
import { SettingsPageSkeleton } from "./pages/SettingsPage";
|
||||
|
||||
posthog.init(process.env.POSTHOG_KEY!, {
|
||||
api_host: process.env.POSTHOG_HOST,
|
||||
@@ -85,7 +102,7 @@ function App() {
|
||||
<Route
|
||||
path="create"
|
||||
element={
|
||||
<Suspense>
|
||||
<Suspense fallback={<CreateOrganizationPageSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<CreateOrganizationPage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
@@ -106,7 +123,7 @@ function App() {
|
||||
<Route
|
||||
path="people"
|
||||
element={
|
||||
<Suspense>
|
||||
<Suspense fallback={<PeopleListPageSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<PeopleListPage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
@@ -116,7 +133,7 @@ function App() {
|
||||
<Route
|
||||
path="people/create"
|
||||
element={
|
||||
<Suspense>
|
||||
<Suspense fallback={<CreatePeoplePageSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<CreatePeoplePage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
@@ -126,7 +143,7 @@ function App() {
|
||||
<Route
|
||||
path="people/:peopleId"
|
||||
element={
|
||||
<Suspense>
|
||||
<Suspense fallback={<PeopleOverviewPageSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<PeopleOverviewPage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
@@ -136,7 +153,7 @@ function App() {
|
||||
<Route
|
||||
path="vendors"
|
||||
element={
|
||||
<Suspense>
|
||||
<Suspense fallback={<VendorListPageSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<VendorListPage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
@@ -156,7 +173,7 @@ function App() {
|
||||
<Route
|
||||
path="frameworks/create"
|
||||
element={
|
||||
<Suspense>
|
||||
<Suspense fallback={<CreateFrameworkPageSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<CreateFrameworkPage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
@@ -166,7 +183,9 @@ function App() {
|
||||
<Route
|
||||
path="frameworks/:frameworkId"
|
||||
element={
|
||||
<Suspense>
|
||||
<Suspense
|
||||
fallback={<FrameworkOverviewPageSkeleton />}
|
||||
>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<FrameworkOverviewPage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
@@ -176,7 +195,7 @@ function App() {
|
||||
<Route
|
||||
path="frameworks/:frameworkId/update"
|
||||
element={
|
||||
<Suspense>
|
||||
<Suspense fallback={<UpdateFrameworkPageSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<UpdateFrameworkPage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
@@ -186,7 +205,7 @@ function App() {
|
||||
<Route
|
||||
path="frameworks/:frameworkId/controls/create"
|
||||
element={
|
||||
<Suspense>
|
||||
<Suspense fallback={<CreateControlPageSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<CreateControlPage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
@@ -196,7 +215,7 @@ function App() {
|
||||
<Route
|
||||
path="frameworks/:frameworkId/controls/:controlId"
|
||||
element={
|
||||
<Suspense>
|
||||
<Suspense fallback={<ControlOverviewPageSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<ControlOverviewPage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
@@ -206,7 +225,7 @@ function App() {
|
||||
<Route
|
||||
path="frameworks/:frameworkId/controls/:controlId/update"
|
||||
element={
|
||||
<Suspense>
|
||||
<Suspense fallback={<UpdateControlPageSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<UpdateControlPage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
@@ -216,7 +235,7 @@ function App() {
|
||||
<Route
|
||||
path="vendors/:vendorId"
|
||||
element={
|
||||
<Suspense>
|
||||
<Suspense fallback={<VendorOverviewPageSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<VendorOverviewPage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
@@ -227,7 +246,7 @@ function App() {
|
||||
<Route
|
||||
path="policies"
|
||||
element={
|
||||
<Suspense>
|
||||
<Suspense fallback={<PolicyListPageSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<PolicyListPage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
@@ -237,7 +256,7 @@ function App() {
|
||||
<Route
|
||||
path="policies/create"
|
||||
element={
|
||||
<Suspense>
|
||||
<Suspense fallback={<CreatePolicyPageSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<CreatePolicyPage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
@@ -247,7 +266,7 @@ function App() {
|
||||
<Route
|
||||
path="policies/:policyId"
|
||||
element={
|
||||
<Suspense>
|
||||
<Suspense fallback={<PolicyOverviewPageSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<PolicyOverviewPage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
@@ -257,7 +276,7 @@ function App() {
|
||||
<Route
|
||||
path="policies/:policyId/update"
|
||||
element={
|
||||
<Suspense>
|
||||
<Suspense fallback={<UpdatePolicyPageSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<UpdatePolicyPage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
@@ -267,7 +286,7 @@ function App() {
|
||||
<Route
|
||||
path="settings"
|
||||
element={
|
||||
<Suspense>
|
||||
<Suspense fallback={<SettingsPageSkeleton />}>
|
||||
<ErrorBoundaryWithLocation>
|
||||
<SettingsPage />
|
||||
</ErrorBoundaryWithLocation>
|
||||
|
||||
@@ -56,8 +56,8 @@ export function PageHeading({
|
||||
|
||||
const PageHeadingSkeleton: FC = () => {
|
||||
return (
|
||||
<div className="py-[3px]">
|
||||
<div className="bg-muted animate rounded-lg w-60 h-7.5" />
|
||||
<div className="py-[3px] w-2/5">
|
||||
<div className="bg-muted animate rounded-lg h-7.5" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -64,7 +64,6 @@ import { Textarea } from "@/components/ui/textarea";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import type { ControlOverviewPageQuery as ControlOverviewPageQueryType } from "./__generated__/ControlOverviewPageQuery.graphql";
|
||||
import type { ControlOverviewPageUpdateTaskStateMutation as ControlOverviewPageUpdateTaskStateMutationType } from "./__generated__/ControlOverviewPageUpdateTaskStateMutation.graphql";
|
||||
import type { ControlOverviewPageCreateTaskMutation as ControlOverviewPageCreateTaskMutationType } from "./__generated__/ControlOverviewPageCreateTaskMutation.graphql";
|
||||
@@ -75,7 +74,7 @@ import type { ControlOverviewPageAssignTaskMutation as ControlOverviewPageAssign
|
||||
import type { ControlOverviewPageUnassignTaskMutation as ControlOverviewPageUnassignTaskMutationType } from "./__generated__/ControlOverviewPageUnassignTaskMutation.graphql";
|
||||
import type { ControlOverviewPageOrganizationQuery$data } from "./__generated__/ControlOverviewPageOrganizationQuery.graphql";
|
||||
import type { ControlOverviewPageUpdateControlStateMutation as ControlOverviewPageUpdateControlStateMutationType } from "./__generated__/ControlOverviewPageUpdateControlStateMutation.graphql";
|
||||
import { PageHeader } from "@/components/PageHeader";
|
||||
import { PageTemplate, PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
|
||||
// Function to format ISO8601 duration to human-readable format
|
||||
const formatDuration = (isoDuration: string): string => {
|
||||
@@ -1128,13 +1127,7 @@ function ControlOverviewPageContent({
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{data.control.name || "Control"} - Probo</title>
|
||||
</Helmet>
|
||||
<div className="container">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
<PageTemplate
|
||||
title={data.control.name ?? ""}
|
||||
actions={
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -1170,7 +1163,7 @@ function ControlOverviewPageContent({
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
>
|
||||
<div className="space-y-4 mb-8">
|
||||
<Card className="mt-4">
|
||||
<CardContent className="pt-6">
|
||||
@@ -1189,10 +1182,7 @@ function ControlOverviewPageContent({
|
||||
<FileIcon className="w-4 h-4 mr-2 text-blue-500" />
|
||||
<span>Drag & drop files onto tasks to add evidence</span>
|
||||
</div>
|
||||
<Dialog
|
||||
open={isCreateTaskOpen}
|
||||
onOpenChange={setIsCreateTaskOpen}
|
||||
>
|
||||
<Dialog open={isCreateTaskOpen} onOpenChange={setIsCreateTaskOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button size="sm" className="flex items-center gap-1">
|
||||
<Plus className="w-4 h-4" />
|
||||
@@ -1203,8 +1193,8 @@ function ControlOverviewPageContent({
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create New Task</DialogTitle>
|
||||
<DialogDescription>
|
||||
Add a new task to this control. Click save when
|
||||
you're done.
|
||||
Add a new task to this control. Click save when you're
|
||||
done.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
@@ -1253,9 +1243,7 @@ function ControlOverviewPageContent({
|
||||
type="number"
|
||||
min="0"
|
||||
value={timeEstimateDays}
|
||||
onChange={(e) =>
|
||||
setTimeEstimateDays(e.target.value)
|
||||
}
|
||||
onChange={(e) => setTimeEstimateDays(e.target.value)}
|
||||
placeholder="0"
|
||||
/>
|
||||
</div>
|
||||
@@ -1272,9 +1260,7 @@ function ControlOverviewPageContent({
|
||||
min="0"
|
||||
max="23"
|
||||
value={timeEstimateHours}
|
||||
onChange={(e) =>
|
||||
setTimeEstimateHours(e.target.value)
|
||||
}
|
||||
onChange={(e) => setTimeEstimateHours(e.target.value)}
|
||||
placeholder="0"
|
||||
/>
|
||||
</div>
|
||||
@@ -1647,10 +1633,7 @@ function ControlOverviewPageContent({
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="bg-white p-2 rounded-md border border-gray-200">
|
||||
{getFileIcon(
|
||||
evidence.mimeType,
|
||||
evidence.type
|
||||
)}
|
||||
{getFileIcon(evidence.mimeType, evidence.type)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-800">
|
||||
@@ -1672,9 +1655,7 @@ function ControlOverviewPageContent({
|
||||
<span>•</span>
|
||||
</>
|
||||
) : null}
|
||||
<span>
|
||||
{formatDate(evidence.createdAt)}
|
||||
</span>
|
||||
<span>{formatDate(evidence.createdAt)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1821,9 +1802,7 @@ function ControlOverviewPageContent({
|
||||
<Textarea
|
||||
id="evidence-description"
|
||||
value={linkEvidenceDescription}
|
||||
onChange={(e) =>
|
||||
setLinkEvidenceDescription(e.target.value)
|
||||
}
|
||||
onChange={(e) => setLinkEvidenceDescription(e.target.value)}
|
||||
placeholder="Describe this evidence (optional)"
|
||||
className="min-h-[100px]"
|
||||
/>
|
||||
@@ -1972,8 +1951,7 @@ function ControlOverviewPageContent({
|
||||
<DialogTitle>Delete Evidence</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to delete the evidence "
|
||||
{evidenceToDelete?.filename}"? This action cannot be
|
||||
undone.
|
||||
{evidenceToDelete?.filename}"? This action cannot be undone.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
@@ -1989,29 +1967,32 @@ function ControlOverviewPageContent({
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function ControlOverviewPageFallback() {
|
||||
export function ControlOverviewPageSkeleton() {
|
||||
return (
|
||||
<div className="min-h-screen bg-white p-6">
|
||||
<div className="mb-8">
|
||||
<div className="h-8 w-48 bg-gray-100 animate-pulse rounded" />
|
||||
<div className="h-4 w-96 bg-gray-100 animate-pulse rounded mt-2" />
|
||||
<PageTemplateSkeleton
|
||||
actions={
|
||||
<div className="flex items-center gap-2 w-1/3">
|
||||
<div className="bg-muted animate-pulse h-8 w-1/3 rounded-lg" />
|
||||
<div className="bg-muted animate-pulse h-8 w-1/3 rounded-lg" />
|
||||
<div className="bg-muted animate-pulse h-8 w-1/3 rounded-full" />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<Card key={i}>
|
||||
<CardContent className="p-6">
|
||||
<div className="h-6 w-48 bg-gray-100 animate-pulse rounded mb-2" />
|
||||
<div className="h-4 w-full bg-gray-100 animate-pulse rounded" />
|
||||
<div className="h-6 w-48 bg-muted animate-pulse rounded mb-2" />
|
||||
<div className="h-4 w-full bg-muted animate-pulse rounded" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2028,11 +2009,11 @@ export default function ControlOverviewPage() {
|
||||
}, [controlId, loadQuery]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <ControlOverviewPageFallback />;
|
||||
return <ControlOverviewPageSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<ControlOverviewPageFallback />}>
|
||||
<Suspense fallback={<ControlOverviewPageSkeleton />}>
|
||||
<ControlOverviewPageContent queryRef={queryRef} />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Suspense, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router";
|
||||
import { graphql, useMutation, ConnectionHandler } from "react-relay";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -21,7 +20,7 @@ import {
|
||||
CreateControlPageCreateControlMutation,
|
||||
ControlImportance,
|
||||
} from "./__generated__/CreateControlPageCreateControlMutation.graphql";
|
||||
import { PageHeader } from "@/components/PageHeader";
|
||||
import { PageTemplate, PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
|
||||
const createControlMutation = graphql`
|
||||
mutation CreateControlPageCreateControlMutation(
|
||||
@@ -182,17 +181,10 @@ function CreateControlPageContent() {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Create Control - Probo</title>
|
||||
</Helmet>
|
||||
<div className="container">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
<PageTemplate
|
||||
title="Create Control"
|
||||
description="Create a new control for your framework"
|
||||
/>
|
||||
|
||||
>
|
||||
<Card className="max-w-2xl">
|
||||
<form onSubmit={handleSubmit} className="p-6 space-y-6">
|
||||
<EditableField
|
||||
@@ -258,14 +250,24 @@ function CreateControlPageContent() {
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
</>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
export function CreateControlPageSkeleton() {
|
||||
return (
|
||||
<PageTemplateSkeleton
|
||||
title="Create Control"
|
||||
description="Create a new control for your framework"
|
||||
>
|
||||
<div className="max-w-2xl aspect-square bg-muted rounded-xl animate-pulse" />
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CreateControlPage() {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Suspense fallback={<CreateControlPageSkeleton />}>
|
||||
<CreateControlPageContent />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Suspense, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router";
|
||||
import { graphql, useMutation, ConnectionHandler } from "react-relay";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -11,6 +10,7 @@ import { useToast } from "@/hooks/use-toast";
|
||||
import { HelpCircle } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CreateFrameworkPageCreateFrameworkMutation } from "./__generated__/CreateFrameworkPageCreateFrameworkMutation.graphql";
|
||||
import { PageTemplate, PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
|
||||
const createFrameworkMutation = graphql`
|
||||
mutation CreateFrameworkPageCreateFrameworkMutation(
|
||||
@@ -69,7 +69,7 @@ function EditableField({
|
||||
}
|
||||
className={cn(
|
||||
"w-full resize-none",
|
||||
required && !value && "border-red-500",
|
||||
required && !value && "border-red-500"
|
||||
)}
|
||||
placeholder={`Enter ${label.toLowerCase()}`}
|
||||
rows={4}
|
||||
@@ -101,7 +101,7 @@ function CreateFrameworkPageContent() {
|
||||
|
||||
const [commit, isInFlight] =
|
||||
useMutation<CreateFrameworkPageCreateFrameworkMutation>(
|
||||
createFrameworkMutation,
|
||||
createFrameworkMutation
|
||||
);
|
||||
|
||||
const handleFieldChange = (field: keyof typeof formData, value: unknown) => {
|
||||
@@ -125,7 +125,7 @@ function CreateFrameworkPageContent() {
|
||||
|
||||
const connectionId = ConnectionHandler.getConnectionID(
|
||||
organizationId!,
|
||||
"FrameworkListPage_frameworks",
|
||||
"FrameworkListPage_frameworks"
|
||||
);
|
||||
|
||||
commit({
|
||||
@@ -153,7 +153,7 @@ function CreateFrameworkPageContent() {
|
||||
});
|
||||
|
||||
navigate(
|
||||
`/organizations/${organizationId}/frameworks/${data.createFramework.frameworkEdge.node.id}`,
|
||||
`/organizations/${organizationId}/frameworks/${data.createFramework.frameworkEdge.node.id}`
|
||||
);
|
||||
},
|
||||
onError(error) {
|
||||
@@ -167,18 +167,10 @@ function CreateFrameworkPageContent() {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Create Framework - Probo</title>
|
||||
</Helmet>
|
||||
<div className="container mx-auto py-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold">Create Framework</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Create a new framework to organize your controls
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<PageTemplate
|
||||
title="Create Framework"
|
||||
description="Create a new framework to organize your controls"
|
||||
>
|
||||
<Card className="max-w-2xl">
|
||||
<form onSubmit={handleSubmit} className="p-6 space-y-6">
|
||||
<EditableField
|
||||
@@ -213,14 +205,24 @@ function CreateFrameworkPageContent() {
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
</>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
export function CreateFrameworkPageSkeleton() {
|
||||
return (
|
||||
<PageTemplateSkeleton
|
||||
title="Create Framework"
|
||||
description="Create a new framework to organize your controls"
|
||||
>
|
||||
<div className="max-w-2xl aspect-square bg-muted rounded-xl animate-pulse" />
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CreateFrameworkPage() {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Suspense fallback={<CreateFrameworkPageSkeleton />}>
|
||||
<CreateFrameworkPageContent />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
useMutation,
|
||||
useLazyLoadQuery,
|
||||
} from "react-relay";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
@@ -21,6 +20,7 @@ import { useToast } from "@/hooks/use-toast";
|
||||
import { HelpCircle } from "lucide-react";
|
||||
import { CreateOrganizationPageCreateOrganizationMutation } from "./__generated__/CreateOrganizationPageCreateOrganizationMutation.graphql";
|
||||
import { CreateOrganizationPageViewerQuery } from "./__generated__/CreateOrganizationPageViewerQuery.graphql";
|
||||
import { PageTemplate, PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
|
||||
const createOrganizationMutation = graphql`
|
||||
mutation CreateOrganizationPageCreateOrganizationMutation(
|
||||
@@ -81,16 +81,27 @@ function EditableField({
|
||||
);
|
||||
}
|
||||
|
||||
export function CreateOrganizationPageSkeleton() {
|
||||
return (
|
||||
<PageTemplateSkeleton
|
||||
title="Create Organization"
|
||||
description="Create a new organization to manage your compliance and security needs."
|
||||
>
|
||||
<div className="max-w-2xl aspect-square bg-muted rounded-xl animate-pulse" />
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CreateOrganizationPage() {
|
||||
const navigate = useNavigate();
|
||||
const { toast } = useToast();
|
||||
const data = useLazyLoadQuery<CreateOrganizationPageViewerQuery>(
|
||||
viewerQuery,
|
||||
{},
|
||||
{}
|
||||
);
|
||||
const [createOrganization] =
|
||||
useMutation<CreateOrganizationPageCreateOrganizationMutation>(
|
||||
createOrganizationMutation,
|
||||
createOrganizationMutation
|
||||
);
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
@@ -114,7 +125,7 @@ export default function CreateOrganizationPage() {
|
||||
connections: [
|
||||
ConnectionHandler.getConnectionID(
|
||||
data.viewer.id,
|
||||
"OrganizationSwitcher_organizations",
|
||||
"OrganizationSwitcher_organizations"
|
||||
),
|
||||
],
|
||||
},
|
||||
@@ -138,24 +149,12 @@ export default function CreateOrganizationPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Create Organization - Probo</title>
|
||||
</Helmet>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">
|
||||
Create Organization
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Create a new organization to manage your compliance and security
|
||||
needs.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<PageTemplate
|
||||
title="Create Organization"
|
||||
description="Create a new organization to manage your compliance and security needs."
|
||||
>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Card>
|
||||
<Card className="max-w-2xl">
|
||||
<CardHeader>
|
||||
<CardTitle>Organization Details</CardTitle>
|
||||
<CardDescription>
|
||||
@@ -177,7 +176,6 @@ export default function CreateOrganizationPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Suspense, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router";
|
||||
import { graphql, useMutation, ConnectionHandler } from "react-relay";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -10,7 +9,7 @@ import { useToast } from "@/hooks/use-toast";
|
||||
import { HelpCircle } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CreatePeoplePageCreatePeopleMutation } from "./__generated__/CreatePeoplePageCreatePeopleMutation.graphql";
|
||||
import { PageHeader } from "@/components/PageHeader";
|
||||
import { PageTemplate, PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
|
||||
const createPeopleMutation = graphql`
|
||||
mutation CreatePeoplePageCreatePeopleMutation(
|
||||
@@ -150,19 +149,12 @@ function CreatePeoplePageContent() {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Create Person - Probo Console</title>
|
||||
</Helmet>
|
||||
<div className="container">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
title="Add a Person"
|
||||
<PageTemplate
|
||||
title="Create Person"
|
||||
description="Add a new person interacting with organization"
|
||||
/>
|
||||
|
||||
>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="max-w-4xl space-y-6">
|
||||
<div className="max-w-2xl space-y-6">
|
||||
<EditableField
|
||||
label="Full Name"
|
||||
value={formData.fullName}
|
||||
@@ -192,14 +184,9 @@ function CreatePeoplePageContent() {
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => {
|
||||
const newEmails = [
|
||||
...formData.additionalEmailAddresses,
|
||||
];
|
||||
const newEmails = [...formData.additionalEmailAddresses];
|
||||
newEmails[index] = e.target.value;
|
||||
handleFieldChange(
|
||||
"additionalEmailAddresses",
|
||||
newEmails
|
||||
);
|
||||
handleFieldChange("additionalEmailAddresses", newEmails);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
@@ -210,10 +197,7 @@ function CreatePeoplePageContent() {
|
||||
formData.additionalEmailAddresses.filter(
|
||||
(_, i) => i !== index
|
||||
);
|
||||
handleFieldChange(
|
||||
"additionalEmailAddresses",
|
||||
newEmails
|
||||
);
|
||||
handleFieldChange("additionalEmailAddresses", newEmails);
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
@@ -238,9 +222,7 @@ function CreatePeoplePageContent() {
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-lg font-medium">
|
||||
Additional Information
|
||||
</h2>
|
||||
<h2 className="text-lg font-medium">Additional Information</h2>
|
||||
<p className="text-sm text-gray-500">
|
||||
Additional details about the person
|
||||
</p>
|
||||
@@ -298,11 +280,7 @@ function CreatePeoplePageContent() {
|
||||
</Card>
|
||||
</div>
|
||||
<div className="fixed bottom-6 right-6 flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<Button type="button" variant="outline" onClick={() => navigate(-1)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
@@ -313,14 +291,24 @@ function CreatePeoplePageContent() {
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
export function CreatePeoplePageSkeleton() {
|
||||
return (
|
||||
<PageTemplateSkeleton
|
||||
title="Create Person"
|
||||
description="Add a new person interacting with organization"
|
||||
>
|
||||
<div className="max-w-2xl aspect-square bg-muted rounded-xl animate-pulse" />
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CreatePeoplePage() {
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<Suspense fallback={<CreatePeoplePageSkeleton />}>
|
||||
<CreatePeoplePageContent />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
usePreloadedQuery,
|
||||
PreloadedQuery,
|
||||
} from "react-relay";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -21,7 +20,7 @@ import PeopleSelector from "@/components/PeopleSelector";
|
||||
import { Suspense } from "react";
|
||||
import type { CreatePolicyPageMutation } from "./__generated__/CreatePolicyPageMutation.graphql";
|
||||
import type { CreatePolicyPageQuery as CreatePolicyPageQueryType } from "./__generated__/CreatePolicyPageQuery.graphql";
|
||||
import { PageHeader } from "@/components/PageHeader";
|
||||
import { PageTemplate, PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
|
||||
const CreatePolicyQuery = graphql`
|
||||
query CreatePolicyPageQuery($organizationId: ID!) {
|
||||
@@ -156,17 +155,10 @@ function CreatePolicyForm({
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Create Policy - Probo Console</title>
|
||||
</Helmet>
|
||||
<div className="container">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
<PageTemplate
|
||||
title="Create Policy"
|
||||
description="Create a new policy for your organization"
|
||||
/>
|
||||
|
||||
>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="grid gap-6">
|
||||
<Card>
|
||||
@@ -234,10 +226,7 @@ function CreatePolicyForm({
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label
|
||||
htmlFor="reviewDate"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Label htmlFor="reviewDate" className="flex items-center gap-2">
|
||||
<Calendar className="h-4 w-4" />
|
||||
Review Date
|
||||
</Label>
|
||||
@@ -267,25 +256,18 @@ function CreatePolicyForm({
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function CreatePolicyPageFallback() {
|
||||
export function CreatePolicyPageSkeleton() {
|
||||
return (
|
||||
<div className="container mx-auto py-6">
|
||||
<div className="flex items-center mb-6">
|
||||
<div className="mr-4">
|
||||
<div className="h-12 w-12 bg-muted animate-pulse rounded-lg" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="h-8 w-48 bg-muted animate-pulse rounded mb-2" />
|
||||
<div className="h-4 w-64 bg-muted animate-pulse rounded" />
|
||||
</div>
|
||||
</div>
|
||||
<PageTemplateSkeleton
|
||||
title="Create Policy"
|
||||
description="Create a new policy for your organization"
|
||||
>
|
||||
<div className="bg-muted animate-pulse rounded-lg h-[600px]" />
|
||||
</div>
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -300,9 +282,13 @@ export default function CreatePolicyPage() {
|
||||
}
|
||||
}, [organizationId, loadQuery]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <CreatePolicyPageSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<CreatePolicyPageFallback />}>
|
||||
{queryRef && <CreatePolicyForm queryRef={queryRef} />}
|
||||
<Suspense fallback={<CreatePolicyPageSkeleton />}>
|
||||
{<CreatePolicyForm queryRef={queryRef} />}
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -320,6 +320,10 @@ export default function FrameworkListPage() {
|
||||
loadQuery({ organizationId: organizationId! });
|
||||
}, [loadQuery, organizationId]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <FrameworkListPageSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<FrameworkListPageSkeleton />}>
|
||||
{queryRef && <FrameworkListPageContent queryRef={queryRef} />}
|
||||
|
||||
@@ -20,8 +20,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import type { FrameworkOverviewPageQuery as FrameworkOverviewPageQueryType } from "./__generated__/FrameworkOverviewPageQuery.graphql";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { PageHeader } from "@/components/PageHeader";
|
||||
import { PageTemplate, PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
|
||||
const FrameworkOverviewPageQuery = graphql`
|
||||
query FrameworkOverviewPageQuery($frameworkId: ID!) {
|
||||
@@ -169,9 +168,7 @@ function FrameworkOverviewPageContent({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container space-y-6">
|
||||
<PageHeader
|
||||
className="mb-10"
|
||||
<PageTemplate
|
||||
title={framework.name ?? ""}
|
||||
description={framework.description ?? ""}
|
||||
actions={
|
||||
@@ -193,8 +190,7 @@ function FrameworkOverviewPageContent({
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
>
|
||||
<div className="grid gap-6">
|
||||
{categories.map((category) => {
|
||||
const isExpanded = expandedCategories.includes(category.id);
|
||||
@@ -326,17 +322,13 @@ function FrameworkOverviewPageContent({
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function FrameworkOverviewPageFallback() {
|
||||
export function FrameworkOverviewPageSkeleton() {
|
||||
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>
|
||||
<PageTemplateSkeleton>
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<Card key={i}>
|
||||
@@ -351,7 +343,7 @@ function FrameworkOverviewPageFallback() {
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -365,14 +357,13 @@ export default function FrameworkOverviewPage() {
|
||||
loadQuery({ frameworkId: frameworkId! });
|
||||
}, [loadQuery, frameworkId]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <FrameworkOverviewPageSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Framework Overview - Probo Console</title>
|
||||
</Helmet>
|
||||
<Suspense fallback={<FrameworkOverviewPageFallback />}>
|
||||
<Suspense fallback={<FrameworkOverviewPageSkeleton />}>
|
||||
{queryRef && <FrameworkOverviewPageContent queryRef={queryRef} />}
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,12 +13,11 @@ import { Badge } from "@/components/ui/badge";
|
||||
import { UserPlus, Trash2, ChevronRight } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Link } from "react-router";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import type { PeopleListPageQuery as PeopleListPageQueryType } from "./__generated__/PeopleListPageQuery.graphql";
|
||||
import type { PeopleListPageDeletePeopleMutation } from "./__generated__/PeopleListPageDeletePeopleMutation.graphql";
|
||||
import { PeopleListPagePaginationQuery } from "./__generated__/PeopleListPagePaginationQuery.graphql";
|
||||
import { PeopleListPage_peoples$key } from "./__generated__/PeopleListPage_peoples.graphql";
|
||||
import { PageHeader } from "@/components/PageHeader";
|
||||
import { PageTemplate, PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
|
||||
const ITEMS_PER_PAGE = 25;
|
||||
|
||||
@@ -174,14 +173,8 @@ function PeopleListContent({
|
||||
const pageInfo = peoplesConnection.peoples.pageInfo;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>People - Probo</title>
|
||||
</Helmet>
|
||||
<div className="container space-y-6">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
title="Employees"
|
||||
<PageTemplate
|
||||
title="People"
|
||||
description="Keep track of your company's workforce and their progress
|
||||
towards completing tasks assigned to them."
|
||||
actions={
|
||||
@@ -197,8 +190,8 @@ function PeopleListContent({
|
||||
</Link>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
{peoples.map((person) => (
|
||||
<Link
|
||||
@@ -296,27 +289,32 @@ function PeopleListContent({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function PeopleListPageFallback() {
|
||||
export function PeopleListPageSkeleton() {
|
||||
return (
|
||||
<PageTemplateSkeleton
|
||||
title="People"
|
||||
description="Keep track of your company's workforce and their progress towards completing tasks assigned to them."
|
||||
actions={<div className="bg-muted animate-pulse h-9 w-1/6 rounded-lg" />}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<div className="h-8 w-48 bg-muted animate-pulse rounded" />
|
||||
<div className="h-4 w-96 bg-muted animate-pulse rounded mt-1" />
|
||||
</div>
|
||||
<div className="rounded-xl border bg-card p-4 space-y-4">
|
||||
<div className="rounded-xl border bg-card p-4 space-y-2">
|
||||
<div className="h-5 w-32 bg-muted animate-pulse rounded" />
|
||||
<div className="h-10 w-full bg-muted animate-pulse rounded" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div key={i} className="h-[72px] bg-muted animate-pulse rounded-xl" />
|
||||
<div
|
||||
key={i}
|
||||
className="h-[72px] bg-muted animate-pulse rounded-xl"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -341,17 +339,12 @@ export default function PeopleListPage() {
|
||||
}, [loadQuery, organizationId, searchParams]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <PeopleListPageFallback />;
|
||||
return <PeopleListPageSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>People - Probo Console</title>
|
||||
</Helmet>
|
||||
<Suspense fallback={<PeopleListPageFallback />}>
|
||||
<Suspense fallback={<PeopleListPageSkeleton />}>
|
||||
<PeopleListContent queryRef={queryRef} />
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,9 +16,8 @@ import {
|
||||
import { Suspense, useEffect, useState, useCallback } from "react";
|
||||
import type { PeopleOverviewPageQuery as PeopleOverviewPageQueryType } from "./__generated__/PeopleOverviewPageQuery.graphql";
|
||||
import { useParams } from "react-router";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { PageHeader } from "@/components/PageHeader";
|
||||
import { PageTemplate, PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
|
||||
const peopleOverviewPageQuery = graphql`
|
||||
query PeopleOverviewPageQuery($peopleId: ID!) {
|
||||
@@ -161,12 +160,7 @@ function PeopleOverviewPageContent({
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Person - Probo</title>
|
||||
</Helmet>
|
||||
<div className="container">
|
||||
<PageHeader className="mb-17" title={formData.fullName} />
|
||||
<PageTemplate title={formData.fullName}>
|
||||
<div className="space-y-6">
|
||||
<div className="mx-auto max-w-4xl space-y-6">
|
||||
<EditableField
|
||||
@@ -196,14 +190,9 @@ function PeopleOverviewPageContent({
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => {
|
||||
const newEmails = [
|
||||
...formData.additionalEmailAddresses,
|
||||
];
|
||||
const newEmails = [...formData.additionalEmailAddresses];
|
||||
newEmails[index] = e.target.value;
|
||||
handleFieldChange(
|
||||
"additionalEmailAddresses",
|
||||
newEmails
|
||||
);
|
||||
handleFieldChange("additionalEmailAddresses", newEmails);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
@@ -213,10 +202,7 @@ function PeopleOverviewPageContent({
|
||||
formData.additionalEmailAddresses.filter(
|
||||
(_, i) => i !== index
|
||||
);
|
||||
handleFieldChange(
|
||||
"additionalEmailAddresses",
|
||||
newEmails
|
||||
);
|
||||
handleFieldChange("additionalEmailAddresses", newEmails);
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
@@ -240,9 +226,7 @@ function PeopleOverviewPageContent({
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-lg font-medium">
|
||||
Additional Information
|
||||
</h2>
|
||||
<h2 className="text-lg font-medium">Additional Information</h2>
|
||||
<p className="text-sm text-gray-500">
|
||||
Additional details about the person
|
||||
</p>
|
||||
@@ -311,14 +295,14 @@ function PeopleOverviewPageContent({
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function PeopleOverviewPageFallback() {
|
||||
export function PeopleOverviewPageSkeleton() {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<PageTemplateSkeleton>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-1">
|
||||
<div className="h-8 w-48 bg-muted animate-pulse rounded" />
|
||||
<div className="h-4 w-96 bg-muted animate-pulse rounded" />
|
||||
@@ -329,6 +313,7 @@ function PeopleOverviewPageFallback() {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -343,17 +328,12 @@ export default function PeopleOverviewPage() {
|
||||
}, [loadQuery, peopleId]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <PeopleOverviewPageFallback />;
|
||||
return <PeopleOverviewPageSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>People Overview - Probo Console</title>
|
||||
</Helmet>
|
||||
<Suspense fallback={<PeopleOverviewPageFallback />}>
|
||||
<Suspense fallback={<PeopleOverviewPageSkeleton />}>
|
||||
<PeopleOverviewPageContent queryRef={queryRef} />
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
} from "react-relay";
|
||||
import { Card, CardContent, CardFooter } from "@/components/ui/card";
|
||||
import { Link, useParams } from "react-router";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
@@ -34,7 +33,7 @@ import {
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { format } from "date-fns";
|
||||
import type { PolicyListPageQuery as PolicyListPageQueryType } from "./__generated__/PolicyListPageQuery.graphql";
|
||||
import { PageHeader } from "@/components/PageHeader";
|
||||
import { PageTemplate, PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
|
||||
const PolicyListPageQuery = graphql`
|
||||
query PolicyListPageQuery($organizationId: ID!) {
|
||||
@@ -220,13 +219,7 @@ function PolicyListPageContent({
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Policies - Probo</title>
|
||||
</Helmet>
|
||||
<div className="container space-y-6">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
<PageTemplate
|
||||
title="Policies"
|
||||
description="Manage your organization's policies"
|
||||
actions={
|
||||
@@ -237,8 +230,7 @@ function PolicyListPageContent({
|
||||
</Link>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
>
|
||||
{/* Search and filter controls */}
|
||||
<div className="flex flex-col md:flex-row gap-4 mb-6">
|
||||
<div className="relative flex-1">
|
||||
@@ -351,22 +343,17 @@ function PolicyListPageContent({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function PolicyListPageFallback() {
|
||||
export function PolicyListPageSkeleton() {
|
||||
return (
|
||||
<div className="container mx-auto py-6">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<div className="h-8 w-48 bg-muted animate-pulse rounded" />
|
||||
<div className="h-4 w-96 bg-muted animate-pulse rounded mt-1" />
|
||||
</div>
|
||||
<div className="h-10 w-36 bg-muted animate-pulse rounded" />
|
||||
</div>
|
||||
|
||||
<PageTemplateSkeleton
|
||||
title="Policies"
|
||||
description="Manage your organization's policies"
|
||||
actions={<div className="bg-muted animate-pulse h-9 w-1/6 rounded-lg" />}
|
||||
>
|
||||
{/* Search and filter controls skeleton */}
|
||||
<div className="flex flex-col md:flex-row gap-4 mb-6">
|
||||
<div className="flex-1 h-10 bg-muted animate-pulse rounded" />
|
||||
@@ -407,7 +394,7 @@ function PolicyListPageFallback() {
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -421,14 +408,13 @@ export default function PolicyListPage() {
|
||||
loadQuery({ organizationId: organizationId! });
|
||||
}, [loadQuery, organizationId]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <PolicyListPageSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Policies - Probo Console</title>
|
||||
</Helmet>
|
||||
<Suspense fallback={<PolicyListPageFallback />}>
|
||||
{queryRef && <PolicyListPageContent queryRef={queryRef} />}
|
||||
<Suspense fallback={<PolicyListPageSkeleton />}>
|
||||
{<PolicyListPageContent queryRef={queryRef} />}
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,10 +15,9 @@ import { Badge } from "@/components/ui/badge";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import type { PolicyOverviewPageQuery as PolicyOverviewPageQueryType } from "./__generated__/PolicyOverviewPageQuery.graphql";
|
||||
import type { PolicyOverviewPageDeleteMutation } from "./__generated__/PolicyOverviewPageDeleteMutation.graphql";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import "../styles/policy-content.css";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { PageHeader } from "@/components/PageHeader";
|
||||
import { PageTemplate, PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
|
||||
const PolicyOverviewPageQuery = graphql`
|
||||
query PolicyOverviewPageQuery($policyId: ID!) {
|
||||
@@ -149,19 +148,11 @@ function PolicyOverviewPageContent({
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{policy.name} - Probo Console</title>
|
||||
</Helmet>
|
||||
<div className="container">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
<PageTemplate
|
||||
title={policy.name ?? ""}
|
||||
description={
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<Badge
|
||||
variant={policy.status === "ACTIVE" ? "default" : "outline"}
|
||||
>
|
||||
<Badge variant={policy.status === "ACTIVE" ? "default" : "outline"}>
|
||||
{policy.status === "ACTIVE" ? "Active" : "Draft"}
|
||||
</Badge>
|
||||
{policy.owner && (
|
||||
@@ -214,8 +205,7 @@ function PolicyOverviewPageContent({
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="lg:col-span-2">
|
||||
<Card className="border shadow-sm">
|
||||
@@ -332,9 +322,7 @@ function PolicyOverviewPageContent({
|
||||
<FileText className="h-4 w-4" />
|
||||
<span className="text-sm">Last Updated</span>
|
||||
</div>
|
||||
<p className="font-medium">
|
||||
{formatDate(policy.updatedAt)}
|
||||
</p>
|
||||
<p className="font-medium">{formatDate(policy.updatedAt)}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -380,8 +368,8 @@ function PolicyOverviewPageContent({
|
||||
<CardContent className="p-6">
|
||||
<h3 className="text-red-500 font-semibold mb-3">Danger Zone</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Permanently delete this policy and all of its data. This
|
||||
action cannot be undone.
|
||||
Permanently delete this policy and all of its data. This action
|
||||
cannot be undone.
|
||||
</p>
|
||||
<Button
|
||||
variant="destructive"
|
||||
@@ -411,14 +399,21 @@ function PolicyOverviewPageContent({
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function PolicyOverviewPageFallback() {
|
||||
export function PolicyOverviewPageSkeleton() {
|
||||
return (
|
||||
<div className="container mx-auto py-6">
|
||||
<PageTemplateSkeleton
|
||||
withDescription
|
||||
actions={
|
||||
<div className="flex gap-2 w-1/3">
|
||||
<div className="bg-muted animate-pulse h-8 w-1/2 rounded-lg" />
|
||||
<div className="bg-muted animate-pulse h-8 w-1/2 rounded-lg" />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="lg:col-span-2">
|
||||
<Card className="border shadow-sm">
|
||||
@@ -505,7 +500,7 @@ function PolicyOverviewPageFallback() {
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -520,8 +515,12 @@ export default function PolicyOverviewPage() {
|
||||
loadQuery({ policyId: policyId! });
|
||||
}, [loadQuery, policyId]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <PolicyOverviewPageSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<PolicyOverviewPageFallback />}>
|
||||
<Suspense fallback={<PolicyOverviewPageSkeleton />}>
|
||||
{queryRef && <PolicyOverviewPageContent queryRef={queryRef} />}
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { Suspense, useEffect, useState, useRef } from "react";
|
||||
import {
|
||||
graphql,
|
||||
@@ -39,7 +38,7 @@ import type { SettingsPageQuery as SettingsPageQueryType } from "./__generated__
|
||||
import type { SettingsPageUpdateOrganizationMutation as SettingsPageUpdateOrganizationMutationType } from "./__generated__/SettingsPageUpdateOrganizationMutation.graphql";
|
||||
import type { SettingsPageInviteUserMutation as SettingsPageInviteUserMutationType } from "./__generated__/SettingsPageInviteUserMutation.graphql";
|
||||
import type { SettingsPageRemoveUserMutation as SettingsPageRemoveUserMutationType } from "./__generated__/SettingsPageRemoveUserMutation.graphql";
|
||||
import { PageHeader } from "@/components/PageHeader";
|
||||
import { PageTemplate, PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
|
||||
const settingsPageQuery = graphql`
|
||||
query SettingsPageQuery($organizationID: ID!) {
|
||||
@@ -290,14 +289,11 @@ function SettingsPageContent({
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="container space-y-6">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
<PageTemplate
|
||||
title="Settings"
|
||||
description="Manage your details and personal preferences here"
|
||||
/>
|
||||
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Organization information</CardTitle>
|
||||
@@ -519,13 +515,17 @@ function SettingsPageContent({
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsPageFallback() {
|
||||
export function SettingsPageSkeleton() {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<PageTemplateSkeleton
|
||||
title="Settings"
|
||||
description="Manage your details and personal preferences here"
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-1">
|
||||
<div className="h-8 w-48 bg-muted animate-pulse rounded" />
|
||||
<div className="h-4 w-96 bg-muted animate-pulse rounded" />
|
||||
@@ -536,6 +536,7 @@ function SettingsPageFallback() {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -550,17 +551,12 @@ export default function SettingsPage() {
|
||||
}, [loadQuery, organizationId]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <SettingsPageFallback />;
|
||||
return <SettingsPageSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Settings - Probo Console</title>
|
||||
</Helmet>
|
||||
<Suspense fallback={<SettingsPageFallback />}>
|
||||
<Suspense fallback={<SettingsPageSkeleton />}>
|
||||
<SettingsPageContent queryRef={queryRef} />
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
PreloadedQuery,
|
||||
useQueryLoader,
|
||||
} from "react-relay";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -28,7 +27,7 @@ import type {
|
||||
ControlState,
|
||||
ControlImportance,
|
||||
} from "./__generated__/UpdateControlPageUpdateControlMutation.graphql";
|
||||
import { PageHeader } from "@/components/PageHeader";
|
||||
import { PageTemplate, PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
|
||||
const updateControlMutation = graphql`
|
||||
mutation UpdateControlPageUpdateControlMutation($input: UpdateControlInput!) {
|
||||
@@ -250,17 +249,10 @@ function UpdateControlPageContent({
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Update Control - Probo</title>
|
||||
</Helmet>
|
||||
<div className="container">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
<PageTemplate
|
||||
title="Update Control"
|
||||
description="Update the control details"
|
||||
/>
|
||||
|
||||
>
|
||||
<Card className="max-w-2xl">
|
||||
<form onSubmit={handleSubmit} className="p-6 space-y-6">
|
||||
<EditableField
|
||||
@@ -292,9 +284,7 @@ function UpdateControlPageContent({
|
||||
</Label>
|
||||
<Select
|
||||
value={formData.importance}
|
||||
onValueChange={(value) =>
|
||||
handleFieldChange("importance", value)
|
||||
}
|
||||
onValueChange={(value) => handleFieldChange("importance", value)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select importance" />
|
||||
@@ -337,13 +327,19 @@ function UpdateControlPageContent({
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
</>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function UpdateControlPageFallback() {
|
||||
return <div>Loading...</div>;
|
||||
export function UpdateControlPageSkeleton() {
|
||||
return (
|
||||
<PageTemplateSkeleton
|
||||
title="Update Control"
|
||||
description="Update the control details"
|
||||
>
|
||||
<div className="max-w-2xl aspect-square bg-muted rounded-xl animate-pulse" />
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
export default function UpdateControlPage() {
|
||||
@@ -358,11 +354,11 @@ export default function UpdateControlPage() {
|
||||
}, [controlId, loadQuery]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <UpdateControlPageFallback />;
|
||||
return <UpdateControlPageSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<UpdateControlPageFallback />}>
|
||||
<Suspense fallback={<UpdateControlPageSkeleton />}>
|
||||
<UpdateControlPageContent queryRef={queryRef} />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
PreloadedQuery,
|
||||
useQueryLoader,
|
||||
} from "react-relay";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -18,7 +17,7 @@ import { HelpCircle } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { UpdateFrameworkPageUpdateFrameworkMutation } from "./__generated__/UpdateFrameworkPageUpdateFrameworkMutation.graphql";
|
||||
import { UpdateFrameworkPageQuery as UpdateFrameworkPageQueryType } from "./__generated__/UpdateFrameworkPageQuery.graphql";
|
||||
import { PageHeader } from "@/components/PageHeader";
|
||||
import { PageTemplate, PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
|
||||
const updateFrameworkMutation = graphql`
|
||||
mutation UpdateFrameworkPageUpdateFrameworkMutation(
|
||||
@@ -199,17 +198,10 @@ function UpdateFrameworkPageContent({
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Update Framework - Probo</title>
|
||||
</Helmet>
|
||||
<div className="container">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
<PageTemplate
|
||||
title="Update Framework"
|
||||
description="Update the framework details"
|
||||
/>
|
||||
|
||||
>
|
||||
<Card className="max-w-2xl">
|
||||
<form onSubmit={handleSubmit} className="p-6 space-y-6">
|
||||
<EditableField
|
||||
@@ -238,13 +230,19 @@ function UpdateFrameworkPageContent({
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
</>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function UpdateFrameworkPageFallback() {
|
||||
return <div>Loading...</div>;
|
||||
export function UpdateFrameworkPageSkeleton() {
|
||||
return (
|
||||
<PageTemplateSkeleton
|
||||
title="Update Framework"
|
||||
description="Update the framework details"
|
||||
>
|
||||
<div className="max-w-2xl aspect-square bg-muted rounded-xl animate-pulse" />
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
export default function UpdateFrameworkPage() {
|
||||
@@ -259,11 +257,11 @@ export default function UpdateFrameworkPage() {
|
||||
}, [frameworkId, loadQuery]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <UpdateFrameworkPageFallback />;
|
||||
return <UpdateFrameworkPageSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<UpdateFrameworkPageFallback />}>
|
||||
<Suspense fallback={<UpdateFrameworkPageSkeleton />}>
|
||||
<UpdateFrameworkPageContent queryRef={queryRef} />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
useQueryLoader,
|
||||
PreloadedQuery,
|
||||
} from "react-relay";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -20,7 +19,7 @@ import PolicyEditor from "@/components/PolicyEditor";
|
||||
import PeopleSelector from "@/components/PeopleSelector";
|
||||
import type { UpdatePolicyPageQuery as UpdatePolicyPageQueryType } from "./__generated__/UpdatePolicyPageQuery.graphql";
|
||||
import type { UpdatePolicyPageMutation as UpdatePolicyPageMutationType } from "./__generated__/UpdatePolicyPageMutation.graphql";
|
||||
import { PageHeader } from "@/components/PageHeader";
|
||||
import { PageTemplate, PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
|
||||
const UpdatePolicyPageQuery = graphql`
|
||||
query UpdatePolicyPageQuery($policyId: ID!, $organizationId: ID!) {
|
||||
@@ -148,17 +147,7 @@ function UpdatePolicyPageContent({
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Update Policy - Probo Console</title>
|
||||
</Helmet>
|
||||
<div className="container">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
title="Update Policy"
|
||||
description="Update an existing policy"
|
||||
/>
|
||||
|
||||
<PageTemplate title="Update Policy" description="Update an existing policy">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="grid gap-6">
|
||||
<Card>
|
||||
@@ -226,10 +215,7 @@ function UpdatePolicyPageContent({
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label
|
||||
htmlFor="reviewDate"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Label htmlFor="reviewDate" className="flex items-center gap-2">
|
||||
<Calendar className="h-4 w-4" />
|
||||
Review Date
|
||||
</Label>
|
||||
@@ -261,14 +247,16 @@ function UpdatePolicyPageContent({
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function UpdatePolicyPageFallback() {
|
||||
export function UpdatePolicyPageSkeleton() {
|
||||
return (
|
||||
<div className="container mx-auto py-6">
|
||||
<PageTemplateSkeleton
|
||||
title="Update Policy"
|
||||
description="Update an existing policy"
|
||||
>
|
||||
<div className="flex items-center mb-6">
|
||||
<div className="mr-4">
|
||||
<div className="h-12 w-12 bg-muted animate-pulse rounded-lg" />
|
||||
@@ -279,7 +267,7 @@ function UpdatePolicyPageFallback() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-muted animate-pulse rounded-lg h-[600px]" />
|
||||
</div>
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -295,8 +283,12 @@ export default function UpdatePolicyPage() {
|
||||
}
|
||||
}, [organizationId, policyId, loadQuery]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <UpdatePolicyPageSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<UpdatePolicyPageFallback />}>
|
||||
<Suspense fallback={<UpdatePolicyPageSkeleton />}>
|
||||
{queryRef && <UpdatePolicyPageContent queryRef={queryRef} />}
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -16,13 +16,12 @@ import { Button } from "@/components/ui/button";
|
||||
import { Link } from "react-router";
|
||||
import Fuse from "fuse.js";
|
||||
import type { VendorListPageQuery as VendorListPageQueryType } from "./__generated__/VendorListPageQuery.graphql";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { VendorListPageCreateVendorMutation } from "./__generated__/VendorListPageCreateVendorMutation.graphql";
|
||||
import { VendorListPageDeleteVendorMutation } from "./__generated__/VendorListPageDeleteVendorMutation.graphql";
|
||||
import { VendorListPagePaginationQuery } from "./__generated__/VendorListPagePaginationQuery.graphql";
|
||||
import { VendorListPage_vendors$key } from "./__generated__/VendorListPage_vendors.graphql";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { PageHeader } from "@/components/PageHeader";
|
||||
import { PageTemplate, PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
|
||||
const ITEMS_PER_PAGE = 25;
|
||||
|
||||
@@ -240,18 +239,11 @@ function VendorListContent({
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Vendors - Probo</title>
|
||||
</Helmet>
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
className="mb-17"
|
||||
<PageTemplate
|
||||
title="Vendors"
|
||||
description="Vendors are third-party services that your company uses. Add them to
|
||||
keep track of their risk and compliance status."
|
||||
/>
|
||||
|
||||
>
|
||||
<div className="rounded-xl border bg-card p-4">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Store className="h-5 w-5" />
|
||||
@@ -269,9 +261,7 @@ function VendorListContent({
|
||||
if (value.trim() === "") {
|
||||
setFilteredVendors([]);
|
||||
} else {
|
||||
const results = fuse
|
||||
.search(value)
|
||||
.map((result) => result.item);
|
||||
const results = fuse.search(value).map((result) => result.item);
|
||||
setFilteredVendors(results);
|
||||
}
|
||||
}}
|
||||
@@ -462,14 +452,17 @@ function VendorListContent({
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function VendorListFallback() {
|
||||
export function VendorListPageSkeleton() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageTemplateSkeleton
|
||||
title="Vendors"
|
||||
description="Vendors are third-party services that your company uses. Add them to
|
||||
keep track of their risk and compliance status."
|
||||
>
|
||||
<div>
|
||||
<div className="h-8 w-48 bg-muted animate-pulse rounded" />
|
||||
<div className="h-4 w-96 bg-muted animate-pulse rounded mt-1" />
|
||||
@@ -486,7 +479,7 @@ function VendorListFallback() {
|
||||
<div key={i} className="h-[72px] bg-muted animate-pulse rounded-xl" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -511,17 +504,12 @@ export default function VendorListPage() {
|
||||
}, [loadQuery, organizationId]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <VendorListFallback />;
|
||||
return <VendorListPageSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Vendors - Probo Console</title>
|
||||
</Helmet>
|
||||
<Suspense fallback={<VendorListFallback />}>
|
||||
<Suspense fallback={<VendorListPageSkeleton />}>
|
||||
<VendorListContent queryRef={queryRef} />
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,9 +16,8 @@ import {
|
||||
import { Suspense, useEffect, useState, useCallback } from "react";
|
||||
import type { VendorOverviewPageQuery as VendorOverviewPageQueryType } from "./__generated__/VendorOverviewPageQuery.graphql";
|
||||
import { useParams } from "react-router";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { PageHeader } from "@/components/PageHeader";
|
||||
import { PageTemplate, PageTemplateSkeleton } from "@/components/PageTemplate";
|
||||
|
||||
const vendorOverviewPageQuery = graphql`
|
||||
query VendorOverviewPageQuery($vendorId: ID!) {
|
||||
@@ -205,13 +204,8 @@ function VendorOverviewPageContent({
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Vendor - Probo</title>
|
||||
</Helmet>
|
||||
<div className="container">
|
||||
<PageHeader className="mb-17" title={formData.name} />
|
||||
<div className="max-w-4xl space-y-6">
|
||||
<PageTemplate title={formData.name}>
|
||||
<div className="max-w-2xl space-y-6">
|
||||
<EditableField
|
||||
label="Name"
|
||||
value={formData.name}
|
||||
@@ -238,9 +232,7 @@ function VendorOverviewPageContent({
|
||||
label="Service Start At"
|
||||
value={formData.serviceStartAt}
|
||||
type="datetime-local"
|
||||
onChange={(value) =>
|
||||
handleFieldChange("serviceStartAt", value)
|
||||
}
|
||||
onChange={(value) => handleFieldChange("serviceStartAt", value)}
|
||||
/>
|
||||
|
||||
<EditableField
|
||||
@@ -326,9 +318,7 @@ function VendorOverviewPageContent({
|
||||
Critical
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
handleFieldChange("riskTier", "SIGNIFICANT")
|
||||
}
|
||||
onClick={() => handleFieldChange("riskTier", "SIGNIFICANT")}
|
||||
className={cn(
|
||||
"rounded-full px-4 py-1 text-sm transition-colors",
|
||||
formData.riskTier === "SIGNIFICANT"
|
||||
@@ -363,9 +353,7 @@ function VendorOverviewPageContent({
|
||||
<EditableField
|
||||
label="Status Page URL"
|
||||
value={formData.statusPageUrl || ""}
|
||||
onChange={(value) =>
|
||||
handleFieldChange("statusPageUrl", value)
|
||||
}
|
||||
onChange={(value) => handleFieldChange("statusPageUrl", value)}
|
||||
/>
|
||||
|
||||
<EditableField
|
||||
@@ -387,7 +375,6 @@ function VendorOverviewPageContent({
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hasChanges && (
|
||||
<div className="fixed bottom-6 right-6 flex gap-2">
|
||||
@@ -402,13 +389,13 @@ function VendorOverviewPageContent({
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</PageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function VendorOverviewPageFallback() {
|
||||
export function VendorOverviewPageSkeleton() {
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<PageTemplateSkeleton>
|
||||
<div className="space-y-1">
|
||||
<div className="h-8 w-48 bg-muted animate-pulse rounded" />
|
||||
<div className="h-4 w-96 bg-muted animate-pulse rounded" />
|
||||
@@ -418,7 +405,7 @@ function VendorOverviewPageFallback() {
|
||||
<div key={i} className="h-20 bg-muted animate-pulse rounded-lg" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</PageTemplateSkeleton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -433,17 +420,12 @@ export default function VendorOverviewPage() {
|
||||
}, [loadQuery, vendorId]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <VendorOverviewPageFallback />;
|
||||
return <VendorOverviewPageSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Vendor Overview - Probo Console</title>
|
||||
</Helmet>
|
||||
<Suspense fallback={<VendorOverviewPageFallback />}>
|
||||
<Suspense fallback={<VendorOverviewPageSkeleton />}>
|
||||
<VendorOverviewPageContent queryRef={queryRef} />
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user