fix: show copy icon on legacy environmentId, reintroduce duplicate survey action (backport #8061) (#8085)

This commit is contained in:
Dhruwang Jariwala
2026-05-21 12:30:59 +05:30
committed by GitHub
3 changed files with 49 additions and 3 deletions
@@ -1,6 +1,7 @@
"use client";
import { EyeIcon, LinkIcon, MoreVertical, SquarePenIcon, TrashIcon } from "lucide-react";
import { useQueryClient } from "@tanstack/react-query";
import { CopyIcon, EyeIcon, LinkIcon, MoreVertical, SquarePenIcon, TrashIcon } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useMemo, useState } from "react";
@@ -9,9 +10,12 @@ import { useTranslation } from "react-i18next";
import { logger } from "@formbricks/logger";
import { useWorkspace } from "@/app/(app)/workspaces/[workspaceId]/context/workspace-context";
import { cn } from "@/lib/cn";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { getV3ApiErrorMessage } from "@/modules/api/lib/v3-client";
import { EditPublicSurveyAlertDialog } from "@/modules/survey/components/edit-public-survey-alert-dialog";
import { copySurveyLink } from "@/modules/survey/lib/client-utils";
import { copySurveyToOtherWorkspaceAction } from "@/modules/survey/list/actions";
import { surveyKeys } from "@/modules/survey/list/lib/query";
import { TSurveyListItem } from "@/modules/survey/list/types/survey-overview";
import { DeleteDialog } from "@/modules/ui/components/delete-dialog";
import {
@@ -42,9 +46,11 @@ export const SurveyDropDownMenu = ({
const { t } = useTranslation();
const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [isDuplicating, setIsDuplicating] = useState(false);
const [isDropDownOpen, setIsDropDownOpen] = useState(false);
const [isCautionDialogOpen, setIsCautionDialogOpen] = useState(false);
const router = useRouter();
const queryClient = useQueryClient();
const editHref = `/workspaces/${workspace?.id}/surveys/${survey.id}/edit`;
@@ -85,6 +91,29 @@ export const SurveyDropDownMenu = ({
setIsCautionDialogOpen(true);
};
const handleDuplicateSurvey = async () => {
if (!workspace?.id) return;
setIsDuplicating(true);
setIsDropDownOpen(false);
try {
const response = await copySurveyToOtherWorkspaceAction({
surveyId: survey.id,
targetWorkspaceId: workspace.id,
});
if (response?.data) {
toast.success(t("workspace.surveys.survey_duplicated_successfully"));
await queryClient.invalidateQueries({ queryKey: surveyKeys.lists() });
return;
}
toast.error(getFormattedErrorMessage(response));
} catch (error) {
logger.error(error);
toast.error(t("common.something_went_wrong_please_try_again"));
} finally {
setIsDuplicating(false);
}
};
if (!hasVisibleActions) {
return null;
}
@@ -120,6 +149,22 @@ export const SurveyDropDownMenu = ({
</Link>
</DropdownMenuItem>
)}
{canManageSurvey && (
<DropdownMenuItem>
<button
type="button"
data-testid="duplicate-survey"
className={cn("flex w-full items-center", isDuplicating && "cursor-not-allowed opacity-50")}
disabled={isDuplicating}
onClick={(e) => {
e.preventDefault();
void handleDuplicateSurvey();
}}>
<CopyIcon className="mr-2 size-4" />
{t("common.duplicate")}
</button>
</DropdownMenuItem>
)}
{canPreviewOrCopyLink && (
<DropdownMenuItem>
<button
@@ -34,7 +34,6 @@ export const AppConnectionPage = async ({ params }: { params: Promise<{ workspac
<IdBadge
id={workspace.legacyEnvironmentId}
label={t("workspace.app-connection.environment_id_legacy")}
copyDisabled
/>
)}
<IdBadge id={WEBAPP_URL} label={t("workspace.app-connection.webapp_url")} />
+3 -1
View File
@@ -224,7 +224,9 @@ test.describe("Survey overview", () => {
});
await page.locator("[data-testid='survey-dropdown-trigger']").click();
await expect(page.getByText("Duplicate", { exact: true })).toHaveCount(0);
// Duplicate stays visible for users who can manage surveys (works on drafts too —
// it creates another draft via copySurveyToOtherWorkspaceAction).
await expect(page.getByTestId("duplicate-survey")).toBeVisible();
await expect(page.getByText("Copy...", { exact: true })).toHaveCount(0);
await expect(page.getByText("Preview", { exact: true })).toHaveCount(0);
await expect(page.getByTestId("copy-link")).toHaveCount(0);