Manage errors

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-10-29 14:03:50 +01:00
parent 55743cbb5c
commit 9a33f7b771
84 changed files with 1483 additions and 331 deletions

View File

@@ -2,9 +2,9 @@ import { z } from "zod";
import { useFormWithSchema } from "../useFormWithSchema";
export const documentSchema = z.object({
title: z.string(),
content: z.string(),
ownerId: z.string(),
title: z.string().min(1, "Title is required"),
content: z.string().min(1, "Content is required"),
ownerId: z.string().min(1, "Owner is required"),
documentType: z.enum(["OTHER", "ISMS", "POLICY", "PROCEDURE"]),
classification: z.enum(["PUBLIC", "INTERNAL", "CONFIDENTIAL", "SECRET"]),
});

View File

@@ -32,10 +32,10 @@ export type RiskKey = useRiskFormFragment$key & { id: string };
// Export the schema so it can be used elsewhere
export const riskSchema = z.object({
category: z.string(),
name: z.string(),
description: z.string(),
ownerId: z.string(),
category: z.string().min(1, "Category is required"),
name: z.string().min(1, "Name is required"),
description: z.string().min(1, "Description is required"),
ownerId: z.string().min(1, "Owner is required"),
treatment: z.enum(["AVOIDED", "MITIGATED", "TRANSFERRED", "ACCEPTED"]),
inherentLikelihood: z.number({ coerce: true }).min(1).max(5),
inherentImpact: z.number({ coerce: true }).min(1).max(5),

View File

@@ -8,21 +8,21 @@ import { useTranslate } from "@probo/i18n";
import { useEffect, useMemo } from "react";
const schema = z.object({
name: z.string(),
description: z.string(),
name: z.string().min(1, "Name is required"),
description: z.string().min(1, "Description is required"),
category: z.string().nullish(),
statusPageUrl: z.string(),
termsOfServiceUrl: z.string(),
privacyPolicyUrl: z.string(),
serviceLevelAgreementUrl: z.string(),
dataProcessingAgreementUrl: z.string(),
websiteUrl: z.string(),
legalName: z.string(),
headquarterAddress: z.string(),
statusPageUrl: z.string().optional(),
termsOfServiceUrl: z.string().optional(),
privacyPolicyUrl: z.string().optional(),
serviceLevelAgreementUrl: z.string().optional(),
dataProcessingAgreementUrl: z.string().optional(),
websiteUrl: z.string().optional(),
legalName: z.string().optional(),
headquarterAddress: z.string().optional(),
certifications: z.array(z.string()),
countries: z.array(z.string()),
securityPageUrl: z.string(),
trustPageUrl: z.string(),
securityPageUrl: z.string().optional(),
trustPageUrl: z.string().optional(),
businessOwnerId: z.string().nullish(),
securityOwnerId: z.string().nullish(),
});
@@ -70,7 +70,7 @@ export function useVendorForm(vendorKey: useVendorFormFragment$key) {
const [mutate] = useMutationWithToasts(vendorUpdateQuery, {
successMessage: __("Vendor updated successfully."),
errorMessage: __("Failed to update vendor. Please try again."),
errorMessage: __("Failed to update vendor"),
});
const defaultValues = useMemo(

View File

@@ -34,7 +34,7 @@ export function useDeleteDocumentMutation() {
deleteDocumentMutation,
{
successMessage: __("Document deleted successfully."),
errorMessage: __("Failed to delete document. Please try again."),
errorMessage: __("Failed to delete document"),
}
);
}
@@ -57,7 +57,7 @@ export function useDeleteDraftDocumentVersionMutation() {
deleteDraftDocumentVersionMutation,
{
successMessage: __("Draft deleted successfully."),
errorMessage: __("Failed to delete draft. Please try again."),
errorMessage: __("Failed to delete draft"),
}
);
}
@@ -79,7 +79,7 @@ export function useBulkDeleteDocumentsMutation() {
bulkDeleteDocumentsMutation,
{
successMessage: __("Documents deleted successfully."),
errorMessage: __("Failed to delete documents. Please try again."),
errorMessage: __("Failed to delete documents"),
}
);
}
@@ -101,7 +101,7 @@ export function useSendSigningNotificationsMutation() {
sendSigningNotificationsMutation,
{
successMessage: __("Signing notifications sent successfully."),
errorMessage: __("Failed to send signing notifications. Please try again."),
errorMessage: __("Failed to send signing notifications"),
}
);
}
@@ -123,7 +123,7 @@ export function useBulkExportDocumentsMutation() {
bulkExportDocumentsMutation,
{
successMessage: __("Document export started successfully. You will receive an email when the export is ready."),
errorMessage: __("Failed to start document export. Please try again."),
errorMessage: __("Failed to start document export"),
}
);
}

