Fix mutation errors

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-01-06 21:19:02 +01:00
committed by Bryan Frimin
parent 62569dc44c
commit ffe5b5ec91
12 changed files with 170 additions and 42 deletions

View File

@@ -8,6 +8,7 @@ import z from "zod";
import { graphql } from "relay-runtime";
import { useMutation } from "react-relay";
import type { ForgotPasswordPageMutation } from "/__generated__/iam/ForgotPasswordPageMutation.graphql";
import { formatError } from "@probo/helpers";
const sendInstructionsMutation = graphql`
mutation ForgotPasswordPageMutation($input: ForgotPasswordInput!) {
@@ -46,11 +47,23 @@ export default function ForgotPasswordPage() {
onError: (e: Error) => {
toast({
title: __("Request failed"),
description: e.message || __("Failed to send reset instructions"),
description: e.message,
variant: "error",
});
},
onCompleted: () => {
onCompleted: (_, e) => {
if (e) {
toast({
title: __("Request failed"),
description: formatError(
__("Failed to send reset instructions"),
e,
),
variant: "error",
});
return;
}
toast({
title: __("Success"),
description: __("Password reset instructions sent to your email"),

View File

@@ -7,6 +7,7 @@ import { useFormWithSchema } from "/hooks/useFormWithSchema";
import { graphql } from "relay-runtime";
import { useMutation } from "react-relay";
import type { ResetPasswordPageMutation } from "/__generated__/iam/ResetPasswordPageMutation.graphql";
import { formatError, type GraphQLError } from "@probo/helpers";
const resetPasswordMutation = graphql`
mutation ResetPasswordPageMutation($input: ResetPasswordInput!) {
@@ -66,11 +67,22 @@ export default function ResetPasswordPage() {
onError: (e: Error) => {
toast({
title: __("Reset failed"),
description: e.message || __("Password reset failed"),
description: e.message,
variant: "error",
});
},
onCompleted: () => {
onCompleted: (_, e) => {
if (e) {
toast({
title: __("Reset failed"),
description: formatError(
__("Password reset failed"),
e as GraphQLError,
),
variant: "error",
});
return;
}
toast({
title: __("Success"),
description: __("Password reset successfully"),

View File

@@ -103,7 +103,6 @@ function NavigateToSSOLoginURL(props: {
useEffect(() => {
if (!ssoLoginURL.ok) {
console.log(ssoLoginURL);
toast({
title: __("Error"),
description:

View File

@@ -6,6 +6,7 @@ import z from "zod";
import { useFormWithSchema } from "/hooks/useFormWithSchema";
import { graphql } from "relay-runtime";
import { useMutation } from "react-relay";
import { formatError } from "@probo/helpers";
const signUpFromIvitationMutation = graphql`
mutation SignUpFromInvitationPageMutation(
@@ -63,7 +64,16 @@ export default function SignUpFromInvitationPage() {
fullName: data.fullName,
},
},
onCompleted: () => {
onCompleted: (_, e) => {
if (e) {
toast({
title: __("Signup failed"),
description: formatError(__("Signup failed"), e),
variant: "error",
});
return;
}
toast({
title: __("Success"),
description: __(
@@ -73,10 +83,10 @@ export default function SignUpFromInvitationPage() {
});
navigate("/", { replace: true });
},
onError: (errorData) => {
onError: (e) => {
toast({
title: __("Signup failed"),
description: errorData.message || __("Signup failed"),
description: e.message,
variant: "error",
});
},

View File

@@ -6,6 +6,7 @@ import z from "zod";
import { useFormWithSchema } from "/hooks/useFormWithSchema";
import { graphql } from "relay-runtime";
import { useMutation } from "react-relay";
import { formatError } from "@probo/helpers";
const signUpMutation = graphql`
mutation SignUpPageMutation($input: SignUpInput!) {
@@ -49,7 +50,16 @@ export default function SignUpPage() {
password: data.password,
fullName: data.fullName,
},
onCompleted: () => {
onCompleted: (_, e) => {
if (e) {
toast({
title: __("Registration failed"),
description: formatError(__("Registration failed"), e),
variant: "error",
});
return;
}
toast({
title: __("Success"),
description: __("Account created successfully"),
@@ -57,10 +67,10 @@ export default function SignUpPage() {
});
navigate("/", { replace: true });
},
onError: (errorData) => {
onError: (e) => {
toast({
title: __("Registration failed"),
description: errorData.message || __("Registration failed"),
description: e.message,
variant: "error",
});
},

View File

@@ -1,6 +1,6 @@
import { formatDate } from "@probo/helpers";
import { formatDate, formatError } from "@probo/helpers";
import { useTranslate } from "@probo/i18n";
import { Button, Card } from "@probo/ui";
import { Button, Card, useToast } from "@probo/ui";
import { graphql } from "relay-runtime";
import { useFragment, useMutation } from "react-relay";
import type { InvitationCardFragment$key } from "/__generated__/iam/InvitationCardFragment.graphql";
@@ -39,6 +39,7 @@ export function InvitationCard(props: InvitationCardProps) {
const navigate = useNavigate();
const { __ } = useTranslate();
const { toast } = useToast();
const invitation = useFragment<InvitationCardFragment$key>(fragment, fKey);
@@ -51,12 +52,23 @@ export function InvitationCard(props: InvitationCardProps) {
invitationId: invitation.id,
},
},
onCompleted: () => {
onCompleted: (_, e) => {
if (e) {
toast({
title: __("Request failed"),
description: formatError(__("Cannot accept invitation"), e),
variant: "error",
});
return;
}
navigate(`/organizations/${invitation.organization.id}`);
},
onError: (err) => {
console.error("Failed to accept invitation:", err);
alert(__("Failed to accept invitation"));
onError: (e) => {
toast({
title: __("Request failed"),
description: e.message,
variant: "error",
});
},
});
};

View File

@@ -13,6 +13,7 @@ import { useOrganizationId } from "/hooks/useOrganizationId";
import { useFragment, useMutation } from "react-relay";
import { useTranslate } from "@probo/i18n";
import type { SessionDropdownFragment$key } from "/__generated__/iam/SessionDropdownFragment.graphql";
import { formatError } from "@probo/helpers";
export const fragment = graphql`
fragment SessionDropdownFragment on Organization {
@@ -57,13 +58,21 @@ export function SessionDropdown(props: { fKey: SessionDropdownFragment$key }) {
signOut({
variables: {},
onCompleted: () => {
onCompleted: (_, e) => {
if (e) {
toast({
title: __("Request failed"),
description: formatError(__("Cannot sign out"), e),
variant: "error",
});
return;
}
window.location.reload();
},
onError: (e) => {
toast({
title: __("Error"),
description: e.message as string,
description: e.message,
variant: "error",
});
},

View File

@@ -3,7 +3,7 @@ import { Button, Card, Field, PageHeader, useToast } from "@probo/ui";
import { graphql, useMutation } from "react-relay";
import type { FormEventHandler } from "react";
import { useNavigate } from "react-router";
import { formatError, type GraphQLError } from "@probo/helpers";
import { formatError } from "@probo/helpers";
import type { NewOrganizationPageMutation } from "/__generated__/iam/NewOrganizationPageMutation.graphql";
import { IAMRelayProvider } from "/providers/IAMRelayProvider";
@@ -45,7 +45,16 @@ function NewOrganizationPage() {
name,
},
},
onCompleted: (r) => {
onCompleted: (r, e) => {
if (e) {
toast({
title: __("Error"),
description: formatError(__("Failed to create organization"), e),
variant: "error",
});
return;
}
const org = r.createOrganization!.organization;
navigate(`/organizations/${org!.id}`);
toast({
@@ -54,10 +63,10 @@ function NewOrganizationPage() {
variant: "success",
});
},
onError: (e: GraphQLError) => {
onError: (e) => {
toast({
title: __("Error"),
description: formatError(__("Failed to create organization"), e),
description: e.message,
variant: "error",
});
},

View File

@@ -42,7 +42,6 @@ export function SCIMSettingsPage(props: {
<div className="space-y-4">
<h2 className="text-base font-medium">{__("SCIM Provisioning")}</h2>
<SCIMConfiguration
organizationId={organization.id}
fKey={organization.scimConfiguration ?? null}
canCreate={organization.canCreateSCIMConfiguration}
canDelete={organization.canDeleteSCIMConfiguration}

View File

@@ -9,6 +9,9 @@ import {
type SAMLConfigurationFormData,
} from "./SAMLConfigurationForm";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
import { formatError } from "@probo/helpers";
import { useTranslate } from "@probo/i18n";
import { useToast } from "@probo/ui";
export const samlConfigurationFormQuery = graphql`
query EditSAMLConfigurationFormQuery($samlConfigurationId: ID!) {
@@ -60,6 +63,8 @@ export function EditSAMLConfigurationForm(props: {
const { onUpdate, queryRef } = props;
const organizationId = useOrganizationId();
const { __ } = useTranslate();
const { toast } = useToast();
const { samlConfiguration } =
usePreloadedQuery<EditSAMLConfigurationFormQuery>(
@@ -94,10 +99,24 @@ export function EditSAMLConfigurationForm(props: {
attributeMappings: data.attributeMappings,
},
},
onCompleted: onUpdate,
onCompleted: (_, e) => {
if (e) {
toast({
variant: "error",
title: __("Error"),
description: formatError(
__("Failed to update SAML configuration"),
e,
),
});
return;
}
onUpdate();
},
});
},
[onUpdate, organizationId, samlConfiguration.id, update],
[onUpdate, organizationId, samlConfiguration.id, update, __, toast],
);
return (

View File

@@ -7,6 +7,9 @@ import {
import { useOrganizationId } from "/hooks/useOrganizationId";
import { useCallback } from "react";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
import { useToast } from "@probo/ui";
import { formatError } from "@probo/helpers";
import { useTranslate } from "@probo/i18n";
const createSAMLConfigurationMutation = graphql`
mutation NewSAMLConfigurationForm_createMutation(
@@ -32,6 +35,9 @@ export function NewSAMLConfigurationForm(props: { onCreate: () => void }) {
const { onCreate } = props;
const organizationId = useOrganizationId();
const { __ } = useTranslate();
const { toast } = useToast();
const [create, isCreating] =
useMutationWithToasts<NewSAMLConfigurationForm_createMutation>(
createSAMLConfigurationMutation,
@@ -61,10 +67,24 @@ export function NewSAMLConfigurationForm(props: { onCreate: () => void }) {
},
connections: [connectionID],
},
onCompleted: onCreate,
onCompleted: (response, e) => {
if (e) {
toast({
variant: "error",
title: __("Error"),
description: formatError(
__("Failed to create SAML configuration"),
e,
),
});
return;
}
onCreate();
},
});
},
[organizationId, create, onCreate],
[organizationId, create, onCreate, __, toast],
);
return (

View File

@@ -15,6 +15,8 @@ import type { SCIMConfigurationCreateMutation } from "/__generated__/iam/SCIMCon
import type { SCIMConfigurationDeleteMutation } from "/__generated__/iam/SCIMConfigurationDeleteMutation.graphql";
import type { SCIMConfigurationRegenerateTokenMutation } from "/__generated__/iam/SCIMConfigurationRegenerateTokenMutation.graphql";
import type { SCIMConfigurationFragment$key } from "/__generated__/iam/SCIMConfigurationFragment.graphql";
import { useOrganizationId } from "/hooks/useOrganizationId";
import { formatError } from "@probo/helpers";
const SCIMConfigurationFragment = graphql`
fragment SCIMConfigurationFragment on SCIMConfiguration {
@@ -72,12 +74,14 @@ const regenerateSCIMTokenMutation = graphql`
`;
export function SCIMConfiguration(props: {
organizationId: string;
fKey: SCIMConfigurationFragment$key | null;
canCreate: boolean;
canDelete: boolean;
}) {
const { organizationId, canCreate, canDelete, fKey } = props;
const { canCreate, canDelete, fKey } = props;
const organizationId = useOrganizationId();
const scimConfiguration = useFragment(SCIMConfigurationFragment, fKey);
const { __ } = useTranslate();
const { toast } = useToast();
@@ -88,15 +92,15 @@ export function SCIMConfiguration(props: {
const [createSCIMConfiguration, isCreatingSAMLConfiguration] =
useMutation<SCIMConfigurationCreateMutation>(
createSCIMConfigurationMutation
createSCIMConfigurationMutation,
);
const [deleteSCIMConfiguration, isDeletingSCIMConfiguration] =
useMutation<SCIMConfigurationDeleteMutation>(
deleteSCIMConfigurationMutation
deleteSCIMConfigurationMutation,
);
const [regenerateSCIMToken, isRegeneratingSCIMToken] =
useMutation<SCIMConfigurationRegenerateTokenMutation>(
regenerateSCIMTokenMutation
regenerateSCIMTokenMutation,
);
const handleCreate = () => {
@@ -106,14 +110,26 @@ export function SCIMConfiguration(props: {
organizationId,
},
},
onCompleted: (response) => {
onCompleted: (response, e) => {
if (e) {
toast({
variant: "error",
title: __("Error"),
description: formatError(
__("SCIM configuration creation failed"),
e,
),
});
return;
}
if (response.createSCIMConfiguration) {
setToken(response.createSCIMConfiguration.token);
}
toast({
title: __("SCIM Configuration Created"),
description: __(
"Copy the bearer token now. It will not be shown again."
"Copy the bearer token now. It will not be shown again.",
),
variant: "success",
});
@@ -144,7 +160,7 @@ export function SCIMConfiguration(props: {
toast({
title: __("SCIM Configuration Deleted"),
description: __(
"All SCIM-provisioned memberships have been changed to manual source."
"All SCIM-provisioned memberships have been changed to manual source.",
),
variant: "success",
});
@@ -176,7 +192,7 @@ export function SCIMConfiguration(props: {
toast({
title: __("Bearer Token Regenerated"),
description: __(
"Copy the new bearer token now. It will not be shown again."
"Copy the new bearer token now. It will not be shown again.",
),
variant: "success",
});
@@ -208,7 +224,7 @@ export function SCIMConfiguration(props: {
<h3 className="font-medium">{__("SCIM is not configured")}</h3>
<p className="text-sm text-txt-secondary mt-1">
{__(
"Enable SCIM to automatically provision users from your identity provider."
"Enable SCIM to automatically provision users from your identity provider.",
)}
</p>
</div>
@@ -236,7 +252,7 @@ export function SCIMConfiguration(props: {
<h3 className="font-medium">{__("SCIM Provisioning Active")}</h3>
<p className="text-sm text-txt-secondary">
{__(
"Automatic user provisioning is enabled for this organization."
"Automatic user provisioning is enabled for this organization.",
)}
</p>
</div>
@@ -256,7 +272,7 @@ export function SCIMConfiguration(props: {
onClick={() =>
copyToClipboard(
scimConfiguration.endpointUrl,
__("SCIM Endpoint URL")
__("SCIM Endpoint URL"),
)
}
icon={IconSquareBehindSquare2}
@@ -318,7 +334,7 @@ export function SCIMConfiguration(props: {
<div className="p-4 space-y-4">
<p>
{__(
"Are you sure you want to delete the SCIM configuration? This will:"
"Are you sure you want to delete the SCIM configuration? This will:",
)}
</p>
<ul className="list-disc list-inside text-sm space-y-1">
@@ -330,7 +346,7 @@ export function SCIMConfiguration(props: {
</ul>
<p className="text-sm text-txt-secondary">
{__(
"Existing users will not be removed, only their membership source will change."
"Existing users will not be removed, only their membership source will change.",
)}
</p>
<div className="flex justify-end gap-2">