Add multi-tenant system

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-02-25 22:52:06 +01:00
parent 29ce55e89c
commit 0c176674a0
35 changed files with 1254 additions and 686 deletions

View File

@@ -12,6 +12,7 @@ import AuthLayout from "./layouts/AuthLayout";
import { RelayEnvironment } from "./RelayEnvironment";
import { AuthProvider } from "./contexts/AuthContext";
import { ProtectedRoute } from "./components/ProtectedRoute";
import { OrganizationProvider } from "./contexts/OrganizationContext";
posthog.init(process.env.POSTHOG_KEY!, {
api_host: process.env.POSTHOG_HOST,
@@ -48,55 +49,14 @@ function App() {
<HelmetProvider>
<BrowserRouter>
<AuthProvider>
<Routes>
{/* Authentication Routes - Accessible without login */}
<Route
path="/login"
element={
<ErrorBoundaryWithLocation>
<AuthLayout />
</ErrorBoundaryWithLocation>
}
>
<OrganizationProvider>
<Routes>
{/* Authentication Routes - Accessible without login */}
<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="/*"
path="/login"
element={
<ErrorBoundaryWithLocation>
<ConsoleLayout />
<AuthLayout />
</ErrorBoundaryWithLocation>
}
>
@@ -105,112 +65,155 @@ function App() {
element={
<Suspense>
<ErrorBoundaryWithLocation>
<HomePage />
<LoginPage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="peoples"
element={
<Suspense>
<ErrorBoundaryWithLocation>
<PeopleListPage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="peoples/create"
element={
<Suspense>
<ErrorBoundaryWithLocation>
<CreatePeoplePage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="peoples/:peopleId"
element={
<Suspense>
<ErrorBoundaryWithLocation>
<PeopleOverviewPage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="vendors"
element={
<Suspense>
<ErrorBoundaryWithLocation>
<VendorListPage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="frameworks"
element={
<Suspense>
<ErrorBoundaryWithLocation>
<FrameworkListPage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="frameworks/:frameworkId"
element={
<Suspense>
<ErrorBoundaryWithLocation>
<FrameworkOverviewPage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="frameworks/:frameworkId/controls/:controlId"
element={
<Suspense>
<ErrorBoundaryWithLocation>
<ControlOverviewPage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="vendors/:vendorId"
element={
<Suspense>
<ErrorBoundaryWithLocation>
<VendorOverviewPage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="settings"
element={
<Suspense>
<ErrorBoundaryWithLocation>
<SettingsPage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="*"
element={
<Suspense>
<NotFoundPage />
</Suspense>
}
/>
</Route>
</Route>
</Routes>
<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>
<ConsoleLayout />
</ErrorBoundaryWithLocation>
}
>
<Route
index
element={
<Suspense>
<ErrorBoundaryWithLocation>
<HomePage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="peoples"
element={
<Suspense>
<ErrorBoundaryWithLocation>
<PeopleListPage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="peoples/create"
element={
<Suspense>
<ErrorBoundaryWithLocation>
<CreatePeoplePage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="peoples/:peopleId"
element={
<Suspense>
<ErrorBoundaryWithLocation>
<PeopleOverviewPage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="vendors"
element={
<Suspense>
<ErrorBoundaryWithLocation>
<VendorListPage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="frameworks"
element={
<Suspense>
<ErrorBoundaryWithLocation>
<FrameworkListPage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="frameworks/:frameworkId"
element={
<Suspense>
<ErrorBoundaryWithLocation>
<FrameworkOverviewPage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="frameworks/:frameworkId/controls/:controlId"
element={
<Suspense>
<ErrorBoundaryWithLocation>
<ControlOverviewPage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="vendors/:vendorId"
element={
<Suspense>
<ErrorBoundaryWithLocation>
<VendorOverviewPage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="settings"
element={
<Suspense>
<ErrorBoundaryWithLocation>
<SettingsPage />
</ErrorBoundaryWithLocation>
</Suspense>
}
/>
<Route
path="*"
element={
<Suspense>
<NotFoundPage />
</Suspense>
}
/>
</Route>
</Route>
</Routes>
</OrganizationProvider>
</AuthProvider>
</BrowserRouter>
</HelmetProvider>

View File

@@ -30,7 +30,7 @@ import {
SidebarMenuItem,
} from "@/components/ui/sidebar";
import type { AppSidebarQuery as AppSidebarQueryType } from "./__generated__/AppSidebarQuery.graphql";
import { TeamSwitcher } from "@/components/TeamSwitcher";
const staticData = {
user: {
name: "shadcn",
@@ -84,12 +84,8 @@ const AppSidebarQuery = graphql`
query AppSidebarQuery {
viewer {
id
organization {
name
logoUrl
createdAt
updatedAt
}
...TeamSwitcher_organizations
...NavUser_viewer
}
}
`;
@@ -101,37 +97,18 @@ function AppSidebarContent({
queryRef: PreloadedQuery<AppSidebarQueryType>;
}) {
const data = usePreloadedQuery(AppSidebarQuery, queryRef);
const organization = data.viewer.organization;
return (
<Sidebar variant="inset" {...props}>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" asChild>
<a href="#">
<img
src={organization.logoUrl}
alt={organization.name}
className="size-8 rounded-lg object-cover"
/>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
{organization.name}
</span>
<span className="truncate text-xs">Enterprise</span>
</div>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
<TeamSwitcher organizations={data.viewer} />
</SidebarHeader>
<SidebarContent>
<NavMain items={staticData.navMain} />
<NavSecondary items={staticData.navSecondary} className="mt-auto" />
</SidebarContent>
<SidebarFooter>
<NavUser user={staticData.user} />
<NavUser viewer={data.viewer} />
</SidebarFooter>
</Sidebar>
);
@@ -162,7 +139,23 @@ function AppSidebarFallback(props: React.ComponentProps<typeof Sidebar>) {
<NavSecondary items={staticData.navSecondary} className="mt-auto" />
</SidebarContent>
<SidebarFooter>
<NavUser user={staticData.user} />
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" asChild>
<a href="#">
<div className="h-8 w-8 rounded-lg bg-muted animate-pulse" />
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
<div className="h-4 w-24 animate-pulse rounded bg-muted" />
</span>
<span className="truncate text-xs">
<div className="h-3 w-32 animate-pulse rounded bg-muted" />
</span>
</div>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
</Sidebar>
);

View File

@@ -8,6 +8,7 @@ import {
LogOut,
Sparkles,
} from "lucide-react";
import { graphql, useFragment } from "react-relay";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
@@ -25,17 +26,22 @@ import {
SidebarMenuItem,
useSidebar,
} from "@/components/ui/sidebar";
import {
NavUser_viewer$key,
NavUser_viewer$data,
} from "./__generated__/NavUser_viewer.graphql";
export function NavUser({
user,
}: {
user: {
name: string;
email: string;
avatar: string;
};
}) {
export const navUserFragment = graphql`
fragment NavUser_viewer on User {
id
fullName
email
}
`;
export function NavUser({ viewer }: { viewer: NavUser_viewer$key }) {
const { isMobile } = useSidebar();
const currentUser = useFragment(navUserFragment, viewer);
return (
<SidebarMenu>
@@ -47,12 +53,18 @@ export function NavUser({
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar className="h-8 w-8 rounded-lg">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
<AvatarFallback className="rounded-lg">
{currentUser.fullName.substring(0, 2).toUpperCase()}
</AvatarFallback>
<AvatarFallback className="rounded-lg">
{currentUser.fullName.substring(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">{user.name}</span>
<span className="truncate text-xs">{user.email}</span>
<span className="truncate font-semibold">
{currentUser.fullName}
</span>
<span className="truncate text-xs">{currentUser.email}</span>
</div>
<ChevronsUpDown className="ml-auto size-4" />
</SidebarMenuButton>
@@ -66,12 +78,18 @@ export function NavUser({
<DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar className="h-8 w-8 rounded-lg">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
<AvatarFallback className="rounded-lg">
{currentUser.fullName.substring(0, 2).toUpperCase()}
</AvatarFallback>
<AvatarFallback className="rounded-lg">
{currentUser.fullName.substring(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">{user.name}</span>
<span className="truncate text-xs">{user.email}</span>
<span className="truncate font-semibold">
{currentUser.fullName}
</span>
<span className="truncate text-xs">{currentUser.email}</span>
</div>
</div>
</DropdownMenuLabel>

View File

@@ -0,0 +1,139 @@
"use client";
import { useState, useEffect } from "react";
import { ChevronsUpDown, Plus } from "lucide-react";
import { graphql, useFragment } from "react-relay";
import { useOrganization } from "@/contexts/OrganizationContext";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "@/components/ui/sidebar";
import {
TeamSwitcher_organizations$key,
TeamSwitcher_organizations$data,
} from "./__generated__/TeamSwitcher_organizations.graphql";
export const teamSwitcherFragment = graphql`
fragment TeamSwitcher_organizations on User {
organizations {
id
name
logoUrl
}
}
`;
// Extended type to include plan field until Relay compiler generates the types
type Organization = TeamSwitcher_organizations$data["organizations"][0] & {
plan?: string;
};
export function TeamSwitcher({
organizations,
}: {
organizations: TeamSwitcher_organizations$key;
}) {
const { isMobile } = useSidebar();
const data = useFragment(teamSwitcherFragment, organizations);
const { currentOrganization, setCurrentOrganization } = useOrganization();
useEffect(() => {
if (
data.organizations &&
data.organizations.length > 0 &&
!currentOrganization
) {
// Type assertion to include plan field
setCurrentOrganization(data.organizations[0] as Organization);
}
}, [data.organizations, currentOrganization, setCurrentOrganization]);
// If no organizations or data is still loading
if (!currentOrganization) {
return null;
}
// Convert logoUrl to a React component
const LogoComponent = ({
org,
className,
}: {
org: Organization;
className?: string;
}) => {
if (org.logoUrl) {
return <img src={org.logoUrl} alt={org.name} className={className} />;
}
return null;
};
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
<LogoComponent org={currentOrganization} className="size-4" />
</div>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
{currentOrganization.name}
</span>
<span className="truncate text-xs">Free</span>
</div>
<ChevronsUpDown className="ml-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
align="start"
side={isMobile ? "bottom" : "right"}
sideOffset={4}
>
<DropdownMenuLabel className="text-xs text-muted-foreground">
Organizations
</DropdownMenuLabel>
{data.organizations.map((org: Organization, index: number) => (
<DropdownMenuItem
key={org.id}
onClick={() => setCurrentOrganization(org)}
className="gap-2 p-2"
>
<div className="flex size-6 items-center justify-center rounded-sm border">
<LogoComponent org={org} className="size-4 shrink-0" />
</div>
{org.name}
<DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
<DropdownMenuItem className="gap-2 p-2">
<div className="flex size-6 items-center justify-center rounded-md border bg-background">
<Plus className="size-4" />
</div>
<div className="font-medium text-muted-foreground">
Add organization
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
);
}

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<717a5c6c939c65b7e0790f84e2c41bcd>>
* @generated SignedSource<<d060c85c907876f442133c643b917eee>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,16 +9,12 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type AppSidebarQuery$variables = Record<PropertyKey, never>;
export type AppSidebarQuery$data = {
readonly viewer: {
readonly id: string;
readonly organization: {
readonly createdAt: any;
readonly logoUrl: string;
readonly name: string;
readonly updatedAt: any;
};
readonly " $fragmentSpreads": FragmentRefs<"NavUser_viewer" | "TeamSwitcher_organizations">;
};
};
export type AppSidebarQuery = {
@@ -33,34 +29,6 @@ var v0 = {
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "logoUrl",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
};
return {
"fragment": {
@@ -79,19 +47,14 @@ return {
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": [
(v1/*: any*/),
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/)
],
"storageKey": null
"kind": "FragmentSpread",
"name": "TeamSwitcher_organizations"
},
{
"args": null,
"kind": "FragmentSpread",
"name": "NavUser_viewer"
}
],
"storageKey": null
@@ -120,16 +83,40 @@ return {
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"name": "organizations",
"plural": true,
"selections": [
(v1/*: any*/),
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v0/*: any*/)
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "logoUrl",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
}
],
"storageKey": null
@@ -137,16 +124,16 @@ return {
]
},
"params": {
"cacheID": "e50b9b44daf91d53b07f8912e6c2088f",
"cacheID": "48137f101a20a5ce116cbf46c2a8cd63",
"id": null,
"metadata": {},
"name": "AppSidebarQuery",
"operationKind": "query",
"text": "query AppSidebarQuery {\n viewer {\n id\n organization {\n name\n logoUrl\n createdAt\n updatedAt\n id\n }\n }\n}\n"
"text": "query AppSidebarQuery {\n viewer {\n id\n ...TeamSwitcher_organizations\n ...NavUser_viewer\n }\n}\n\nfragment NavUser_viewer on User {\n id\n fullName\n email\n}\n\nfragment TeamSwitcher_organizations on User {\n organizations {\n id\n name\n logoUrl\n }\n}\n"
}
};
})();
(node as any).hash = "d450e65862def2f7ebd6f4b8d0475be0";
(node as any).hash = "d5e28d934b5913169dd822042f4eca07";
export default node;

View File

@@ -0,0 +1,58 @@
/**
* @generated SignedSource<<0965f25c8dedb59a9316e4df4d773af6>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type NavUser_viewer$data = {
readonly email: string;
readonly fullName: string;
readonly id: string;
readonly " $fragmentType": "NavUser_viewer";
};
export type NavUser_viewer$key = {
readonly " $data"?: NavUser_viewer$data;
readonly " $fragmentSpreads": FragmentRefs<"NavUser_viewer">;
};
const node: ReaderFragment = {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "NavUser_viewer",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
}
],
"type": "User",
"abstractKey": null
};
(node as any).hash = "7ee85a272f543b514c5a6a3148fd1613";
export default node;

View File

@@ -0,0 +1,71 @@
/**
* @generated SignedSource<<8ecb9304d0a498d483d1a8c251f8c49a>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type TeamSwitcher_organizations$data = {
readonly organizations: ReadonlyArray<{
readonly id: string;
readonly logoUrl: string;
readonly name: string;
}>;
readonly " $fragmentType": "TeamSwitcher_organizations";
};
export type TeamSwitcher_organizations$key = {
readonly " $data"?: TeamSwitcher_organizations$data;
readonly " $fragmentSpreads": FragmentRefs<"TeamSwitcher_organizations">;
};
const node: ReaderFragment = {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "TeamSwitcher_organizations",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organizations",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "logoUrl",
"storageKey": null
}
],
"storageKey": null
}
],
"type": "User",
"abstractKey": null
};
(node as any).hash = "318f68b82b5b87500ec99ffcdd2929ab";
export default node;

View File

@@ -9,6 +9,8 @@ interface AuthContextType {
logout: () => Promise<void>;
}
// TODO: should be remove when backend returns proper authenticated error
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: React.ReactNode }) {

View File

@@ -0,0 +1,43 @@
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>
);
}

View File

@@ -24,8 +24,7 @@ const createPeoplePageQuery = graphql`
query CreatePeoplePageQuery {
viewer {
id
organization {
organizations {
id
name
}

View File

@@ -9,12 +9,12 @@ import { Card, CardContent } from "@/components/ui/card";
import { Link } from "react-router";
import type { FrameworkListPageQuery as FrameworkListPageQueryType } from "./__generated__/FrameworkListPageQuery.graphql";
import { Helmet } from "react-helmet-async";
import { useOrganization } from "@/contexts/OrganizationContext";
const FrameworkListPageQuery = graphql`
query FrameworkListPageQuery {
viewer {
id
organization {
query FrameworkListPageQuery($organizationId: ID!) {
organization: node(id: $organizationId) {
... on Organization {
frameworks {
edges {
node {
@@ -87,7 +87,7 @@ function FrameworkListPageContent({
}) {
const data = usePreloadedQuery(FrameworkListPageQuery, queryRef);
const frameworks =
data.viewer.organization.frameworks?.edges.map((edge) => edge?.node) ?? [];
data.organization.frameworks?.edges.map((edge) => edge?.node) ?? [];
return (
<div className="space-y-6">
@@ -164,9 +164,11 @@ export default function FrameworkListPage() {
FrameworkListPageQuery
);
const { currentOrganization } = useOrganization();
useEffect(() => {
loadQuery({});
}, [loadQuery]);
loadQuery({ organizationId: currentOrganization!.id });
}, [loadQuery, currentOrganization]);
return (
<>

View File

@@ -12,7 +12,7 @@ export const HomePageQuery = graphql`
query HomePageQuery {
viewer {
id
organization {
organizations {
name
createdAt
updatedAt

View File

@@ -18,28 +18,34 @@ import type { PeopleListPageQuery as PeopleListPageQueryType } from "./__generat
import type { PeopleListPageDeletePeopleMutation } from "./__generated__/PeopleListPageDeletePeopleMutation.graphql";
import { PeopleListPagePaginationQuery } from "./__generated__/PeopleListPagePaginationQuery.graphql";
import { PeopleListPage_peoples$key } from "./__generated__/PeopleListPage_peoples.graphql";
import { useOrganization } from "@/contexts/OrganizationContext";
const ITEMS_PER_PAGE = 25;
const peopleListPageQuery = graphql`
query PeopleListPageQuery(
$organizationId: ID!
$first: Int
$after: CursorKey
$last: Int
$before: CursorKey
) {
viewer {
id
organization {
...PeopleListPage_peoples
}
organization: node(id: $organizationId) {
...PeopleListPage_peoples
@arguments(first: $first, after: $after, last: $last, before: $before)
}
}
`;
const peopleListFragment = graphql`
fragment PeopleListPage_peoples on Organization
@refetchable(queryName: "PeopleListPagePaginationQuery") {
@refetchable(queryName: "PeopleListPagePaginationQuery")
@argumentDefinitions(
first: { type: "Int" }
after: { type: "CursorKey" }
last: { type: "Int" }
before: { type: "CursorKey" }
) {
id
peoples(first: $first, after: $after, last: $last, before: $before)
@connection(key: "PeopleListPage_peoples") {
@@ -147,7 +153,7 @@ function PeopleListContent({
} = usePaginationFragment<
PeopleListPagePaginationQuery,
PeopleListPage_peoples$key
>(peopleListFragment, data.viewer.organization);
>(peopleListFragment, data.organization);
const peoples =
peoplesConnection.peoples.edges.map((edge) => edge.node) ?? [];
@@ -301,17 +307,20 @@ export default function PeopleListPage() {
const [queryRef, loadQuery] =
useQueryLoader<PeopleListPageQueryType>(peopleListPageQuery);
const { currentOrganization } = useOrganization();
useEffect(() => {
const after = searchParams.get("after");
const before = searchParams.get("before");
loadQuery({
organizationId: currentOrganization!.id,
first: before ? undefined : ITEMS_PER_PAGE,
after: after || undefined,
last: before ? ITEMS_PER_PAGE : undefined,
before: before || undefined,
});
}, [loadQuery]);
}, [loadQuery, currentOrganization]);
if (!queryRef) {
return <PeopleListPageFallback />;

View File

@@ -28,7 +28,7 @@ const settingsPageQuery = graphql`
query SettingsPageQuery {
viewer {
id
organization {
organizations {
name
logoUrl
}
@@ -49,7 +49,7 @@ function SettingsPageContent({
queryRef: PreloadedQuery<SettingsPageQueryType>;
}) {
const data = usePreloadedQuery(settingsPageQuery, queryRef);
const organization = data.viewer.organization;
const organization = data.viewer.organizations[0];
const members: Member[] = [];
return (

View File

@@ -22,28 +22,35 @@ import { VendorListPageDeleteVendorMutation } from "./__generated__/VendorListPa
import { toast } from "@/hooks/use-toast";
import { VendorListPagePaginationQuery } from "./__generated__/VendorListPagePaginationQuery.graphql";
import { VendorListPage_vendors$key } from "./__generated__/VendorListPage_vendors.graphql";
import { useToast } from "@/hooks/use-toast";
import { useOrganization } from "@/contexts/OrganizationContext";
const ITEMS_PER_PAGE = 25;
const vendorListPageQuery = graphql`
query VendorListPageQuery(
$organizationId: ID!
$first: Int
$after: CursorKey
$last: Int
$before: CursorKey
) {
viewer {
id
organization {
...VendorListPage_vendors
}
organization: node(id: $organizationId) {
...VendorListPage_vendors
@arguments(first: $first, after: $after, last: $last, before: $before)
}
}
`;
const vendorListFragment = graphql`
fragment VendorListPage_vendors on Organization
@refetchable(queryName: "VendorListPagePaginationQuery") {
@refetchable(queryName: "VendorListPagePaginationQuery")
@argumentDefinitions(
first: { type: "Int" }
after: { type: "CursorKey" }
last: { type: "Int" }
before: { type: "CursorKey" }
) {
id
vendors(first: $first, after: $after, last: $last, before: $before)
@connection(key: "VendorListPage_vendors") {
@@ -96,8 +103,15 @@ const deleteVendorMutation = graphql`
}
`;
// Define a proper type for the vendor items
interface VendorItem {
id: string;
name: string;
createdAt: string;
}
// TODO: Remove this once we have a real list of vendors
const vendorsList = [
const vendorsList: VendorItem[] = [
{ id: "1", name: "Amazon Web Services", createdAt: new Date().toISOString() },
{
id: "2",
@@ -172,6 +186,7 @@ function VendorListContent({
}: {
queryRef: PreloadedQuery<VendorListPageQueryType>;
}) {
const { toast } = useToast();
const data = usePreloadedQuery<VendorListPageQueryType>(
vendorListPageQuery,
queryRef
@@ -179,7 +194,7 @@ function VendorListContent({
const [searchParams, setSearchParams] = useSearchParams();
const [isPending, startTransition] = useTransition();
const [searchTerm, setSearchTerm] = useState("");
const [filteredVendors, setFilteredVendors] = useState<Array<any>>([]);
const [filteredVendors, setFilteredVendors] = useState<VendorItem[]>([]);
const [createVendor] =
useMutation<VendorListPageCreateVendorMutation>(createVendorMutation);
const [deleteVendor] =
@@ -196,7 +211,7 @@ function VendorListContent({
} = usePaginationFragment<
VendorListPagePaginationQuery,
VendorListPage_vendors$key
>(vendorListFragment, data.viewer.organization);
>(vendorListFragment, data.organization);
const vendors =
vendorsConnection.vendors.edges.map((edge) => edge.node) ?? [];
@@ -245,7 +260,7 @@ function VendorListContent({
style={{ borderRadius: "0.3rem" }}
className="absolute top-full left-0 mt-1 w-[calc(100%-100px)] max-h-48 overflow-y-auto border bg-popover shadow-md z-10"
>
{filteredVendors.map((vendor) => (
{filteredVendors.map((vendor: VendorItem) => (
<button
key={vendor.id}
className="w-full px-3 py-2 text-left hover:bg-accent"
@@ -254,7 +269,7 @@ function VendorListContent({
variables: {
connections: [vendorsConnection.vendors.__id],
input: {
organizationId: data.viewer.organization.id,
organizationId: organization.id,
name: vendor.name,
description: "",
serviceStartAt: new Date().toISOString(),
@@ -410,17 +425,20 @@ export default function VendorListPage() {
const [queryRef, loadQuery] =
useQueryLoader<VendorListPageQueryType>(vendorListPageQuery);
const { currentOrganization } = useOrganization();
useEffect(() => {
const after = searchParams.get("after");
const before = searchParams.get("before");
loadQuery({
organizationId: currentOrganization!.id,
first: before ? undefined : ITEMS_PER_PAGE,
after: after || undefined,
last: before ? ITEMS_PER_PAGE : undefined,
before: before || undefined,
});
}, [loadQuery]);
}, [loadQuery, currentOrganization]);
if (!queryRef) {
return <VendorListFallback />;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<bcc0482212bee6fc9682545cc8255139>>
* @generated SignedSource<<9453bfb4e947c003e812ae63a4f1ed0a>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -13,10 +13,10 @@ export type CreatePeoplePageQuery$variables = Record<PropertyKey, never>;
export type CreatePeoplePageQuery$data = {
readonly viewer: {
readonly id: string;
readonly organization: {
readonly organizations: ReadonlyArray<{
readonly id: string;
readonly name: string;
};
}>;
};
};
export type CreatePeoplePageQuery = {
@@ -47,8 +47,8 @@ v1 = [
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"name": "organizations",
"plural": true,
"selections": [
(v0/*: any*/),
{
@@ -83,16 +83,16 @@ return {
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "e0b72d4317b700564caa46c609276aed",
"cacheID": "74b036184be646c06a001772776c445b",
"id": null,
"metadata": {},
"name": "CreatePeoplePageQuery",
"operationKind": "query",
"text": "query CreatePeoplePageQuery {\n viewer {\n id\n organization {\n id\n name\n }\n }\n}\n"
"text": "query CreatePeoplePageQuery {\n viewer {\n id\n organizations {\n id\n name\n }\n }\n}\n"
}
};
})();
(node as any).hash = "89be7e6fd93829bdc8ac374983ca0a9b";
(node as any).hash = "751fa313df4e5ed06f8100d1e7e2d4ea";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<903f19e5b3c193151107de5969fe5259>>
* @generated SignedSource<<ea02ef2ebc2c52a72d443c130a44087c>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -10,30 +10,29 @@
import { ConcreteRequest } from 'relay-runtime';
export type ControlState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED";
export type FrameworkListPageQuery$variables = Record<PropertyKey, never>;
export type FrameworkListPageQuery$variables = {
organizationId: string;
};
export type FrameworkListPageQuery$data = {
readonly viewer: {
readonly id: string;
readonly organization: {
readonly frameworks: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly controls: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly state: ControlState;
};
}>;
};
readonly createdAt: any;
readonly description: string;
readonly id: string;
readonly name: string;
readonly updatedAt: any;
readonly organization: {
readonly frameworks?: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly controls: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly state: ControlState;
};
}>;
};
}>;
};
readonly createdAt: any;
readonly description: string;
readonly id: string;
readonly name: string;
readonly updatedAt: any;
};
}>;
};
};
};
@@ -43,82 +42,102 @@ export type FrameworkListPageQuery = {
};
const node: ConcreteRequest = (function(){
var v0 = {
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "organizationId"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "organizationId"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v1 = {
"alias": null,
"args": null,
"concreteType": "FrameworkConnection",
"kind": "LinkedField",
"name": "frameworks",
"plural": false,
v3 = {
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "FrameworkEdge",
"concreteType": "FrameworkConnection",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"name": "frameworks",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Framework",
"concreteType": "FrameworkEdge",
"kind": "LinkedField",
"name": "node",
"plural": false,
"name": "edges",
"plural": true,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "ControlConnection",
"concreteType": "Framework",
"kind": "LinkedField",
"name": "controls",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "ControlEdge",
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "ControlConnection",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"name": "controls",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Control",
"concreteType": "ControlEdge",
"kind": "LinkedField",
"name": "node",
"plural": false,
"name": "edges",
"plural": true,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"concreteType": "Control",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"storageKey": null
}
],
"storageKey": null
}
],
@@ -126,23 +145,23 @@ v1 = {
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
}
],
"storageKey": null
@@ -151,36 +170,25 @@ v1 = {
"storageKey": null
}
],
"storageKey": null
"type": "Organization",
"abstractKey": null
};
return {
"fragment": {
"argumentDefinitions": [],
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "FrameworkListPageQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "User",
"alias": "organization",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "viewer",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": [
(v1/*: any*/)
],
"storageKey": null
}
(v3/*: any*/)
],
"storageKey": null
}
@@ -190,48 +198,43 @@ return {
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "FrameworkListPageQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "User",
"alias": "organization",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "viewer",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
},
(v3/*: any*/),
(v2/*: any*/)
],
"storageKey": null
}
]
},
"params": {
"cacheID": "8f573b1a89e302e61a765711c1c6e73a",
"cacheID": "e45d4e64a00b3e27cd6dc52179f47d52",
"id": null,
"metadata": {},
"name": "FrameworkListPageQuery",
"operationKind": "query",
"text": "query FrameworkListPageQuery {\n viewer {\n id\n organization {\n frameworks {\n edges {\n node {\n id\n name\n description\n controls {\n edges {\n node {\n id\n state\n }\n }\n }\n createdAt\n updatedAt\n }\n }\n }\n id\n }\n }\n}\n"
"text": "query FrameworkListPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n frameworks {\n edges {\n node {\n id\n name\n description\n controls {\n edges {\n node {\n id\n state\n }\n }\n }\n createdAt\n updatedAt\n }\n }\n }\n }\n id\n }\n}\n"
}
};
})();
(node as any).hash = "ac0337411177ab9b1c8ef81e6bb47a06";
(node as any).hash = "5cd314faffe31e716de2336e70cca4ee";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<7ca2f2186b657853d9fce4c7a21a7db1>>
* @generated SignedSource<<17467071cb25b3e291532a4fb9074d67>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -16,7 +16,7 @@ export type HomePageQuery$variables = Record<PropertyKey, never>;
export type HomePageQuery$data = {
readonly viewer: {
readonly id: string;
readonly organization: {
readonly organizations: ReadonlyArray<{
readonly createdAt: any;
readonly frameworks: {
readonly edges: ReadonlyArray<{
@@ -124,7 +124,7 @@ export type HomePageQuery$data = {
};
}>;
};
};
}>;
};
};
export type HomePageQuery = {
@@ -626,8 +626,8 @@ return {
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"name": "organizations",
"plural": true,
"selections": [
(v1/*: any*/),
(v2/*: any*/),
@@ -665,8 +665,8 @@ return {
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"name": "organizations",
"plural": true,
"selections": [
(v1/*: any*/),
(v2/*: any*/),
@@ -684,16 +684,16 @@ return {
]
},
"params": {
"cacheID": "9f8dd0b1a2698dd2d944e75de5d32087",
"cacheID": "d40cf8a4787ed129284c566abd99fdaf",
"id": null,
"metadata": {},
"name": "HomePageQuery",
"operationKind": "query",
"text": "query HomePageQuery {\n viewer {\n id\n organization {\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"
"text": "query HomePageQuery {\n viewer {\n id\n organizations {\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"
}
};
})();
(node as any).hash = "875e681cb7e936d3f1a543e88c4cb2d0";
(node as any).hash = "e9da69d4e7095f003eb28bf51e7c399d";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<9ff37ab5131d3343de8f2d0c843377d1>>
* @generated SignedSource<<48cb6bd6a591f9b4a0c44d3ac0b54ee4>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -60,21 +60,7 @@ v5 = [
"variableName": "id"
}
],
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v8 = [
v6 = [
{
"kind": "Variable",
"name": "after",
@@ -95,7 +81,21 @@ v8 = [
"name": "last",
"variableName": "last"
}
];
],
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
@@ -118,7 +118,7 @@ return {
"plural": false,
"selections": [
{
"args": null,
"args": (v6/*: any*/),
"kind": "FragmentSpread",
"name": "PeopleListPage_peoples"
}
@@ -149,14 +149,14 @@ return {
"name": "node",
"plural": false,
"selections": [
(v6/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": (v8/*: any*/),
"args": (v6/*: any*/),
"concreteType": "PeopleConnection",
"kind": "LinkedField",
"name": "peoples",
@@ -178,7 +178,7 @@ return {
"name": "node",
"plural": false,
"selections": [
(v7/*: any*/),
(v8/*: any*/),
{
"alias": null,
"args": null,
@@ -221,7 +221,7 @@ return {
"name": "updatedAt",
"storageKey": null
},
(v6/*: any*/)
(v7/*: any*/)
],
"storageKey": null
},
@@ -291,7 +291,7 @@ return {
},
{
"alias": null,
"args": (v8/*: any*/),
"args": (v6/*: any*/),
"filters": null,
"handle": "connection",
"key": "PeopleListPage_peoples",
@@ -308,16 +308,16 @@ return {
]
},
"params": {
"cacheID": "17a418660f5fd222c0400f2825634ed4",
"cacheID": "f20190ee884fd80e3f9157895667cb9e",
"id": null,
"metadata": {},
"name": "PeopleListPagePaginationQuery",
"operationKind": "query",
"text": "query PeopleListPagePaginationQuery(\n $after: CursorKey\n $before: CursorKey\n $first: Int\n $last: Int\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...PeopleListPage_peoples\n id\n }\n}\n\nfragment PeopleListPage_peoples on Organization {\n id\n peoples(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n additionalEmailAddresses\n kind\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n}\n"
"text": "query PeopleListPagePaginationQuery(\n $after: CursorKey\n $before: CursorKey\n $first: Int\n $last: Int\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...PeopleListPage_peoples_pbnwq\n id\n }\n}\n\nfragment PeopleListPage_peoples_pbnwq on Organization {\n id\n peoples(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n additionalEmailAddresses\n kind\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n}\n"
}
};
})();
(node as any).hash = "6602dd60c3adf0db89580e752495c910";
(node as any).hash = "3d7335649a48a459415be45828b7e265";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<fd54290261cf29d9b3cf42b70a68f398>>
* @generated SignedSource<<7b8897ff225dacac2f05e25136520b59>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -15,13 +15,11 @@ export type PeopleListPageQuery$variables = {
before?: any | null | undefined;
first?: number | null | undefined;
last?: number | null | undefined;
organizationId: string;
};
export type PeopleListPageQuery$data = {
readonly viewer: {
readonly id: string;
readonly organization: {
readonly " $fragmentSpreads": FragmentRefs<"PeopleListPage_peoples">;
};
readonly organization: {
readonly " $fragmentSpreads": FragmentRefs<"PeopleListPage_peoples">;
};
};
export type PeopleListPageQuery = {
@@ -51,13 +49,18 @@ v3 = {
"name": "last"
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
"defaultValue": null,
"kind": "LocalArgument",
"name": "organizationId"
},
v5 = [
{
"kind": "Variable",
"name": "id",
"variableName": "organizationId"
}
],
v6 = [
{
"kind": "Variable",
"name": "after",
@@ -78,43 +81,46 @@ v5 = [
"name": "last",
"variableName": "last"
}
];
],
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/),
(v2/*: any*/),
(v3/*: any*/)
(v3/*: any*/),
(v4/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "PeopleListPageQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "User",
"alias": "organization",
"args": (v5/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "viewer",
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": [
{
"args": null,
"kind": "FragmentSpread",
"name": "PeopleListPage_peoples"
}
],
"storageKey": null
"args": (v6/*: any*/),
"kind": "FragmentSpread",
"name": "PeopleListPage_peoples"
}
],
"storageKey": null
@@ -126,6 +132,7 @@ return {
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v4/*: any*/),
(v2/*: any*/),
(v0/*: any*/),
(v3/*: any*/),
@@ -135,26 +142,21 @@ return {
"name": "PeopleListPageQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "User",
"alias": "organization",
"args": (v5/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "viewer",
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"kind": "InlineFragment",
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": (v5/*: any*/),
"args": (v6/*: any*/),
"concreteType": "PeopleConnection",
"kind": "LinkedField",
"name": "peoples",
@@ -176,7 +178,7 @@ return {
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
(v8/*: any*/),
{
"alias": null,
"args": null,
@@ -219,13 +221,7 @@ return {
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
(v7/*: any*/)
],
"storageKey": null
},
@@ -295,7 +291,7 @@ return {
},
{
"alias": null,
"args": (v5/*: any*/),
"args": (v6/*: any*/),
"filters": null,
"handle": "connection",
"key": "PeopleListPage_peoples",
@@ -303,7 +299,8 @@ return {
"name": "peoples"
}
],
"storageKey": null
"type": "Organization",
"abstractKey": null
}
],
"storageKey": null
@@ -311,16 +308,16 @@ return {
]
},
"params": {
"cacheID": "d9b0af5656fcb50e2cedf0f831c0394d",
"cacheID": "ceb15735cc88f1993e35ac9a539a7ad0",
"id": null,
"metadata": {},
"name": "PeopleListPageQuery",
"operationKind": "query",
"text": "query PeopleListPageQuery(\n $first: Int\n $after: CursorKey\n $last: Int\n $before: CursorKey\n) {\n viewer {\n id\n organization {\n ...PeopleListPage_peoples\n id\n }\n }\n}\n\nfragment PeopleListPage_peoples on Organization {\n id\n peoples(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n additionalEmailAddresses\n kind\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n}\n"
"text": "query PeopleListPageQuery(\n $organizationId: ID!\n $first: Int\n $after: CursorKey\n $last: Int\n $before: CursorKey\n) {\n organization: node(id: $organizationId) {\n __typename\n ...PeopleListPage_peoples_pbnwq\n id\n }\n}\n\nfragment PeopleListPage_peoples_pbnwq on Organization {\n id\n peoples(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n additionalEmailAddresses\n kind\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n}\n"
}
};
})();
(node as any).hash = "9646de6e2d6b844cb87945b8a26c0999";
(node as any).hash = "0a1918698d276906f5e17baa40f756e3";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<5e071fe96262050a43c9471c27ff667e>>
* @generated SignedSource<<4291ff1f33eb226774e68544740b4331>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -54,19 +54,23 @@ v1 = {
return {
"argumentDefinitions": [
{
"kind": "RootArgument",
"defaultValue": null,
"kind": "LocalArgument",
"name": "after"
},
{
"kind": "RootArgument",
"defaultValue": null,
"kind": "LocalArgument",
"name": "before"
},
{
"kind": "RootArgument",
"defaultValue": null,
"kind": "LocalArgument",
"name": "first"
},
{
"kind": "RootArgument",
"defaultValue": null,
"kind": "LocalArgument",
"name": "last"
}
],
@@ -252,6 +256,6 @@ return {
};
})();
(node as any).hash = "6602dd60c3adf0db89580e752495c910";
(node as any).hash = "3d7335649a48a459415be45828b7e265";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<0b5e0bcf4f6519e1ccb1d7ffa3e488cb>>
* @generated SignedSource<<f2e46ea14241587a319e9a6af9072bb9>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -13,10 +13,10 @@ export type SettingsPageQuery$variables = Record<PropertyKey, never>;
export type SettingsPageQuery$data = {
readonly viewer: {
readonly id: string;
readonly organization: {
readonly organizations: ReadonlyArray<{
readonly logoUrl: string;
readonly name: string;
};
}>;
};
};
export type SettingsPageQuery = {
@@ -67,8 +67,8 @@ return {
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"name": "organizations",
"plural": true,
"selections": [
(v1/*: any*/),
(v2/*: any*/)
@@ -102,8 +102,8 @@ return {
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"name": "organizations",
"plural": true,
"selections": [
(v1/*: any*/),
(v2/*: any*/),
@@ -117,16 +117,16 @@ return {
]
},
"params": {
"cacheID": "77d3f8660df07b02aa515461c9ec1449",
"cacheID": "5a3f5c431e1f886a330d220fd33a33f3",
"id": null,
"metadata": {},
"name": "SettingsPageQuery",
"operationKind": "query",
"text": "query SettingsPageQuery {\n viewer {\n id\n organization {\n name\n logoUrl\n id\n }\n }\n}\n"
"text": "query SettingsPageQuery {\n viewer {\n id\n organizations {\n name\n logoUrl\n id\n }\n }\n}\n"
}
};
})();
(node as any).hash = "c639e9c45b175beec64550bb72fe5111";
(node as any).hash = "cef332dd829051516fa47315c3a60d1f";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<035db05f931b88ee09b1bbdd2c39a7e7>>
* @generated SignedSource<<8a3c352a16aeb823ac25ee61446cc573>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -60,21 +60,7 @@ v5 = [
"variableName": "id"
}
],
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v8 = [
v6 = [
{
"kind": "Variable",
"name": "after",
@@ -95,7 +81,21 @@ v8 = [
"name": "last",
"variableName": "last"
}
];
],
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
@@ -118,7 +118,7 @@ return {
"plural": false,
"selections": [
{
"args": null,
"args": (v6/*: any*/),
"kind": "FragmentSpread",
"name": "VendorListPage_vendors"
}
@@ -149,14 +149,14 @@ return {
"name": "node",
"plural": false,
"selections": [
(v6/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": (v8/*: any*/),
"args": (v6/*: any*/),
"concreteType": "VendorConnection",
"kind": "LinkedField",
"name": "vendors",
@@ -178,7 +178,7 @@ return {
"name": "node",
"plural": false,
"selections": [
(v7/*: any*/),
(v8/*: any*/),
{
"alias": null,
"args": null,
@@ -207,7 +207,7 @@ return {
"name": "updatedAt",
"storageKey": null
},
(v6/*: any*/)
(v7/*: any*/)
],
"storageKey": null
},
@@ -277,7 +277,7 @@ return {
},
{
"alias": null,
"args": (v8/*: any*/),
"args": (v6/*: any*/),
"filters": null,
"handle": "connection",
"key": "VendorListPage_vendors",
@@ -294,16 +294,16 @@ return {
]
},
"params": {
"cacheID": "61e9346ec50a3f4b1397fa2e6165ab3e",
"cacheID": "8fa8530a89995a836fec656b1d141b89",
"id": null,
"metadata": {},
"name": "VendorListPagePaginationQuery",
"operationKind": "query",
"text": "query VendorListPagePaginationQuery(\n $after: CursorKey\n $before: CursorKey\n $first: Int\n $last: Int\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...VendorListPage_vendors\n id\n }\n}\n\nfragment VendorListPage_vendors on Organization {\n id\n vendors(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n name\n description\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n}\n"
"text": "query VendorListPagePaginationQuery(\n $after: CursorKey\n $before: CursorKey\n $first: Int\n $last: Int\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...VendorListPage_vendors_pbnwq\n id\n }\n}\n\nfragment VendorListPage_vendors_pbnwq on Organization {\n id\n vendors(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n name\n description\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n}\n"
}
};
})();
(node as any).hash = "b5d797660f73e909467c3ab4e988001f";
(node as any).hash = "7ab226392b5cd58f332515753f1099ae";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<42f5a79d59c05d56dc664163fd507fed>>
* @generated SignedSource<<15bedd2878042e53e79e430f79d767cb>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -15,13 +15,11 @@ export type VendorListPageQuery$variables = {
before?: any | null | undefined;
first?: number | null | undefined;
last?: number | null | undefined;
organizationId: string;
};
export type VendorListPageQuery$data = {
readonly viewer: {
readonly id: string;
readonly organization: {
readonly " $fragmentSpreads": FragmentRefs<"VendorListPage_vendors">;
};
readonly organization: {
readonly " $fragmentSpreads": FragmentRefs<"VendorListPage_vendors">;
};
};
export type VendorListPageQuery = {
@@ -51,13 +49,18 @@ v3 = {
"name": "last"
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
"defaultValue": null,
"kind": "LocalArgument",
"name": "organizationId"
},
v5 = [
{
"kind": "Variable",
"name": "id",
"variableName": "organizationId"
}
],
v6 = [
{
"kind": "Variable",
"name": "after",
@@ -78,43 +81,46 @@ v5 = [
"name": "last",
"variableName": "last"
}
];
],
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/),
(v2/*: any*/),
(v3/*: any*/)
(v3/*: any*/),
(v4/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "VendorListPageQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "User",
"alias": "organization",
"args": (v5/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "viewer",
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": [
{
"args": null,
"kind": "FragmentSpread",
"name": "VendorListPage_vendors"
}
],
"storageKey": null
"args": (v6/*: any*/),
"kind": "FragmentSpread",
"name": "VendorListPage_vendors"
}
],
"storageKey": null
@@ -126,6 +132,7 @@ return {
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v4/*: any*/),
(v2/*: any*/),
(v0/*: any*/),
(v3/*: any*/),
@@ -135,26 +142,21 @@ return {
"name": "VendorListPageQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "User",
"alias": "organization",
"args": (v5/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "viewer",
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"kind": "InlineFragment",
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": (v5/*: any*/),
"args": (v6/*: any*/),
"concreteType": "VendorConnection",
"kind": "LinkedField",
"name": "vendors",
@@ -176,7 +178,7 @@ return {
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
(v8/*: any*/),
{
"alias": null,
"args": null,
@@ -205,13 +207,7 @@ return {
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
(v7/*: any*/)
],
"storageKey": null
},
@@ -281,7 +277,7 @@ return {
},
{
"alias": null,
"args": (v5/*: any*/),
"args": (v6/*: any*/),
"filters": null,
"handle": "connection",
"key": "VendorListPage_vendors",
@@ -289,7 +285,8 @@ return {
"name": "vendors"
}
],
"storageKey": null
"type": "Organization",
"abstractKey": null
}
],
"storageKey": null
@@ -297,16 +294,16 @@ return {
]
},
"params": {
"cacheID": "0c3f0f24d810d8a510fa30ca3f3f5890",
"cacheID": "4f036b643119ac6840eede9bc148c6ac",
"id": null,
"metadata": {},
"name": "VendorListPageQuery",
"operationKind": "query",
"text": "query VendorListPageQuery(\n $first: Int\n $after: CursorKey\n $last: Int\n $before: CursorKey\n) {\n viewer {\n id\n organization {\n ...VendorListPage_vendors\n id\n }\n }\n}\n\nfragment VendorListPage_vendors on Organization {\n id\n vendors(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n name\n description\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n}\n"
"text": "query VendorListPageQuery(\n $organizationId: ID!\n $first: Int\n $after: CursorKey\n $last: Int\n $before: CursorKey\n) {\n organization: node(id: $organizationId) {\n __typename\n ...VendorListPage_vendors_pbnwq\n id\n }\n}\n\nfragment VendorListPage_vendors_pbnwq on Organization {\n id\n vendors(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n name\n description\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n}\n"
}
};
})();
(node as any).hash = "ffd0aaecc51a29f19ff874adbb38dab4";
(node as any).hash = "a0a4881ec7e60051fa196830f5c62f45";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<aa140c75b78a370fac81e7180d3537fc>>
* @generated SignedSource<<d8da23ca67cc47188e916e129dac0be0>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -51,19 +51,23 @@ v1 = {
return {
"argumentDefinitions": [
{
"kind": "RootArgument",
"defaultValue": null,
"kind": "LocalArgument",
"name": "after"
},
{
"kind": "RootArgument",
"defaultValue": null,
"kind": "LocalArgument",
"name": "before"
},
{
"kind": "RootArgument",
"defaultValue": null,
"kind": "LocalArgument",
"name": "first"
},
{
"kind": "RootArgument",
"defaultValue": null,
"kind": "LocalArgument",
"name": "last"
}
],
@@ -235,6 +239,6 @@ return {
};
})();
(node as any).hash = "b5d797660f73e909467c3ab4e988001f";
(node as any).hash = "7ab226392b5cd58f332515753f1099ae";
export default node;