View File

@@ -32,7 +32,7 @@ export function useDeleteMeasureMutation() {
deleteMeasureMutation,
{
successMessage: __("Measure deleted successfully."),
errorMessage: __("Failed to delete measure. Please try again."),
errorMessage: __("Failed to delete measure"),
}
);
}
@@ -83,6 +83,6 @@ export const useUpdateMeasure = () => {
return useMutationWithToasts(measureUpdateMutation, {
successMessage: __("Measure updated successfully."),
errorMessage: __("Failed to update measure. Please try again."),
errorMessage: __("Failed to update measure"),
});
};

View File

@@ -33,7 +33,7 @@ export function useDeleteOrganizationMutation() {
deleteOrganizationMutation,
{
successMessage: __("Organization deleted successfully."),
errorMessage: __("Failed to delete organization. Please try again."),
errorMessage: __("Failed to delete organization"),
}
);
}

View File

@@ -10,9 +10,9 @@ import {
import { useMemo } from "react";
import type { PeopleGraphPaginatedQuery } from "./__generated__/PeopleGraphPaginatedQuery.graphql";
import type { PeopleGraphPaginatedFragment$key } from "./__generated__/PeopleGraphPaginatedFragment.graphql";
import { useConfirm } from "@probo/ui";
import { useConfirm, useToast } from "@probo/ui";
import type { PeopleGraphDeleteMutation } from "./__generated__/PeopleGraphDeleteMutation.graphql";
import { promisifyMutation, sprintf } from "@probo/helpers";
import { promisifyMutation, sprintf, formatError, type GraphQLError } from "@probo/helpers";
import { useTranslate } from "@probo/i18n";
const peopleQuery = graphql`
@@ -132,6 +132,7 @@ export const useDeletePeople = (
) => {
const [mutate] = useMutation<PeopleGraphDeleteMutation>(deletePeopleMutation);
const confirm = useConfirm();
const { toast } = useToast();
const { __ } = useTranslate();
return () => {
@@ -147,6 +148,12 @@ export const useDeletePeople = (
},
connections: [connectionId],
},
}).catch((error) => {
toast({
title: __("Error"),
description: formatError(__("Failed to delete people"), error as GraphQLError),
variant: "error",
});
}),
{
message: sprintf(

View File

@@ -26,7 +26,7 @@ export function useDeleteRiskMutation() {
return useMutationWithToasts<RiskGraphDeleteMutation>(deleteRiskMutation, {
successMessage: __("Risk deleted successfully."),
errorMessage: __("Failed to delete risk. Please try again."),
errorMessage: __("Failed to delete risk"),
});
}

View File

@@ -137,7 +137,7 @@ export function useCreateSAMLConfigurationMutation() {
createSAMLConfigurationMutation,
{
successMessage: "SAML configuration created successfully.",
errorMessage: "Failed to create SAML configuration. Please try again.",
errorMessage: "Failed to create SAML configuration",
}
);
}

View File

@@ -22,7 +22,7 @@ export function useTrustCenterAuditUpdate() {
trustCenterAuditUpdateMutation,
{
successMessage: __("Audit visibility updated successfully."),
errorMessage: __("Failed to update audit visibility. Please try again."),
errorMessage: __("Failed to update audit visibility"),
}
);
}

View File

@@ -40,7 +40,7 @@ export function useUpdateDocumentVisibilityMutation() {
updateDocumentVisibilityMutation,
{
successMessage: __("Document visibility updated successfully."),
errorMessage: __("Failed to update document visibility. Please try again."),
errorMessage: __("Failed to update document visibility"),
}
);
}

View File

@@ -22,7 +22,7 @@ export function useTrustCenterVendorUpdate() {
trustCenterVendorUpdateMutation,
{
successMessage: __("Vendor visibility updated successfully."),
errorMessage: __("Failed to update vendor visibility. Please try again."),
errorMessage: __("Failed to update vendor visibility"),
}
);
}

View File

@@ -36,7 +36,7 @@ export function useCreateVendorMutation() {
createVendorMutation,
{
successMessage: __("Vendor created successfully."),
errorMessage: __("Failed to create vendor. Please try again."),
errorMessage: __("Failed to create vendor"),
}
);
}

View File

@@ -10,6 +10,9 @@ import {
type GraphQLTaggedNode,
type MutationParameters,
} from "relay-runtime";
import { useToast } from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { formatError, type GraphQLError } from "@probo/helpers";
const defaultOptions = {
field: "totalCount",
@@ -26,17 +29,27 @@ export function useMutationWithIncrement<T extends MutationParameters>(
node: string;
field?: string;
value?: 1 | -1;
errorMessage?: string;
},
) {
const [mutate, isLoading] = useMutation<T>(query);
const relayEnv = useRelayEnvironment();
const { toast } = useToast();
const { __ } = useTranslate();
const options = { ...defaultOptions, ...baseOptions };
const mutateAndIncrement = useCallback(
(queryOptions: UseMutationConfig<T>) => {
return mutate({
...queryOptions,
onCompleted: (response, error) => {
if (!error) {
if (error) {
const errorTitle = options.errorMessage ?? __("Failed to commit this operation");
toast({
title: __("Error"),
description: formatError(errorTitle, error as GraphQLError[]),
variant: "error",
});
} else {
updateStoreCounter(
relayEnv,
options.id,
@@ -47,9 +60,18 @@ export function useMutationWithIncrement<T extends MutationParameters>(
}
queryOptions.onCompleted?.(response, error);
},
onError: (error) => {
const errorTitle = options.errorMessage ?? __("Failed to commit this operation");
toast({
title: __("Error"),
description: formatError(errorTitle, error as GraphQLError),
variant: "error",
});
queryOptions.onError?.(error);
},
});
},
[mutate, options.id, options.node, options.field, options.value, relayEnv],
[mutate, options.id, options.node, options.field, options.value, options.errorMessage, relayEnv, toast, __],
);
return [mutateAndIncrement, isLoading] as const;

View File

@@ -3,6 +3,7 @@ import { useMutation, type UseMutationConfig } from "react-relay";
import { useToast } from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import type { MutationParameters, GraphQLTaggedNode } from "relay-runtime";
import { formatError, type GraphQLError } from "@probo/helpers";
/**
* A decorated useMutation hook that emits toast notifications on success or error.
@@ -32,11 +33,10 @@ export function useMutationWithToasts<T extends MutationParameters>(
onCompleted: (response, error) => {
options.onCompleted?.(response, error);
if (error) {
const errorTitle = options.errorMessage ?? __("Failed to commit this operation");
toast({
title: __("Error"),
description:
options.errorMessage ??
__("Failed to commit this operation."),
description: formatError(errorTitle, error as GraphQLError[]),
variant: "error",
});
reject(error);
@@ -57,10 +57,10 @@ export function useMutationWithToasts<T extends MutationParameters>(
resolve();
},
onError: (error) => {
const errorTitle = options.errorMessage ?? __("Failed to commit this operation");
toast({
title: __("Error"),
description:
options.errorMessage ?? __("Failed to commit this operation."),
description: formatError(errorTitle, error as GraphQLError),
variant: "error",
});
reject(error);