mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-26 10:42:16 -06:00
Feature/duplicate surveys (#268)
* add duplicate to menu * add duplicate survey API endpoint --------- Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
@@ -10,11 +10,12 @@ import {
|
||||
} from "@/components/shared/DropdownMenu";
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import SurveyStatusIndicator from "@/components/shared/SurveyStatusIndicator";
|
||||
import { deleteSurvey, useSurveys } from "@/lib/surveys/surveys";
|
||||
import { deleteSurvey, duplicateSurvey, useSurveys } from "@/lib/surveys/surveys";
|
||||
import { Badge, ErrorComponent } from "@formbricks/ui";
|
||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||
import {
|
||||
ComputerDesktopIcon,
|
||||
DocumentDuplicateIcon,
|
||||
EllipsisHorizontalIcon,
|
||||
LinkIcon,
|
||||
PencilSquareIcon,
|
||||
@@ -52,6 +53,16 @@ export default function SurveysList({ environmentId }) {
|
||||
}
|
||||
};
|
||||
|
||||
const duplicateSurveyAndRefresh = async (surveyId) => {
|
||||
try {
|
||||
await duplicateSurvey(environmentId, surveyId);
|
||||
mutateSurveys();
|
||||
toast.success("Survey duplicated successfully.");
|
||||
} catch (error) {
|
||||
toast.error("Failed to duplicate the survey.");
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoadingSurveys) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
@@ -136,6 +147,24 @@ export default function SurveysList({ environmentId }) {
|
||||
Edit
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<button
|
||||
className="flex w-full items-center"
|
||||
onClick={async () => {
|
||||
duplicateSurveyAndRefresh(survey.id);
|
||||
}}>
|
||||
<DocumentDuplicateIcon className="mr-2 h-4 w-4" />
|
||||
Duplicate
|
||||
</button>
|
||||
</DropdownMenuItem>
|
||||
{/* <DropdownMenuItem>
|
||||
<Link
|
||||
className="flex w-full items-center"
|
||||
href={`/environments/${environmentId}/surveys/${survey.id}/edit`}>
|
||||
<ArrowUturnUpIcon className="mr-2 h-4 w-4" />
|
||||
Copy to Production
|
||||
</Link>
|
||||
</DropdownMenuItem> */}
|
||||
<DropdownMenuItem>
|
||||
<button
|
||||
className="flex w-full items-center"
|
||||
|
||||
@@ -7,10 +7,10 @@ import { useSurvey } from "@/lib/surveys/surveys";
|
||||
import type { QuestionSummary } from "@formbricks/types/responses";
|
||||
import { ErrorComponent } from "@formbricks/ui";
|
||||
import { useMemo } from "react";
|
||||
import MultipleChoiceSummary from "./MultipleChoiceSummary";
|
||||
import OpenTextSummary from "./OpenTextSummary";
|
||||
import NPSSummary from "./NPSSummary";
|
||||
import CTASummary from "./CTASummary";
|
||||
import MultipleChoiceSummary from "./MultipleChoiceSummary";
|
||||
import NPSSummary from "./NPSSummary";
|
||||
import OpenTextSummary from "./OpenTextSummary";
|
||||
import RatingSummary from "./RatingSummary";
|
||||
|
||||
export default function SummaryList({ environmentId, surveyId }) {
|
||||
|
||||
@@ -103,3 +103,16 @@ export const getSurveyPage = (survey, pageId) => {
|
||||
}
|
||||
return page;
|
||||
};
|
||||
|
||||
export const duplicateSurvey = async (environmentId: string, surveyId: string) => {
|
||||
try {
|
||||
const res = await fetch(`/api/v1/environments/${environmentId}/surveys/${surveyId}/duplicate`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
return await res.json();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw Error(`duplicateSurvey: unable to duplicate survey: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
const environmentId = req.query.environmentId?.toString();
|
||||
|
||||
const surveyId = req.query.surveyId?.toString();
|
||||
|
||||
if (environmentId === undefined) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
}
|
||||
if (surveyId === undefined) {
|
||||
return res.status(400).json({ message: "Missing surveyId" });
|
||||
}
|
||||
|
||||
const hasAccess = await hasEnvironmentAccess(req, res, environmentId);
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({ message: "Not authorized" });
|
||||
}
|
||||
|
||||
// POST
|
||||
else if (req.method === "POST") {
|
||||
// duplicate current survey including its triggers
|
||||
const existingSurvey = await prisma.survey.findFirst({
|
||||
where: {
|
||||
id: surveyId,
|
||||
environmentId,
|
||||
},
|
||||
include: {
|
||||
triggers: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingSurvey) {
|
||||
return res.status(404).json({ message: "Survey not found" });
|
||||
}
|
||||
|
||||
// create new survey with the data of the existing survey
|
||||
const newSurvey = await prisma.survey.create({
|
||||
data: {
|
||||
...existingSurvey,
|
||||
id: undefined, // id is auto-generated
|
||||
environmentId: undefined, // environmentId is set below
|
||||
name: `${existingSurvey.name} (copy)`,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
status: "draft",
|
||||
questions: JSON.parse(JSON.stringify(existingSurvey.questions)),
|
||||
thankYouCard: JSON.parse(JSON.stringify(existingSurvey.thankYouCard)),
|
||||
triggers: {
|
||||
create: existingSurvey.triggers.map((trigger) => ({
|
||||
eventClassId: trigger.eventClassId,
|
||||
})),
|
||||
},
|
||||
environment: {
|
||||
connect: {
|
||||
id: environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return res.json(newSurvey);
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(`The HTTP ${req.method} method is not supported by this route.`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user