From 64640a34278b75bd86603bd6accb298a36efcc94 Mon Sep 17 00:00:00 2001 From: Dhruwang Date: Thu, 25 Dec 2025 17:00:21 +0530 Subject: [PATCH] refactor: enhance error handling and type safety in contact attribute actions - Introduced specific error handling using ResourceNotFoundError for missing contact attribute keys. - Updated action input types for create, update, and delete contact attribute key actions to improve type safety. - Refactored error handling in create and update attribute modals to use try-catch blocks for better user feedback. --- .../modules/ee/contacts/attributes/actions.ts | 52 ++++++++++++------- .../components/attributes-table.tsx | 3 +- .../components/create-attribute-modal.tsx | 34 +++++++----- .../components/edit-attribute-modal.tsx | 32 +++++++----- .../ee/contacts/lib/contact-attribute-keys.ts | 5 +- 5 files changed, 76 insertions(+), 50 deletions(-) diff --git a/apps/web/modules/ee/contacts/attributes/actions.ts b/apps/web/modules/ee/contacts/attributes/actions.ts index 2f42f04881..322d16b5fb 100644 --- a/apps/web/modules/ee/contacts/attributes/actions.ts +++ b/apps/web/modules/ee/contacts/attributes/actions.ts @@ -2,42 +2,44 @@ import { z } from "zod"; import { ZId } from "@formbricks/types/common"; +import { ResourceNotFoundError } from "@formbricks/types/errors"; import { authenticatedActionClient } from "@/lib/utils/action-client"; import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware"; import { AuthenticatedActionClientCtx } from "@/lib/utils/action-client/types/context"; -import { - getOrganizationIdFromEnvironmentId, - getProjectIdFromEnvironmentId, -} from "@/lib/utils/helper"; +import { getOrganizationIdFromEnvironmentId, getProjectIdFromEnvironmentId } from "@/lib/utils/helper"; import { isSafeIdentifier } from "@/lib/utils/safe-identifier"; import { withAuditLogging } from "@/modules/ee/audit-logs/lib/handler"; import { createContactAttributeKey, - updateContactAttributeKey, deleteContactAttributeKey, getContactAttributeKeyById, + updateContactAttributeKey, } from "@/modules/ee/contacts/lib/contact-attribute-keys"; const ZCreateContactAttributeKeyAction = z.object({ environmentId: ZId, - key: z.string().refine( - (val) => isSafeIdentifier(val), - { - message: - "Key must be a safe identifier: only lowercase letters, numbers, and underscores, and must start with a letter", - } - ), + key: z.string().refine((val) => isSafeIdentifier(val), { + message: + "Key must be a safe identifier: only lowercase letters, numbers, and underscores, and must start with a letter", + }), name: z.string().optional(), description: z.string().optional(), }); +type TCreateContactAttributeKeyActionInput = z.infer; export const createContactAttributeKeyAction = authenticatedActionClient .schema(ZCreateContactAttributeKeyAction) .action( withAuditLogging( "created", "contactAttributeKey", - async ({ ctx, parsedInput }: { ctx: AuthenticatedActionClientCtx; parsedInput: Record }) => { + async ({ + ctx, + parsedInput, + }: { + ctx: AuthenticatedActionClientCtx; + parsedInput: TCreateContactAttributeKeyActionInput; + }) => { const organizationId = await getOrganizationIdFromEnvironmentId(parsedInput.environmentId); const projectId = await getProjectIdFromEnvironmentId(parsedInput.environmentId); @@ -78,19 +80,25 @@ const ZUpdateContactAttributeKeyAction = z.object({ name: z.string().optional(), description: z.string().optional(), }); - +type TUpdateContactAttributeKeyActionInput = z.infer; export const updateContactAttributeKeyAction = authenticatedActionClient .schema(ZUpdateContactAttributeKeyAction) .action( withAuditLogging( "updated", "contactAttributeKey", - async ({ ctx, parsedInput }: { ctx: AuthenticatedActionClientCtx; parsedInput: Record }) => { + async ({ + ctx, + parsedInput, + }: { + ctx: AuthenticatedActionClientCtx; + parsedInput: TUpdateContactAttributeKeyActionInput; + }) => { // Fetch existing key to check authorization and get environmentId const existingKey = await getContactAttributeKeyById(parsedInput.id); if (!existingKey) { - throw new Error("Contact attribute key not found"); + throw new ResourceNotFoundError("contactAttributeKey", parsedInput.id); } const organizationId = await getOrganizationIdFromEnvironmentId(existingKey.environmentId); @@ -130,6 +138,7 @@ export const updateContactAttributeKeyAction = authenticatedActionClient const ZDeleteContactAttributeKeyAction = z.object({ id: ZId, }); +type TDeleteContactAttributeKeyActionInput = z.infer; export const deleteContactAttributeKeyAction = authenticatedActionClient .schema(ZDeleteContactAttributeKeyAction) @@ -137,12 +146,18 @@ export const deleteContactAttributeKeyAction = authenticatedActionClient withAuditLogging( "deleted", "contactAttributeKey", - async ({ ctx, parsedInput }: { ctx: AuthenticatedActionClientCtx; parsedInput: Record }) => { + async ({ + ctx, + parsedInput, + }: { + ctx: AuthenticatedActionClientCtx; + parsedInput: TDeleteContactAttributeKeyActionInput; + }) => { // Fetch existing key to check authorization and get environmentId const existingKey = await getContactAttributeKeyById(parsedInput.id); if (!existingKey) { - throw new Error("Contact attribute key not found"); + throw new ResourceNotFoundError("contactAttributeKey", parsedInput.id); } const organizationId = await getOrganizationIdFromEnvironmentId(existingKey.environmentId); @@ -173,4 +188,3 @@ export const deleteContactAttributeKeyAction = authenticatedActionClient } ) ); - diff --git a/apps/web/modules/ee/contacts/attributes/components/attributes-table.tsx b/apps/web/modules/ee/contacts/attributes/components/attributes-table.tsx index 18ce61271a..3697de4c41 100644 --- a/apps/web/modules/ee/contacts/attributes/components/attributes-table.tsx +++ b/apps/web/modules/ee/contacts/attributes/components/attributes-table.tsx @@ -16,7 +16,6 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import { VisibilityState, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { useRouter } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; -import { toast } from "react-hot-toast"; import { useTranslation } from "react-i18next"; import { TContactAttributeKey } from "@formbricks/types/contact-attribute-key"; import { TUserLocale } from "@formbricks/types/user"; @@ -220,7 +219,7 @@ export const AttributesTable = ({ const deleteContactAttributeKeyResponse = await deleteContactAttributeKeyAction({ id: attributeId }); if (!deleteContactAttributeKeyResponse?.data) { const errorMessage = getFormattedErrorMessage(deleteContactAttributeKeyResponse); - toast.error(errorMessage); + throw new Error(errorMessage); } }; diff --git a/apps/web/modules/ee/contacts/attributes/components/create-attribute-modal.tsx b/apps/web/modules/ee/contacts/attributes/components/create-attribute-modal.tsx index 909cf54b96..6b6dc11e77 100644 --- a/apps/web/modules/ee/contacts/attributes/components/create-attribute-modal.tsx +++ b/apps/web/modules/ee/contacts/attributes/components/create-attribute-modal.tsx @@ -85,24 +85,30 @@ export function CreateAttributeModal({ environmentId }: Readonly) => { diff --git a/apps/web/modules/ee/contacts/attributes/components/edit-attribute-modal.tsx b/apps/web/modules/ee/contacts/attributes/components/edit-attribute-modal.tsx index 29710a9ba3..db5d7a7657 100644 --- a/apps/web/modules/ee/contacts/attributes/components/edit-attribute-modal.tsx +++ b/apps/web/modules/ee/contacts/attributes/components/edit-attribute-modal.tsx @@ -36,23 +36,29 @@ export function EditAttributeModal({ attribute, open, setOpen }: Readonly { setIsUpdating(true); - const updateContactAttributeKeyResponse = await updateContactAttributeKeyAction({ - id: attribute.id, - name: formData.name || undefined, - description: formData.description || undefined, - }); - if (!updateContactAttributeKeyResponse?.data) { - const errorMessage = getFormattedErrorMessage(updateContactAttributeKeyResponse); + try { + const updateContactAttributeKeyResponse = await updateContactAttributeKeyAction({ + id: attribute.id, + name: formData.name || undefined, + description: formData.description || undefined, + }); + + if (!updateContactAttributeKeyResponse?.data) { + const errorMessage = getFormattedErrorMessage(updateContactAttributeKeyResponse); + toast.error(errorMessage); + return; + } + + toast.success(t("environments.contacts.attribute_updated_successfully")); + setOpen(false); + router.refresh(); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : t("common.something_went_wrong"); toast.error(errorMessage); + } finally { setIsUpdating(false); - return; } - - toast.success(t("environments.contacts.attribute_updated_successfully")); - setOpen(false); - router.refresh(); - setIsUpdating(false); }; const handleSubmit = async (e: React.FormEvent) => { diff --git a/apps/web/modules/ee/contacts/lib/contact-attribute-keys.ts b/apps/web/modules/ee/contacts/lib/contact-attribute-keys.ts index 4d43692f2c..1e3c2423da 100644 --- a/apps/web/modules/ee/contacts/lib/contact-attribute-keys.ts +++ b/apps/web/modules/ee/contacts/lib/contact-attribute-keys.ts @@ -2,7 +2,7 @@ import { cache as reactCache } from "react"; import { prisma } from "@formbricks/database"; import { PrismaErrorType } from "@formbricks/database/types/error"; import { TContactAttributeKey } from "@formbricks/types/contact-attribute-key"; -import { DatabaseError, OperationNotAllowedError, ResourceNotFoundError } from "@formbricks/types/errors"; +import { InvalidInputError, OperationNotAllowedError, ResourceNotFoundError } from "@formbricks/types/errors"; export const getContactAttributeKeys = reactCache( async (environmentId: string): Promise => { @@ -44,7 +44,7 @@ export const createContactAttributeKey = async (data: { } catch (error) { if (error instanceof Error && "code" in error) { if (error.code === PrismaErrorType.UniqueConstraintViolation) { - throw new DatabaseError("Attribute key already exists"); + throw new InvalidInputError("Attribute key already exists"); } } throw error; @@ -63,6 +63,7 @@ export const updateContactAttributeKey = async ( }); if (!existingKey) { + console.log("throwing resource not found error"); throw new ResourceNotFoundError("contactAttributeKey", id); }