Migrate current organization from in memory to url state
Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
@@ -53,6 +53,7 @@
|
|||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"babel-plugin-relay": "^18.2.0",
|
"babel-plugin-relay": "^18.2.0",
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
|
"eslint": "^9.21.0",
|
||||||
"relay-compiler": "^18.2.0",
|
"relay-compiler": "^18.2.0",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.7.3"
|
||||||
|
|||||||
@@ -4,7 +4,13 @@ import { lazy, StrictMode, Suspense } from "react";
|
|||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { HelmetProvider } from "react-helmet-async";
|
import { HelmetProvider } from "react-helmet-async";
|
||||||
import { RelayEnvironmentProvider } from "react-relay";
|
import { RelayEnvironmentProvider } from "react-relay";
|
||||||
import { BrowserRouter, Route, Routes, useLocation } from "react-router";
|
import {
|
||||||
|
BrowserRouter,
|
||||||
|
Route,
|
||||||
|
Routes,
|
||||||
|
useLocation,
|
||||||
|
Navigate,
|
||||||
|
} from "react-router";
|
||||||
import "App.css";
|
import "App.css";
|
||||||
import ErrorBoundary from "./components/ErrorBoundary";
|
import ErrorBoundary from "./components/ErrorBoundary";
|
||||||
import ConsoleLayout from "./layouts/ConsoleLayout";
|
import ConsoleLayout from "./layouts/ConsoleLayout";
|
||||||
@@ -12,7 +18,6 @@ import AuthLayout from "./layouts/AuthLayout";
|
|||||||
import { RelayEnvironment } from "./RelayEnvironment";
|
import { RelayEnvironment } from "./RelayEnvironment";
|
||||||
import { AuthProvider } from "./contexts/AuthContext";
|
import { AuthProvider } from "./contexts/AuthContext";
|
||||||
import { ProtectedRoute } from "./components/ProtectedRoute";
|
import { ProtectedRoute } from "./components/ProtectedRoute";
|
||||||
import { OrganizationProvider } from "./contexts/OrganizationContext";
|
|
||||||
|
|
||||||
posthog.init(process.env.POSTHOG_KEY!, {
|
posthog.init(process.env.POSTHOG_KEY!, {
|
||||||
api_host: process.env.POSTHOG_HOST,
|
api_host: process.env.POSTHOG_HOST,
|
||||||
@@ -24,6 +29,9 @@ posthog.init(process.env.POSTHOG_KEY!, {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const OrganizationSelectionPage = lazy(
|
||||||
|
() => import("./pages/OrganizationSelectionPage")
|
||||||
|
);
|
||||||
const HomePage = lazy(() => import("./pages/HomePage"));
|
const HomePage = lazy(() => import("./pages/HomePage"));
|
||||||
const NotFoundPage = lazy(() => import("./pages/NotFoundPage"));
|
const NotFoundPage = lazy(() => import("./pages/NotFoundPage"));
|
||||||
const PeopleListPage = lazy(() => import("./pages/PeopleListPage"));
|
const PeopleListPage = lazy(() => import("./pages/PeopleListPage"));
|
||||||
@@ -52,181 +60,194 @@ function App() {
|
|||||||
<HelmetProvider>
|
<HelmetProvider>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<OrganizationProvider>
|
<Routes>
|
||||||
<Routes>
|
{/* Authentication Routes - Accessible without login */}
|
||||||
{/* Authentication Routes - Accessible without login */}
|
<Route
|
||||||
|
path="/login"
|
||||||
|
element={
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<AuthLayout />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
}
|
||||||
|
>
|
||||||
<Route
|
<Route
|
||||||
path="/login"
|
index
|
||||||
element={
|
element={
|
||||||
<ErrorBoundaryWithLocation>
|
<Suspense>
|
||||||
<AuthLayout />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Route
|
|
||||||
index
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<LoginPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/register"
|
|
||||||
element={
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<AuthLayout />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Route
|
|
||||||
index
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<RegisterPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
{/* Protected Routes - Require authentication */}
|
|
||||||
<Route element={<ProtectedRoute />}>
|
|
||||||
<Route
|
|
||||||
path="/*"
|
|
||||||
element={
|
|
||||||
<ErrorBoundaryWithLocation>
|
<ErrorBoundaryWithLocation>
|
||||||
<ConsoleLayout />
|
<LoginPage />
|
||||||
</ErrorBoundaryWithLocation>
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/register"
|
||||||
|
element={
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<AuthLayout />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route
|
||||||
|
index
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<RegisterPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
{/* Protected Routes - Require authentication */}
|
||||||
|
<Route element={<ProtectedRoute />}>
|
||||||
|
{/* Organization Selection Route */}
|
||||||
|
<Route
|
||||||
|
path="/"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<OrganizationSelectionPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Organization Creation Route */}
|
||||||
|
<Route
|
||||||
|
path="/organizations/create"
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<CreateOrganizationPage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Organization-specific Routes */}
|
||||||
|
<Route
|
||||||
|
path="/organizations/:organizationId/*"
|
||||||
|
element={
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<ConsoleLayout />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route
|
||||||
|
index
|
||||||
|
element={
|
||||||
|
<Suspense>
|
||||||
|
<ErrorBoundaryWithLocation>
|
||||||
|
<HomePage />
|
||||||
|
</ErrorBoundaryWithLocation>
|
||||||
|
</Suspense>
|
||||||
}
|
}
|
||||||
>
|
/>
|
||||||
<Route
|
<Route
|
||||||
index
|
path="peoples"
|
||||||
element={
|
element={
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<ErrorBoundaryWithLocation>
|
<ErrorBoundaryWithLocation>
|
||||||
<HomePage />
|
<PeopleListPage />
|
||||||
</ErrorBoundaryWithLocation>
|
</ErrorBoundaryWithLocation>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="peoples"
|
path="peoples/create"
|
||||||
element={
|
element={
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<ErrorBoundaryWithLocation>
|
<ErrorBoundaryWithLocation>
|
||||||
<PeopleListPage />
|
<CreatePeoplePage />
|
||||||
</ErrorBoundaryWithLocation>
|
</ErrorBoundaryWithLocation>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="peoples/create"
|
path="peoples/:peopleId"
|
||||||
element={
|
element={
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<ErrorBoundaryWithLocation>
|
<ErrorBoundaryWithLocation>
|
||||||
<CreatePeoplePage />
|
<PeopleOverviewPage />
|
||||||
</ErrorBoundaryWithLocation>
|
</ErrorBoundaryWithLocation>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="peoples/:peopleId"
|
path="vendors"
|
||||||
element={
|
element={
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<ErrorBoundaryWithLocation>
|
<ErrorBoundaryWithLocation>
|
||||||
<PeopleOverviewPage />
|
<VendorListPage />
|
||||||
</ErrorBoundaryWithLocation>
|
</ErrorBoundaryWithLocation>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="vendors"
|
path="frameworks"
|
||||||
element={
|
element={
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<ErrorBoundaryWithLocation>
|
<ErrorBoundaryWithLocation>
|
||||||
<VendorListPage />
|
<FrameworkListPage />
|
||||||
</ErrorBoundaryWithLocation>
|
</ErrorBoundaryWithLocation>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="frameworks"
|
path="frameworks/:frameworkId"
|
||||||
element={
|
element={
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<ErrorBoundaryWithLocation>
|
<ErrorBoundaryWithLocation>
|
||||||
<FrameworkListPage />
|
<FrameworkOverviewPage />
|
||||||
</ErrorBoundaryWithLocation>
|
</ErrorBoundaryWithLocation>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="frameworks/:frameworkId"
|
path="frameworks/:frameworkId/controls/:controlId"
|
||||||
element={
|
element={
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<ErrorBoundaryWithLocation>
|
<ErrorBoundaryWithLocation>
|
||||||
<FrameworkOverviewPage />
|
<ControlOverviewPage />
|
||||||
</ErrorBoundaryWithLocation>
|
</ErrorBoundaryWithLocation>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="frameworks/:frameworkId/controls/:controlId"
|
path="vendors/:vendorId"
|
||||||
element={
|
element={
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<ErrorBoundaryWithLocation>
|
<ErrorBoundaryWithLocation>
|
||||||
<ControlOverviewPage />
|
<VendorOverviewPage />
|
||||||
</ErrorBoundaryWithLocation>
|
</ErrorBoundaryWithLocation>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="vendors/:vendorId"
|
path="settings"
|
||||||
element={
|
element={
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<ErrorBoundaryWithLocation>
|
<ErrorBoundaryWithLocation>
|
||||||
<VendorOverviewPage />
|
<SettingsPage />
|
||||||
</ErrorBoundaryWithLocation>
|
</ErrorBoundaryWithLocation>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="settings"
|
path="*"
|
||||||
element={
|
element={
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<ErrorBoundaryWithLocation>
|
<NotFoundPage />
|
||||||
<SettingsPage />
|
</Suspense>
|
||||||
</ErrorBoundaryWithLocation>
|
}
|
||||||
</Suspense>
|
/>
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="organizations/create"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<ErrorBoundaryWithLocation>
|
|
||||||
<CreateOrganizationPage />
|
|
||||||
</ErrorBoundaryWithLocation>
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="*"
|
|
||||||
element={
|
|
||||||
<Suspense>
|
|
||||||
<NotFoundPage />
|
|
||||||
</Suspense>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Route>
|
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Route>
|
||||||
</OrganizationProvider>
|
</Routes>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</HelmetProvider>
|
</HelmetProvider>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Suspense, useEffect } from "react";
|
import { Suspense, useEffect } from "react";
|
||||||
|
import { useParams } from "react-router";
|
||||||
import {
|
import {
|
||||||
BookOpen,
|
BookOpen,
|
||||||
Users,
|
Users,
|
||||||
@@ -31,55 +32,52 @@ import {
|
|||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
import type { AppSidebarQuery as AppSidebarQueryType } from "./__generated__/AppSidebarQuery.graphql";
|
import type { AppSidebarQuery as AppSidebarQueryType } from "./__generated__/AppSidebarQuery.graphql";
|
||||||
import { TeamSwitcher } from "@/components/TeamSwitcher";
|
import { TeamSwitcher } from "@/components/TeamSwitcher";
|
||||||
import { url } from "inspector";
|
|
||||||
const staticData = {
|
function getNavItems(organizationId: string) {
|
||||||
user: {
|
return {
|
||||||
name: "shadcn",
|
navMain: [
|
||||||
email: "m@example.com",
|
{
|
||||||
avatar: "/avatars/shadcn.jpg",
|
title: "Frameworks",
|
||||||
},
|
url: `/organizations/${organizationId}/frameworks`,
|
||||||
navMain: [
|
icon: BookOpen,
|
||||||
{
|
},
|
||||||
title: "Frameworks",
|
{
|
||||||
url: "/frameworks",
|
title: "Organizations",
|
||||||
icon: BookOpen,
|
icon: Building,
|
||||||
},
|
url: `/organizations/${organizationId}/peoples`,
|
||||||
{
|
items: [
|
||||||
title: "Organizations",
|
{
|
||||||
icon: Building,
|
title: "Peoples",
|
||||||
url: "/peoples",
|
url: `/organizations/${organizationId}/peoples`,
|
||||||
items: [
|
icon: Users,
|
||||||
{
|
},
|
||||||
title: "Peoples",
|
{
|
||||||
url: "/peoples",
|
title: "Vendors",
|
||||||
icon: Users,
|
url: `/organizations/${organizationId}/vendors`,
|
||||||
},
|
icon: ToyBrick,
|
||||||
{
|
},
|
||||||
title: "Vendors",
|
],
|
||||||
url: "/vendors",
|
},
|
||||||
icon: ToyBrick,
|
{
|
||||||
},
|
title: "Settings",
|
||||||
],
|
url: `/organizations/${organizationId}/settings`,
|
||||||
},
|
icon: Settings,
|
||||||
{
|
},
|
||||||
title: "Settings",
|
],
|
||||||
url: "/settings",
|
navSecondary: [
|
||||||
icon: Settings,
|
{
|
||||||
},
|
title: "Support",
|
||||||
],
|
url: "#",
|
||||||
navSecondary: [
|
icon: LifeBuoy,
|
||||||
{
|
},
|
||||||
title: "Support",
|
{
|
||||||
url: "#",
|
title: "Feedback",
|
||||||
icon: LifeBuoy,
|
url: "#",
|
||||||
},
|
icon: Send,
|
||||||
{
|
},
|
||||||
title: "Feedback",
|
],
|
||||||
url: "#",
|
};
|
||||||
icon: Send,
|
}
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const AppSidebarQuery = graphql`
|
const AppSidebarQuery = graphql`
|
||||||
query AppSidebarQuery {
|
query AppSidebarQuery {
|
||||||
@@ -97,7 +95,9 @@ function AppSidebarContent({
|
|||||||
}: React.ComponentProps<typeof Sidebar> & {
|
}: React.ComponentProps<typeof Sidebar> & {
|
||||||
queryRef: PreloadedQuery<AppSidebarQueryType>;
|
queryRef: PreloadedQuery<AppSidebarQueryType>;
|
||||||
}) {
|
}) {
|
||||||
|
const { organizationId } = useParams();
|
||||||
const data = usePreloadedQuery(AppSidebarQuery, queryRef);
|
const data = usePreloadedQuery(AppSidebarQuery, queryRef);
|
||||||
|
const navItems = getNavItems(organizationId!);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar variant="inset" {...props}>
|
<Sidebar variant="inset" {...props}>
|
||||||
@@ -105,8 +105,8 @@ function AppSidebarContent({
|
|||||||
<TeamSwitcher organizations={data.viewer} />
|
<TeamSwitcher organizations={data.viewer} />
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
<NavMain items={staticData.navMain} />
|
<NavMain items={navItems.navMain} />
|
||||||
<NavSecondary items={staticData.navSecondary} className="mt-auto" />
|
<NavSecondary items={navItems.navSecondary} className="mt-auto" />
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarFooter>
|
<SidebarFooter>
|
||||||
<NavUser viewer={data.viewer} />
|
<NavUser viewer={data.viewer} />
|
||||||
@@ -121,39 +121,37 @@ function AppSidebarFallback(props: React.ComponentProps<typeof Sidebar>) {
|
|||||||
<SidebarHeader>
|
<SidebarHeader>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton size="lg" asChild>
|
<SidebarMenuButton size="lg">
|
||||||
<a href="#">
|
<div className="h-8 w-8 rounded-lg bg-sidebar-muted" />
|
||||||
<div className="size-8 rounded-lg bg-muted animate-pulse" />
|
<div className="flex-1 space-y-1">
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
<div className="h-4 w-3/4 rounded-lg bg-sidebar-muted" />
|
||||||
<span className="truncate font-semibold">
|
<div className="h-3 w-1/2 rounded-lg bg-sidebar-muted" />
|
||||||
<div className="h-4 w-24 animate-pulse rounded bg-muted" />
|
</div>
|
||||||
</span>
|
|
||||||
<span className="truncate text-xs">Enterprise</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
<NavMain items={staticData.navMain} />
|
<SidebarMenu>
|
||||||
<NavSecondary items={staticData.navSecondary} className="mt-auto" />
|
{Array.from({ length: 3 }).map((_, i) => (
|
||||||
|
<SidebarMenuItem key={i}>
|
||||||
|
<SidebarMenuButton>
|
||||||
|
<div className="h-4 w-4 rounded-lg bg-sidebar-muted" />
|
||||||
|
<div className="h-4 w-24 rounded-lg bg-sidebar-muted" />
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
))}
|
||||||
|
</SidebarMenu>
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarFooter>
|
<SidebarFooter>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton size="lg" asChild>
|
<SidebarMenuButton size="lg">
|
||||||
<a href="#">
|
<div className="h-8 w-8 rounded-lg bg-sidebar-muted" />
|
||||||
<div className="h-8 w-8 rounded-lg bg-muted animate-pulse" />
|
<div className="flex-1 space-y-1">
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
<div className="h-4 w-3/4 rounded-lg bg-sidebar-muted" />
|
||||||
<span className="truncate font-semibold">
|
<div className="h-3 w-1/2 rounded-lg bg-sidebar-muted" />
|
||||||
<div className="h-4 w-24 animate-pulse rounded bg-muted" />
|
</div>
|
||||||
</span>
|
|
||||||
<span className="truncate text-xs">
|
|
||||||
<div className="h-3 w-32 animate-pulse rounded bg-muted" />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
Sparkles,
|
Sparkles,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { graphql, useFragment } from "react-relay";
|
import { graphql, useFragment } from "react-relay";
|
||||||
|
import { useNavigate } from "react-router";
|
||||||
|
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import {
|
import {
|
||||||
@@ -26,6 +27,7 @@ import {
|
|||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
useSidebar,
|
useSidebar,
|
||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
|
import { useAuth } from "@/contexts/AuthContext";
|
||||||
import {
|
import {
|
||||||
NavUser_viewer$key,
|
NavUser_viewer$key,
|
||||||
NavUser_viewer$data,
|
NavUser_viewer$data,
|
||||||
@@ -42,6 +44,13 @@ export const navUserFragment = graphql`
|
|||||||
export function NavUser({ viewer }: { viewer: NavUser_viewer$key }) {
|
export function NavUser({ viewer }: { viewer: NavUser_viewer$key }) {
|
||||||
const { isMobile } = useSidebar();
|
const { isMobile } = useSidebar();
|
||||||
const currentUser = useFragment(navUserFragment, viewer);
|
const currentUser = useFragment(navUserFragment, viewer);
|
||||||
|
const { logout } = useAuth();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
await logout();
|
||||||
|
navigate("/login");
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
@@ -116,7 +125,7 @@ export function NavUser({ viewer }: { viewer: NavUser_viewer$key }) {
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem onClick={handleLogout}>
|
||||||
<LogOut />
|
<LogOut />
|
||||||
Log out
|
Log out
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|||||||
@@ -3,8 +3,7 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { ChevronsUpDown, Plus } from "lucide-react";
|
import { ChevronsUpDown, Plus } from "lucide-react";
|
||||||
import { graphql, useFragment } from "react-relay";
|
import { graphql, useFragment } from "react-relay";
|
||||||
import { useOrganization } from "@/contexts/OrganizationContext";
|
import { Link, useNavigate, useParams } from "react-router";
|
||||||
import { Link } from "react-router";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -54,20 +53,22 @@ export function TeamSwitcher({
|
|||||||
}) {
|
}) {
|
||||||
const { isMobile } = useSidebar();
|
const { isMobile } = useSidebar();
|
||||||
const data = useFragment(teamSwitcherFragment, organizations);
|
const data = useFragment(teamSwitcherFragment, organizations);
|
||||||
const { currentOrganization, setCurrentOrganization } = useOrganization();
|
const navigate = useNavigate();
|
||||||
|
const { organizationId } = useParams();
|
||||||
|
const [currentOrganization, setCurrentOrganization] =
|
||||||
|
useState<Organization | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(data.organizations);
|
if (data.organizations && data.organizations.edges.length > 0) {
|
||||||
|
// Find the current organization based on the URL parameter
|
||||||
if (
|
const org = data.organizations.edges.find(
|
||||||
data.organizations &&
|
(edge) => edge.node.id === organizationId
|
||||||
data.organizations.edges.length > 0 &&
|
);
|
||||||
!currentOrganization
|
if (org) {
|
||||||
) {
|
setCurrentOrganization(org.node);
|
||||||
// Type assertion to include plan field
|
}
|
||||||
setCurrentOrganization(data.organizations.edges[0].node);
|
|
||||||
}
|
}
|
||||||
}, [data.organizations, currentOrganization, setCurrentOrganization]);
|
}, [data.organizations, organizationId]);
|
||||||
|
|
||||||
// If no organizations or data is still loading
|
// If no organizations or data is still loading
|
||||||
if (!currentOrganization) {
|
if (!currentOrganization) {
|
||||||
@@ -88,6 +89,11 @@ export function TeamSwitcher({
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOrganizationSwitch = (org: Organization) => {
|
||||||
|
// Navigate to the selected organization
|
||||||
|
navigate(`/organizations/${org.id}`);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
@@ -121,7 +127,7 @@ export function TeamSwitcher({
|
|||||||
{data.organizations.edges.map((edge, index) => (
|
{data.organizations.edges.map((edge, index) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={edge.node.id}
|
key={edge.node.id}
|
||||||
onClick={() => setCurrentOrganization(edge.node)}
|
onClick={() => handleOrganizationSwitch(edge.node)}
|
||||||
className="gap-2 p-2"
|
className="gap-2 p-2"
|
||||||
>
|
>
|
||||||
<div className="flex size-6 items-center justify-center rounded-sm border">
|
<div className="flex size-6 items-center justify-center rounded-sm border">
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import React, { createContext, useContext, useState, ReactNode } from "react";
|
|
||||||
|
|
||||||
interface Organization {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
logoUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OrganizationContextType {
|
|
||||||
currentOrganization: Organization | null;
|
|
||||||
setCurrentOrganization: (organization: Organization) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const OrganizationContext = createContext<OrganizationContextType | undefined>(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
export function useOrganization() {
|
|
||||||
const context = useContext(OrganizationContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error(
|
|
||||||
"useOrganization must be used within an OrganizationProvider",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OrganizationProviderProps {
|
|
||||||
children: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function OrganizationProvider({ children }: OrganizationProviderProps) {
|
|
||||||
const [currentOrganization, setCurrentOrganization] =
|
|
||||||
useState<Organization | null>(null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<OrganizationContext.Provider
|
|
||||||
value={{ currentOrganization, setCurrentOrganization }}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</OrganizationContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -29,14 +29,32 @@ import { ConsoleLayoutBreadcrumbFrameworkOverviewQuery } from "./__generated__/C
|
|||||||
import { ConsoleLayoutBreadcrumbPeopleOverviewQuery } from "./__generated__/ConsoleLayoutBreadcrumbPeopleOverviewQuery.graphql";
|
import { ConsoleLayoutBreadcrumbPeopleOverviewQuery } from "./__generated__/ConsoleLayoutBreadcrumbPeopleOverviewQuery.graphql";
|
||||||
import { ConsoleLayoutBreadcrumbVendorOverviewQuery } from "./__generated__/ConsoleLayoutBreadcrumbVendorOverviewQuery.graphql";
|
import { ConsoleLayoutBreadcrumbVendorOverviewQuery } from "./__generated__/ConsoleLayoutBreadcrumbVendorOverviewQuery.graphql";
|
||||||
import { ConsoleLayoutBreadcrumbControlOverviewQuery } from "./__generated__/ConsoleLayoutBreadcrumbControlOverviewQuery.graphql";
|
import { ConsoleLayoutBreadcrumbControlOverviewQuery } from "./__generated__/ConsoleLayoutBreadcrumbControlOverviewQuery.graphql";
|
||||||
|
import { ConsoleLayoutOrganizationQuery } from "./__generated__/ConsoleLayoutOrganizationQuery.graphql";
|
||||||
|
|
||||||
function BreadcrumbHome({ children }: { children: React.ReactNode }) {
|
function BreadcrumbHome({ children }: { children: React.ReactNode }) {
|
||||||
|
const { organizationId } = useParams();
|
||||||
|
const data = useLazyLoadQuery<ConsoleLayoutOrganizationQuery>(
|
||||||
|
graphql`
|
||||||
|
query ConsoleLayoutOrganizationQuery($organizationId: ID!) {
|
||||||
|
organization: node(id: $organizationId) {
|
||||||
|
... on Organization {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
{ organizationId: organizationId! },
|
||||||
|
{ fetchPolicy: "store-or-network" }
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<BreadcrumbList>
|
<BreadcrumbList>
|
||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
<BreadcrumbLink asChild>
|
<BreadcrumbLink asChild>
|
||||||
<Link to="/">Home</Link>
|
<Link to={`/organizations/${organizationId}`}>
|
||||||
|
{data.organization?.name || "Home"}
|
||||||
|
</Link>
|
||||||
</BreadcrumbLink>
|
</BreadcrumbLink>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
{children}
|
{children}
|
||||||
@@ -46,12 +64,15 @@ function BreadcrumbHome({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbFrameworkList() {
|
function BreadcrumbFrameworkList() {
|
||||||
|
const { organizationId } = useParams();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BreadcrumbSeparator />
|
<BreadcrumbSeparator />
|
||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
<BreadcrumbLink asChild>
|
<BreadcrumbLink asChild>
|
||||||
<Link to="/frameworks">Frameworks</Link>
|
<Link to={`/organizations/${organizationId}/frameworks`}>
|
||||||
|
Frameworks
|
||||||
|
</Link>
|
||||||
</BreadcrumbLink>
|
</BreadcrumbLink>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
@@ -60,7 +81,7 @@ function BreadcrumbFrameworkList() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbFrameworkOverview() {
|
function BreadcrumbFrameworkOverview() {
|
||||||
const { frameworkId } = useParams();
|
const { organizationId, frameworkId } = useParams();
|
||||||
const data = useLazyLoadQuery<ConsoleLayoutBreadcrumbFrameworkOverviewQuery>(
|
const data = useLazyLoadQuery<ConsoleLayoutBreadcrumbFrameworkOverviewQuery>(
|
||||||
graphql`
|
graphql`
|
||||||
query ConsoleLayoutBreadcrumbFrameworkOverviewQuery($frameworkId: ID!) {
|
query ConsoleLayoutBreadcrumbFrameworkOverviewQuery($frameworkId: ID!) {
|
||||||
@@ -81,7 +102,9 @@ function BreadcrumbFrameworkOverview() {
|
|||||||
<BreadcrumbSeparator />
|
<BreadcrumbSeparator />
|
||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
<BreadcrumbLink asChild>
|
<BreadcrumbLink asChild>
|
||||||
<Link to={`/frameworks/${data.framework.id}`}>
|
<Link
|
||||||
|
to={`/organizations/${organizationId}/frameworks/${data.framework.id}`}
|
||||||
|
>
|
||||||
{data.framework.name}
|
{data.framework.name}
|
||||||
</Link>
|
</Link>
|
||||||
</BreadcrumbLink>
|
</BreadcrumbLink>
|
||||||
@@ -91,71 +114,14 @@ function BreadcrumbFrameworkOverview() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbPeopleList() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<BreadcrumbSeparator />
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbLink asChild>
|
|
||||||
<Link to="/peoples">People</Link>
|
|
||||||
</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<Outlet />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function BreadcrumbCreatePeople() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<BreadcrumbSeparator />
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbLink asChild>
|
|
||||||
<Link to="/peoples/create">Create People</Link>
|
|
||||||
</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<Outlet />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function BreadcrumbPeopleOverview() {
|
|
||||||
const { peopleId } = useParams();
|
|
||||||
const data = useLazyLoadQuery<ConsoleLayoutBreadcrumbPeopleOverviewQuery>(
|
|
||||||
graphql`
|
|
||||||
query ConsoleLayoutBreadcrumbPeopleOverviewQuery($peopleId: ID!) {
|
|
||||||
people: node(id: $peopleId) {
|
|
||||||
id
|
|
||||||
... on People {
|
|
||||||
fullName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
{ peopleId: peopleId! },
|
|
||||||
{ fetchPolicy: "store-or-network" }
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<BreadcrumbSeparator />
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbLink asChild>
|
|
||||||
<Link to={`/peoples/${data.people.id}`}>{data.people.fullName}</Link>
|
|
||||||
</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<Outlet />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function BreadcrumbVendorList() {
|
function BreadcrumbVendorList() {
|
||||||
|
const { organizationId } = useParams();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BreadcrumbSeparator />
|
<BreadcrumbSeparator />
|
||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
<BreadcrumbLink asChild>
|
<BreadcrumbLink asChild>
|
||||||
<Link to="/vendors">Vendors</Link>
|
<Link to={`/organizations/${organizationId}/vendors`}>Vendors</Link>
|
||||||
</BreadcrumbLink>
|
</BreadcrumbLink>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
@@ -164,7 +130,7 @@ function BreadcrumbVendorList() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbVendorOverview() {
|
function BreadcrumbVendorOverview() {
|
||||||
const { vendorId } = useParams();
|
const { organizationId, vendorId } = useParams();
|
||||||
const data = useLazyLoadQuery<ConsoleLayoutBreadcrumbVendorOverviewQuery>(
|
const data = useLazyLoadQuery<ConsoleLayoutBreadcrumbVendorOverviewQuery>(
|
||||||
graphql`
|
graphql`
|
||||||
query ConsoleLayoutBreadcrumbVendorOverviewQuery($vendorId: ID!) {
|
query ConsoleLayoutBreadcrumbVendorOverviewQuery($vendorId: ID!) {
|
||||||
@@ -185,15 +151,91 @@ function BreadcrumbVendorOverview() {
|
|||||||
<BreadcrumbSeparator />
|
<BreadcrumbSeparator />
|
||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
<BreadcrumbLink asChild>
|
<BreadcrumbLink asChild>
|
||||||
<Link to={`/vendors/${data.vendor.id}`}>{data.vendor.name}</Link>
|
<Link
|
||||||
|
to={`/organizations/${organizationId}/vendors/${data.vendor.id}`}
|
||||||
|
>
|
||||||
|
{data.vendor.name}
|
||||||
|
</Link>
|
||||||
</BreadcrumbLink>
|
</BreadcrumbLink>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
|
<Outlet />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbPeopleList() {
|
||||||
|
const { organizationId } = useParams();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BreadcrumbSeparator />
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbLink asChild>
|
||||||
|
<Link to={`/organizations/${organizationId}/peoples`}>People</Link>
|
||||||
|
</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<Outlet />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbCreatePeople() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BreadcrumbSeparator />
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbPage>Create</BreadcrumbPage>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbPeopleOverview() {
|
||||||
|
const { organizationId, peopleId } = useParams();
|
||||||
|
const data = useLazyLoadQuery<ConsoleLayoutBreadcrumbPeopleOverviewQuery>(
|
||||||
|
graphql`
|
||||||
|
query ConsoleLayoutBreadcrumbPeopleOverviewQuery($peopleId: ID!) {
|
||||||
|
people: node(id: $peopleId) {
|
||||||
|
id
|
||||||
|
... on People {
|
||||||
|
fullName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
{ peopleId: peopleId! },
|
||||||
|
{ fetchPolicy: "store-or-network" }
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BreadcrumbSeparator />
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbLink asChild>
|
||||||
|
<Link
|
||||||
|
to={`/organizations/${organizationId}/peoples/${data.people.id}`}
|
||||||
|
>
|
||||||
|
{data.people.fullName}
|
||||||
|
</Link>
|
||||||
|
</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<Outlet />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbCreateOrganization() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BreadcrumbSeparator />
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbPage>Create Organization</BreadcrumbPage>
|
||||||
|
</BreadcrumbItem>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbControlOverview() {
|
function BreadcrumbControlOverview() {
|
||||||
const { frameworkId, controlId } = useParams();
|
const { organizationId, frameworkId, controlId } = useParams();
|
||||||
const data = useLazyLoadQuery<ConsoleLayoutBreadcrumbControlOverviewQuery>(
|
const data = useLazyLoadQuery<ConsoleLayoutBreadcrumbControlOverviewQuery>(
|
||||||
graphql`
|
graphql`
|
||||||
query ConsoleLayoutBreadcrumbControlOverviewQuery(
|
query ConsoleLayoutBreadcrumbControlOverviewQuery(
|
||||||
@@ -223,7 +265,9 @@ function BreadcrumbControlOverview() {
|
|||||||
<BreadcrumbSeparator />
|
<BreadcrumbSeparator />
|
||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
<BreadcrumbLink asChild>
|
<BreadcrumbLink asChild>
|
||||||
<Link to={`/frameworks/${data.framework.id}`}>
|
<Link
|
||||||
|
to={`/organizations/${organizationId}/frameworks/${data.framework.id}`}
|
||||||
|
>
|
||||||
{data.framework.name}
|
{data.framework.name}
|
||||||
</Link>
|
</Link>
|
||||||
</BreadcrumbLink>
|
</BreadcrumbLink>
|
||||||
@@ -232,7 +276,7 @@ function BreadcrumbControlOverview() {
|
|||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
<BreadcrumbLink asChild>
|
<BreadcrumbLink asChild>
|
||||||
<Link
|
<Link
|
||||||
to={`/frameworks/${data.framework.id}/controls/${data.control.id}`}
|
to={`/organizations/${organizationId}/frameworks/${data.framework.id}/controls/${data.control.id}`}
|
||||||
>
|
>
|
||||||
{data.control.name}
|
{data.control.name}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -243,21 +287,9 @@ function BreadcrumbControlOverview() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbCreateOrganization() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<BreadcrumbSeparator />
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbLink asChild>
|
|
||||||
<Link to="/organizations/create">Create Organization</Link>
|
|
||||||
</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<Outlet />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ConsoleLayout() {
|
export default function ConsoleLayout() {
|
||||||
|
const { organizationId } = useParams();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<AppSidebar />
|
<AppSidebar />
|
||||||
@@ -268,36 +300,29 @@ export default function ConsoleLayout() {
|
|||||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||||
<BreadcrumbHome>
|
<BreadcrumbHome>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/frameworks" element={<BreadcrumbFrameworkList />}>
|
<Route path="frameworks" element={<BreadcrumbFrameworkList />}>
|
||||||
<Route
|
<Route
|
||||||
path="/frameworks/:frameworkId"
|
path=":frameworkId"
|
||||||
element={<BreadcrumbFrameworkOverview />}
|
element={<BreadcrumbFrameworkOverview />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/frameworks/:frameworkId/controls/:controlId"
|
path=":frameworkId/controls/:controlId"
|
||||||
element={<BreadcrumbControlOverview />}
|
element={<BreadcrumbControlOverview />}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/peoples" element={<BreadcrumbPeopleList />}>
|
<Route path="peoples" element={<BreadcrumbPeopleList />}>
|
||||||
<Route
|
<Route
|
||||||
path="/peoples/:peopleId"
|
path=":peopleId"
|
||||||
element={<BreadcrumbPeopleOverview />}
|
element={<BreadcrumbPeopleOverview />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route path="create" element={<BreadcrumbCreatePeople />} />
|
||||||
path="/peoples/create"
|
|
||||||
element={<BreadcrumbCreatePeople />}
|
|
||||||
/>
|
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/vendors" element={<BreadcrumbVendorList />}>
|
<Route path="vendors" element={<BreadcrumbVendorList />}>
|
||||||
<Route
|
<Route
|
||||||
path="/vendors/:vendorId"
|
path=":vendorId"
|
||||||
element={<BreadcrumbVendorOverview />}
|
element={<BreadcrumbVendorOverview />}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route
|
|
||||||
path="/organizations/create"
|
|
||||||
element={<BreadcrumbCreateOrganization />}
|
|
||||||
/>
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</BreadcrumbHome>
|
</BreadcrumbHome>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
124
apps/console/src/layouts/__generated__/ConsoleLayoutOrganizationQuery.graphql.ts
generated
Normal file
124
apps/console/src/layouts/__generated__/ConsoleLayoutOrganizationQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<93800eee21fa3f30cc45da37370359fd>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type ConsoleLayoutOrganizationQuery$variables = {
|
||||||
|
organizationId: string;
|
||||||
|
};
|
||||||
|
export type ConsoleLayoutOrganizationQuery$data = {
|
||||||
|
readonly organization: {
|
||||||
|
readonly name?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type ConsoleLayoutOrganizationQuery = {
|
||||||
|
response: ConsoleLayoutOrganizationQuery$data;
|
||||||
|
variables: ConsoleLayoutOrganizationQuery$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "organizationId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "id",
|
||||||
|
"variableName": "organizationId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v2 = {
|
||||||
|
"kind": "InlineFragment",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Organization",
|
||||||
|
"abstractKey": null
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "ConsoleLayoutOrganizationQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": "organization",
|
||||||
|
"args": (v1/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v2/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Query",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "ConsoleLayoutOrganizationQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": "organization",
|
||||||
|
"args": (v1/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "__typename",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
(v2/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "e275faa366380b1b423251e6a06ab743",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "ConsoleLayoutOrganizationQuery",
|
||||||
|
"operationKind": "query",
|
||||||
|
"text": "query ConsoleLayoutOrganizationQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n name\n }\n id\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "3310c40eb8f1e3874139c934fb08cbb1";
|
||||||
|
|
||||||
|
export default node;
|
||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
} from "react-relay";
|
} from "react-relay";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useOrganization } from "@/contexts/OrganizationContext";
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
@@ -84,7 +83,7 @@ function EditableField({
|
|||||||
|
|
||||||
export default function CreateOrganizationPage() {
|
export default function CreateOrganizationPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { setCurrentOrganization } = useOrganization();
|
const { toast } = useToast();
|
||||||
const data = useLazyLoadQuery<CreateOrganizationPageViewerQuery>(
|
const data = useLazyLoadQuery<CreateOrganizationPageViewerQuery>(
|
||||||
viewerQuery,
|
viewerQuery,
|
||||||
{}
|
{}
|
||||||
@@ -93,7 +92,6 @@ export default function CreateOrganizationPage() {
|
|||||||
useMutation<CreateOrganizationPageCreateOrganizationMutation>(
|
useMutation<CreateOrganizationPageCreateOrganizationMutation>(
|
||||||
createOrganizationMutation
|
createOrganizationMutation
|
||||||
);
|
);
|
||||||
const { toast } = useToast();
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: "",
|
name: "",
|
||||||
});
|
});
|
||||||
@@ -122,13 +120,12 @@ export default function CreateOrganizationPage() {
|
|||||||
},
|
},
|
||||||
onCompleted: (response) => {
|
onCompleted: (response) => {
|
||||||
const newOrg = response.createOrganization.organizationEdge.node;
|
const newOrg = response.createOrganization.organizationEdge.node;
|
||||||
setCurrentOrganization(newOrg);
|
|
||||||
toast({
|
toast({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
description: "Organization created successfully",
|
description: "Organization created successfully",
|
||||||
variant: "default",
|
variant: "default",
|
||||||
});
|
});
|
||||||
navigate("/");
|
navigate(`/organizations/${newOrg.id}`);
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast({
|
toast({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Suspense, useEffect, useState } from "react";
|
import { Suspense, useEffect, useState } from "react";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate, useParams } from "react-router";
|
||||||
import {
|
import {
|
||||||
graphql,
|
graphql,
|
||||||
PreloadedQuery,
|
PreloadedQuery,
|
||||||
@@ -18,7 +18,6 @@ import { useToast } from "@/hooks/use-toast";
|
|||||||
import { HelpCircle } from "lucide-react";
|
import { HelpCircle } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { CreatePeoplePageCreatePeopleMutation } from "./__generated__/CreatePeoplePageCreatePeopleMutation.graphql";
|
import { CreatePeoplePageCreatePeopleMutation } from "./__generated__/CreatePeoplePageCreatePeopleMutation.graphql";
|
||||||
import { useOrganization } from "@/contexts/OrganizationContext";
|
|
||||||
|
|
||||||
const createPeopleMutation = graphql`
|
const createPeopleMutation = graphql`
|
||||||
mutation CreatePeoplePageCreatePeopleMutation(
|
mutation CreatePeoplePageCreatePeopleMutation(
|
||||||
@@ -75,7 +74,7 @@ function EditableField({
|
|||||||
|
|
||||||
function CreatePeoplePageContent() {
|
function CreatePeoplePageContent() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { currentOrganization } = useOrganization();
|
const { organizationId } = useParams();
|
||||||
|
|
||||||
const [createPeople] =
|
const [createPeople] =
|
||||||
useMutation<CreatePeoplePageCreatePeopleMutation>(createPeopleMutation);
|
useMutation<CreatePeoplePageCreatePeopleMutation>(createPeopleMutation);
|
||||||
@@ -97,7 +96,7 @@ function CreatePeoplePageContent() {
|
|||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const peopleConnectionId = ConnectionHandler.getConnectionID(
|
const peopleConnectionId = ConnectionHandler.getConnectionID(
|
||||||
currentOrganization!.id,
|
organizationId!,
|
||||||
"PeopleListPage_peoples"
|
"PeopleListPage_peoples"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -105,7 +104,7 @@ function CreatePeoplePageContent() {
|
|||||||
variables: {
|
variables: {
|
||||||
connections: [peopleConnectionId],
|
connections: [peopleConnectionId],
|
||||||
input: {
|
input: {
|
||||||
organizationId: currentOrganization!.id,
|
organizationId: organizationId!,
|
||||||
fullName: formData.fullName,
|
fullName: formData.fullName,
|
||||||
primaryEmailAddress: formData.primaryEmailAddress,
|
primaryEmailAddress: formData.primaryEmailAddress,
|
||||||
additionalEmailAddresses: formData.additionalEmailAddresses,
|
additionalEmailAddresses: formData.additionalEmailAddresses,
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ import {
|
|||||||
useQueryLoader,
|
useQueryLoader,
|
||||||
} from "react-relay";
|
} from "react-relay";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Link } from "react-router";
|
import { Link, useParams } from "react-router";
|
||||||
import type { FrameworkListPageQuery as FrameworkListPageQueryType } from "./__generated__/FrameworkListPageQuery.graphql";
|
import type { FrameworkListPageQuery as FrameworkListPageQueryType } from "./__generated__/FrameworkListPageQuery.graphql";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useOrganization } from "@/contexts/OrganizationContext";
|
|
||||||
|
|
||||||
const FrameworkListPageQuery = graphql`
|
const FrameworkListPageQuery = graphql`
|
||||||
query FrameworkListPageQuery($organizationId: ID!) {
|
query FrameworkListPageQuery($organizationId: ID!) {
|
||||||
@@ -86,6 +85,7 @@ function FrameworkListPageContent({
|
|||||||
queryRef: PreloadedQuery<FrameworkListPageQueryType>;
|
queryRef: PreloadedQuery<FrameworkListPageQueryType>;
|
||||||
}) {
|
}) {
|
||||||
const data = usePreloadedQuery(FrameworkListPageQuery, queryRef);
|
const data = usePreloadedQuery(FrameworkListPageQuery, queryRef);
|
||||||
|
const { organizationId } = useParams();
|
||||||
const frameworks =
|
const frameworks =
|
||||||
data.organization.frameworks?.edges.map((edge) => edge?.node) ?? [];
|
data.organization.frameworks?.edges.map((edge) => edge?.node) ?? [];
|
||||||
|
|
||||||
@@ -101,12 +101,15 @@ function FrameworkListPageContent({
|
|||||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-2">
|
||||||
{frameworks.map((framework) => {
|
{frameworks.map((framework) => {
|
||||||
const validatedControls = framework.controls.edges.filter(
|
const validatedControls = framework.controls.edges.filter(
|
||||||
(edge) => edge?.node?.state === "IMPLEMENTED",
|
(edge) => edge?.node?.state === "IMPLEMENTED"
|
||||||
).length;
|
).length;
|
||||||
const totalControls = framework.controls.edges.length;
|
const totalControls = framework.controls.edges.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link key={framework.id} to={`/frameworks/${framework.id}`}>
|
<Link
|
||||||
|
key={framework.id}
|
||||||
|
to={`/organizations/${organizationId}/frameworks/${framework.id}`}
|
||||||
|
>
|
||||||
<FrameworkCard
|
<FrameworkCard
|
||||||
title={framework.name}
|
title={framework.name}
|
||||||
description={framework.description}
|
description={framework.description}
|
||||||
@@ -161,14 +164,14 @@ function FrameworkListPageFallback() {
|
|||||||
|
|
||||||
export default function FrameworkListPage() {
|
export default function FrameworkListPage() {
|
||||||
const [queryRef, loadQuery] = useQueryLoader<FrameworkListPageQueryType>(
|
const [queryRef, loadQuery] = useQueryLoader<FrameworkListPageQueryType>(
|
||||||
FrameworkListPageQuery,
|
FrameworkListPageQuery
|
||||||
);
|
);
|
||||||
|
|
||||||
const { currentOrganization } = useOrganization();
|
const { organizationId } = useParams();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadQuery({ organizationId: currentOrganization!.id });
|
loadQuery({ organizationId: organizationId! });
|
||||||
}, [loadQuery, currentOrganization]);
|
}, [loadQuery, organizationId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -54,19 +54,17 @@ function FrameworkOverviewPageContent({
|
|||||||
} | null>(null);
|
} | null>(null);
|
||||||
const [isTooltipHovered, setIsTooltipHovered] = useState(false);
|
const [isTooltipHovered, setIsTooltipHovered] = useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { organizationId } = useParams();
|
||||||
|
|
||||||
// Group controls by their category
|
// Group controls by their category
|
||||||
const controlsByCategory = controls.reduce(
|
const controlsByCategory = controls.reduce((acc, control) => {
|
||||||
(acc, control) => {
|
if (!control?.category) return acc;
|
||||||
if (!control?.category) return acc;
|
if (!acc[control.category]) {
|
||||||
if (!acc[control.category]) {
|
acc[control.category] = [];
|
||||||
acc[control.category] = [];
|
}
|
||||||
}
|
acc[control.category].push(control);
|
||||||
acc[control.category].push(control);
|
return acc;
|
||||||
return acc;
|
}, {} as Record<string, typeof controls>);
|
||||||
},
|
|
||||||
{} as Record<string, typeof controls>,
|
|
||||||
);
|
|
||||||
|
|
||||||
const controlCards = Object.entries(controlsByCategory).map(
|
const controlCards = Object.entries(controlsByCategory).map(
|
||||||
([category, controls]) => ({
|
([category, controls]) => ({
|
||||||
@@ -74,11 +72,11 @@ function FrameworkOverviewPageContent({
|
|||||||
controls,
|
controls,
|
||||||
completed: controls.filter((c) => c?.state === "IMPLEMENTED").length,
|
completed: controls.filter((c) => c?.state === "IMPLEMENTED").length,
|
||||||
total: controls.length,
|
total: controls.length,
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const totalImplemented = controls.filter(
|
const totalImplemented = controls.filter(
|
||||||
(c) => c?.state === "IMPLEMENTED",
|
(c) => c?.state === "IMPLEMENTED"
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -190,7 +188,7 @@ function FrameworkOverviewPageContent({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (control?.id) {
|
if (control?.id) {
|
||||||
navigate(
|
navigate(
|
||||||
`/frameworks/${framework.id}/controls/${control.id}`,
|
`/organizations/${organizationId}/frameworks/${framework.id}/controls/${control.id}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -274,7 +272,7 @@ function FrameworkOverviewPageContent({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
document.body,
|
document.body
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -308,7 +306,7 @@ function FrameworkOverviewPageFallback() {
|
|||||||
export default function FrameworkOverviewPage() {
|
export default function FrameworkOverviewPage() {
|
||||||
const { frameworkId } = useParams();
|
const { frameworkId } = useParams();
|
||||||
const [queryRef, loadQuery] = useQueryLoader<FrameworkOverviewPageQueryType>(
|
const [queryRef, loadQuery] = useQueryLoader<FrameworkOverviewPageQueryType>(
|
||||||
FrameworkOverviewPageQuery,
|
FrameworkOverviewPageQuery
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -5,116 +5,112 @@ import {
|
|||||||
usePreloadedQuery,
|
usePreloadedQuery,
|
||||||
useQueryLoader,
|
useQueryLoader,
|
||||||
} from "react-relay";
|
} from "react-relay";
|
||||||
import { Link } from "react-router";
|
import { Link, useParams } from "react-router";
|
||||||
import type { HomePageQuery as HomePageQueryType } from "./__generated__/HomePageQuery.graphql";
|
import type { HomePageQuery as HomePageQueryType } from "./__generated__/HomePageQuery.graphql";
|
||||||
|
|
||||||
export const HomePageQuery = graphql`
|
export const HomePageQuery = graphql`
|
||||||
query HomePageQuery {
|
query HomePageQuery($organizationId: ID!) {
|
||||||
viewer {
|
organization: node(id: $organizationId) {
|
||||||
id
|
... on Organization {
|
||||||
organizations {
|
id
|
||||||
edges {
|
name
|
||||||
node {
|
createdAt
|
||||||
name
|
updatedAt
|
||||||
createdAt
|
vendors {
|
||||||
updatedAt
|
edges {
|
||||||
vendors {
|
node {
|
||||||
edges {
|
id
|
||||||
node {
|
name
|
||||||
id
|
createdAt
|
||||||
name
|
updatedAt
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
peoples {
|
}
|
||||||
edges {
|
}
|
||||||
node {
|
peoples {
|
||||||
id
|
edges {
|
||||||
fullName
|
node {
|
||||||
primaryEmailAddress
|
id
|
||||||
additionalEmailAddresses
|
fullName
|
||||||
createdAt
|
primaryEmailAddress
|
||||||
updatedAt
|
additionalEmailAddresses
|
||||||
}
|
createdAt
|
||||||
}
|
updatedAt
|
||||||
}
|
}
|
||||||
frameworks {
|
}
|
||||||
pageInfo {
|
}
|
||||||
hasNextPage
|
frameworks {
|
||||||
hasPreviousPage
|
pageInfo {
|
||||||
startCursor
|
hasNextPage
|
||||||
endCursor
|
hasPreviousPage
|
||||||
}
|
startCursor
|
||||||
edges {
|
endCursor
|
||||||
cursor
|
}
|
||||||
node {
|
edges {
|
||||||
id
|
cursor
|
||||||
name
|
node {
|
||||||
description
|
id
|
||||||
controls {
|
name
|
||||||
edges {
|
description
|
||||||
node {
|
controls {
|
||||||
id
|
edges {
|
||||||
name
|
node {
|
||||||
state
|
id
|
||||||
stateTransisions {
|
name
|
||||||
edges {
|
state
|
||||||
node {
|
stateTransisions {
|
||||||
id
|
edges {
|
||||||
toState
|
node {
|
||||||
fromState
|
id
|
||||||
createdAt
|
toState
|
||||||
updatedAt
|
fromState
|
||||||
}
|
createdAt
|
||||||
}
|
updatedAt
|
||||||
}
|
}
|
||||||
tasks {
|
}
|
||||||
edges {
|
}
|
||||||
node {
|
tasks {
|
||||||
id
|
edges {
|
||||||
name
|
node {
|
||||||
state
|
id
|
||||||
evidences {
|
name
|
||||||
edges {
|
state
|
||||||
node {
|
evidences {
|
||||||
id
|
edges {
|
||||||
state
|
node {
|
||||||
fileUrl
|
id
|
||||||
stateTransisions {
|
state
|
||||||
edges {
|
fileUrl
|
||||||
node {
|
stateTransisions {
|
||||||
id
|
edges {
|
||||||
fromState
|
node {
|
||||||
toState
|
id
|
||||||
reason
|
fromState
|
||||||
createdAt
|
toState
|
||||||
updatedAt
|
reason
|
||||||
}
|
createdAt
|
||||||
}
|
updatedAt
|
||||||
}
|
}
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
}
|
}
|
||||||
stateTransisions {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
toState
|
|
||||||
fromState
|
|
||||||
reason
|
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stateTransisions {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
toState
|
||||||
|
fromState
|
||||||
|
reason
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,10 +126,15 @@ export const HomePageQuery = graphql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
|
const { organizationId } = useParams();
|
||||||
const [queryRef, loadQuery] =
|
const [queryRef, loadQuery] =
|
||||||
useQueryLoader<HomePageQueryType>(HomePageQuery);
|
useQueryLoader<HomePageQueryType>(HomePageQuery);
|
||||||
|
|
||||||
useEffect(() => loadQuery({}), [loadQuery]);
|
useEffect(() => {
|
||||||
|
if (organizationId) {
|
||||||
|
loadQuery({ organizationId });
|
||||||
|
}
|
||||||
|
}, [organizationId, loadQuery]);
|
||||||
|
|
||||||
if (!queryRef) {
|
if (!queryRef) {
|
||||||
return <HomePageFallback />;
|
return <HomePageFallback />;
|
||||||
@@ -155,6 +156,7 @@ function HomePageContent({
|
|||||||
}: {
|
}: {
|
||||||
queryRef: PreloadedQuery<HomePageQueryType>;
|
queryRef: PreloadedQuery<HomePageQueryType>;
|
||||||
}) {
|
}) {
|
||||||
|
const { organizationId } = useParams();
|
||||||
const data = usePreloadedQuery(HomePageQuery, queryRef);
|
const data = usePreloadedQuery(HomePageQuery, queryRef);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -166,7 +168,9 @@ function HomePageContent({
|
|||||||
</div>
|
</div>
|
||||||
<div className="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min">
|
<div className="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min">
|
||||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||||
<Link to="/foobar">Go to FooPage</Link>
|
<Link to={`/organizations/${organizationId}/frameworks`}>
|
||||||
|
Go to Frameworks
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,16 +22,6 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!email || !password) {
|
|
||||||
toast({
|
|
||||||
title: "Error",
|
|
||||||
description: "Email and password are required",
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -40,24 +30,30 @@ export default function LoginPage() {
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
credentials: "include",
|
|
||||||
body: JSON.stringify({ email, password }),
|
body: JSON.stringify({ email, password }),
|
||||||
|
credentials: "include",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const error = await response.json();
|
||||||
throw new Error(errorData.message || "Login failed");
|
throw new Error(error.message || "Failed to login");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check authentication status after login
|
const authenticated = await checkAuth();
|
||||||
await checkAuth();
|
if (authenticated) {
|
||||||
|
toast({
|
||||||
// Redirect to the original page or home after successful login
|
title: "Success",
|
||||||
navigate(from, { replace: true });
|
description: "Logged in successfully",
|
||||||
} catch (error) {
|
variant: "default",
|
||||||
|
});
|
||||||
|
navigate("/");
|
||||||
|
} else {
|
||||||
|
throw new Error("Authentication failed");
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
toast({
|
toast({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
description: error instanceof Error ? error.message : "Login failed",
|
description: error.message || "Failed to login",
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@@ -66,58 +62,86 @@ export default function LoginPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex flex-col space-y-6">
|
||||||
<Helmet>
|
<div className="flex flex-col space-y-2 text-center">
|
||||||
<title>Log in - Probo</title>
|
<h1 className="text-2xl font-semibold tracking-tight">
|
||||||
</Helmet>
|
Login to your account
|
||||||
|
</h1>
|
||||||
<div className="space-y-6">
|
<p className="text-sm text-muted-foreground">
|
||||||
<div className="space-y-2 text-center">
|
Enter your email below to login to your account
|
||||||
<h1 className="text-3xl font-bold">Log in</h1>
|
</p>
|
||||||
<p className="text-gray-500 dark:text-gray-400">
|
</div>
|
||||||
Enter your credentials to access your account
|
<div className="grid gap-6">
|
||||||
</p>
|
<form onSubmit={handleSubmit}>
|
||||||
</div>
|
<div className="grid gap-4">
|
||||||
|
<div className="grid gap-2">
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<Label htmlFor="email">Email</Label>
|
||||||
<div className="space-y-2">
|
<Input
|
||||||
<Label htmlFor="email">Email</Label>
|
id="email"
|
||||||
<Input
|
placeholder="name@example.com"
|
||||||
id="email"
|
type="email"
|
||||||
type="email"
|
autoCapitalize="none"
|
||||||
placeholder="name@example.com"
|
autoComplete="email"
|
||||||
value={email}
|
autoCorrect="off"
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
disabled={isLoading}
|
||||||
required
|
value={email}
|
||||||
/>
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label htmlFor="password">Password</Label>
|
||||||
|
<Link
|
||||||
|
to="/forgot-password"
|
||||||
|
className="text-sm font-medium text-primary underline-offset-4 hover:underline"
|
||||||
|
>
|
||||||
|
Forgot password?
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoComplete="current-password"
|
||||||
|
autoCorrect="off"
|
||||||
|
disabled={isLoading}
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button disabled={isLoading}>
|
||||||
|
{isLoading ? "Logging in..." : "Login"}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="password">Password</Label>
|
|
||||||
<Input
|
|
||||||
id="password"
|
|
||||||
type="password"
|
|
||||||
placeholder="••••••••"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button type="submit" className="w-full" disabled={isLoading}>
|
|
||||||
{isLoading ? "Logging in..." : "Log in with email"}
|
|
||||||
</Button>
|
|
||||||
</form>
|
</form>
|
||||||
|
<div className="relative">
|
||||||
<div className="text-center">
|
<div className="absolute inset-0 flex items-center">
|
||||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
<span className="w-full border-t" />
|
||||||
Don't have an account?{" "}
|
</div>
|
||||||
<Link to="/register" className="underline">
|
<div className="relative flex justify-center text-xs uppercase">
|
||||||
Sign up here
|
<span className="bg-muted/40 px-2 text-muted-foreground">
|
||||||
</Link>
|
Or continue with
|
||||||
</p>
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col space-y-2">
|
||||||
|
<Button variant="outline" type="button" disabled={isLoading}>
|
||||||
|
GitHub
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" type="button" disabled={isLoading}>
|
||||||
|
Google
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
<div className="text-center text-sm">
|
||||||
|
Don't have an account?{" "}
|
||||||
|
<Link
|
||||||
|
to="/register"
|
||||||
|
className="font-medium text-primary underline-offset-4 hover:underline"
|
||||||
|
>
|
||||||
|
Register
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
136
apps/console/src/pages/OrganizationSelectionPage.tsx
Normal file
136
apps/console/src/pages/OrganizationSelectionPage.tsx
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useNavigate } from "react-router";
|
||||||
|
import { graphql, useLazyLoadQuery } from "react-relay";
|
||||||
|
import { Helmet } from "react-helmet-async";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
CardDescription,
|
||||||
|
CardContent,
|
||||||
|
CardFooter,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { useToast } from "@/hooks/use-toast";
|
||||||
|
import { Plus } from "lucide-react";
|
||||||
|
import { OrganizationSelectionPageQuery } from "./__generated__/OrganizationSelectionPageQuery.graphql";
|
||||||
|
|
||||||
|
const organizationSelectionQuery = graphql`
|
||||||
|
query OrganizationSelectionPageQuery {
|
||||||
|
viewer {
|
||||||
|
id
|
||||||
|
organizations(first: 25) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
logoUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function OrganizationSelectionPage() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const data = useLazyLoadQuery<OrganizationSelectionPageQuery>(
|
||||||
|
organizationSelectionQuery,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
const organizations = data.viewer.organizations.edges.map(
|
||||||
|
(edge) => edge.node
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// If there's only one organization, redirect to it automatically
|
||||||
|
if (organizations.length === 1) {
|
||||||
|
navigate(`/organizations/${organizations[0].id}`);
|
||||||
|
}
|
||||||
|
}, [organizations, navigate]);
|
||||||
|
|
||||||
|
// If no organizations, show a message to create one
|
||||||
|
if (organizations.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto py-10">
|
||||||
|
<Helmet>
|
||||||
|
<title>Select Organization - Probo</title>
|
||||||
|
</Helmet>
|
||||||
|
<div className="flex flex-col items-center justify-center h-[70vh]">
|
||||||
|
<Card className="w-full max-w-md">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Welcome to Probo</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
You don't have any organizations yet. Create your first one to
|
||||||
|
get started.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardFooter>
|
||||||
|
<Button
|
||||||
|
className="w-full"
|
||||||
|
onClick={() => navigate("/organizations/create")}
|
||||||
|
>
|
||||||
|
<Plus className="mr-2 h-4 w-4" /> Create Organization
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If multiple organizations, show a selection screen
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto py-10">
|
||||||
|
<Helmet>
|
||||||
|
<title>Select Organization - Probo</title>
|
||||||
|
</Helmet>
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<h1 className="text-2xl font-bold mb-6">Select an Organization</h1>
|
||||||
|
<div className="grid gap-4 w-full max-w-2xl">
|
||||||
|
{organizations.map((org) => (
|
||||||
|
<Card key={org.id} className="w-full">
|
||||||
|
<CardHeader className="flex flex-row items-center gap-4">
|
||||||
|
{org.logoUrl && (
|
||||||
|
<div className="flex h-12 w-12 items-center justify-center rounded-lg border">
|
||||||
|
<img src={org.logoUrl} alt={org.name} className="h-8 w-8" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<CardTitle>{org.name}</CardTitle>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardFooter>
|
||||||
|
<Button
|
||||||
|
className="w-full"
|
||||||
|
onClick={() => navigate(`/organizations/${org.id}`)}
|
||||||
|
>
|
||||||
|
Select
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
<Card className="w-full">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Create a New Organization</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Add a new organization to your account
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full"
|
||||||
|
onClick={() => navigate("/organizations/create")}
|
||||||
|
>
|
||||||
|
<Plus className="mr-2 h-4 w-4" /> Create Organization
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
useMutation,
|
useMutation,
|
||||||
usePaginationFragment,
|
usePaginationFragment,
|
||||||
} from "react-relay";
|
} from "react-relay";
|
||||||
import { useSearchParams } from "react-router";
|
import { useSearchParams, useParams } from "react-router";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { UserPlus, Trash2, ChevronRight } from "lucide-react";
|
import { UserPlus, Trash2, ChevronRight } from "lucide-react";
|
||||||
@@ -18,7 +18,6 @@ import type { PeopleListPageQuery as PeopleListPageQueryType } from "./__generat
|
|||||||
import type { PeopleListPageDeletePeopleMutation } from "./__generated__/PeopleListPageDeletePeopleMutation.graphql";
|
import type { PeopleListPageDeletePeopleMutation } from "./__generated__/PeopleListPageDeletePeopleMutation.graphql";
|
||||||
import { PeopleListPagePaginationQuery } from "./__generated__/PeopleListPagePaginationQuery.graphql";
|
import { PeopleListPagePaginationQuery } from "./__generated__/PeopleListPagePaginationQuery.graphql";
|
||||||
import { PeopleListPage_peoples$key } from "./__generated__/PeopleListPage_peoples.graphql";
|
import { PeopleListPage_peoples$key } from "./__generated__/PeopleListPage_peoples.graphql";
|
||||||
import { useOrganization } from "@/contexts/OrganizationContext";
|
|
||||||
|
|
||||||
const ITEMS_PER_PAGE = 25;
|
const ITEMS_PER_PAGE = 25;
|
||||||
|
|
||||||
@@ -141,6 +140,7 @@ function PeopleListContent({
|
|||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
const [deletePeople] =
|
const [deletePeople] =
|
||||||
useMutation<PeopleListPageDeletePeopleMutation>(deletePeopleMutation);
|
useMutation<PeopleListPageDeletePeopleMutation>(deletePeopleMutation);
|
||||||
|
const { organizationId } = useParams();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: peoplesConnection,
|
data: peoplesConnection,
|
||||||
@@ -177,7 +177,7 @@ function PeopleListContent({
|
|||||||
style={{ borderRadius: "0.5rem" }}
|
style={{ borderRadius: "0.5rem" }}
|
||||||
className="gap-2"
|
className="gap-2"
|
||||||
>
|
>
|
||||||
<Link to="/peoples/create">
|
<Link to={`/organizations/${organizationId}/peoples/create`}>
|
||||||
<UserPlus className="h-4 w-4" />
|
<UserPlus className="h-4 w-4" />
|
||||||
Add a people
|
Add a people
|
||||||
</Link>
|
</Link>
|
||||||
@@ -188,7 +188,7 @@ function PeopleListContent({
|
|||||||
{peoples.map((person) => (
|
{peoples.map((person) => (
|
||||||
<Link
|
<Link
|
||||||
key={person?.id}
|
key={person?.id}
|
||||||
to={`/peoples/${person?.id}`}
|
to={`/organizations/${organizationId}/peoples/${person?.id}`}
|
||||||
className="block"
|
className="block"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between p-4 rounded-xl border bg-card hover:bg-accent/5 transition-colors">
|
<div className="flex items-center justify-between p-4 rounded-xl border bg-card hover:bg-accent/5 transition-colors">
|
||||||
@@ -307,20 +307,20 @@ export default function PeopleListPage() {
|
|||||||
const [queryRef, loadQuery] =
|
const [queryRef, loadQuery] =
|
||||||
useQueryLoader<PeopleListPageQueryType>(peopleListPageQuery);
|
useQueryLoader<PeopleListPageQueryType>(peopleListPageQuery);
|
||||||
|
|
||||||
const { currentOrganization } = useOrganization();
|
const { organizationId } = useParams();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const after = searchParams.get("after");
|
const after = searchParams.get("after");
|
||||||
const before = searchParams.get("before");
|
const before = searchParams.get("before");
|
||||||
|
|
||||||
loadQuery({
|
loadQuery({
|
||||||
organizationId: currentOrganization!.id,
|
organizationId: organizationId!,
|
||||||
first: before ? undefined : ITEMS_PER_PAGE,
|
first: before ? undefined : ITEMS_PER_PAGE,
|
||||||
after: after || undefined,
|
after: after || undefined,
|
||||||
last: before ? ITEMS_PER_PAGE : undefined,
|
last: before ? ITEMS_PER_PAGE : undefined,
|
||||||
before: before || undefined,
|
before: before || undefined,
|
||||||
});
|
});
|
||||||
}, [loadQuery, currentOrganization]);
|
}, [loadQuery, organizationId, searchParams]);
|
||||||
|
|
||||||
if (!queryRef) {
|
if (!queryRef) {
|
||||||
return <PeopleListPageFallback />;
|
return <PeopleListPageFallback />;
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import {
|
|||||||
usePreloadedQuery,
|
usePreloadedQuery,
|
||||||
useQueryLoader,
|
useQueryLoader,
|
||||||
} from "react-relay";
|
} from "react-relay";
|
||||||
|
import { useParams } from "react-router";
|
||||||
import type { SettingsPageQuery as SettingsPageQueryType } from "./__generated__/SettingsPageQuery.graphql";
|
import type { SettingsPageQuery as SettingsPageQueryType } from "./__generated__/SettingsPageQuery.graphql";
|
||||||
import { useOrganization } from "@/contexts/OrganizationContext";
|
|
||||||
|
|
||||||
const settingsPageQuery = graphql`
|
const settingsPageQuery = graphql`
|
||||||
query SettingsPageQuery($organizationID: ID!) {
|
query SettingsPageQuery($organizationID: ID!) {
|
||||||
@@ -217,11 +217,11 @@ export default function SettingsPage() {
|
|||||||
const [queryRef, loadQuery] =
|
const [queryRef, loadQuery] =
|
||||||
useQueryLoader<SettingsPageQueryType>(settingsPageQuery);
|
useQueryLoader<SettingsPageQueryType>(settingsPageQuery);
|
||||||
|
|
||||||
const { currentOrganization } = useOrganization();
|
const { organizationId } = useParams();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadQuery({ organizationID: currentOrganization!.id });
|
loadQuery({ organizationID: organizationId! });
|
||||||
}, [loadQuery, currentOrganization]);
|
}, [loadQuery, organizationId]);
|
||||||
|
|
||||||
if (!queryRef) {
|
if (!queryRef) {
|
||||||
return <SettingsPageFallback />;
|
return <SettingsPageFallback />;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
useMutation,
|
useMutation,
|
||||||
usePaginationFragment,
|
usePaginationFragment,
|
||||||
} from "react-relay";
|
} from "react-relay";
|
||||||
import { useSearchParams } from "react-router";
|
import { useSearchParams, useParams } from "react-router";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Store, ChevronRight, Trash2 } from "lucide-react";
|
import { Store, ChevronRight, Trash2 } from "lucide-react";
|
||||||
@@ -22,7 +22,6 @@ import { VendorListPageDeleteVendorMutation } from "./__generated__/VendorListPa
|
|||||||
import { VendorListPagePaginationQuery } from "./__generated__/VendorListPagePaginationQuery.graphql";
|
import { VendorListPagePaginationQuery } from "./__generated__/VendorListPagePaginationQuery.graphql";
|
||||||
import { VendorListPage_vendors$key } from "./__generated__/VendorListPage_vendors.graphql";
|
import { VendorListPage_vendors$key } from "./__generated__/VendorListPage_vendors.graphql";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { useOrganization } from "@/contexts/OrganizationContext";
|
|
||||||
|
|
||||||
const ITEMS_PER_PAGE = 25;
|
const ITEMS_PER_PAGE = 25;
|
||||||
|
|
||||||
@@ -199,6 +198,7 @@ function VendorListContent({
|
|||||||
useMutation<VendorListPageCreateVendorMutation>(createVendorMutation);
|
useMutation<VendorListPageCreateVendorMutation>(createVendorMutation);
|
||||||
const [deleteVendor] =
|
const [deleteVendor] =
|
||||||
useMutation<VendorListPageDeleteVendorMutation>(deleteVendorMutation);
|
useMutation<VendorListPageDeleteVendorMutation>(deleteVendorMutation);
|
||||||
|
const { organizationId } = useParams();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: vendorsConnection,
|
data: vendorsConnection,
|
||||||
@@ -300,7 +300,7 @@ function VendorListContent({
|
|||||||
{vendors.map((vendor) => (
|
{vendors.map((vendor) => (
|
||||||
<Link
|
<Link
|
||||||
key={vendor?.id}
|
key={vendor?.id}
|
||||||
to={`/vendors/${vendor?.id}`}
|
to={`/organizations/${organizationId}/vendors/${vendor?.id}`}
|
||||||
className="block"
|
className="block"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between p-4 rounded-xl border bg-card hover:bg-accent/5 transition-colors">
|
<div className="flex items-center justify-between p-4 rounded-xl border bg-card hover:bg-accent/5 transition-colors">
|
||||||
@@ -425,20 +425,20 @@ export default function VendorListPage() {
|
|||||||
const [queryRef, loadQuery] =
|
const [queryRef, loadQuery] =
|
||||||
useQueryLoader<VendorListPageQueryType>(vendorListPageQuery);
|
useQueryLoader<VendorListPageQueryType>(vendorListPageQuery);
|
||||||
|
|
||||||
const { currentOrganization } = useOrganization();
|
const { organizationId } = useParams();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const after = searchParams.get("after");
|
const after = searchParams.get("after");
|
||||||
const before = searchParams.get("before");
|
const before = searchParams.get("before");
|
||||||
|
|
||||||
loadQuery({
|
loadQuery({
|
||||||
organizationId: currentOrganization!.id,
|
organizationId: organizationId!,
|
||||||
first: before ? undefined : ITEMS_PER_PAGE,
|
first: before ? undefined : ITEMS_PER_PAGE,
|
||||||
after: after || undefined,
|
after: after || undefined,
|
||||||
last: before ? ITEMS_PER_PAGE : undefined,
|
last: before ? ITEMS_PER_PAGE : undefined,
|
||||||
before: before || undefined,
|
before: before || undefined,
|
||||||
});
|
});
|
||||||
}, [loadQuery, currentOrganization]);
|
}, [loadQuery, organizationId, searchParams]);
|
||||||
|
|
||||||
if (!queryRef) {
|
if (!queryRef) {
|
||||||
return <VendorListFallback />;
|
return <VendorListFallback />;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<f40d67f614a103849ba940fbd14f71af>>
|
* @generated SignedSource<<72fcf5e9542bdba3b547fa523e55378d>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -12,73 +12,52 @@ import { ConcreteRequest } from 'relay-runtime';
|
|||||||
export type ControlState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED";
|
export type ControlState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED";
|
||||||
export type EvidenceState = "EXPIRED" | "INVALID" | "VALID";
|
export type EvidenceState = "EXPIRED" | "INVALID" | "VALID";
|
||||||
export type TaskState = "DONE" | "TODO";
|
export type TaskState = "DONE" | "TODO";
|
||||||
export type HomePageQuery$variables = Record<PropertyKey, never>;
|
export type HomePageQuery$variables = {
|
||||||
|
organizationId: string;
|
||||||
|
};
|
||||||
export type HomePageQuery$data = {
|
export type HomePageQuery$data = {
|
||||||
readonly viewer: {
|
readonly organization: {
|
||||||
readonly id: string;
|
readonly createdAt?: any;
|
||||||
readonly organizations: {
|
readonly frameworks?: {
|
||||||
readonly edges: ReadonlyArray<{
|
readonly edges: ReadonlyArray<{
|
||||||
|
readonly cursor: any;
|
||||||
readonly node: {
|
readonly node: {
|
||||||
readonly createdAt: any;
|
readonly controls: {
|
||||||
readonly frameworks: {
|
|
||||||
readonly edges: ReadonlyArray<{
|
readonly edges: ReadonlyArray<{
|
||||||
readonly cursor: any;
|
|
||||||
readonly node: {
|
readonly node: {
|
||||||
readonly controls: {
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly state: ControlState;
|
||||||
|
readonly stateTransisions: {
|
||||||
readonly edges: ReadonlyArray<{
|
readonly edges: ReadonlyArray<{
|
||||||
readonly node: {
|
readonly node: {
|
||||||
|
readonly createdAt: any;
|
||||||
|
readonly fromState: ControlState | null | undefined;
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly name: string;
|
readonly toState: ControlState;
|
||||||
readonly state: ControlState;
|
readonly updatedAt: any;
|
||||||
readonly stateTransisions: {
|
};
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
readonly tasks: {
|
||||||
|
readonly edges: ReadonlyArray<{
|
||||||
|
readonly node: {
|
||||||
|
readonly createdAt: any;
|
||||||
|
readonly evidences: {
|
||||||
readonly edges: ReadonlyArray<{
|
readonly edges: ReadonlyArray<{
|
||||||
readonly node: {
|
readonly node: {
|
||||||
readonly createdAt: any;
|
readonly createdAt: any;
|
||||||
readonly fromState: ControlState | null | undefined;
|
readonly fileUrl: string;
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly toState: ControlState;
|
readonly state: EvidenceState;
|
||||||
readonly updatedAt: any;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
readonly tasks: {
|
|
||||||
readonly edges: ReadonlyArray<{
|
|
||||||
readonly node: {
|
|
||||||
readonly createdAt: any;
|
|
||||||
readonly evidences: {
|
|
||||||
readonly edges: ReadonlyArray<{
|
|
||||||
readonly node: {
|
|
||||||
readonly createdAt: any;
|
|
||||||
readonly fileUrl: string;
|
|
||||||
readonly id: string;
|
|
||||||
readonly state: EvidenceState;
|
|
||||||
readonly stateTransisions: {
|
|
||||||
readonly edges: ReadonlyArray<{
|
|
||||||
readonly node: {
|
|
||||||
readonly createdAt: any;
|
|
||||||
readonly fromState: EvidenceState | null | undefined;
|
|
||||||
readonly id: string;
|
|
||||||
readonly reason: string | null | undefined;
|
|
||||||
readonly toState: EvidenceState;
|
|
||||||
readonly updatedAt: any;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
readonly updatedAt: any;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
readonly id: string;
|
|
||||||
readonly name: string;
|
|
||||||
readonly state: TaskState;
|
|
||||||
readonly stateTransisions: {
|
readonly stateTransisions: {
|
||||||
readonly edges: ReadonlyArray<{
|
readonly edges: ReadonlyArray<{
|
||||||
readonly node: {
|
readonly node: {
|
||||||
readonly createdAt: any;
|
readonly createdAt: any;
|
||||||
readonly fromState: TaskState | null | undefined;
|
readonly fromState: EvidenceState | null | undefined;
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly reason: string | null | undefined;
|
readonly reason: string | null | undefined;
|
||||||
readonly toState: TaskState;
|
readonly toState: EvidenceState;
|
||||||
readonly updatedAt: any;
|
readonly updatedAt: any;
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
@@ -87,45 +66,62 @@ export type HomePageQuery$data = {
|
|||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly state: TaskState;
|
||||||
|
readonly stateTransisions: {
|
||||||
|
readonly edges: ReadonlyArray<{
|
||||||
|
readonly node: {
|
||||||
|
readonly createdAt: any;
|
||||||
|
readonly fromState: TaskState | null | undefined;
|
||||||
|
readonly id: string;
|
||||||
|
readonly reason: string | null | undefined;
|
||||||
|
readonly toState: TaskState;
|
||||||
|
readonly updatedAt: any;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
readonly updatedAt: any;
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
readonly description: string;
|
|
||||||
readonly id: string;
|
|
||||||
readonly name: string;
|
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
readonly pageInfo: {
|
|
||||||
readonly endCursor: any | null | undefined;
|
|
||||||
readonly hasNextPage: boolean;
|
|
||||||
readonly hasPreviousPage: boolean;
|
|
||||||
readonly startCursor: any | null | undefined;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
readonly description: string;
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
readonly pageInfo: {
|
||||||
|
readonly endCursor: any | null | undefined;
|
||||||
|
readonly hasNextPage: boolean;
|
||||||
|
readonly hasPreviousPage: boolean;
|
||||||
|
readonly startCursor: any | null | undefined;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
readonly id?: string;
|
||||||
|
readonly name?: string;
|
||||||
|
readonly peoples?: {
|
||||||
|
readonly edges: ReadonlyArray<{
|
||||||
|
readonly node: {
|
||||||
|
readonly additionalEmailAddresses: ReadonlyArray<string>;
|
||||||
|
readonly createdAt: any;
|
||||||
|
readonly fullName: string;
|
||||||
|
readonly id: string;
|
||||||
|
readonly primaryEmailAddress: string;
|
||||||
|
readonly updatedAt: any;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
readonly updatedAt?: any;
|
||||||
|
readonly vendors?: {
|
||||||
|
readonly edges: ReadonlyArray<{
|
||||||
|
readonly node: {
|
||||||
|
readonly createdAt: any;
|
||||||
|
readonly id: string;
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly peoples: {
|
|
||||||
readonly edges: ReadonlyArray<{
|
|
||||||
readonly node: {
|
|
||||||
readonly additionalEmailAddresses: ReadonlyArray<string>;
|
|
||||||
readonly createdAt: any;
|
|
||||||
readonly fullName: string;
|
|
||||||
readonly id: string;
|
|
||||||
readonly primaryEmailAddress: string;
|
|
||||||
readonly updatedAt: any;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
readonly updatedAt: any;
|
readonly updatedAt: any;
|
||||||
readonly vendors: {
|
|
||||||
readonly edges: ReadonlyArray<{
|
|
||||||
readonly node: {
|
|
||||||
readonly createdAt: any;
|
|
||||||
readonly id: string;
|
|
||||||
readonly name: string;
|
|
||||||
readonly updatedAt: any;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
@@ -137,35 +133,49 @@ export type HomePageQuery = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const node: ConcreteRequest = (function(){
|
const node: ConcreteRequest = (function(){
|
||||||
var v0 = {
|
var v0 = [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "organizationId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "id",
|
||||||
|
"variableName": "organizationId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v2 = {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"kind": "ScalarField",
|
"kind": "ScalarField",
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
v1 = {
|
v3 = {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"kind": "ScalarField",
|
"kind": "ScalarField",
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
v2 = {
|
v4 = {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"kind": "ScalarField",
|
"kind": "ScalarField",
|
||||||
"name": "createdAt",
|
"name": "createdAt",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
v3 = {
|
v5 = {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"kind": "ScalarField",
|
"kind": "ScalarField",
|
||||||
"name": "updatedAt",
|
"name": "updatedAt",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
v4 = {
|
v6 = {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"concreteType": "VendorConnection",
|
"concreteType": "VendorConnection",
|
||||||
@@ -189,10 +199,10 @@ v4 = {
|
|||||||
"name": "node",
|
"name": "node",
|
||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
(v0/*: any*/),
|
|
||||||
(v1/*: any*/),
|
|
||||||
(v2/*: any*/),
|
(v2/*: any*/),
|
||||||
(v3/*: any*/)
|
(v3/*: any*/),
|
||||||
|
(v4/*: any*/),
|
||||||
|
(v5/*: any*/)
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
}
|
}
|
||||||
@@ -202,7 +212,7 @@ v4 = {
|
|||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
v5 = {
|
v7 = {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"concreteType": "PeopleConnection",
|
"concreteType": "PeopleConnection",
|
||||||
@@ -226,7 +236,7 @@ v5 = {
|
|||||||
"name": "node",
|
"name": "node",
|
||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
(v0/*: any*/),
|
(v2/*: any*/),
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
@@ -248,8 +258,8 @@ v5 = {
|
|||||||
"name": "additionalEmailAddresses",
|
"name": "additionalEmailAddresses",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
(v2/*: any*/),
|
(v4/*: any*/),
|
||||||
(v3/*: any*/)
|
(v5/*: any*/)
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
}
|
}
|
||||||
@@ -259,35 +269,35 @@ v5 = {
|
|||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
v6 = {
|
v8 = {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"kind": "ScalarField",
|
"kind": "ScalarField",
|
||||||
"name": "state",
|
"name": "state",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
v7 = {
|
v9 = {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"kind": "ScalarField",
|
"kind": "ScalarField",
|
||||||
"name": "toState",
|
"name": "toState",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
v8 = {
|
v10 = {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"kind": "ScalarField",
|
"kind": "ScalarField",
|
||||||
"name": "fromState",
|
"name": "fromState",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
v9 = {
|
v11 = {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"kind": "ScalarField",
|
"kind": "ScalarField",
|
||||||
"name": "reason",
|
"name": "reason",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
v10 = {
|
v12 = {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"concreteType": "FrameworkConnection",
|
"concreteType": "FrameworkConnection",
|
||||||
@@ -357,8 +367,8 @@ v10 = {
|
|||||||
"name": "node",
|
"name": "node",
|
||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
(v0/*: any*/),
|
(v2/*: any*/),
|
||||||
(v1/*: any*/),
|
(v3/*: any*/),
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
@@ -390,9 +400,9 @@ v10 = {
|
|||||||
"name": "node",
|
"name": "node",
|
||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
(v0/*: any*/),
|
(v2/*: any*/),
|
||||||
(v1/*: any*/),
|
(v3/*: any*/),
|
||||||
(v6/*: any*/),
|
(v8/*: any*/),
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
@@ -417,11 +427,11 @@ v10 = {
|
|||||||
"name": "node",
|
"name": "node",
|
||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
(v0/*: any*/),
|
|
||||||
(v7/*: any*/),
|
|
||||||
(v8/*: any*/),
|
|
||||||
(v2/*: any*/),
|
(v2/*: any*/),
|
||||||
(v3/*: any*/)
|
(v9/*: any*/),
|
||||||
|
(v10/*: any*/),
|
||||||
|
(v4/*: any*/),
|
||||||
|
(v5/*: any*/)
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
}
|
}
|
||||||
@@ -455,9 +465,9 @@ v10 = {
|
|||||||
"name": "node",
|
"name": "node",
|
||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
(v0/*: any*/),
|
(v2/*: any*/),
|
||||||
(v1/*: any*/),
|
(v3/*: any*/),
|
||||||
(v6/*: any*/),
|
(v8/*: any*/),
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
@@ -482,8 +492,8 @@ v10 = {
|
|||||||
"name": "node",
|
"name": "node",
|
||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
(v0/*: any*/),
|
(v2/*: any*/),
|
||||||
(v6/*: any*/),
|
(v8/*: any*/),
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
@@ -515,12 +525,12 @@ v10 = {
|
|||||||
"name": "node",
|
"name": "node",
|
||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
(v0/*: any*/),
|
|
||||||
(v8/*: any*/),
|
|
||||||
(v7/*: any*/),
|
|
||||||
(v9/*: any*/),
|
|
||||||
(v2/*: any*/),
|
(v2/*: any*/),
|
||||||
(v3/*: any*/)
|
(v10/*: any*/),
|
||||||
|
(v9/*: any*/),
|
||||||
|
(v11/*: any*/),
|
||||||
|
(v4/*: any*/),
|
||||||
|
(v5/*: any*/)
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
}
|
}
|
||||||
@@ -530,8 +540,8 @@ v10 = {
|
|||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
(v2/*: any*/),
|
(v4/*: any*/),
|
||||||
(v3/*: any*/)
|
(v5/*: any*/)
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
}
|
}
|
||||||
@@ -565,12 +575,12 @@ v10 = {
|
|||||||
"name": "node",
|
"name": "node",
|
||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
(v0/*: any*/),
|
|
||||||
(v7/*: any*/),
|
|
||||||
(v8/*: any*/),
|
|
||||||
(v9/*: any*/),
|
|
||||||
(v2/*: any*/),
|
(v2/*: any*/),
|
||||||
(v3/*: any*/)
|
(v9/*: any*/),
|
||||||
|
(v10/*: any*/),
|
||||||
|
(v11/*: any*/),
|
||||||
|
(v4/*: any*/),
|
||||||
|
(v5/*: any*/)
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
}
|
}
|
||||||
@@ -580,8 +590,8 @@ v10 = {
|
|||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
(v2/*: any*/),
|
(v4/*: any*/),
|
||||||
(v3/*: any*/)
|
(v5/*: any*/)
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
}
|
}
|
||||||
@@ -611,58 +621,32 @@ v10 = {
|
|||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
"fragment": {
|
"fragment": {
|
||||||
"argumentDefinitions": [],
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
"kind": "Fragment",
|
"kind": "Fragment",
|
||||||
"metadata": null,
|
"metadata": null,
|
||||||
"name": "HomePageQuery",
|
"name": "HomePageQuery",
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": "organization",
|
||||||
"args": null,
|
"args": (v1/*: any*/),
|
||||||
"concreteType": "User",
|
"concreteType": null,
|
||||||
"kind": "LinkedField",
|
"kind": "LinkedField",
|
||||||
"name": "viewer",
|
"name": "node",
|
||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
(v0/*: any*/),
|
|
||||||
{
|
{
|
||||||
"alias": null,
|
"kind": "InlineFragment",
|
||||||
"args": null,
|
|
||||||
"concreteType": "OrganizationConnection",
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "organizations",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
(v2/*: any*/),
|
||||||
"alias": null,
|
(v3/*: any*/),
|
||||||
"args": null,
|
(v4/*: any*/),
|
||||||
"concreteType": "OrganizationEdge",
|
(v5/*: any*/),
|
||||||
"kind": "LinkedField",
|
(v6/*: any*/),
|
||||||
"name": "edges",
|
(v7/*: any*/),
|
||||||
"plural": true,
|
(v12/*: any*/)
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"concreteType": "Organization",
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "node",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
(v1/*: any*/),
|
|
||||||
(v2/*: any*/),
|
|
||||||
(v3/*: any*/),
|
|
||||||
(v4/*: any*/),
|
|
||||||
(v5/*: any*/),
|
|
||||||
(v10/*: any*/)
|
|
||||||
],
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"type": "Organization",
|
||||||
|
"abstractKey": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
@@ -673,58 +657,38 @@ return {
|
|||||||
},
|
},
|
||||||
"kind": "Request",
|
"kind": "Request",
|
||||||
"operation": {
|
"operation": {
|
||||||
"argumentDefinitions": [],
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
"kind": "Operation",
|
"kind": "Operation",
|
||||||
"name": "HomePageQuery",
|
"name": "HomePageQuery",
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": "organization",
|
||||||
"args": null,
|
"args": (v1/*: any*/),
|
||||||
"concreteType": "User",
|
"concreteType": null,
|
||||||
"kind": "LinkedField",
|
"kind": "LinkedField",
|
||||||
"name": "viewer",
|
"name": "node",
|
||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
(v0/*: any*/),
|
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"concreteType": "OrganizationConnection",
|
"kind": "ScalarField",
|
||||||
"kind": "LinkedField",
|
"name": "__typename",
|
||||||
"name": "organizations",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"concreteType": "OrganizationEdge",
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "edges",
|
|
||||||
"plural": true,
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"concreteType": "Organization",
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "node",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
(v1/*: any*/),
|
|
||||||
(v2/*: any*/),
|
|
||||||
(v3/*: any*/),
|
|
||||||
(v4/*: any*/),
|
|
||||||
(v5/*: any*/),
|
|
||||||
(v10/*: any*/),
|
|
||||||
(v0/*: any*/)
|
|
||||||
],
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
|
},
|
||||||
|
(v2/*: any*/),
|
||||||
|
{
|
||||||
|
"kind": "InlineFragment",
|
||||||
|
"selections": [
|
||||||
|
(v3/*: any*/),
|
||||||
|
(v4/*: any*/),
|
||||||
|
(v5/*: any*/),
|
||||||
|
(v6/*: any*/),
|
||||||
|
(v7/*: any*/),
|
||||||
|
(v12/*: any*/)
|
||||||
|
],
|
||||||
|
"type": "Organization",
|
||||||
|
"abstractKey": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
@@ -732,16 +696,16 @@ return {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "5a56fefd066703ca0ebac1ef3e9a7e1f",
|
"cacheID": "29440183dcc30ec5e0bcce9c1910b92f",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"name": "HomePageQuery",
|
"name": "HomePageQuery",
|
||||||
"operationKind": "query",
|
"operationKind": "query",
|
||||||
"text": "query HomePageQuery {\n viewer {\n id\n organizations {\n edges {\n node {\n name\n createdAt\n updatedAt\n vendors {\n edges {\n node {\n id\n name\n createdAt\n updatedAt\n }\n }\n }\n peoples {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n additionalEmailAddresses\n createdAt\n updatedAt\n }\n }\n }\n frameworks {\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n edges {\n cursor\n node {\n id\n name\n description\n controls {\n edges {\n node {\n id\n name\n state\n stateTransisions {\n edges {\n node {\n id\n toState\n fromState\n createdAt\n updatedAt\n }\n }\n }\n tasks {\n edges {\n node {\n id\n name\n state\n evidences {\n edges {\n node {\n id\n state\n fileUrl\n stateTransisions {\n edges {\n node {\n id\n fromState\n toState\n reason\n createdAt\n updatedAt\n }\n }\n }\n createdAt\n updatedAt\n }\n }\n }\n stateTransisions {\n edges {\n node {\n id\n toState\n fromState\n reason\n createdAt\n updatedAt\n }\n }\n }\n createdAt\n updatedAt\n }\n }\n }\n }\n }\n }\n }\n }\n }\n id\n }\n }\n }\n }\n}\n"
|
"text": "query HomePageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n name\n createdAt\n updatedAt\n vendors {\n edges {\n node {\n id\n name\n createdAt\n updatedAt\n }\n }\n }\n peoples {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n additionalEmailAddresses\n createdAt\n updatedAt\n }\n }\n }\n frameworks {\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n edges {\n cursor\n node {\n id\n name\n description\n controls {\n edges {\n node {\n id\n name\n state\n stateTransisions {\n edges {\n node {\n id\n toState\n fromState\n createdAt\n updatedAt\n }\n }\n }\n tasks {\n edges {\n node {\n id\n name\n state\n evidences {\n edges {\n node {\n id\n state\n fileUrl\n stateTransisions {\n edges {\n node {\n id\n fromState\n toState\n reason\n createdAt\n updatedAt\n }\n }\n }\n createdAt\n updatedAt\n }\n }\n }\n stateTransisions {\n edges {\n node {\n id\n toState\n fromState\n reason\n createdAt\n updatedAt\n }\n }\n }\n createdAt\n updatedAt\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n id\n }\n}\n"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "7a1699535af738f939d954a88c6daa9e";
|
(node as any).hash = "d4f516b436d096fe9c91ce532a75b022";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
|
|||||||
138
apps/console/src/pages/__generated__/OrganizationSelectionPageQuery.graphql.ts
generated
Normal file
138
apps/console/src/pages/__generated__/OrganizationSelectionPageQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<59c4e8be77fdb5ebcc76c2a611f38052>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type OrganizationSelectionPageQuery$variables = Record<PropertyKey, never>;
|
||||||
|
export type OrganizationSelectionPageQuery$data = {
|
||||||
|
readonly viewer: {
|
||||||
|
readonly id: string;
|
||||||
|
readonly organizations: {
|
||||||
|
readonly edges: ReadonlyArray<{
|
||||||
|
readonly node: {
|
||||||
|
readonly id: string;
|
||||||
|
readonly logoUrl: string;
|
||||||
|
readonly name: string;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type OrganizationSelectionPageQuery = {
|
||||||
|
response: OrganizationSelectionPageQuery$data;
|
||||||
|
variables: OrganizationSelectionPageQuery$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "User",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "viewer",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v0/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"kind": "Literal",
|
||||||
|
"name": "first",
|
||||||
|
"value": 25
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"concreteType": "OrganizationConnection",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "organizations",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "OrganizationEdge",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "edges",
|
||||||
|
"plural": true,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Organization",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v0/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "logoUrl",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": "organizations(first:25)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": [],
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "OrganizationSelectionPageQuery",
|
||||||
|
"selections": (v1/*: any*/),
|
||||||
|
"type": "Query",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": [],
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "OrganizationSelectionPageQuery",
|
||||||
|
"selections": (v1/*: any*/)
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "634e8f413a8b1f2685cfc1f4fd3c13cc",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "OrganizationSelectionPageQuery",
|
||||||
|
"operationKind": "query",
|
||||||
|
"text": "query OrganizationSelectionPageQuery {\n viewer {\n id\n organizations(first: 25) {\n edges {\n node {\n id\n name\n logoUrl\n }\n }\n }\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "786037ac4c8d61f3b7cf6b4536aadbbb";
|
||||||
|
|
||||||
|
export default node;
|
||||||
914
package-lock.json
generated
914
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user