From 28cf3f167ae86bf386761a8e80d14fa15756d112 Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Thu, 2 Apr 2026 16:28:23 +0200 Subject: [PATCH] Fix lint Signed-off-by: Bryan Frimin --- .../access-reviews/AccessReviewLayout.tsx | 49 +----------------- .../AccessReviewLayoutLoader.tsx | 26 +--------- .../campaigns/AccessReviewCampaignsTab.tsx | 38 ++++++++++---- .../AccessReviewCampaignsTabLoader.tsx | 39 +++++++++++++++ .../campaigns/CampaignDetailPage.tsx | 4 -- .../dialogs/AddAccessSourceDialog.tsx | 22 +++++--- .../sources/AccessReviewSourcesTab.tsx | 50 ++++++++++++++----- .../sources/AccessReviewSourcesTabLoader.tsx | 39 +++++++++++++++ apps/console/src/routes/accessReviewRoutes.ts | 4 +- 9 files changed, 165 insertions(+), 106 deletions(-) create mode 100644 apps/console/src/pages/organizations/access-reviews/campaigns/AccessReviewCampaignsTabLoader.tsx create mode 100644 apps/console/src/pages/organizations/access-reviews/sources/AccessReviewSourcesTabLoader.tsx diff --git a/apps/console/src/pages/organizations/access-reviews/AccessReviewLayout.tsx b/apps/console/src/pages/organizations/access-reviews/AccessReviewLayout.tsx index 4f791cc0c..55351880c 100644 --- a/apps/console/src/pages/organizations/access-reviews/AccessReviewLayout.tsx +++ b/apps/console/src/pages/organizations/access-reviews/AccessReviewLayout.tsx @@ -21,55 +21,16 @@ import { TabLink, Tabs, } from "@probo/ui"; -import { type PreloadedQuery, usePreloadedQuery } from "react-relay"; import { Outlet } from "react-router"; -import { graphql } from "relay-runtime"; -import type { AccessReviewLayoutQuery } from "#/__generated__/core/AccessReviewLayoutQuery.graphql"; import { useOrganizationId } from "#/hooks/useOrganizationId"; -export const accessReviewLayoutQuery = graphql` - 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; -}) { +export default function AccessReviewLayout() { const { __ } = useTranslate(); const organizationId = useOrganizationId(); usePageTitle(__("Access Reviews")); - const { organization } = usePreloadedQuery(accessReviewLayoutQuery, queryRef); - if (organization.__typename !== "Organization") { - throw new Error("Organization not found"); - } - return (
- +
); } diff --git a/apps/console/src/pages/organizations/access-reviews/AccessReviewLayoutLoader.tsx b/apps/console/src/pages/organizations/access-reviews/AccessReviewLayoutLoader.tsx index 2eef36dbe..b2894b7a7 100644 --- a/apps/console/src/pages/organizations/access-reviews/AccessReviewLayoutLoader.tsx +++ b/apps/console/src/pages/organizations/access-reviews/AccessReviewLayoutLoader.tsx @@ -12,30 +12,8 @@ // 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 { AccessReviewLayoutQuery } from "#/__generated__/core/AccessReviewLayoutQuery.graphql"; -import { PageSkeleton } from "#/components/skeletons/PageSkeleton"; -import { useOrganizationId } from "#/hooks/useOrganizationId"; - -import AccessReviewLayout, { accessReviewLayoutQuery } from "./AccessReviewLayout"; +import AccessReviewLayout from "./AccessReviewLayout"; export default function AccessReviewLayoutLoader() { - const organizationId = useOrganizationId(); - const [queryRef, loadQuery] = useQueryLoader(accessReviewLayoutQuery); - - useEffect(() => { - if (!queryRef) { - loadQuery({ organizationId }); - } - }, [loadQuery, organizationId]); - - if (!queryRef) return ; - - return ( - }> - - - ); + return ; } diff --git a/apps/console/src/pages/organizations/access-reviews/campaigns/AccessReviewCampaignsTab.tsx b/apps/console/src/pages/organizations/access-reviews/campaigns/AccessReviewCampaignsTab.tsx index 96b08421d..52d693351 100644 --- a/apps/console/src/pages/organizations/access-reviews/campaigns/AccessReviewCampaignsTab.tsx +++ b/apps/console/src/pages/organizations/access-reviews/campaigns/AccessReviewCampaignsTab.tsx @@ -25,16 +25,29 @@ import { Thead, Tr, } from "@probo/ui"; -import { graphql, usePaginationFragment } from "react-relay"; -import { useOutletContext } from "react-router"; +import type { PreloadedQuery } from "react-relay"; +import { graphql, usePaginationFragment, usePreloadedQuery } from "react-relay"; import type { AccessReviewCampaignsTabFragment$key } from "#/__generated__/core/AccessReviewCampaignsTabFragment.graphql"; import type { AccessReviewCampaignsTabPaginationQuery } from "#/__generated__/core/AccessReviewCampaignsTabPaginationQuery.graphql"; +import type { AccessReviewCampaignsTabQuery } from "#/__generated__/core/AccessReviewCampaignsTabQuery.graphql"; import { useOrganizationId } from "#/hooks/useOrganizationId"; import { statusBadgeVariant, statusLabel } from "../_components/accessReviewHelpers"; 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` fragment AccessReviewCampaignsTabFragment on Organization @refetchable(queryName: "AccessReviewCampaignsTabPaginationQuery") @@ -58,21 +71,24 @@ const campaignsFragment = graphql` name status createdAt - startedAt - completedAt } } } } `; -export default function AccessReviewCampaignsTab() { +type Props = { + queryRef: PreloadedQuery; +}; + +export default function AccessReviewCampaignsTab({ queryRef }: Props) { const { __, dateFormat } = useTranslate(); const organizationId = useOrganizationId(); - const { organizationRef, canCreateCampaign } = useOutletContext<{ - organizationRef: AccessReviewCampaignsTabFragment$key; - canCreateCampaign: boolean; - }>(); + + const { organization } = usePreloadedQuery(accessReviewCampaignsTabQuery, queryRef); + if (organization.__typename !== "Organization") { + throw new Error("Organization not found"); + } const { data: { accessReviewCampaigns }, @@ -82,12 +98,12 @@ export default function AccessReviewCampaignsTab() { } = usePaginationFragment< AccessReviewCampaignsTabPaginationQuery, AccessReviewCampaignsTabFragment$key - >(campaignsFragment, organizationRef); + >(campaignsFragment, organization); return (
- {canCreateCampaign && ( + {organization.canCreateCampaign && ( . +// +// 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); + + useEffect(() => { + loadQuery({ organizationId }); + }, [loadQuery, organizationId]); + + if (!queryRef) return ; + + return ( + }> + + + ); +} diff --git a/apps/console/src/pages/organizations/access-reviews/campaigns/CampaignDetailPage.tsx b/apps/console/src/pages/organizations/access-reviews/campaigns/CampaignDetailPage.tsx index 5b9d69de0..a5b613a55 100644 --- a/apps/console/src/pages/organizations/access-reviews/campaigns/CampaignDetailPage.tsx +++ b/apps/console/src/pages/organizations/access-reviews/campaigns/CampaignDetailPage.tsx @@ -131,10 +131,6 @@ export const campaignDetailPageQuery = graphql` id name status - createdAt - startedAt - completedAt - pendingEntryCount scopeSources { id source { diff --git a/apps/console/src/pages/organizations/access-reviews/dialogs/AddAccessSourceDialog.tsx b/apps/console/src/pages/organizations/access-reviews/dialogs/AddAccessSourceDialog.tsx index e726dd539..b12c10b61 100644 --- a/apps/console/src/pages/organizations/access-reviews/dialogs/AddAccessSourceDialog.tsx +++ b/apps/console/src/pages/organizations/access-reviews/dialogs/AddAccessSourceDialog.tsx @@ -37,19 +37,29 @@ import { useMutation } from "react-relay"; import { Link } from "react-router"; 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 { AddAccessSourceDialogCreateClientCredentialsConnectorMutation } from "#/__generated__/core/AddAccessSourceDialogCreateClientCredentialsConnectorMutation.graphql"; import type { CreateAccessSourceDialogMutation } from "#/__generated__/core/CreateAccessSourceDialogMutation.graphql"; import { createAccessSourceMutation } from "./CreateAccessSourceDialog"; -type OrganizationData = Extract< - AccessReviewLayoutQuery$data["organization"], - { readonly __typename: "Organization" } ->; +export const addAccessSourceDialogConnectorProviderInfoFragment = graphql` + fragment AddAccessSourceDialogConnectorProviderInfoFragment on ConnectorProviderInfo @relay(plural: true) { + provider + displayName + oauthConfigured + apiKeySupported + clientCredentialsSupported + extraSettings { + key + label + required + } + } +`; -export type ProviderInfo = OrganizationData["connectorProviderInfos"][number]; +export type ProviderInfo = AddAccessSourceDialogConnectorProviderInfoFragment$data[number]; type Props = { children: ReactNode; diff --git a/apps/console/src/pages/organizations/access-reviews/sources/AccessReviewSourcesTab.tsx b/apps/console/src/pages/organizations/access-reviews/sources/AccessReviewSourcesTab.tsx index b29368f52..a15c8270e 100644 --- a/apps/console/src/pages/organizations/access-reviews/sources/AccessReviewSourcesTab.tsx +++ b/apps/console/src/pages/organizations/access-reviews/sources/AccessReviewSourcesTab.tsx @@ -26,18 +26,36 @@ import { useToast, } from "@probo/ui"; import { useEffect, useMemo, useRef } from "react"; -import { graphql, useMutation, usePaginationFragment } from "react-relay"; -import { useOutletContext, useSearchParams } from "react-router"; +import type { PreloadedQuery } from "react-relay"; +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 { 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 { useOrganizationId } from "#/hooks/useOrganizationId"; import { AccessSourceRow } from "../_components/AccessSourceRow"; -import { AddAccessSourceDialog, type ProviderInfo } from "../dialogs/AddAccessSourceDialog"; +import { AddAccessSourceDialog, addAccessSourceDialogConnectorProviderInfoFragment } from "../dialogs/AddAccessSourceDialog"; 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` fragment AccessReviewSourcesTabFragment on Organization @refetchable(queryName: "AccessReviewSourcesTabPaginationQuery") @@ -62,7 +80,6 @@ const sourcesFragment = graphql` edges { node { id - name connectorId connector { provider @@ -74,17 +91,26 @@ const sourcesFragment = graphql` } `; -export default function AccessReviewSourcesTab() { +type Props = { + queryRef: PreloadedQuery; +}; + +export default function AccessReviewSourcesTab({ queryRef }: Props) { const { __ } = useTranslate(); const { toast } = useToast(); const organizationId = useOrganizationId(); const [searchParams, setSearchParams] = useSearchParams(); const processedConnectorIdRef = useRef(null); - const { organizationRef, canCreateSource, connectorProviderInfos } = useOutletContext<{ - organizationRef: AccessReviewSourcesTabFragment$key; - canCreateSource: boolean; - connectorProviderInfos: ReadonlyArray; - }>(); + + const { organization } = usePreloadedQuery(accessReviewSourcesTabQuery, queryRef); + if (organization.__typename !== "Organization") { + throw new Error("Organization not found"); + } + + const connectorProviderInfos = useFragment( + addAccessSourceDialogConnectorProviderInfoFragment, + organization.connectorProviderInfos, + ); const { data: { accessSources }, @@ -94,7 +120,7 @@ export default function AccessReviewSourcesTab() { } = usePaginationFragment< AccessReviewSourcesTabPaginationQuery, AccessReviewSourcesTabFragment$key - >(sourcesFragment, organizationRef); + >(sourcesFragment, organization); const existingSourceProviders = useMemo( () => @@ -211,7 +237,7 @@ export default function AccessReviewSourcesTab() { return (
- {canCreateSource && ( + {organization.canCreateSource && ( . +// +// 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); + + useEffect(() => { + loadQuery({ organizationId }); + }, [loadQuery, organizationId]); + + if (!queryRef) return ; + + return ( + }> + + + ); +} diff --git a/apps/console/src/routes/accessReviewRoutes.ts b/apps/console/src/routes/accessReviewRoutes.ts index 8afd15c36..2e69fbf5c 100644 --- a/apps/console/src/routes/accessReviewRoutes.ts +++ b/apps/console/src/routes/accessReviewRoutes.ts @@ -15,14 +15,14 @@ export const accessReviewRoutes = [ index: true, Fallback: PageSkeleton, Component: lazy( - () => import("#/pages/organizations/access-reviews/campaigns/AccessReviewCampaignsTab"), + () => import("#/pages/organizations/access-reviews/campaigns/AccessReviewCampaignsTabLoader"), ), }, { path: "sources", Fallback: PageSkeleton, Component: lazy( - () => import("#/pages/organizations/access-reviews/sources/AccessReviewSourcesTab"), + () => import("#/pages/organizations/access-reviews/sources/AccessReviewSourcesTabLoader"), ), }, ],