Replug organizations page without assume or user dropdown
Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
@@ -1,161 +0,0 @@
|
|||||||
/**
|
|
||||||
* @generated SignedSource<<f441167b6ed9402b92fcdf6dbaa77170>>
|
|
||||||
* @lightSyntaxTransform
|
|
||||||
* @nogrep
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
// @ts-nocheck
|
|
||||||
|
|
||||||
import { ConcreteRequest } from 'relay-runtime';
|
|
||||||
export type MainLayoutQuery$variables = {
|
|
||||||
organizationId: string;
|
|
||||||
};
|
|
||||||
export type MainLayoutQuery$data = {
|
|
||||||
readonly organization: {
|
|
||||||
readonly id?: string;
|
|
||||||
readonly logoUrl?: string | null | undefined;
|
|
||||||
readonly name?: string;
|
|
||||||
};
|
|
||||||
readonly viewer: {
|
|
||||||
readonly id: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
export type MainLayoutQuery = {
|
|
||||||
response: MainLayoutQuery$data;
|
|
||||||
variables: MainLayoutQuery$variables;
|
|
||||||
};
|
|
||||||
|
|
||||||
const node: ConcreteRequest = (function(){
|
|
||||||
var v0 = [
|
|
||||||
{
|
|
||||||
"defaultValue": null,
|
|
||||||
"kind": "LocalArgument",
|
|
||||||
"name": "organizationId"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
v1 = {
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "id",
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
v2 = {
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"concreteType": "Viewer",
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "viewer",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
(v1/*: any*/)
|
|
||||||
],
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
v3 = [
|
|
||||||
{
|
|
||||||
"kind": "Variable",
|
|
||||||
"name": "id",
|
|
||||||
"variableName": "organizationId"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
v4 = {
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "name",
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
v5 = {
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "logoUrl",
|
|
||||||
"storageKey": null
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
"fragment": {
|
|
||||||
"argumentDefinitions": (v0/*: any*/),
|
|
||||||
"kind": "Fragment",
|
|
||||||
"metadata": null,
|
|
||||||
"name": "MainLayoutQuery",
|
|
||||||
"selections": [
|
|
||||||
(v2/*: any*/),
|
|
||||||
{
|
|
||||||
"alias": "organization",
|
|
||||||
"args": (v3/*: any*/),
|
|
||||||
"concreteType": null,
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "node",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"kind": "InlineFragment",
|
|
||||||
"selections": [
|
|
||||||
(v1/*: any*/),
|
|
||||||
(v4/*: any*/),
|
|
||||||
(v5/*: any*/)
|
|
||||||
],
|
|
||||||
"type": "Organization",
|
|
||||||
"abstractKey": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"type": "Query",
|
|
||||||
"abstractKey": null
|
|
||||||
},
|
|
||||||
"kind": "Request",
|
|
||||||
"operation": {
|
|
||||||
"argumentDefinitions": (v0/*: any*/),
|
|
||||||
"kind": "Operation",
|
|
||||||
"name": "MainLayoutQuery",
|
|
||||||
"selections": [
|
|
||||||
(v2/*: any*/),
|
|
||||||
{
|
|
||||||
"alias": "organization",
|
|
||||||
"args": (v3/*: any*/),
|
|
||||||
"concreteType": null,
|
|
||||||
"kind": "LinkedField",
|
|
||||||
"name": "node",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "__typename",
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
(v1/*: any*/),
|
|
||||||
{
|
|
||||||
"kind": "InlineFragment",
|
|
||||||
"selections": [
|
|
||||||
(v4/*: any*/),
|
|
||||||
(v5/*: any*/)
|
|
||||||
],
|
|
||||||
"type": "Organization",
|
|
||||||
"abstractKey": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"params": {
|
|
||||||
"cacheID": "2ec49ff40720bacec29e7b6b1bf1408b",
|
|
||||||
"id": null,
|
|
||||||
"metadata": {},
|
|
||||||
"name": "MainLayoutQuery",
|
|
||||||
"operationKind": "query",
|
|
||||||
"text": "query MainLayoutQuery(\n $organizationId: ID!\n) {\n viewer {\n id\n }\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n name\n logoUrl\n }\n id\n }\n}\n"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
(node as any).hash = "d958a2acbd9d13698b9c2b350b05d5a0";
|
|
||||||
|
|
||||||
export default node;
|
|
||||||
@@ -1,364 +0,0 @@
|
|||||||
import { useTranslate } from "@probo/i18n";
|
|
||||||
import { useEffect, useMemo, useState } from "react";
|
|
||||||
import { Link, useNavigate } from "react-router";
|
|
||||||
import {
|
|
||||||
Avatar,
|
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
IconPlusLarge,
|
|
||||||
IconCheckmark1,
|
|
||||||
IconLock,
|
|
||||||
IconClock,
|
|
||||||
IconMagnifyingGlass,
|
|
||||||
Badge,
|
|
||||||
Input,
|
|
||||||
} from "@probo/ui";
|
|
||||||
import { usePageTitle } from "@probo/hooks";
|
|
||||||
import { formatDate } from "@probo/helpers";
|
|
||||||
|
|
||||||
interface Organization {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
logoUrl?: string | null;
|
|
||||||
authenticationMethod: string;
|
|
||||||
authStatus: "authenticated" | "unauthenticated" | "expired";
|
|
||||||
loginUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Invitation {
|
|
||||||
id: string;
|
|
||||||
email: string;
|
|
||||||
fullName: string;
|
|
||||||
role: string;
|
|
||||||
expiresAt: string;
|
|
||||||
acceptedAt?: string | null;
|
|
||||||
createdAt: string;
|
|
||||||
organization: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function OrganizationsPage() {
|
|
||||||
const { __ } = useTranslate();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const [organizations, setOrganizations] = useState<Organization[]>([]);
|
|
||||||
const [isLoadingOrganizations, setIsLoadingOrganizations] = useState(true);
|
|
||||||
const [invitations, setInvitations] = useState<Invitation[]>([]);
|
|
||||||
const [isLoadingInvitations, setIsLoadingInvitations] = useState(true);
|
|
||||||
const [isAccepting, setIsAccepting] = useState(false);
|
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
|
|
||||||
const filteredOrganizations = useMemo(() => {
|
|
||||||
if (!search.trim()) {
|
|
||||||
return organizations;
|
|
||||||
}
|
|
||||||
return organizations.filter((org) =>
|
|
||||||
org.name.toLowerCase().includes(search.toLowerCase())
|
|
||||||
);
|
|
||||||
}, [organizations, search]);
|
|
||||||
|
|
||||||
// Fetch organizations from REST endpoint
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchOrganizations = async () => {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/connect/organizations', {
|
|
||||||
credentials: 'include',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to fetch organizations');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data: { organizations: Organization[] } = await response.json();
|
|
||||||
setOrganizations(data.organizations);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to fetch organizations:', err);
|
|
||||||
} finally {
|
|
||||||
setIsLoadingOrganizations(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchOrganizations();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Fetch pending invitations from REST endpoint
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchInvitations = async () => {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/connect/invitations', {
|
|
||||||
credentials: 'include',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to fetch invitations');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data: { invitations: Invitation[] } = await response.json();
|
|
||||||
setInvitations(data.invitations);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to fetch invitations:', err);
|
|
||||||
} finally {
|
|
||||||
setIsLoadingInvitations(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchInvitations();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleAcceptInvitation = async (invitationId: string, organizationId: string) => {
|
|
||||||
setIsAccepting(true);
|
|
||||||
try {
|
|
||||||
const response = await fetch('/connect/invitations/accept', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
|
||||||
body: JSON.stringify({ invitationId }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to accept invitation');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Navigate to the organization after successful acceptance
|
|
||||||
navigate(`/organizations/${organizationId}`);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to accept invitation:', err);
|
|
||||||
alert(__('Failed to accept invitation'));
|
|
||||||
} finally {
|
|
||||||
setIsAccepting(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
usePageTitle(__("Select an organization"));
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Only auto-navigate once both organizations and invitations are loaded
|
|
||||||
if (!isLoadingOrganizations && !isLoadingInvitations) {
|
|
||||||
if (organizations.length === 1 && invitations.length === 0) {
|
|
||||||
navigate(`/organizations/${organizations[0].id}`);
|
|
||||||
} else if (organizations.length === 0 && invitations.length === 0) {
|
|
||||||
navigate("/organizations/new");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [organizations, invitations, isLoadingOrganizations, isLoadingInvitations, navigate]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="space-y-6 w-full py-6">
|
|
||||||
<h1 className="text-3xl font-bold text-center">
|
|
||||||
{__("Select an organization")}
|
|
||||||
</h1>
|
|
||||||
<div className="space-y-4 w-full">
|
|
||||||
{invitations.length > 0 && (
|
|
||||||
<div className="space-y-3">
|
|
||||||
<h2 className="text-xl font-semibold">
|
|
||||||
{__("Pending invitations")}
|
|
||||||
</h2>
|
|
||||||
{invitations.map((invitation) => (
|
|
||||||
<InvitationCard
|
|
||||||
key={invitation.id}
|
|
||||||
invitation={invitation}
|
|
||||||
onAccept={handleAcceptInvitation}
|
|
||||||
isAccepting={isAccepting}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{organizations.length > 0 && (
|
|
||||||
<div className="space-y-3">
|
|
||||||
{invitations.length > 0 && (
|
|
||||||
<h2 className="text-xl font-semibold">
|
|
||||||
{__("Your organizations")}
|
|
||||||
</h2>
|
|
||||||
)}
|
|
||||||
{organizations.length > 3 && (
|
|
||||||
<div className="w-full">
|
|
||||||
<Input
|
|
||||||
icon={IconMagnifyingGlass}
|
|
||||||
placeholder={__("Search organizations...")}
|
|
||||||
value={search}
|
|
||||||
onValueChange={setSearch}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{filteredOrganizations.length === 0 ? (
|
|
||||||
<div className="text-center text-txt-secondary py-4">
|
|
||||||
{__("No organizations found")}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
filteredOrganizations.map((organization) => (
|
|
||||||
<OrganizationCard
|
|
||||||
key={organization.id}
|
|
||||||
organization={organization}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Card padded>
|
|
||||||
<h2 className="text-xl font-semibold mb-1">
|
|
||||||
{__("Create an organization")}
|
|
||||||
</h2>
|
|
||||||
<p className="text-txt-tertiary mb-4">
|
|
||||||
{__("Add a new organization to your account")}
|
|
||||||
</p>
|
|
||||||
<Button
|
|
||||||
to="/organizations/new"
|
|
||||||
variant="quaternary"
|
|
||||||
icon={IconPlusLarge}
|
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
{__("Create organization")}
|
|
||||||
</Button>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type InvitationCardProps = {
|
|
||||||
invitation: Invitation;
|
|
||||||
onAccept: (invitationId: string, organizationId: string) => void;
|
|
||||||
isAccepting: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
function InvitationCard({ invitation, onAccept, isAccepting }: InvitationCardProps) {
|
|
||||||
const { __ } = useTranslate();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card padded className="w-full">
|
|
||||||
<div className="flex items-start justify-between gap-4">
|
|
||||||
<div className="flex-1 space-y-1">
|
|
||||||
<h3 className="text-lg font-semibold">
|
|
||||||
{invitation.organization.name}
|
|
||||||
</h3>
|
|
||||||
<p className="text-sm text-txt-secondary">
|
|
||||||
{__("Role")}: <span className="font-medium">{invitation.role}</span>
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-txt-tertiary">
|
|
||||||
{__("Invited on")} {formatDate(invitation.createdAt)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
onClick={() => onAccept(invitation.id, invitation.organization.id)}
|
|
||||||
disabled={isAccepting}
|
|
||||||
>
|
|
||||||
{isAccepting ? __("Accepting...") : __("Accept invitation")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type OrganizationCardProps = {
|
|
||||||
organization: Organization;
|
|
||||||
};
|
|
||||||
|
|
||||||
function OrganizationCard({ organization }: OrganizationCardProps) {
|
|
||||||
const { __ } = useTranslate();
|
|
||||||
|
|
||||||
const isAuthenticated = organization.authStatus === "authenticated";
|
|
||||||
const isExpired = organization.authStatus === "expired";
|
|
||||||
const needsAuth = organization.authStatus === "unauthenticated";
|
|
||||||
|
|
||||||
// Determine target URL and button text based on auth status
|
|
||||||
const targetUrl = isAuthenticated
|
|
||||||
? `/organizations/${organization.id}`
|
|
||||||
: organization.loginUrl;
|
|
||||||
|
|
||||||
const getAuthBadge = () => {
|
|
||||||
if (isAuthenticated) {
|
|
||||||
return (
|
|
||||||
<Badge variant="success" className="flex items-center gap-1">
|
|
||||||
<IconCheckmark1 size={14} />
|
|
||||||
{__("Authenticated")}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isExpired) {
|
|
||||||
return (
|
|
||||||
<Badge variant="warning" className="flex items-center gap-1">
|
|
||||||
<IconClock size={14} />
|
|
||||||
{__("Session expired")}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needsAuth) {
|
|
||||||
return (
|
|
||||||
<Badge variant="neutral" className="flex items-center gap-1">
|
|
||||||
<IconLock size={14} />
|
|
||||||
{__("Authentication required")}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getButtonText = () => {
|
|
||||||
if (isAuthenticated) return __("Select");
|
|
||||||
if (organization.authenticationMethod === "saml") return __("Login with SAML");
|
|
||||||
return __("Login");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if the URL is a backend SAML endpoint
|
|
||||||
const isSAMLUrl = targetUrl.includes('/connect/saml/');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card padded className="w-full">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
{isSAMLUrl ? (
|
|
||||||
<a
|
|
||||||
href={targetUrl}
|
|
||||||
className="flex items-center gap-4 hover:text-primary flex-1"
|
|
||||||
>
|
|
||||||
<Avatar
|
|
||||||
src={organization.logoUrl}
|
|
||||||
name={organization.name}
|
|
||||||
size="l"
|
|
||||||
/>
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<h2 className="font-semibold text-xl">{organization.name}</h2>
|
|
||||||
{getAuthBadge()}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
<Link
|
|
||||||
to={targetUrl}
|
|
||||||
className="flex items-center gap-4 hover:text-primary flex-1"
|
|
||||||
>
|
|
||||||
<Avatar
|
|
||||||
src={organization.logoUrl}
|
|
||||||
name={organization.name}
|
|
||||||
size="l"
|
|
||||||
/>
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<h2 className="font-semibold text-xl">{organization.name}</h2>
|
|
||||||
{getAuthBadge()}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Button asChild>
|
|
||||||
{isSAMLUrl ? (
|
|
||||||
<a href={targetUrl}>
|
|
||||||
{getButtonText()}
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
<Link to={targetUrl}>
|
|
||||||
{getButtonText()}
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import {
|
||||||
|
RelayEnvironmentProvider,
|
||||||
|
usePreloadedQuery,
|
||||||
|
type PreloadedQuery,
|
||||||
|
} from "react-relay";
|
||||||
|
import { organizationLayoutQuery } from "./OrganizationLayoutQuery";
|
||||||
|
import { Outlet } from "react-router";
|
||||||
|
import { Layout } from "@probo/ui";
|
||||||
|
import { Sidebar } from "./_components/Sidebar";
|
||||||
|
import { OrganizationDropdown } from "./_components/OrganizationDropdown";
|
||||||
|
import { consoleEnvironment } from "/environments";
|
||||||
|
import type { OrganizationLayoutQuery } from "./__generated__/OrganizationLayoutQuery.graphql";
|
||||||
|
import { PermissionsProvider } from "/providers/PermissionsProvider";
|
||||||
|
|
||||||
|
interface OrganizationLayoutProps {
|
||||||
|
queryRef: PreloadedQuery<OrganizationLayoutQuery>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function OrganizationLayout(props: OrganizationLayoutProps) {
|
||||||
|
const { queryRef } = props;
|
||||||
|
|
||||||
|
const data = usePreloadedQuery<OrganizationLayoutQuery>(
|
||||||
|
organizationLayoutQuery,
|
||||||
|
queryRef,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PermissionsProvider>
|
||||||
|
<Layout
|
||||||
|
header={
|
||||||
|
<>
|
||||||
|
<div className="mr-auto">
|
||||||
|
<OrganizationDropdown fKey={data.organization} />
|
||||||
|
</div>
|
||||||
|
{/* <Suspense fallback={<Skeleton className="w-32 h-8" />}>
|
||||||
|
<UserDropdown organizationId={organizationId} />
|
||||||
|
</Suspense> */}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
sidebar={<Sidebar />}
|
||||||
|
>
|
||||||
|
<RelayEnvironmentProvider environment={consoleEnvironment}>
|
||||||
|
<Outlet />
|
||||||
|
</RelayEnvironmentProvider>
|
||||||
|
</Layout>
|
||||||
|
</PermissionsProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { lazy } from "@probo/react-lazy";
|
||||||
|
import { useQueryLoader } from "react-relay";
|
||||||
|
import { graphql } from "relay-runtime";
|
||||||
|
import { Suspense, useEffect } from "react";
|
||||||
|
import { Skeleton } from "@probo/ui";
|
||||||
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
|
import type { OrganizationLayoutQuery } from "./__generated__/OrganizationLayoutQuery.graphql";
|
||||||
|
|
||||||
|
const Layout = lazy(() => import("./OrganizationLayout"));
|
||||||
|
|
||||||
|
export const organizationLayoutQuery = graphql`
|
||||||
|
query OrganizationLayoutQuery($organizationId: ID!) {
|
||||||
|
organization: node(id: $organizationId) @required(action: THROW) {
|
||||||
|
... on Organization {
|
||||||
|
...OrganizationDropdownFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewer {
|
||||||
|
pendingInvitations {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function OrganizationLayoutQuery() {
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
|
const [queryRef, loadQuery] = useQueryLoader<OrganizationLayoutQuery>(
|
||||||
|
organizationLayoutQuery,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadQuery({
|
||||||
|
organizationId,
|
||||||
|
});
|
||||||
|
}, [loadQuery, organizationId]);
|
||||||
|
|
||||||
|
if (!queryRef) {
|
||||||
|
return <Skeleton className="w-full h-screen" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<Skeleton className="w-full h-screen" />}>
|
||||||
|
<Layout queryRef={queryRef} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -9,15 +9,12 @@ import {
|
|||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { InvitationCard } from "./_components/InvitationCard";
|
import { InvitationCard } from "./_components/InvitationCard";
|
||||||
import type {
|
import { organizationsPageQuery } from "./OrganizationsPageQuery";
|
||||||
OrganizationsQuery,
|
|
||||||
OrganizationsQuery$data,
|
|
||||||
} from "./__generated__/OrganizationsQuery.graphql";
|
|
||||||
import { organizationsQuery } from "./OrganizationsQuery";
|
|
||||||
import { MembershipCard } from "./_components/MembershipCard";
|
import { MembershipCard } from "./_components/MembershipCard";
|
||||||
|
import type { OrganizationsPageQuery } from "./__generated__/OrganizationsPageQuery.graphql";
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
queryRef: PreloadedQuery<OrganizationsQuery>;
|
queryRef: PreloadedQuery<OrganizationsPageQuery>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Page(props: PageProps) {
|
export default function Page(props: PageProps) {
|
||||||
@@ -30,11 +27,12 @@ export default function Page(props: PageProps) {
|
|||||||
memberships: { edges: initialMemberships },
|
memberships: { edges: initialMemberships },
|
||||||
pendingInvitations: { edges: invitations },
|
pendingInvitations: { edges: invitations },
|
||||||
},
|
},
|
||||||
} = usePreloadedQuery<OrganizationsQuery>(organizationsQuery, queryRef);
|
} = usePreloadedQuery<OrganizationsPageQuery>(
|
||||||
|
organizationsPageQuery,
|
||||||
|
queryRef,
|
||||||
|
);
|
||||||
|
|
||||||
const memberships = useMemo<
|
const memberships = useMemo(() => {
|
||||||
OrganizationsQuery$data["viewer"]["memberships"]["edges"]
|
|
||||||
>(() => {
|
|
||||||
if (!search.trim()) {
|
if (!search.trim()) {
|
||||||
return initialMemberships;
|
return initialMemberships;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,22 @@
|
|||||||
import { lazy } from "@probo/react-lazy";
|
import { lazy } from "@probo/react-lazy";
|
||||||
import { useQueryLoader } from "react-relay";
|
import { useQueryLoader } from "react-relay";
|
||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
import type { OrganizationsQuery } from "./__generated__/OrganizationsQuery.graphql";
|
|
||||||
import { Suspense, useEffect } from "react";
|
import { Suspense, useEffect } from "react";
|
||||||
import { CenteredLayoutSkeleton } from "@probo/ui";
|
import { CenteredLayoutSkeleton } from "@probo/ui";
|
||||||
|
import type { OrganizationsPageQuery } from "./__generated__/OrganizationsPageQuery.graphql";
|
||||||
|
|
||||||
const Page = lazy(() => import("./OrganizationsPage"));
|
const Page = lazy(() => import("./OrganizationsPage"));
|
||||||
|
|
||||||
export const organizationsQuery = graphql`
|
export const organizationsPageQuery = graphql`
|
||||||
query OrganizationsQuery {
|
query OrganizationsPageQuery {
|
||||||
viewer @required(action: THROW) {
|
viewer @required(action: THROW) {
|
||||||
memberships(
|
memberships(first: 1000, orderBy: { direction: DESC, field: CREATED_AT })
|
||||||
first: 1000
|
@required(action: THROW) {
|
||||||
orderBy: { direction: DESC, field: CREATED_AT }
|
edges @required(action: THROW) {
|
||||||
) {
|
node @required(action: THROW) {
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
id
|
||||||
...MembershipCardFragment
|
...MembershipCardFragment
|
||||||
organization {
|
organization @required(action: THROW) {
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,9 +25,9 @@ export const organizationsQuery = graphql`
|
|||||||
pendingInvitations(
|
pendingInvitations(
|
||||||
first: 1000
|
first: 1000
|
||||||
orderBy: { direction: DESC, field: CREATED_AT }
|
orderBy: { direction: DESC, field: CREATED_AT }
|
||||||
) {
|
) @required(action: THROW) {
|
||||||
edges {
|
edges @required(action: THROW) {
|
||||||
node {
|
node @required(action: THROW) {
|
||||||
id
|
id
|
||||||
...InvitationCardFragment
|
...InvitationCardFragment
|
||||||
}
|
}
|
||||||
@@ -39,9 +37,10 @@ export const organizationsQuery = graphql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function OrganizationsQuery() {
|
export function OrganizationsPageQuery() {
|
||||||
const [queryRef, loadQuery] =
|
const [queryRef, loadQuery] = useQueryLoader<OrganizationsPageQuery>(
|
||||||
useQueryLoader<OrganizationsQuery>(organizationsQuery);
|
organizationsPageQuery,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadQuery({});
|
loadQuery({});
|
||||||
188
apps/console/src/pages/iam/organizations/__generated__/OrganizationLayoutQuery.graphql.ts
generated
Normal file
188
apps/console/src/pages/iam/organizations/__generated__/OrganizationLayoutQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<e99ea3ab9f033b6a5384d389dd964f06>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
import { FragmentRefs } from "relay-runtime";
|
||||||
|
export type OrganizationLayoutQuery$variables = {
|
||||||
|
organizationId: string;
|
||||||
|
};
|
||||||
|
export type OrganizationLayoutQuery$data = {
|
||||||
|
readonly organization: {
|
||||||
|
readonly " $fragmentSpreads": FragmentRefs<"OrganizationDropdownFragment">;
|
||||||
|
};
|
||||||
|
readonly viewer: {
|
||||||
|
readonly pendingInvitations: {
|
||||||
|
readonly totalCount: number | null | undefined;
|
||||||
|
} | null | undefined;
|
||||||
|
} | null | undefined;
|
||||||
|
};
|
||||||
|
export type OrganizationLayoutQuery = {
|
||||||
|
response: OrganizationLayoutQuery$data;
|
||||||
|
variables: OrganizationLayoutQuery$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "organizationId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "id",
|
||||||
|
"variableName": "organizationId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v2 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "InvitationConnection",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "pendingInvitations",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "totalCount",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v3 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "OrganizationLayoutQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
|
"alias": "organization",
|
||||||
|
"args": (v1/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"kind": "InlineFragment",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"args": null,
|
||||||
|
"kind": "FragmentSpread",
|
||||||
|
"name": "OrganizationDropdownFragment"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Organization",
|
||||||
|
"abstractKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
"action": "THROW"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Identity",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "viewer",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v2/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Query",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "OrganizationLayoutQuery",
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "InlineFragment",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Organization",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Identity",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "viewer",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v2/*: any*/),
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "b991c1a406cc9c0df5c67099285c31a8",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "OrganizationLayoutQuery",
|
||||||
|
"operationKind": "query",
|
||||||
|
"text": "query OrganizationLayoutQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...OrganizationDropdownFragment\n }\n id\n }\n viewer {\n pendingInvitations {\n totalCount\n }\n id\n }\n}\n\nfragment OrganizationDropdownFragment on Organization {\n name\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "2e8a1c50dc999d218830ab3df10cdeb5";
|
||||||
|
|
||||||
|
export default node;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<97119b2ea90f368155a13a32941b99d3>>
|
* @generated SignedSource<<6ac0b24d615666de1a5ea1c751095e6d>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
import { ConcreteRequest } from 'relay-runtime';
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
import { FragmentRefs } from "relay-runtime";
|
import { FragmentRefs } from "relay-runtime";
|
||||||
export type OrganizationsQuery$variables = Record<PropertyKey, never>;
|
export type OrganizationsPageQuery$variables = Record<PropertyKey, never>;
|
||||||
export type OrganizationsQuery$data = {
|
export type OrganizationsPageQuery$data = {
|
||||||
readonly viewer: {
|
readonly viewer: {
|
||||||
readonly memberships: {
|
readonly memberships: {
|
||||||
readonly edges: ReadonlyArray<{
|
readonly edges: ReadonlyArray<{
|
||||||
@@ -34,9 +34,9 @@ export type OrganizationsQuery$data = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type OrganizationsQuery = {
|
export type OrganizationsPageQuery = {
|
||||||
response: OrganizationsQuery$data;
|
response: OrganizationsPageQuery$data;
|
||||||
variables: OrganizationsQuery$variables;
|
variables: OrganizationsPageQuery$variables;
|
||||||
};
|
};
|
||||||
|
|
||||||
const node: ConcreteRequest = (function(){
|
const node: ConcreteRequest = (function(){
|
||||||
@@ -74,7 +74,7 @@ return {
|
|||||||
"argumentDefinitions": [],
|
"argumentDefinitions": [],
|
||||||
"kind": "Fragment",
|
"kind": "Fragment",
|
||||||
"metadata": null,
|
"metadata": null,
|
||||||
"name": "OrganizationsQuery",
|
"name": "OrganizationsPageQuery",
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
"kind": "RequiredField",
|
"kind": "RequiredField",
|
||||||
@@ -87,6 +87,8 @@ return {
|
|||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": (v0/*: any*/),
|
"args": (v0/*: any*/),
|
||||||
"concreteType": "MembershipConnection",
|
"concreteType": "MembershipConnection",
|
||||||
@@ -95,6 +97,8 @@ return {
|
|||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"concreteType": "MembershipEdge",
|
"concreteType": "MembershipEdge",
|
||||||
@@ -103,6 +107,8 @@ return {
|
|||||||
"plural": true,
|
"plural": true,
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"concreteType": "Membership",
|
"concreteType": "Membership",
|
||||||
@@ -117,6 +123,8 @@ return {
|
|||||||
"name": "MembershipCardFragment"
|
"name": "MembershipCardFragment"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"concreteType": "Organization",
|
"concreteType": "Organization",
|
||||||
@@ -127,17 +135,27 @@ return {
|
|||||||
(v2/*: any*/)
|
(v2/*: any*/)
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
|
},
|
||||||
|
"action": "THROW"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
|
},
|
||||||
|
"action": "THROW"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
|
},
|
||||||
|
"action": "THROW"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"storageKey": "memberships(first:1000,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
|
"storageKey": "memberships(first:1000,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
|
||||||
},
|
},
|
||||||
|
"action": "THROW"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": (v0/*: any*/),
|
"args": (v0/*: any*/),
|
||||||
"concreteType": "InvitationConnection",
|
"concreteType": "InvitationConnection",
|
||||||
@@ -146,6 +164,8 @@ return {
|
|||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"concreteType": "InvitationEdge",
|
"concreteType": "InvitationEdge",
|
||||||
@@ -154,6 +174,8 @@ return {
|
|||||||
"plural": true,
|
"plural": true,
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"concreteType": "Invitation",
|
"concreteType": "Invitation",
|
||||||
@@ -169,12 +191,18 @@ return {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
|
},
|
||||||
|
"action": "THROW"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
|
},
|
||||||
|
"action": "THROW"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"storageKey": "pendingInvitations(first:1000,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
|
"storageKey": "pendingInvitations(first:1000,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
|
||||||
|
},
|
||||||
|
"action": "THROW"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
@@ -189,7 +217,7 @@ return {
|
|||||||
"operation": {
|
"operation": {
|
||||||
"argumentDefinitions": [],
|
"argumentDefinitions": [],
|
||||||
"kind": "Operation",
|
"kind": "Operation",
|
||||||
"name": "OrganizationsQuery",
|
"name": "OrganizationsPageQuery",
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
@@ -229,7 +257,7 @@ return {
|
|||||||
"args": null,
|
"args": null,
|
||||||
"concreteType": "Session",
|
"concreteType": "Session",
|
||||||
"kind": "LinkedField",
|
"kind": "LinkedField",
|
||||||
"name": "activeSession",
|
"name": "lastSession",
|
||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
(v1/*: any*/),
|
(v1/*: any*/),
|
||||||
@@ -340,16 +368,16 @@ return {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "5dad37a0c652055f81de5bd2c09995c2",
|
"cacheID": "146deb87c5d32960212f323b4f5c792c",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"name": "OrganizationsQuery",
|
"name": "OrganizationsPageQuery",
|
||||||
"operationKind": "query",
|
"operationKind": "query",
|
||||||
"text": "query OrganizationsQuery {\n viewer {\n memberships(first: 1000, orderBy: {direction: DESC, field: CREATED_AT}) {\n edges {\n node {\n id\n ...MembershipCardFragment\n organization {\n name\n id\n }\n }\n }\n }\n pendingInvitations(first: 1000, orderBy: {direction: DESC, field: CREATED_AT}) {\n edges {\n node {\n id\n ...InvitationCardFragment\n }\n }\n }\n id\n }\n}\n\nfragment InvitationCardFragment on Invitation {\n id\n role\n createdAt\n organization {\n id\n name\n }\n}\n\nfragment MembershipCardFragment on Membership {\n activeSession {\n id\n expiresAt\n }\n organization {\n id\n name\n logoUrl\n }\n}\n"
|
"text": "query OrganizationsPageQuery {\n viewer {\n memberships(first: 1000, orderBy: {direction: DESC, field: CREATED_AT}) {\n edges {\n node {\n id\n ...MembershipCardFragment\n organization {\n name\n id\n }\n }\n }\n }\n pendingInvitations(first: 1000, orderBy: {direction: DESC, field: CREATED_AT}) {\n edges {\n node {\n id\n ...InvitationCardFragment\n }\n }\n }\n id\n }\n}\n\nfragment InvitationCardFragment on Invitation {\n id\n role\n createdAt\n organization {\n id\n name\n }\n}\n\nfragment MembershipCardFragment on Membership {\n lastSession {\n id\n expiresAt\n }\n organization {\n id\n name\n logoUrl\n }\n}\n"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "dc4b2e28600292c73ea31b5cb450dba7";
|
(node as any).hash = "7c03287e592920408306cac9bfa7fc81";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
/**
|
|
||||||
* @generated SignedSource<<b6a869ebbc0ded2da70f59b7856b906f>>
|
|
||||||
* @lightSyntaxTransform
|
|
||||||
* @nogrep
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
// @ts-nocheck
|
|
||||||
|
|
||||||
import { ReaderFragment } from 'relay-runtime';
|
|
||||||
import { FragmentRefs } from "relay-runtime";
|
|
||||||
export type sessionIsExpiredFragment$data = {
|
|
||||||
readonly expiresAt: any;
|
|
||||||
readonly " $fragmentType": "sessionIsExpiredFragment";
|
|
||||||
};
|
|
||||||
export type sessionIsExpiredFragment$key = {
|
|
||||||
readonly " $data"?: sessionIsExpiredFragment$data;
|
|
||||||
readonly " $fragmentSpreads": FragmentRefs<"sessionIsExpiredFragment">;
|
|
||||||
};
|
|
||||||
|
|
||||||
const node: ReaderFragment = {
|
|
||||||
"argumentDefinitions": [],
|
|
||||||
"kind": "Fragment",
|
|
||||||
"metadata": null,
|
|
||||||
"name": "sessionIsExpiredFragment",
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "expiresAt",
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"type": "Session",
|
|
||||||
"abstractKey": null
|
|
||||||
};
|
|
||||||
|
|
||||||
(node as any).hash = "ef7bc8bbfa6d2573b0df09b5b13b00b2";
|
|
||||||
|
|
||||||
export default node;
|
|
||||||
@@ -10,7 +10,7 @@ const fragment = graphql`
|
|||||||
id
|
id
|
||||||
role
|
role
|
||||||
createdAt
|
createdAt
|
||||||
organization {
|
organization @required(action: THROW) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ import { parseDate } from "@probo/helpers";
|
|||||||
|
|
||||||
const fragment = graphql`
|
const fragment = graphql`
|
||||||
fragment MembershipCardFragment on Membership {
|
fragment MembershipCardFragment on Membership {
|
||||||
activeSession {
|
lastSession {
|
||||||
id
|
id
|
||||||
expiresAt
|
expiresAt
|
||||||
}
|
}
|
||||||
organization {
|
organization @required(action: THROW) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
logoUrl
|
logoUrl
|
||||||
@@ -36,11 +36,13 @@ export function MembershipCard(props: MembershipCardProps) {
|
|||||||
const { fKey } = props;
|
const { fKey } = props;
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
const { activeSession, organization } =
|
const { lastSession, organization } = useFragment<MembershipCardFragment$key>(
|
||||||
useFragment<MembershipCardFragment$key>(fragment, fKey);
|
fragment,
|
||||||
const isAuthenticated = !!activeSession;
|
fKey,
|
||||||
|
);
|
||||||
|
const isAuthenticated = !!lastSession;
|
||||||
const isExpired =
|
const isExpired =
|
||||||
activeSession && parseDate(activeSession.expiresAt) >= new Date();
|
lastSession && parseDate(lastSession.expiresAt) >= new Date();
|
||||||
|
|
||||||
// Determine target URL and button text based on auth status
|
// Determine target URL and button text based on auth status
|
||||||
// const targetUrl = isAuthenticated
|
// const targetUrl = isAuthenticated
|
||||||
|
|||||||
@@ -1,126 +0,0 @@
|
|||||||
import { useTranslate } from "@probo/i18n";
|
|
||||||
import { Avatar, Badge, Button, Card, IconLock } from "@probo/ui";
|
|
||||||
import { Link } from "react-router";
|
|
||||||
import { graphql } from "relay-runtime";
|
|
||||||
import { useFragment } from "react-relay";
|
|
||||||
import type { OrganizationCardFragment$key } from "./__generated__/OrganizationCardFragment.graphql";
|
|
||||||
|
|
||||||
const fragment = graphql`
|
|
||||||
fragment OrganizationCardFragment on Organization {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
logoUrl
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface OrganizationCardProps {
|
|
||||||
fKey: OrganizationCardFragment$key;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function OrganizationCard(props: OrganizationCardProps) {
|
|
||||||
const { fKey } = props;
|
|
||||||
const { __ } = useTranslate();
|
|
||||||
|
|
||||||
const organization = useFragment<OrganizationCardFragment$key>(
|
|
||||||
fragment,
|
|
||||||
fKey,
|
|
||||||
);
|
|
||||||
// const isAuthenticated = organization.authStatus === "authenticated";
|
|
||||||
// const isExpired = organization.authStatus === "expired";
|
|
||||||
// const needsAuth = organization.authStatus === "unauthenticated";
|
|
||||||
|
|
||||||
// Determine target URL and button text based on auth status
|
|
||||||
// const targetUrl = isAuthenticated
|
|
||||||
// ? `/organizations/${organization.id}`
|
|
||||||
// : organization.loginUrl;
|
|
||||||
const targetUrl = `/organizations/${organization.id}`;
|
|
||||||
|
|
||||||
const getAuthBadge = () => {
|
|
||||||
// if (isAuthenticated) {
|
|
||||||
// return (
|
|
||||||
// <Badge variant="success" className="flex items-center gap-1">
|
|
||||||
// <IconCheckmark1 size={14} />
|
|
||||||
// {__("Authenticated")}
|
|
||||||
// </Badge>
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (isExpired) {
|
|
||||||
// return (
|
|
||||||
// <Badge variant="warning" className="flex items-center gap-1">
|
|
||||||
// <IconClock size={14} />
|
|
||||||
// {__("Session expired")}
|
|
||||||
// </Badge>
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (needsAuth) {
|
|
||||||
return (
|
|
||||||
<Badge variant="neutral" className="flex items-center gap-1">
|
|
||||||
<IconLock size={14} />
|
|
||||||
{__("Authentication required")}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
// }
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// const getButtonText = () => {
|
|
||||||
// if (isAuthenticated) return __("Select");
|
|
||||||
// if (organization.authenticationMethod === "saml")
|
|
||||||
// return __("Login with SAML");
|
|
||||||
// return __("Login");
|
|
||||||
// };
|
|
||||||
|
|
||||||
// Check if the URL is a backend SAML endpoint
|
|
||||||
const isSAMLUrl = targetUrl.includes("/connect/saml/");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card padded className="w-full">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
{isSAMLUrl ? (
|
|
||||||
<a
|
|
||||||
href={targetUrl}
|
|
||||||
className="flex items-center gap-4 hover:text-primary flex-1"
|
|
||||||
>
|
|
||||||
<Avatar
|
|
||||||
src={organization.logoUrl}
|
|
||||||
name={organization.name}
|
|
||||||
size="l"
|
|
||||||
/>
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<h2 className="font-semibold text-xl">{organization.name}</h2>
|
|
||||||
{getAuthBadge()}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
<Link
|
|
||||||
to={targetUrl}
|
|
||||||
className="flex items-center gap-4 hover:text-primary flex-1"
|
|
||||||
>
|
|
||||||
<Avatar
|
|
||||||
src={organization.logoUrl}
|
|
||||||
name={organization.name}
|
|
||||||
size="l"
|
|
||||||
/>
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<h2 className="font-semibold text-xl">{organization.name}</h2>
|
|
||||||
{getAuthBadge()}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Button asChild>
|
|
||||||
{/* {isSAMLUrl ? (
|
|
||||||
<a href={targetUrl}>{getButtonText()}</a>
|
|
||||||
) : (
|
|
||||||
<Link to={targetUrl}>{getButtonText()}</Link>
|
|
||||||
)} */}
|
|
||||||
<Link to={targetUrl}>LOGIN</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { useFragment, useQueryLoader } from "react-relay";
|
||||||
|
import { graphql } from "relay-runtime";
|
||||||
|
import type { OrganizationDropdownFragment$key } from "./__generated__/OrganizationDropdownFragment.graphql";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dropdown,
|
||||||
|
DropdownSeparator,
|
||||||
|
IconChevronGrabberVertical,
|
||||||
|
IconMagnifyingGlass,
|
||||||
|
Input,
|
||||||
|
} from "@probo/ui";
|
||||||
|
import { Suspense, useCallback, useState } from "react";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import {
|
||||||
|
OrganizationDropdownMenu,
|
||||||
|
organizationDropdownMenuQuery,
|
||||||
|
} from "./OrganizationDropdownMenu";
|
||||||
|
import type { OrganizationDropdownMenuQuery } from "./__generated__/OrganizationDropdownMenuQuery.graphql";
|
||||||
|
|
||||||
|
const fragment = graphql`
|
||||||
|
fragment OrganizationDropdownFragment on Organization {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function OrganizationDropdown(props: {
|
||||||
|
fKey: OrganizationDropdownFragment$key;
|
||||||
|
}) {
|
||||||
|
const { fKey } = props;
|
||||||
|
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
const currentOrganization = useFragment(fragment, fKey);
|
||||||
|
const [queryRef, loadQuery] = useQueryLoader<OrganizationDropdownMenuQuery>(
|
||||||
|
organizationDropdownMenuQuery,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOpenMenu = useCallback(
|
||||||
|
(open: boolean) => {
|
||||||
|
if (open) loadQuery({});
|
||||||
|
},
|
||||||
|
[loadQuery],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Dropdown
|
||||||
|
onOpenChange={handleOpenMenu}
|
||||||
|
toggle={
|
||||||
|
<Button
|
||||||
|
className="-ml-3"
|
||||||
|
variant="tertiary"
|
||||||
|
iconAfter={IconChevronGrabberVertical}
|
||||||
|
>
|
||||||
|
{currentOrganization.name}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="px-3 py-2">
|
||||||
|
<Input
|
||||||
|
icon={IconMagnifyingGlass}
|
||||||
|
placeholder={__("Search organizations...")}
|
||||||
|
value={search}
|
||||||
|
onValueChange={setSearch}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="max-h-150 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-transparent hover:scrollbar-thumb-gray-400">
|
||||||
|
{queryRef && (
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<div className="px-3 py-2 text-gray-500">
|
||||||
|
{__("Loading organizations...")}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<OrganizationDropdownMenu search={search} queryRef={queryRef} />
|
||||||
|
</Suspense>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<DropdownSeparator />
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { graphql, usePreloadedQuery, type PreloadedQuery } from "react-relay";
|
||||||
|
import type { OrganizationDropdownMenuQuery } from "./__generated__/OrganizationDropdownMenuQuery.graphql";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { OrganizationDropdownMenuItem } from "./OrganizationDropdownMenuItem";
|
||||||
|
|
||||||
|
export const organizationDropdownMenuQuery = graphql`
|
||||||
|
query OrganizationDropdownMenuQuery {
|
||||||
|
viewer @required(action: THROW) {
|
||||||
|
memberships(first: 1000, orderBy: { direction: DESC, field: CREATED_AT })
|
||||||
|
@required(action: THROW) {
|
||||||
|
edges @required(action: THROW) {
|
||||||
|
node @required(action: THROW) {
|
||||||
|
id
|
||||||
|
organization @required(action: THROW) {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
...OrganizationDropdownMenuItemFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface OrganizationDropdownMenuProps {
|
||||||
|
queryRef: PreloadedQuery<OrganizationDropdownMenuQuery>;
|
||||||
|
search: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OrganizationDropdownMenu(props: OrganizationDropdownMenuProps) {
|
||||||
|
const { queryRef, search } = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
viewer: {
|
||||||
|
memberships: { edges: initialMemberships },
|
||||||
|
},
|
||||||
|
} = usePreloadedQuery<OrganizationDropdownMenuQuery>(
|
||||||
|
organizationDropdownMenuQuery,
|
||||||
|
queryRef,
|
||||||
|
);
|
||||||
|
|
||||||
|
const memberships = useMemo(() => {
|
||||||
|
if (!search) {
|
||||||
|
return initialMemberships;
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialMemberships.filter(({ node: { organization } }) =>
|
||||||
|
organization.name.toLowerCase().includes(search.toLowerCase()),
|
||||||
|
);
|
||||||
|
}, [initialMemberships, search]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{memberships.map(({ node }) => (
|
||||||
|
<OrganizationDropdownMenuItem fKey={node} key={node.id} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
DropdownItem,
|
||||||
|
IconCheckmark1,
|
||||||
|
IconClock,
|
||||||
|
IconLock,
|
||||||
|
} from "@probo/ui";
|
||||||
|
import { graphql } from "relay-runtime";
|
||||||
|
import type { OrganizationDropdownMenuItemFragment$key } from "./__generated__/OrganizationDropdownMenuItemFragment.graphql";
|
||||||
|
import { useFragment } from "react-relay";
|
||||||
|
import { parseDate } from "@probo/helpers";
|
||||||
|
|
||||||
|
const fragment = graphql`
|
||||||
|
fragment OrganizationDropdownMenuItemFragment on Membership {
|
||||||
|
id
|
||||||
|
lastSession {
|
||||||
|
id
|
||||||
|
expiresAt
|
||||||
|
}
|
||||||
|
organization @required(action: THROW) {
|
||||||
|
logoUrl
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function OrganizationDropdownMenuItem(props: {
|
||||||
|
fKey: OrganizationDropdownMenuItemFragment$key;
|
||||||
|
}) {
|
||||||
|
const { fKey } = props;
|
||||||
|
const { id, lastSession, organization } =
|
||||||
|
useFragment<OrganizationDropdownMenuItemFragment$key>(fragment, fKey);
|
||||||
|
|
||||||
|
const isAuthenticated = !!lastSession;
|
||||||
|
const isExpired =
|
||||||
|
lastSession && parseDate(lastSession.expiresAt) >= new Date();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownItem key={id}>
|
||||||
|
{/* TODO add link or anchor */}
|
||||||
|
<Avatar name={organization.name} src={organization.logoUrl} />
|
||||||
|
<span className="flex-1">{organization.name}</span>
|
||||||
|
{isAuthenticated && (
|
||||||
|
<IconCheckmark1 size={16} className="text-green-600" />
|
||||||
|
)}
|
||||||
|
{isExpired && <IconClock size={16} className="text-orange-600" />}
|
||||||
|
{!lastSession && <IconLock size={16} className="text-gray-400" />}
|
||||||
|
</DropdownItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
166
apps/console/src/pages/iam/organizations/_components/Sidebar.tsx
Normal file
166
apps/console/src/pages/iam/organizations/_components/Sidebar.tsx
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
import { use } from "react";
|
||||||
|
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||||
|
import {
|
||||||
|
IconBank,
|
||||||
|
IconBook,
|
||||||
|
IconBox,
|
||||||
|
IconCalendar1,
|
||||||
|
IconCircleProgress,
|
||||||
|
IconClock,
|
||||||
|
IconCrossLargeX,
|
||||||
|
IconFire3,
|
||||||
|
IconGroup1,
|
||||||
|
IconInboxEmpty,
|
||||||
|
IconListStack,
|
||||||
|
IconMedal,
|
||||||
|
IconPageTextLine,
|
||||||
|
IconRotateCw,
|
||||||
|
IconSettingsGear2,
|
||||||
|
IconShield,
|
||||||
|
IconStore,
|
||||||
|
IconTodo,
|
||||||
|
SidebarItem,
|
||||||
|
} from "@probo/ui";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
|
|
||||||
|
export function Sidebar() {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
|
|
||||||
|
const { isAuthorized } = use(PermissionsContext);
|
||||||
|
|
||||||
|
const prefix = `/organizations/${organizationId}`;
|
||||||
|
|
||||||
|
console.log(isAuthorized("Organization", "listMeetings"));
|
||||||
|
return (
|
||||||
|
<ul className="space-y-[2px]">
|
||||||
|
{isAuthorized("Organization", "listMeetings") && (
|
||||||
|
<SidebarItem
|
||||||
|
label={__("Meetings")}
|
||||||
|
icon={IconCalendar1}
|
||||||
|
to={`${prefix}/meetings`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isAuthorized("Organization", "listTasks") && (
|
||||||
|
<SidebarItem
|
||||||
|
label={__("Tasks")}
|
||||||
|
icon={IconInboxEmpty}
|
||||||
|
to={`${prefix}/tasks`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isAuthorized("Organization", "listMeasures") && (
|
||||||
|
<SidebarItem
|
||||||
|
label={__("Measures")}
|
||||||
|
icon={IconTodo}
|
||||||
|
to={`${prefix}/measures`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isAuthorized("Organization", "listRisks") && (
|
||||||
|
<SidebarItem
|
||||||
|
label={__("Risks")}
|
||||||
|
icon={IconFire3}
|
||||||
|
to={`${prefix}/risks`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isAuthorized("Organization", "listFrameworks") && (
|
||||||
|
<SidebarItem
|
||||||
|
label={__("Frameworks")}
|
||||||
|
icon={IconBank}
|
||||||
|
to={`${prefix}/frameworks`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isAuthorized("Organization", "listPeople") && (
|
||||||
|
<SidebarItem
|
||||||
|
label={__("People")}
|
||||||
|
icon={IconGroup1}
|
||||||
|
to={`${prefix}/people`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isAuthorized("Organization", "listVendors") && (
|
||||||
|
<SidebarItem
|
||||||
|
label={__("Vendors")}
|
||||||
|
icon={IconStore}
|
||||||
|
to={`${prefix}/vendors`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isAuthorized("Organization", "listDocuments") && (
|
||||||
|
<SidebarItem
|
||||||
|
label={__("Documents")}
|
||||||
|
icon={IconPageTextLine}
|
||||||
|
to={`${prefix}/documents`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isAuthorized("Organization", "listAssets") && (
|
||||||
|
<SidebarItem
|
||||||
|
label={__("Assets")}
|
||||||
|
icon={IconBox}
|
||||||
|
to={`${prefix}/assets`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isAuthorized("Organization", "listData") && (
|
||||||
|
<SidebarItem
|
||||||
|
label={__("Data")}
|
||||||
|
icon={IconListStack}
|
||||||
|
to={`${prefix}/data`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isAuthorized("Organization", "listAudits") && (
|
||||||
|
<SidebarItem
|
||||||
|
label={__("Audits")}
|
||||||
|
icon={IconMedal}
|
||||||
|
to={`${prefix}/audits`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isAuthorized("Organization", "listNonconformities") && (
|
||||||
|
<SidebarItem
|
||||||
|
label={__("Nonconformities")}
|
||||||
|
icon={IconCrossLargeX}
|
||||||
|
to={`${prefix}/nonconformities`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isAuthorized("Organization", "listObligations") && (
|
||||||
|
<SidebarItem
|
||||||
|
label={__("Obligations")}
|
||||||
|
icon={IconBook}
|
||||||
|
to={`${prefix}/obligations`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isAuthorized("Organization", "listContinualImprovements") && (
|
||||||
|
<SidebarItem
|
||||||
|
label={__("Continual Improvements")}
|
||||||
|
icon={IconRotateCw}
|
||||||
|
to={`${prefix}/continual-improvements`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isAuthorized("Organization", "listProcessingActivities") && (
|
||||||
|
<SidebarItem
|
||||||
|
label={__("Processing Activities")}
|
||||||
|
icon={IconCircleProgress}
|
||||||
|
to={`${prefix}/processing-activities`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isAuthorized("Organization", "listSnapshots") && (
|
||||||
|
<SidebarItem
|
||||||
|
label={__("Snapshots")}
|
||||||
|
icon={IconClock}
|
||||||
|
to={`${prefix}/snapshots`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isAuthorized("Organization", "getTrustCenter") && (
|
||||||
|
<SidebarItem
|
||||||
|
label={__("Trust Center")}
|
||||||
|
icon={IconShield}
|
||||||
|
to={`${prefix}/trust-center`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isAuthorized("Organization", "listMembers") && (
|
||||||
|
<SidebarItem
|
||||||
|
label={__("Settings")}
|
||||||
|
icon={IconSettingsGear2}
|
||||||
|
to={`${prefix}/settings`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<5193da6ee4b5da0bed510c255c3407b1>>
|
* @generated SignedSource<<acb8bfd444063a884a8a3a35eed8971f>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -56,6 +56,8 @@ return {
|
|||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"concreteType": "Organization",
|
"concreteType": "Organization",
|
||||||
@@ -73,6 +75,8 @@ return {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
|
},
|
||||||
|
"action": "THROW"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": "Invitation",
|
"type": "Invitation",
|
||||||
@@ -80,6 +84,6 @@ return {
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "1f61b5f07abc69ad33c880bc39cd0e96";
|
(node as any).hash = "57aedeabc62e474c6e5b995af1d31888";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<30ba0cd2d4738694d58bf346410d4d68>>
|
* @generated SignedSource<<4660f07fbf06d0125ce01cd54019f389>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
import { ReaderFragment } from 'relay-runtime';
|
import { ReaderFragment } from 'relay-runtime';
|
||||||
import { FragmentRefs } from "relay-runtime";
|
import { FragmentRefs } from "relay-runtime";
|
||||||
export type MembershipCardFragment$data = {
|
export type MembershipCardFragment$data = {
|
||||||
readonly activeSession: {
|
readonly lastSession: {
|
||||||
readonly expiresAt: any;
|
readonly expiresAt: any;
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
} | null | undefined;
|
} | null | undefined;
|
||||||
@@ -46,7 +46,7 @@ return {
|
|||||||
"args": null,
|
"args": null,
|
||||||
"concreteType": "Session",
|
"concreteType": "Session",
|
||||||
"kind": "LinkedField",
|
"kind": "LinkedField",
|
||||||
"name": "activeSession",
|
"name": "lastSession",
|
||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
(v0/*: any*/),
|
(v0/*: any*/),
|
||||||
@@ -61,6 +61,8 @@ return {
|
|||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"concreteType": "Organization",
|
"concreteType": "Organization",
|
||||||
@@ -85,6 +87,8 @@ return {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
|
},
|
||||||
|
"action": "THROW"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": "Membership",
|
"type": "Membership",
|
||||||
@@ -92,6 +96,6 @@ return {
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "2145f6030b486447df7b7d9ff798ec06";
|
(node as any).hash = "5a2532aed755a94df49c2f9ce0929a1f";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
/**
|
|
||||||
* @generated SignedSource<<06aa55abfa21c09c85bb308f813565d8>>
|
|
||||||
* @lightSyntaxTransform
|
|
||||||
* @nogrep
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
// @ts-nocheck
|
|
||||||
|
|
||||||
import { ReaderFragment } from 'relay-runtime';
|
|
||||||
import { FragmentRefs } from "relay-runtime";
|
|
||||||
export type OrganizationCardFragment$data = {
|
|
||||||
readonly id: string;
|
|
||||||
readonly logoUrl: string | null | undefined;
|
|
||||||
readonly name: string;
|
|
||||||
readonly " $fragmentType": "OrganizationCardFragment";
|
|
||||||
};
|
|
||||||
export type OrganizationCardFragment$key = {
|
|
||||||
readonly " $data"?: OrganizationCardFragment$data;
|
|
||||||
readonly " $fragmentSpreads": FragmentRefs<"OrganizationCardFragment">;
|
|
||||||
};
|
|
||||||
|
|
||||||
const node: ReaderFragment = {
|
|
||||||
"argumentDefinitions": [],
|
|
||||||
"kind": "Fragment",
|
|
||||||
"metadata": null,
|
|
||||||
"name": "OrganizationCardFragment",
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"type": "Organization",
|
|
||||||
"abstractKey": null
|
|
||||||
};
|
|
||||||
|
|
||||||
(node as any).hash = "a8b9f4a515650db79f41507fb52f7b96";
|
|
||||||
|
|
||||||
export default node;
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<06f02e43b64c316083067f5b3978a08a>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ReaderFragment } from 'relay-runtime';
|
||||||
|
import { FragmentRefs } from "relay-runtime";
|
||||||
|
export type OrganizationDropdownFragment$data = {
|
||||||
|
readonly name: string;
|
||||||
|
readonly " $fragmentType": "OrganizationDropdownFragment";
|
||||||
|
};
|
||||||
|
export type OrganizationDropdownFragment$key = {
|
||||||
|
readonly " $data"?: OrganizationDropdownFragment$data;
|
||||||
|
readonly " $fragmentSpreads": FragmentRefs<"OrganizationDropdownFragment">;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ReaderFragment = {
|
||||||
|
"argumentDefinitions": [],
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "OrganizationDropdownFragment",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Organization",
|
||||||
|
"abstractKey": null
|
||||||
|
};
|
||||||
|
|
||||||
|
(node as any).hash = "9f19581f86736e6345912284668a8f25";
|
||||||
|
|
||||||
|
export default node;
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<ef66c9eab961d5c3c28669629b82cfd6>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ReaderFragment } from 'relay-runtime';
|
||||||
|
import { FragmentRefs } from "relay-runtime";
|
||||||
|
export type OrganizationDropdownMenuItemFragment$data = {
|
||||||
|
readonly id: string;
|
||||||
|
readonly lastSession: {
|
||||||
|
readonly expiresAt: any;
|
||||||
|
readonly id: string;
|
||||||
|
} | null | undefined;
|
||||||
|
readonly organization: {
|
||||||
|
readonly logoUrl: string | null | undefined;
|
||||||
|
readonly name: string;
|
||||||
|
};
|
||||||
|
readonly " $fragmentType": "OrganizationDropdownMenuItemFragment";
|
||||||
|
};
|
||||||
|
export type OrganizationDropdownMenuItemFragment$key = {
|
||||||
|
readonly " $data"?: OrganizationDropdownMenuItemFragment$data;
|
||||||
|
readonly " $fragmentSpreads": FragmentRefs<"OrganizationDropdownMenuItemFragment">;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ReaderFragment = (function(){
|
||||||
|
var v0 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
"argumentDefinitions": [],
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "OrganizationDropdownMenuItemFragment",
|
||||||
|
"selections": [
|
||||||
|
(v0/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Session",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "lastSession",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v0/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "expiresAt",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Organization",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "organization",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "logoUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
"action": "THROW"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Membership",
|
||||||
|
"abstractKey": null
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "6be7f721fd63d6d758b479e58c68b754";
|
||||||
|
|
||||||
|
export default node;
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<2bde2fba4e4897436af08d9dd700b265>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
import { FragmentRefs } from "relay-runtime";
|
||||||
|
export type OrganizationDropdownMenuQuery$variables = Record<PropertyKey, never>;
|
||||||
|
export type OrganizationDropdownMenuQuery$data = {
|
||||||
|
readonly viewer: {
|
||||||
|
readonly memberships: {
|
||||||
|
readonly edges: ReadonlyArray<{
|
||||||
|
readonly node: {
|
||||||
|
readonly id: string;
|
||||||
|
readonly organization: {
|
||||||
|
readonly name: string;
|
||||||
|
};
|
||||||
|
readonly " $fragmentSpreads": FragmentRefs<"OrganizationDropdownMenuItemFragment">;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type OrganizationDropdownMenuQuery = {
|
||||||
|
response: OrganizationDropdownMenuQuery$data;
|
||||||
|
variables: OrganizationDropdownMenuQuery$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"kind": "Literal",
|
||||||
|
"name": "first",
|
||||||
|
"value": 1000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "Literal",
|
||||||
|
"name": "orderBy",
|
||||||
|
"value": {
|
||||||
|
"direction": "DESC",
|
||||||
|
"field": "CREATED_AT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v2 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": [],
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "OrganizationDropdownMenuQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Identity",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "viewer",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
|
"alias": null,
|
||||||
|
"args": (v0/*: any*/),
|
||||||
|
"concreteType": "MembershipConnection",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "memberships",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "MembershipEdge",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "edges",
|
||||||
|
"plural": true,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Membership",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v1/*: any*/),
|
||||||
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Organization",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "organization",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v2/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
"action": "THROW"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"args": null,
|
||||||
|
"kind": "FragmentSpread",
|
||||||
|
"name": "OrganizationDropdownMenuItemFragment"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
"action": "THROW"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
"action": "THROW"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": "memberships(first:1000,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
|
||||||
|
},
|
||||||
|
"action": "THROW"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
"action": "THROW"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Query",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": [],
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "OrganizationDropdownMenuQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Identity",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "viewer",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v0/*: any*/),
|
||||||
|
"concreteType": "MembershipConnection",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "memberships",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "MembershipEdge",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "edges",
|
||||||
|
"plural": true,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Membership",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v1/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Organization",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "organization",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v2/*: any*/),
|
||||||
|
(v1/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "logoUrl",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Session",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "lastSession",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v1/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "expiresAt",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": "memberships(first:1000,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
|
||||||
|
},
|
||||||
|
(v1/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "8338998501d59b0036d1eaf98dff1ffb",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "OrganizationDropdownMenuQuery",
|
||||||
|
"operationKind": "query",
|
||||||
|
"text": "query OrganizationDropdownMenuQuery {\n viewer {\n memberships(first: 1000, orderBy: {direction: DESC, field: CREATED_AT}) {\n edges {\n node {\n id\n organization {\n name\n id\n }\n ...OrganizationDropdownMenuItemFragment\n }\n }\n }\n id\n }\n}\n\nfragment OrganizationDropdownMenuItemFragment on Membership {\n id\n lastSession {\n id\n expiresAt\n }\n organization {\n logoUrl\n name\n id\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "27c82735b5dcef01422d87312c127519";
|
||||||
|
|
||||||
|
export default node;
|
||||||
@@ -1,31 +1,41 @@
|
|||||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
|
||||||
import { type PropsWithChildren } from "react";
|
import { type PropsWithChildren } from "react";
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { PermissionsContext } from "./PermissionsContext";
|
||||||
import { PermissionsContext, type PermissionsResponse } from "./PermissionsContext";
|
import { Role } from "@probo/helpers";
|
||||||
|
|
||||||
export function PermissionsProvider(props: PropsWithChildren) {
|
export function PermissionsProvider(props: PropsWithChildren) {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
|
|
||||||
const organizationId = useOrganizationId();
|
// const organizationId = useOrganizationId();
|
||||||
|
|
||||||
const { data } = useSuspenseQuery<PermissionsResponse>({
|
// const { data } = useSuspenseQuery<PermissionsResponse>({
|
||||||
queryKey: ["permissions", organizationId],
|
// queryKey: ["permissions", organizationId],
|
||||||
queryFn: async () => {
|
// queryFn: async () => {
|
||||||
const response = await fetch(`/authz/${organizationId}/permissions`, { credentials: "include" });
|
// const response = await fetch(`/authz/${organizationId}/permissions`, { credentials: "include" });
|
||||||
if (!response.ok) {
|
// if (!response.ok) {
|
||||||
throw new Error("Failed to fetch permissions");
|
// throw new Error("Failed to fetch permissions");
|
||||||
}
|
// }
|
||||||
return response.json() as Promise<PermissionsResponse>;
|
// return response.json() as Promise<PermissionsResponse>;
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
|
|
||||||
|
// @ts-expect-error wip refactor
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const isAuthorized = (entity: string, action: string) => {
|
const isAuthorized = (entity: string, action: string) => {
|
||||||
return data.permissions[entity]?.[action] ?? false;
|
// return data.permissions[entity]?.[action] ?? false;
|
||||||
}
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PermissionsContext value={{ ...data, isAuthorized }}>
|
<PermissionsContext
|
||||||
|
value={{ permissions: {}, role: Role.OWNER, isAuthorized }}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</PermissionsContext>
|
</PermissionsContext>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <PermissionsContext value={{ ...data, isAuthorized }}>
|
||||||
|
// {children}
|
||||||
|
// </PermissionsContext>
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
redirect,
|
redirect,
|
||||||
useRouteError,
|
useRouteError,
|
||||||
} from "react-router";
|
} from "react-router";
|
||||||
import { MainLayout } from "./layouts/MainLayout";
|
|
||||||
import { EmployeeLayout } from "./layouts/EmployeeLayout";
|
import { EmployeeLayout } from "./layouts/EmployeeLayout";
|
||||||
import { AuthLayout, CenteredLayout, CenteredLayoutSkeleton } from "@probo/ui";
|
import { AuthLayout, CenteredLayout, CenteredLayoutSkeleton } from "@probo/ui";
|
||||||
import { PageSkeleton } from "./components/skeletons/PageSkeleton.tsx";
|
import { PageSkeleton } from "./components/skeletons/PageSkeleton.tsx";
|
||||||
@@ -49,7 +48,8 @@ import {
|
|||||||
UnAuthenticatedError,
|
UnAuthenticatedError,
|
||||||
UnauthorizedError,
|
UnauthorizedError,
|
||||||
} from "@probo/relay";
|
} from "@probo/relay";
|
||||||
import { OrganizationsQuery } from "./pages/iam/organizations/OrganizationsQuery.tsx";
|
import { OrganizationsPageQuery } from "./pages/iam/organizations/OrganizationsPageQuery.tsx";
|
||||||
|
import { OrganizationLayoutQuery } from "./pages/iam/organizations/OrganizationLayoutQuery.tsx";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Top level error boundary
|
* Top level error boundary
|
||||||
@@ -121,7 +121,7 @@ const routes = [
|
|||||||
index: true,
|
index: true,
|
||||||
Component: () => (
|
Component: () => (
|
||||||
<RelayEnvironmentProvider environment={connectEnvironment}>
|
<RelayEnvironmentProvider environment={connectEnvironment}>
|
||||||
<OrganizationsQuery />
|
<OrganizationsPageQuery />
|
||||||
</RelayEnvironmentProvider>
|
</RelayEnvironmentProvider>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -184,8 +184,8 @@ const routes = [
|
|||||||
{
|
{
|
||||||
path: "/organizations/:organizationId",
|
path: "/organizations/:organizationId",
|
||||||
Component: () => (
|
Component: () => (
|
||||||
<RelayEnvironmentProvider environment={consoleEnvironment}>
|
<RelayEnvironmentProvider environment={connectEnvironment}>
|
||||||
<MainLayout />
|
<OrganizationLayoutQuery />
|
||||||
</RelayEnvironmentProvider>
|
</RelayEnvironmentProvider>
|
||||||
),
|
),
|
||||||
ErrorBoundary: ErrorBoundary,
|
ErrorBoundary: ErrorBoundary,
|
||||||
|
|||||||
@@ -10,15 +10,16 @@ type Props = PropsWithChildren<{
|
|||||||
toggle?: ReactNode;
|
toggle?: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
|
onOpenChange?: (open: boolean) => void;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export const dropdown = tv({
|
export const dropdown = tv({
|
||||||
base: "z-50 p-2 shadow-mid min-w-[8rem] bg-level-1 overflow-y-auto overflow-x-hidden rounded-2xl border-border-low",
|
base: "z-50 p-2 shadow-mid min-w-[8rem] bg-level-1 overflow-y-auto overflow-x-hidden rounded-2xl border-border-low",
|
||||||
});
|
});
|
||||||
|
|
||||||
export function Dropdown({ children, toggle, className, open }: Props) {
|
export function Dropdown({ children, toggle, className, open, onOpenChange }: Props) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.Root open={open}>
|
<DropdownMenu.Root open={open} onOpenChange={onOpenChange}>
|
||||||
{toggle && (
|
{toggle && (
|
||||||
<DropdownMenu.Trigger asChild>{toggle}</DropdownMenu.Trigger>
|
<DropdownMenu.Trigger asChild>{toggle}</DropdownMenu.Trigger>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user