mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-08 02:58:40 -06:00
fix: check serverError before showing success toast
Server actions return HTTP 200 with serverError field on failure. Frontend was only checking HTTP status, not the response body, causing success toasts to show when operations actually failed. Fixed handlers: - handleDeleteSurvey (survey-dropdown-menu.tsx) - handleLeaveOrganization (organization-actions.tsx) - handleDeleteMember (member-actions.tsx) - handleDeleteTeam (delete-team.tsx) - handleDeleteSegment (segment-settings.tsx) - performLanguageDeletion, handleSaveChanges (edit-language.tsx) - onSubmit, handleDeleteAction (ActionSettingsTab.tsx) - handleSaveSegment (targeting-card.tsx) Each now checks result?.serverError before showing success toast. Fixes #3302
This commit is contained in:
@@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { TOrganization } from "@formbricks/types/organizations";
|
||||
import { deleteOrganizationAction } from "@/app/(app)/environments/[environmentId]/settings/(organization)/general/actions";
|
||||
import { FORMBRICKS_ENVIRONMENT_ID_LS } from "@/lib/localStorage";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { Alert, AlertDescription } from "@/modules/ui/components/alert";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { DeleteDialog } from "@/modules/ui/components/delete-dialog";
|
||||
@@ -32,7 +33,12 @@ export const DeleteOrganization = ({
|
||||
setIsDeleting(true);
|
||||
|
||||
try {
|
||||
await deleteOrganizationAction({ organizationId: organization.id });
|
||||
const result = await deleteOrganizationAction({ organizationId: organization.id });
|
||||
if (result?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(result));
|
||||
setIsDeleting(false);
|
||||
return;
|
||||
}
|
||||
toast.success(t("environments.settings.general.organization_deleted_successfully"));
|
||||
if (typeof localStorage !== "undefined") {
|
||||
localStorage.removeItem(FORMBRICKS_ENVIRONMENT_ID_LS);
|
||||
|
||||
@@ -21,6 +21,7 @@ import { createOrUpdateIntegrationAction } from "@/app/(app)/environments/[envir
|
||||
import { BaseSelectDropdown } from "@/app/(app)/environments/[environmentId]/workspace/integrations/airtable/components/BaseSelectDropdown";
|
||||
import { fetchTables } from "@/app/(app)/environments/[environmentId]/workspace/integrations/airtable/lib/airtable";
|
||||
import AirtableLogo from "@/images/airtableLogo.svg";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { recallToHeadline } from "@/lib/utils/recall";
|
||||
import { getElementsFromBlocks } from "@/modules/survey/lib/client-utils";
|
||||
import { AdditionalIntegrationSettings } from "@/modules/ui/components/additional-integration-settings";
|
||||
@@ -268,7 +269,14 @@ export const AddIntegrationModal = ({
|
||||
airtableIntegrationData.config?.data.push(integrationData);
|
||||
}
|
||||
|
||||
await createOrUpdateIntegrationAction({ environmentId, integrationData: airtableIntegrationData });
|
||||
const result = await createOrUpdateIntegrationAction({
|
||||
environmentId,
|
||||
integrationData: airtableIntegrationData,
|
||||
});
|
||||
if (result?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(result));
|
||||
return;
|
||||
}
|
||||
if (isEditMode) {
|
||||
toast.success(t("environments.integrations.integration_updated_successfully"));
|
||||
} else {
|
||||
@@ -304,7 +312,11 @@ export const AddIntegrationModal = ({
|
||||
const integrationData = structuredClone(airtableIntegrationData);
|
||||
integrationData.config.data.splice(index, 1);
|
||||
|
||||
await createOrUpdateIntegrationAction({ environmentId, integrationData });
|
||||
const result = await createOrUpdateIntegrationAction({ environmentId, integrationData });
|
||||
if (result?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(result));
|
||||
return;
|
||||
}
|
||||
handleClose();
|
||||
router.refresh();
|
||||
|
||||
|
||||
@@ -165,7 +165,14 @@ export const AddIntegrationModal = ({
|
||||
// create action
|
||||
googleSheetIntegrationData.config.data.push(integrationData);
|
||||
}
|
||||
await createOrUpdateIntegrationAction({ environmentId, integrationData: googleSheetIntegrationData });
|
||||
const result = await createOrUpdateIntegrationAction({
|
||||
environmentId,
|
||||
integrationData: googleSheetIntegrationData,
|
||||
});
|
||||
if (result?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(result));
|
||||
return;
|
||||
}
|
||||
if (selectedIntegration) {
|
||||
toast.success(t("environments.integrations.integration_updated_successfully"));
|
||||
} else {
|
||||
@@ -205,7 +212,14 @@ export const AddIntegrationModal = ({
|
||||
googleSheetIntegrationData.config.data.splice(selectedIntegration!.index, 1);
|
||||
try {
|
||||
setIsDeleting(true);
|
||||
await createOrUpdateIntegrationAction({ environmentId, integrationData: googleSheetIntegrationData });
|
||||
const result = await createOrUpdateIntegrationAction({
|
||||
environmentId,
|
||||
integrationData: googleSheetIntegrationData,
|
||||
});
|
||||
if (result?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(result));
|
||||
return;
|
||||
}
|
||||
toast.success(t("environments.integrations.integration_removed_successfully"));
|
||||
setOpen(false);
|
||||
} catch (error) {
|
||||
@@ -266,7 +280,7 @@ export const AddIntegrationModal = ({
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="Surveys">{t("common.questions")}</Label>
|
||||
<div className="mt-1 max-h-[15vh] overflow-x-hidden overflow-y-auto rounded-lg border border-slate-200">
|
||||
<div className="mt-1 max-h-[15vh] overflow-y-auto overflow-x-hidden rounded-lg border border-slate-200">
|
||||
<div className="grid content-center rounded-lg bg-slate-50 p-3 text-left text-sm text-slate-900">
|
||||
{surveyElements.map((question) => (
|
||||
<div key={question.id} className="my-1 flex items-center space-x-2">
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
createEmptyMapping,
|
||||
} from "@/app/(app)/environments/[environmentId]/workspace/integrations/notion/components/MappingRow";
|
||||
import NotionLogo from "@/images/notion.png";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { recallToHeadline } from "@/lib/utils/recall";
|
||||
import { getElementsFromBlocks } from "@/modules/survey/lib/client-utils";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
@@ -217,7 +218,14 @@ export const AddIntegrationModal = ({
|
||||
notionIntegrationData.config.data.push(integrationData);
|
||||
}
|
||||
|
||||
await createOrUpdateIntegrationAction({ environmentId, integrationData: notionIntegrationData });
|
||||
const result = await createOrUpdateIntegrationAction({
|
||||
environmentId,
|
||||
integrationData: notionIntegrationData,
|
||||
});
|
||||
if (result?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(result));
|
||||
return;
|
||||
}
|
||||
if (selectedIntegration) {
|
||||
toast.success(t("environments.integrations.integration_updated_successfully"));
|
||||
} else {
|
||||
@@ -236,7 +244,14 @@ export const AddIntegrationModal = ({
|
||||
notionIntegrationData.config.data.splice(selectedIntegration!.index, 1);
|
||||
try {
|
||||
setIsDeleting(true);
|
||||
await createOrUpdateIntegrationAction({ environmentId, integrationData: notionIntegrationData });
|
||||
const result = await createOrUpdateIntegrationAction({
|
||||
environmentId,
|
||||
integrationData: notionIntegrationData,
|
||||
});
|
||||
if (result?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(result));
|
||||
return;
|
||||
}
|
||||
toast.success(t("environments.integrations.integration_removed_successfully"));
|
||||
setOpen(false);
|
||||
} catch (error) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { getTextContent } from "@formbricks/types/surveys/validation";
|
||||
import { createOrUpdateIntegrationAction } from "@/app/(app)/environments/[environmentId]/workspace/integrations/actions";
|
||||
import SlackLogo from "@/images/slacklogo.png";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { recallToHeadline } from "@/lib/utils/recall";
|
||||
import { getElementsFromBlocks } from "@/modules/survey/lib/client-utils";
|
||||
import { AdditionalIntegrationSettings } from "@/modules/ui/components/additional-integration-settings";
|
||||
@@ -144,7 +145,14 @@ export const AddChannelMappingModal = ({
|
||||
// create action
|
||||
slackIntegrationData.config.data.push(integrationData);
|
||||
}
|
||||
await createOrUpdateIntegrationAction({ environmentId, integrationData: slackIntegrationData });
|
||||
const result = await createOrUpdateIntegrationAction({
|
||||
environmentId,
|
||||
integrationData: slackIntegrationData,
|
||||
});
|
||||
if (result?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(result));
|
||||
return;
|
||||
}
|
||||
if (selectedIntegration) {
|
||||
toast.success(t("environments.integrations.integration_updated_successfully"));
|
||||
} else {
|
||||
@@ -181,7 +189,14 @@ export const AddChannelMappingModal = ({
|
||||
slackIntegrationData.config.data.splice(selectedIntegration!.index, 1);
|
||||
try {
|
||||
setIsDeleting(true);
|
||||
await createOrUpdateIntegrationAction({ environmentId, integrationData: slackIntegrationData });
|
||||
const result = await createOrUpdateIntegrationAction({
|
||||
environmentId,
|
||||
integrationData: slackIntegrationData,
|
||||
});
|
||||
if (result?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(result));
|
||||
return;
|
||||
}
|
||||
toast.success(t("environments.integrations.integration_removed_successfully"));
|
||||
setOpen(false);
|
||||
} catch (error) {
|
||||
|
||||
@@ -109,7 +109,13 @@ export function SegmentSettings({
|
||||
const handleDeleteSegment = async () => {
|
||||
try {
|
||||
setIsDeletingSegment(true);
|
||||
await deleteSegmentAction({ segmentId: segment.id });
|
||||
const result = await deleteSegmentAction({ segmentId: segment.id });
|
||||
|
||||
if (result?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(result));
|
||||
setIsDeletingSegment(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsDeletingSegment(false);
|
||||
toast.success(t("environments.segments.segment_deleted_successfully"));
|
||||
|
||||
@@ -17,6 +17,7 @@ import type {
|
||||
import type { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { cn } from "@/lib/cn";
|
||||
import { structuredClone } from "@/lib/pollyfills/structuredClone";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import {
|
||||
cloneSegmentAction,
|
||||
createSegmentAction,
|
||||
@@ -135,7 +136,11 @@ export function TargetingCard({
|
||||
const handleSaveSegment = async (data: TSegmentUpdateInput) => {
|
||||
try {
|
||||
if (!segment) throw new Error(t("environments.segments.invalid_segment"));
|
||||
await updateSegmentAction({ segmentId: segment.id, environmentId, data });
|
||||
const result = await updateSegmentAction({ segmentId: segment.id, environmentId, data });
|
||||
if (result?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(result));
|
||||
return;
|
||||
}
|
||||
toast.success(t("environments.segments.segment_saved_successfully"));
|
||||
|
||||
setIsSegmentEditorOpen(false);
|
||||
|
||||
@@ -154,7 +154,12 @@ export function EditLanguage({
|
||||
|
||||
const performLanguageDeletion = async (languageId: string) => {
|
||||
try {
|
||||
await deleteLanguageAction({ languageId, projectId: project.id });
|
||||
const result = await deleteLanguageAction({ languageId, projectId: project.id });
|
||||
if (result?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(result));
|
||||
setConfirmationModal((prev) => ({ ...prev, isOpen: false }));
|
||||
return;
|
||||
}
|
||||
setLanguages((prev) => prev.filter((lang) => lang.id !== languageId));
|
||||
toast.success(t("environments.workspace.languages.language_deleted_successfully"));
|
||||
// Close the modal after deletion
|
||||
@@ -187,7 +192,7 @@ export function EditLanguage({
|
||||
|
||||
const handleSaveChanges = async () => {
|
||||
if (!validateLanguages(languages, t)) return;
|
||||
await Promise.all(
|
||||
const results = await Promise.all(
|
||||
languages.map((lang) => {
|
||||
return lang.id === "new"
|
||||
? createLanguageAction({
|
||||
@@ -201,6 +206,11 @@ export function EditLanguage({
|
||||
});
|
||||
})
|
||||
);
|
||||
const errorResult = results.find((result) => result?.serverError);
|
||||
if (errorResult) {
|
||||
toast.error(getFormattedErrorMessage(errorResult));
|
||||
return;
|
||||
}
|
||||
toast.success(t("environments.workspace.languages.languages_updated_successfully"));
|
||||
router.refresh();
|
||||
setIsEditing(false);
|
||||
@@ -239,7 +249,7 @@ export function EditLanguage({
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<p className="text-sm text-slate-500 italic">
|
||||
<p className="text-sm italic text-slate-500">
|
||||
{t("environments.workspace.languages.no_language_found")}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { deleteTeamAction } from "@/modules/ee/teams/team-list/actions";
|
||||
import { TTeam } from "@/modules/ee/teams/team-list/types/team";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
@@ -27,6 +28,12 @@ export const DeleteTeam = ({ teamId, onDelete, isOwnerOrManager }: DeleteTeamPro
|
||||
setIsDeleting(true);
|
||||
|
||||
const deleteTeamActionResponse = await deleteTeamAction({ teamId });
|
||||
if (deleteTeamActionResponse?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(deleteTeamActionResponse));
|
||||
setIsDeleteDialogOpen(false);
|
||||
setIsDeleting(false);
|
||||
return;
|
||||
}
|
||||
if (deleteTeamActionResponse?.data) {
|
||||
toast.success(t("environments.settings.teams.team_deleted_successfully"));
|
||||
onDelete?.();
|
||||
|
||||
@@ -42,14 +42,27 @@ export const MemberActions = ({ organization, member, invite, showDeleteButton }
|
||||
if (!member && invite) {
|
||||
// This is an invite
|
||||
|
||||
await deleteInviteAction({ inviteId: invite?.id, organizationId: organization.id });
|
||||
const result = await deleteInviteAction({ inviteId: invite?.id, organizationId: organization.id });
|
||||
if (result?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(result));
|
||||
setIsDeleting(false);
|
||||
return;
|
||||
}
|
||||
toast.success(t("environments.settings.general.invite_deleted_successfully"));
|
||||
}
|
||||
|
||||
if (member && !invite) {
|
||||
// This is a member
|
||||
|
||||
await deleteMembershipAction({ userId: member.userId, organizationId: organization.id });
|
||||
const result = await deleteMembershipAction({
|
||||
userId: member.userId,
|
||||
organizationId: organization.id,
|
||||
});
|
||||
if (result?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(result));
|
||||
setIsDeleting(false);
|
||||
return;
|
||||
}
|
||||
toast.success(t("environments.settings.general.member_deleted_successfully"));
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,12 @@ export const OrganizationActions = ({
|
||||
const handleLeaveOrganization = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await leaveOrganizationAction({ organizationId: organization.id });
|
||||
const result = await leaveOrganizationAction({ organizationId: organization.id });
|
||||
if (result?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(result));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
toast.success(t("environments.settings.general.member_deleted_successfully"));
|
||||
router.refresh();
|
||||
setLoading(false);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { FormProvider, useForm } from "react-hook-form";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TActionClass, TActionClassInput } from "@formbricks/types/action-classes";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import {
|
||||
deleteActionClassAction,
|
||||
updateActionClassAction,
|
||||
@@ -92,10 +93,14 @@ export const ActionSettingsTab = ({
|
||||
validatePermissions(isReadOnly, t);
|
||||
const updatedAction = buildActionObject(data, actionClass.environmentId, t);
|
||||
|
||||
await updateActionClassAction({
|
||||
const result = await updateActionClassAction({
|
||||
actionClassId: actionClass.id,
|
||||
updatedAction: updatedAction,
|
||||
});
|
||||
if (result?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(result));
|
||||
return;
|
||||
}
|
||||
setOpen(false);
|
||||
router.refresh();
|
||||
toast.success(t("environments.actions.action_updated_successfully"));
|
||||
@@ -109,7 +114,11 @@ export const ActionSettingsTab = ({
|
||||
const handleDeleteAction = async () => {
|
||||
try {
|
||||
setIsDeletingAction(true);
|
||||
await deleteActionClassAction({ actionClassId: actionClass.id });
|
||||
const result = await deleteActionClassAction({ actionClassId: actionClass.id });
|
||||
if (result?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(result));
|
||||
return;
|
||||
}
|
||||
router.refresh();
|
||||
toast.success(t("environments.actions.action_deleted_successfully"));
|
||||
setOpen(false);
|
||||
|
||||
@@ -69,7 +69,11 @@ export const SurveyDropDownMenu = ({
|
||||
const handleDeleteSurvey = async (surveyId: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await deleteSurveyAction({ surveyId });
|
||||
const result = await deleteSurveyAction({ surveyId });
|
||||
if (result?.serverError) {
|
||||
toast.error(getFormattedErrorMessage(result));
|
||||
return;
|
||||
}
|
||||
deleteSurvey(surveyId);
|
||||
toast.success(t("environments.surveys.survey_deleted_successfully"));
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user