diff --git a/apps/console/relay.config.json b/apps/console/relay.config.json index ec6e89044..38c9a36cf 100644 --- a/apps/console/relay.config.json +++ b/apps/console/relay.config.json @@ -36,6 +36,7 @@ }, "sources": { "apps/console/src/pages/iam": "iam", + "apps/console/src/hooks/iam": "iam", "apps/console/src": "core" } } diff --git a/apps/console/src/__generated__/iam/PasswordSignInPageMutation.graphql.ts b/apps/console/src/__generated__/iam/PasswordSignInPageMutation.graphql.ts index ae514bff7..a80db9771 100644 --- a/apps/console/src/__generated__/iam/PasswordSignInPageMutation.graphql.ts +++ b/apps/console/src/__generated__/iam/PasswordSignInPageMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<3bef621b9a5eb43afa619cd76b2bb6b6>> * @lightSyntaxTransform * @nogrep */ @@ -11,6 +11,7 @@ import { ConcreteRequest } from 'relay-runtime'; export type SignInInput = { email: string; + organizationId?: string | null | undefined; password: string; }; export type PasswordSignInPageMutation$variables = { diff --git a/apps/console/src/__generated__/iam/ViewerMembershipLayoutLoader_assumeMutation.graphql.ts b/apps/console/src/__generated__/iam/useAssumeMutation.graphql.ts similarity index 79% rename from apps/console/src/__generated__/iam/ViewerMembershipLayoutLoader_assumeMutation.graphql.ts rename to apps/console/src/__generated__/iam/useAssumeMutation.graphql.ts index 58555d6ea..7e4bffbff 100644 --- a/apps/console/src/__generated__/iam/ViewerMembershipLayoutLoader_assumeMutation.graphql.ts +++ b/apps/console/src/__generated__/iam/useAssumeMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<09963c0483a8416abf9e411f4a0b716b>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -13,10 +13,10 @@ export type ReauthenticationReason = "POLICY_REQUIREMENT" | "SENSITIVE_ACTION" | export type AssumeOrganizationSessionInput = { organizationId: string; }; -export type ViewerMembershipLayoutLoader_assumeMutation$variables = { +export type useAssumeMutation$variables = { input: AssumeOrganizationSessionInput; }; -export type ViewerMembershipLayoutLoader_assumeMutation$data = { +export type useAssumeMutation$data = { readonly assumeOrganizationSession: { readonly result: { readonly __typename: "OrganizationSessionCreated"; @@ -41,9 +41,9 @@ export type ViewerMembershipLayoutLoader_assumeMutation$data = { }; } | null | undefined; }; -export type ViewerMembershipLayoutLoader_assumeMutation = { - response: ViewerMembershipLayoutLoader_assumeMutation$data; - variables: ViewerMembershipLayoutLoader_assumeMutation$variables; +export type useAssumeMutation = { + response: useAssumeMutation$data; + variables: useAssumeMutation$variables; }; const node: ConcreteRequest = (function(){ @@ -171,7 +171,7 @@ return { "argumentDefinitions": (v0/*: any*/), "kind": "Fragment", "metadata": null, - "name": "ViewerMembershipLayoutLoader_assumeMutation", + "name": "useAssumeMutation", "selections": (v3/*: any*/), "type": "Mutation", "abstractKey": null @@ -180,20 +180,20 @@ return { "operation": { "argumentDefinitions": (v0/*: any*/), "kind": "Operation", - "name": "ViewerMembershipLayoutLoader_assumeMutation", + "name": "useAssumeMutation", "selections": (v3/*: any*/) }, "params": { - "cacheID": "3c73e9c20476f178c97c687dfd90b7e6", + "cacheID": "6c65712730108428976b1d0007ca2993", "id": null, "metadata": {}, - "name": "ViewerMembershipLayoutLoader_assumeMutation", + "name": "useAssumeMutation", "operationKind": "mutation", - "text": "mutation ViewerMembershipLayoutLoader_assumeMutation(\n $input: AssumeOrganizationSessionInput!\n) {\n assumeOrganizationSession(input: $input) {\n result {\n __typename\n ... on OrganizationSessionCreated {\n membership {\n id\n lastSession {\n id\n expiresAt\n }\n }\n }\n ... on PasswordRequired {\n reason\n }\n ... on SAMLAuthenticationRequired {\n reason\n redirectUrl\n }\n }\n }\n}\n" + "text": "mutation useAssumeMutation(\n $input: AssumeOrganizationSessionInput!\n) {\n assumeOrganizationSession(input: $input) {\n result {\n __typename\n ... on OrganizationSessionCreated {\n membership {\n id\n lastSession {\n id\n expiresAt\n }\n }\n }\n ... on PasswordRequired {\n reason\n }\n ... on SAMLAuthenticationRequired {\n reason\n redirectUrl\n }\n }\n }\n}\n" } }; })(); -(node as any).hash = "d885b41930ea8c7e5c14cadb7ae40eb3"; +(node as any).hash = "1ccd539abfc276084939857e65421be9"; export default node; diff --git a/apps/console/src/hooks/iam/useAssume.ts b/apps/console/src/hooks/iam/useAssume.ts new file mode 100644 index 000000000..89c737644 --- /dev/null +++ b/apps/console/src/hooks/iam/useAssume.ts @@ -0,0 +1,84 @@ +import { UnAuthenticatedError } from "@probo/relay"; +import { useEffect } from "react"; +import { useMutation } from "react-relay"; +import { useNavigate } from "react-router"; +import { graphql } from "relay-runtime"; + +import type { useAssumeMutation } from "#/__generated__/iam/useAssumeMutation.graphql"; + +import { useOrganizationId } from "../useOrganizationId"; + +interface UseAssumeParameters { + onSuccess: () => void; +} + +const assumeMutation = graphql` + mutation useAssumeMutation( + $input: AssumeOrganizationSessionInput! + ) { + assumeOrganizationSession(input: $input) { + result { + __typename + ... on OrganizationSessionCreated { + membership { + id + lastSession { + id + expiresAt + } + } + } + ... on PasswordRequired { + reason + } + ... on SAMLAuthenticationRequired { + reason + redirectUrl + } + } + } + } +`; + +export function useAssume(params: UseAssumeParameters) { + const { onSuccess } = params; + + const organizationId = useOrganizationId(); + const navigate = useNavigate(); + + const [assumeOrganizationSession] = useMutation(assumeMutation); + + useEffect(() => { + assumeOrganizationSession({ + variables: { + input: { organizationId }, + }, + onError: (error) => { + if (error instanceof UnAuthenticatedError) { + void navigate("/auth/login"); + return; + } + }, + onCompleted: ({ assumeOrganizationSession }) => { + if (!assumeOrganizationSession) { + throw new Error("complete mutation result is empty"); + } + + const { result } = assumeOrganizationSession; + + switch (result.__typename) { + case "PasswordRequired": + void navigate("/auth/login"); + break; + case "SAMLAuthenticationRequired": + window.location.href = result.redirectUrl; + break; + default: + onSuccess(); + } + }, + }); + }, [onSuccess, navigate, assumeOrganizationSession, organizationId]); + + return; +} diff --git a/apps/console/src/pages/iam/organizations/ViewerMembershipLayoutLoader.tsx b/apps/console/src/pages/iam/organizations/ViewerMembershipLayoutLoader.tsx index 4a8f52ba0..16eb3d155 100644 --- a/apps/console/src/pages/iam/organizations/ViewerMembershipLayoutLoader.tsx +++ b/apps/console/src/pages/iam/organizations/ViewerMembershipLayoutLoader.tsx @@ -1,11 +1,9 @@ -import { UnAuthenticatedError } from "@probo/relay"; import { Skeleton } from "@probo/ui"; -import { Suspense, useEffect } from "react"; -import { graphql, useMutation, useQueryLoader } from "react-relay"; -import { useNavigate } from "react-router"; +import { Suspense, useCallback } from "react"; +import { useQueryLoader } from "react-relay"; -import type { ViewerMembershipLayoutLoader_assumeMutation } from "#/__generated__/iam/ViewerMembershipLayoutLoader_assumeMutation.graphql"; import type { ViewerMembershipLayoutQuery } from "#/__generated__/iam/ViewerMembershipLayoutQuery.graphql"; +import { useAssume } from "#/hooks/iam/useAssume"; import { useOrganizationId } from "#/hooks/useOrganizationId"; import { IAMRelayProvider } from "#/providers/IAMRelayProvider"; @@ -14,80 +12,23 @@ import { viewerMembershipLayoutQuery, } from "./ViewerMembershipLayout"; -const ensureAssumingMutation = graphql` - mutation ViewerMembershipLayoutLoader_assumeMutation( - $input: AssumeOrganizationSessionInput! - ) { - assumeOrganizationSession(input: $input) { - result { - __typename - ... on OrganizationSessionCreated { - membership { - id - lastSession { - id - expiresAt - } - } - } - ... on PasswordRequired { - reason - } - ... on SAMLAuthenticationRequired { - reason - redirectUrl - } - } - } - } -`; - function ViewerMembershipLayoutQueryLoader() { const organizationId = useOrganizationId(); - const navigate = useNavigate(); - const [assumeOrganizationSession] - = useMutation( - ensureAssumingMutation, - ); const [queryRef, loadQuery] = useQueryLoader( viewerMembershipLayoutQuery, ); - useEffect(() => { - assumeOrganizationSession({ - variables: { - input: { organizationId }, - }, - onError: (error) => { - if (error instanceof UnAuthenticatedError) { - void navigate("/auth/login"); - return; - } - }, - onCompleted: ({ assumeOrganizationSession }) => { - if (!assumeOrganizationSession) { - throw new Error("complete mutation result is empty"); - } + const onAssumeSuccess = useCallback( + () => + loadQuery({ + organizationId, + hideSidebar: false, + }), + [loadQuery, organizationId], + ); - const { result } = assumeOrganizationSession; - - switch (result.__typename) { - case "PasswordRequired": - void navigate("/auth/login"); - break; - case "SAMLAuthenticationRequired": - window.location.href = result.redirectUrl; - break; - default: - loadQuery({ - organizationId, - hideSidebar: false, - }); - } - }, - }); - }, [navigate, assumeOrganizationSession, loadQuery, organizationId]); + useAssume({ onSuccess: onAssumeSuccess }); if (!queryRef) { return ; diff --git a/apps/console/src/pages/organizations/employee/EmployeeLayoutLoader.tsx b/apps/console/src/pages/organizations/employee/EmployeeLayoutLoader.tsx index e29ca2342..b23446765 100644 --- a/apps/console/src/pages/organizations/employee/EmployeeLayoutLoader.tsx +++ b/apps/console/src/pages/organizations/employee/EmployeeLayoutLoader.tsx @@ -1,8 +1,9 @@ import { Skeleton } from "@probo/ui"; -import { Suspense, useEffect } from "react"; +import { Suspense, useCallback } from "react"; import { useQueryLoader } from "react-relay"; import type { ViewerMembershipLayoutQuery } from "#/__generated__/iam/ViewerMembershipLayoutQuery.graphql"; +import { useAssume } from "#/hooks/iam/useAssume"; import { useOrganizationId } from "#/hooks/useOrganizationId"; import { IAMRelayProvider } from "#/providers/IAMRelayProvider"; @@ -17,12 +18,16 @@ function EmployeeLayoutQueryLoader() { viewerMembershipLayoutQuery, ); - useEffect(() => { - loadQuery({ - organizationId, - hideSidebar: true, - }); - }, [loadQuery, organizationId]); + const onAssumeSuccess = useCallback( + () => + loadQuery({ + organizationId, + hideSidebar: false, + }), + [loadQuery, organizationId], + ); + + useAssume({ onSuccess: onAssumeSuccess }); if (!queryRef) { return ; diff --git a/apps/console/vite.config.ts b/apps/console/vite.config.ts index 3c99337e6..914cff4a2 100644 --- a/apps/console/vite.config.ts +++ b/apps/console/vite.config.ts @@ -8,7 +8,11 @@ import { defineConfig } from "vite"; export default defineConfig({ plugins: [ react({ - exclude: ["src/pages/iam/**/*", "src/components/connectors/**/*"], + exclude: [ + "src/hooks/iam/**/*", + "src/pages/iam/**/*", + "src/components/connectors/**/*", + ], babel: { plugins: [ [ @@ -22,7 +26,11 @@ export default defineConfig({ }, }), react({ - include: ["src/pages/iam/**/*", "src/components/connectors/**/*"], + include: [ + "src/hooks/iam/**/*", + "src/pages/iam/**/*", + "src/components/connectors/**/*", + ], babel: { plugins: [ [