@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -103,7 +103,6 @@ function NavigateToSSOLoginURL(props: {
|
||||
|
||||
useEffect(() => {
|
||||
if (!ssoLoginURL.ok) {
|
||||
console.log(ssoLoginURL);
|
||||
toast({
|
||||
title: __("Error"),
|
||||
description:
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user