@@ -21,55 +21,16 @@ import {
|
|||||||
TabLink,
|
TabLink,
|
||||||
Tabs,
|
Tabs,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
|
|
||||||
import { Outlet } from "react-router";
|
import { Outlet } from "react-router";
|
||||||
import { graphql } from "relay-runtime";
|
|
||||||
|
|
||||||
import type { AccessReviewLayoutQuery } from "#/__generated__/core/AccessReviewLayoutQuery.graphql";
|
|
||||||
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||||
|
|
||||||
export const accessReviewLayoutQuery = graphql`
|
export default function AccessReviewLayout() {
|
||||||
query AccessReviewLayoutQuery($organizationId: ID!) {
|
|
||||||
organization: node(id: $organizationId) {
|
|
||||||
__typename
|
|
||||||
... on Organization {
|
|
||||||
id
|
|
||||||
canCreateSource: permission(action: "core:access-source:create")
|
|
||||||
canCreateCampaign: permission(action: "core:access-review-campaign:create")
|
|
||||||
connectorProviderInfos {
|
|
||||||
provider
|
|
||||||
displayName
|
|
||||||
oauthConfigured
|
|
||||||
apiKeySupported
|
|
||||||
clientCredentialsSupported
|
|
||||||
extraSettings {
|
|
||||||
key
|
|
||||||
label
|
|
||||||
required
|
|
||||||
}
|
|
||||||
}
|
|
||||||
...AccessReviewCampaignsTabFragment
|
|
||||||
...AccessReviewSourcesTabFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default function AccessReviewLayout({
|
|
||||||
queryRef,
|
|
||||||
}: {
|
|
||||||
queryRef: PreloadedQuery<AccessReviewLayoutQuery>;
|
|
||||||
}) {
|
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
|
|
||||||
usePageTitle(__("Access Reviews"));
|
usePageTitle(__("Access Reviews"));
|
||||||
|
|
||||||
const { organization } = usePreloadedQuery(accessReviewLayoutQuery, queryRef);
|
|
||||||
if (organization.__typename !== "Organization") {
|
|
||||||
throw new Error("Organization not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
@@ -90,13 +51,7 @@ export default function AccessReviewLayout({
|
|||||||
</TabLink>
|
</TabLink>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<Outlet context={{
|
<Outlet />
|
||||||
organizationRef: organization,
|
|
||||||
canCreateSource: organization.canCreateSource,
|
|
||||||
canCreateCampaign: organization.canCreateCampaign,
|
|
||||||
connectorProviderInfos: organization.connectorProviderInfos,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,30 +12,8 @@
|
|||||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
import { Suspense, useEffect } from "react";
|
import AccessReviewLayout from "./AccessReviewLayout";
|
||||||
import { useQueryLoader } from "react-relay";
|
|
||||||
|
|
||||||
import type { AccessReviewLayoutQuery } from "#/__generated__/core/AccessReviewLayoutQuery.graphql";
|
|
||||||
import { PageSkeleton } from "#/components/skeletons/PageSkeleton";
|
|
||||||
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
|
||||||
|
|
||||||
import AccessReviewLayout, { accessReviewLayoutQuery } from "./AccessReviewLayout";
|
|
||||||
|
|
||||||
export default function AccessReviewLayoutLoader() {
|
export default function AccessReviewLayoutLoader() {
|
||||||
const organizationId = useOrganizationId();
|
return <AccessReviewLayout />;
|
||||||
const [queryRef, loadQuery] = useQueryLoader<AccessReviewLayoutQuery>(accessReviewLayoutQuery);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!queryRef) {
|
|
||||||
loadQuery({ organizationId });
|
|
||||||
}
|
|
||||||
}, [loadQuery, organizationId]);
|
|
||||||
|
|
||||||
if (!queryRef) return <PageSkeleton />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Suspense fallback={<PageSkeleton />}>
|
|
||||||
<AccessReviewLayout queryRef={queryRef} />
|
|
||||||
</Suspense>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,16 +25,29 @@ import {
|
|||||||
Thead,
|
Thead,
|
||||||
Tr,
|
Tr,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { graphql, usePaginationFragment } from "react-relay";
|
import type { PreloadedQuery } from "react-relay";
|
||||||
import { useOutletContext } from "react-router";
|
import { graphql, usePaginationFragment, usePreloadedQuery } from "react-relay";
|
||||||
|
|
||||||
import type { AccessReviewCampaignsTabFragment$key } from "#/__generated__/core/AccessReviewCampaignsTabFragment.graphql";
|
import type { AccessReviewCampaignsTabFragment$key } from "#/__generated__/core/AccessReviewCampaignsTabFragment.graphql";
|
||||||
import type { AccessReviewCampaignsTabPaginationQuery } from "#/__generated__/core/AccessReviewCampaignsTabPaginationQuery.graphql";
|
import type { AccessReviewCampaignsTabPaginationQuery } from "#/__generated__/core/AccessReviewCampaignsTabPaginationQuery.graphql";
|
||||||
|
import type { AccessReviewCampaignsTabQuery } from "#/__generated__/core/AccessReviewCampaignsTabQuery.graphql";
|
||||||
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||||
|
|
||||||
import { statusBadgeVariant, statusLabel } from "../_components/accessReviewHelpers";
|
import { statusBadgeVariant, statusLabel } from "../_components/accessReviewHelpers";
|
||||||
import { CreateAccessReviewCampaignDialog } from "../dialogs/CreateAccessReviewCampaignDialog";
|
import { CreateAccessReviewCampaignDialog } from "../dialogs/CreateAccessReviewCampaignDialog";
|
||||||
|
|
||||||
|
export const accessReviewCampaignsTabQuery = graphql`
|
||||||
|
query AccessReviewCampaignsTabQuery($organizationId: ID!) {
|
||||||
|
organization: node(id: $organizationId) {
|
||||||
|
__typename
|
||||||
|
... on Organization {
|
||||||
|
canCreateCampaign: permission(action: "core:access-review-campaign:create")
|
||||||
|
...AccessReviewCampaignsTabFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const campaignsFragment = graphql`
|
const campaignsFragment = graphql`
|
||||||
fragment AccessReviewCampaignsTabFragment on Organization
|
fragment AccessReviewCampaignsTabFragment on Organization
|
||||||
@refetchable(queryName: "AccessReviewCampaignsTabPaginationQuery")
|
@refetchable(queryName: "AccessReviewCampaignsTabPaginationQuery")
|
||||||
@@ -58,21 +71,24 @@ const campaignsFragment = graphql`
|
|||||||
name
|
name
|
||||||
status
|
status
|
||||||
createdAt
|
createdAt
|
||||||
startedAt
|
|
||||||
completedAt
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default function AccessReviewCampaignsTab() {
|
type Props = {
|
||||||
|
queryRef: PreloadedQuery<AccessReviewCampaignsTabQuery>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AccessReviewCampaignsTab({ queryRef }: Props) {
|
||||||
const { __, dateFormat } = useTranslate();
|
const { __, dateFormat } = useTranslate();
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { organizationRef, canCreateCampaign } = useOutletContext<{
|
|
||||||
organizationRef: AccessReviewCampaignsTabFragment$key;
|
const { organization } = usePreloadedQuery(accessReviewCampaignsTabQuery, queryRef);
|
||||||
canCreateCampaign: boolean;
|
if (organization.__typename !== "Organization") {
|
||||||
}>();
|
throw new Error("Organization not found");
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { accessReviewCampaigns },
|
data: { accessReviewCampaigns },
|
||||||
@@ -82,12 +98,12 @@ export default function AccessReviewCampaignsTab() {
|
|||||||
} = usePaginationFragment<
|
} = usePaginationFragment<
|
||||||
AccessReviewCampaignsTabPaginationQuery,
|
AccessReviewCampaignsTabPaginationQuery,
|
||||||
AccessReviewCampaignsTabFragment$key
|
AccessReviewCampaignsTabFragment$key
|
||||||
>(campaignsFragment, organizationRef);
|
>(campaignsFragment, organization);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-end">
|
<div className="flex items-center justify-end">
|
||||||
{canCreateCampaign && (
|
{organization.canCreateCampaign && (
|
||||||
<CreateAccessReviewCampaignDialog
|
<CreateAccessReviewCampaignDialog
|
||||||
organizationId={organizationId}
|
organizationId={organizationId}
|
||||||
connectionId={accessReviewCampaigns.__id}
|
connectionId={accessReviewCampaigns.__id}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
import { Suspense, useEffect } from "react";
|
||||||
|
import { useQueryLoader } from "react-relay";
|
||||||
|
|
||||||
|
import type { AccessReviewCampaignsTabQuery } from "#/__generated__/core/AccessReviewCampaignsTabQuery.graphql";
|
||||||
|
import { PageSkeleton } from "#/components/skeletons/PageSkeleton";
|
||||||
|
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||||
|
|
||||||
|
import AccessReviewCampaignsTab, { accessReviewCampaignsTabQuery } from "./AccessReviewCampaignsTab";
|
||||||
|
|
||||||
|
export default function AccessReviewCampaignsTabLoader() {
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
|
const [queryRef, loadQuery] = useQueryLoader<AccessReviewCampaignsTabQuery>(accessReviewCampaignsTabQuery);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadQuery({ organizationId });
|
||||||
|
}, [loadQuery, organizationId]);
|
||||||
|
|
||||||
|
if (!queryRef) return <PageSkeleton />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<PageSkeleton />}>
|
||||||
|
<AccessReviewCampaignsTab queryRef={queryRef} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -131,10 +131,6 @@ export const campaignDetailPageQuery = graphql`
|
|||||||
id
|
id
|
||||||
name
|
name
|
||||||
status
|
status
|
||||||
createdAt
|
|
||||||
startedAt
|
|
||||||
completedAt
|
|
||||||
pendingEntryCount
|
|
||||||
scopeSources {
|
scopeSources {
|
||||||
id
|
id
|
||||||
source {
|
source {
|
||||||
|
|||||||
@@ -37,19 +37,29 @@ import { useMutation } from "react-relay";
|
|||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
|
|
||||||
import type { AccessReviewLayoutQuery$data } from "#/__generated__/core/AccessReviewLayoutQuery.graphql";
|
import type { AddAccessSourceDialogConnectorProviderInfoFragment$data } from "#/__generated__/core/AddAccessSourceDialogConnectorProviderInfoFragment.graphql";
|
||||||
import type { AddAccessSourceDialogCreateAPIKeyConnectorMutation } from "#/__generated__/core/AddAccessSourceDialogCreateAPIKeyConnectorMutation.graphql";
|
import type { AddAccessSourceDialogCreateAPIKeyConnectorMutation } from "#/__generated__/core/AddAccessSourceDialogCreateAPIKeyConnectorMutation.graphql";
|
||||||
import type { AddAccessSourceDialogCreateClientCredentialsConnectorMutation } from "#/__generated__/core/AddAccessSourceDialogCreateClientCredentialsConnectorMutation.graphql";
|
import type { AddAccessSourceDialogCreateClientCredentialsConnectorMutation } from "#/__generated__/core/AddAccessSourceDialogCreateClientCredentialsConnectorMutation.graphql";
|
||||||
import type { CreateAccessSourceDialogMutation } from "#/__generated__/core/CreateAccessSourceDialogMutation.graphql";
|
import type { CreateAccessSourceDialogMutation } from "#/__generated__/core/CreateAccessSourceDialogMutation.graphql";
|
||||||
|
|
||||||
import { createAccessSourceMutation } from "./CreateAccessSourceDialog";
|
import { createAccessSourceMutation } from "./CreateAccessSourceDialog";
|
||||||
|
|
||||||
type OrganizationData = Extract<
|
export const addAccessSourceDialogConnectorProviderInfoFragment = graphql`
|
||||||
AccessReviewLayoutQuery$data["organization"],
|
fragment AddAccessSourceDialogConnectorProviderInfoFragment on ConnectorProviderInfo @relay(plural: true) {
|
||||||
{ readonly __typename: "Organization" }
|
provider
|
||||||
>;
|
displayName
|
||||||
|
oauthConfigured
|
||||||
|
apiKeySupported
|
||||||
|
clientCredentialsSupported
|
||||||
|
extraSettings {
|
||||||
|
key
|
||||||
|
label
|
||||||
|
required
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export type ProviderInfo = OrganizationData["connectorProviderInfos"][number];
|
export type ProviderInfo = AddAccessSourceDialogConnectorProviderInfoFragment$data[number];
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
|||||||
@@ -26,18 +26,36 @@ import {
|
|||||||
useToast,
|
useToast,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
import { useEffect, useMemo, useRef } from "react";
|
||||||
import { graphql, useMutation, usePaginationFragment } from "react-relay";
|
import type { PreloadedQuery } from "react-relay";
|
||||||
import { useOutletContext, useSearchParams } from "react-router";
|
import { graphql, useFragment, useMutation, usePaginationFragment, usePreloadedQuery } from "react-relay";
|
||||||
|
import { useSearchParams } from "react-router";
|
||||||
|
|
||||||
import type { AccessReviewSourcesTabFragment$key } from "#/__generated__/core/AccessReviewSourcesTabFragment.graphql";
|
import type { AccessReviewSourcesTabFragment$key } from "#/__generated__/core/AccessReviewSourcesTabFragment.graphql";
|
||||||
import type { AccessReviewSourcesTabPaginationQuery } from "#/__generated__/core/AccessReviewSourcesTabPaginationQuery.graphql";
|
import type { AccessReviewSourcesTabPaginationQuery } from "#/__generated__/core/AccessReviewSourcesTabPaginationQuery.graphql";
|
||||||
|
import type { AccessReviewSourcesTabQuery } from "#/__generated__/core/AccessReviewSourcesTabQuery.graphql";
|
||||||
|
import type { AddAccessSourceDialogConnectorProviderInfoFragment$key } from "#/__generated__/core/AddAccessSourceDialogConnectorProviderInfoFragment.graphql";
|
||||||
import type { CreateAccessSourceDialogMutation } from "#/__generated__/core/CreateAccessSourceDialogMutation.graphql";
|
import type { CreateAccessSourceDialogMutation } from "#/__generated__/core/CreateAccessSourceDialogMutation.graphql";
|
||||||
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||||
|
|
||||||
import { AccessSourceRow } from "../_components/AccessSourceRow";
|
import { AccessSourceRow } from "../_components/AccessSourceRow";
|
||||||
import { AddAccessSourceDialog, type ProviderInfo } from "../dialogs/AddAccessSourceDialog";
|
import { AddAccessSourceDialog, addAccessSourceDialogConnectorProviderInfoFragment } from "../dialogs/AddAccessSourceDialog";
|
||||||
import { createAccessSourceMutation } from "../dialogs/CreateAccessSourceDialog";
|
import { createAccessSourceMutation } from "../dialogs/CreateAccessSourceDialog";
|
||||||
|
|
||||||
|
export const accessReviewSourcesTabQuery = graphql`
|
||||||
|
query AccessReviewSourcesTabQuery($organizationId: ID!) {
|
||||||
|
organization: node(id: $organizationId) {
|
||||||
|
__typename
|
||||||
|
... on Organization {
|
||||||
|
canCreateSource: permission(action: "core:access-source:create")
|
||||||
|
connectorProviderInfos {
|
||||||
|
...AddAccessSourceDialogConnectorProviderInfoFragment
|
||||||
|
}
|
||||||
|
...AccessReviewSourcesTabFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const sourcesFragment = graphql`
|
const sourcesFragment = graphql`
|
||||||
fragment AccessReviewSourcesTabFragment on Organization
|
fragment AccessReviewSourcesTabFragment on Organization
|
||||||
@refetchable(queryName: "AccessReviewSourcesTabPaginationQuery")
|
@refetchable(queryName: "AccessReviewSourcesTabPaginationQuery")
|
||||||
@@ -62,7 +80,6 @@ const sourcesFragment = graphql`
|
|||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
name
|
|
||||||
connectorId
|
connectorId
|
||||||
connector {
|
connector {
|
||||||
provider
|
provider
|
||||||
@@ -74,17 +91,26 @@ const sourcesFragment = graphql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default function AccessReviewSourcesTab() {
|
type Props = {
|
||||||
|
queryRef: PreloadedQuery<AccessReviewSourcesTabQuery>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AccessReviewSourcesTab({ queryRef }: Props) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const processedConnectorIdRef = useRef<string | null>(null);
|
const processedConnectorIdRef = useRef<string | null>(null);
|
||||||
const { organizationRef, canCreateSource, connectorProviderInfos } = useOutletContext<{
|
|
||||||
organizationRef: AccessReviewSourcesTabFragment$key;
|
const { organization } = usePreloadedQuery(accessReviewSourcesTabQuery, queryRef);
|
||||||
canCreateSource: boolean;
|
if (organization.__typename !== "Organization") {
|
||||||
connectorProviderInfos: ReadonlyArray<ProviderInfo>;
|
throw new Error("Organization not found");
|
||||||
}>();
|
}
|
||||||
|
|
||||||
|
const connectorProviderInfos = useFragment<AddAccessSourceDialogConnectorProviderInfoFragment$key>(
|
||||||
|
addAccessSourceDialogConnectorProviderInfoFragment,
|
||||||
|
organization.connectorProviderInfos,
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { accessSources },
|
data: { accessSources },
|
||||||
@@ -94,7 +120,7 @@ export default function AccessReviewSourcesTab() {
|
|||||||
} = usePaginationFragment<
|
} = usePaginationFragment<
|
||||||
AccessReviewSourcesTabPaginationQuery,
|
AccessReviewSourcesTabPaginationQuery,
|
||||||
AccessReviewSourcesTabFragment$key
|
AccessReviewSourcesTabFragment$key
|
||||||
>(sourcesFragment, organizationRef);
|
>(sourcesFragment, organization);
|
||||||
|
|
||||||
const existingSourceProviders = useMemo(
|
const existingSourceProviders = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -211,7 +237,7 @@ export default function AccessReviewSourcesTab() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-end">
|
<div className="flex items-center justify-end">
|
||||||
{canCreateSource && (
|
{organization.canCreateSource && (
|
||||||
<AddAccessSourceDialog
|
<AddAccessSourceDialog
|
||||||
organizationId={organizationId}
|
organizationId={organizationId}
|
||||||
connectionId={accessSources.__id}
|
connectionId={accessSources.__id}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
import { Suspense, useEffect } from "react";
|
||||||
|
import { useQueryLoader } from "react-relay";
|
||||||
|
|
||||||
|
import type { AccessReviewSourcesTabQuery } from "#/__generated__/core/AccessReviewSourcesTabQuery.graphql";
|
||||||
|
import { PageSkeleton } from "#/components/skeletons/PageSkeleton";
|
||||||
|
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||||
|
|
||||||
|
import AccessReviewSourcesTab, { accessReviewSourcesTabQuery } from "./AccessReviewSourcesTab";
|
||||||
|
|
||||||
|
export default function AccessReviewSourcesTabLoader() {
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
|
const [queryRef, loadQuery] = useQueryLoader<AccessReviewSourcesTabQuery>(accessReviewSourcesTabQuery);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadQuery({ organizationId });
|
||||||
|
}, [loadQuery, organizationId]);
|
||||||
|
|
||||||
|
if (!queryRef) return <PageSkeleton />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<PageSkeleton />}>
|
||||||
|
<AccessReviewSourcesTab queryRef={queryRef} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -15,14 +15,14 @@ export const accessReviewRoutes = [
|
|||||||
index: true,
|
index: true,
|
||||||
Fallback: PageSkeleton,
|
Fallback: PageSkeleton,
|
||||||
Component: lazy(
|
Component: lazy(
|
||||||
() => import("#/pages/organizations/access-reviews/campaigns/AccessReviewCampaignsTab"),
|
() => import("#/pages/organizations/access-reviews/campaigns/AccessReviewCampaignsTabLoader"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "sources",
|
path: "sources",
|
||||||
Fallback: PageSkeleton,
|
Fallback: PageSkeleton,
|
||||||
Component: lazy(
|
Component: lazy(
|
||||||
() => import("#/pages/organizations/access-reviews/sources/AccessReviewSourcesTab"),
|
() => import("#/pages/organizations/access-reviews/sources/AccessReviewSourcesTabLoader"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user