Create assume page to redirect to when AssumptionRequired catched in boundary

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-02-10 15:01:52 +04:00
parent ab497ad6a7
commit 5fa4477d5d
5 changed files with 136 additions and 94 deletions

View File

@@ -15,7 +15,7 @@ export function OrganizationErrorBoundary() {
} }
if (error instanceof AssumptionRequiredError) { if (error instanceof AssumptionRequiredError) {
return <Navigate to="/auth/login" state={{ from: location.pathname, organizationId }} />; return <Navigate to={`/organizations/${organizationId}/assume`} state={{ from: location.pathname }} />;
} }
return <PageError error={error instanceof Error ? error : new Error("unknown error")} />; return <PageError error={error instanceof Error ? error : new Error("unknown error")} />;

View File

@@ -1,9 +1,12 @@
import { Card, Logo } from "@probo/ui"; import { Card, Logo } from "@probo/ui";
import type { PropsWithChildren } from "react";
import { Outlet } from "react-router"; import { Outlet } from "react-router";
import { IAMRelayProvider } from "#/providers/IAMRelayProvider"; import { IAMRelayProvider } from "#/providers/IAMRelayProvider";
export default function AuthLayout() { export default function AuthLayout(props: PropsWithChildren) {
const { children } = props;
return ( return (
<div className="min-h-screen text-txt-primary bg-level-0 flex flex-col items-center justify-center"> <div className="min-h-screen text-txt-primary bg-level-0 flex flex-col items-center justify-center">
<Card className="w-full max-w-lg px-12 py-8 flex flex-col items-center justify-center"> <Card className="w-full max-w-lg px-12 py-8 flex flex-col items-center justify-center">
@@ -12,7 +15,7 @@ export default function AuthLayout() {
<div className="w-full border-t border-t-border-mid" /> <div className="w-full border-t border-t-border-mid" />
</div> </div>
<IAMRelayProvider> <IAMRelayProvider>
<Outlet /> {children ?? <Outlet />}
</IAMRelayProvider> </IAMRelayProvider>
</Card> </Card>
</div> </div>

View File

@@ -0,0 +1,35 @@
import { useTranslate } from "@probo/i18n";
import { useLocation, useNavigate } from "react-router";
import { useAssume } from "#/hooks/iam/useAssume";
import AuthLayout from "../auth/AuthLayout";
interface State {
from: string;
}
export default function AssumePage() {
const navigate = useNavigate();
const location = useLocation();
const state = location.state as State;
const { __ } = useTranslate();
useAssume({
onSuccess: () => void navigate(state.from),
});
return (
<AuthLayout>
<div className="space-y-6 w-full max-w-md mx-auto pt-8">
<div className="space-y-2 text-center">
<h1 className="text-3xl font-bold">{__("Sign in Redirection")}</h1>
<p className="text-txt-tertiary">
{__("Redirecting you to your authentication URL…")}
</p>
</div>
</div>
</AuthLayout>
);
}

View File

@@ -121,7 +121,14 @@ const routes = [
), ),
}, },
{ {
path: "/organizations/:organizationId/employee", path: "/organizations/:organizationId",
children: [
{
path: "assume",
Component: lazy(() => import("./pages/iam/organizations/AssumePage")),
},
{
path: "employee",
Component: lazy( Component: lazy(
() => import("./pages/organizations/employee/EmployeeLayoutLoader"), () => import("./pages/organizations/employee/EmployeeLayoutLoader"),
), ),
@@ -144,14 +151,12 @@ const routes = [
], ],
}, },
{ {
path: "/organizations/:organizationId",
Component: lazy( Component: lazy(
() => import("./pages/iam/organizations/ViewerMembershipLayoutLoader"), () => import("./pages/iam/organizations/ViewerMembershipLayoutLoader"),
), ),
ErrorBoundary: OrganizationErrorBoundary, ErrorBoundary: OrganizationErrorBoundary,
children: [ children: [
{ {
path: "",
Component: () => { Component: () => {
const { role } = use(CurrentUser); const { role } = use(CurrentUser);
switch (role) { switch (role) {
@@ -233,6 +238,9 @@ const routes = [
}, },
], ],
}, },
],
},
// Fallback URL to the NotFound Page // Fallback URL to the NotFound Page
{ {
path: "*", path: "*",

View File

@@ -410,13 +410,9 @@ func (r *mutationResolver) SignIn(ctx context.Context, input types.SignInInput)
var err error var err error
session, _, err = r.iam.SessionService.OpenPasswordChildSessionForOrganization(ctx, session.ID, *input.OrganizationID) session, _, err = r.iam.SessionService.OpenPasswordChildSessionForOrganization(ctx, session.ID, *input.OrganizationID)
if err != nil { if err != nil {
var errSessionExpired *iam.ErrSessionExpired
var errMembershipNotFound *iam.ErrMembershipNotFound var errMembershipNotFound *iam.ErrMembershipNotFound
var errMembershipInactive *iam.ErrMembershipInactive var errMembershipInactive *iam.ErrMembershipInactive
if errors.As(err, errSessionExpired) {
return nil, gqlutils.Unauthenticated(ctx, err)
}
if errors.As(err, errMembershipNotFound) || errors.As(err, errMembershipInactive) { if errors.As(err, errMembershipNotFound) || errors.As(err, errMembershipInactive) {
return nil, gqlutils.Forbidden(ctx, err) return nil, gqlutils.Forbidden(ctx, err)
} }