From 78f7b4d03ecdd2469638c8c54678fef944d05799 Mon Sep 17 00:00:00 2001 From: Johannes <72809645+jobenjada@users.noreply.github.com> Date: Wed, 31 May 2023 09:52:58 +0200 Subject: [PATCH] Duplicate Questions, Add Survey Name to Summary, Update Login Screen (#322) * Duplicate Questions, Add Survey Name to Summary, Update Login Screen --------- Co-authored-by: Matthias Nannt --- apps/web/app/auth/login/page.tsx | 12 ++--- apps/web/app/auth/signup/page.tsx | 44 ++++++++++--------- .../surveys/[surveyId]/SurveyResultsTabs.tsx | 2 +- .../surveys/[surveyId]/edit/LogicEditor.tsx | 28 +++++------- .../edit/MultipleChoiceMultiForm.tsx | 2 +- .../edit/MultipleChoiceSingleForm.tsx | 3 +- .../surveys/[surveyId]/edit/QuestionCard.tsx | 5 ++- .../[surveyId]/edit/QuestionDropdown.tsx | 34 ++++++++++---- .../surveys/[surveyId]/edit/QuestionsView.tsx | 34 +++++++++++--- apps/web/components/auth/FormWrapper.tsx | 2 +- apps/web/components/auth/SigninForm.tsx | 12 ++--- apps/web/components/auth/SignupForm.tsx | 9 ++-- apps/web/components/auth/Testimonial.tsx | 14 +++--- .../components/environments/SecondNavBar.tsx | 21 +++++++-- .../components/shared/SurveyNavBarName.tsx | 37 ++++++++++++++++ 15 files changed, 178 insertions(+), 81 deletions(-) create mode 100644 apps/web/components/shared/SurveyNavBarName.tsx diff --git a/apps/web/app/auth/login/page.tsx b/apps/web/app/auth/login/page.tsx index 013b8e30ca..d078b71b67 100644 --- a/apps/web/app/auth/login/page.tsx +++ b/apps/web/app/auth/login/page.tsx @@ -4,13 +4,15 @@ import FormWrapper from "@/components/auth/FormWrapper"; export default function SignInPage() { return ( -
-
+
+
- - - +
+ + + +
); } diff --git a/apps/web/app/auth/signup/page.tsx b/apps/web/app/auth/signup/page.tsx index 136464c09b..4591dbf0b7 100644 --- a/apps/web/app/auth/signup/page.tsx +++ b/apps/web/app/auth/signup/page.tsx @@ -5,29 +5,31 @@ import Testimonial from "@/components/auth/Testimonial"; export default function SignUpPage() { return ( -
-
+
+
- - {process.env.NEXT_PUBLIC_SIGNUP_DISABLED === "1" ? ( - <> -

Sign up disabled

-

- The account creation is disabled in this instance. Please contact the site administrator to - create an account. -

-
- - Login - - - ) : ( - - )} -
+
+ + {process.env.NEXT_PUBLIC_SIGNUP_DISABLED === "1" ? ( + <> +

Sign up disabled

+

+ The account creation is disabled in this instance. Please contact the site administrator to + create an account. +

+
+ + Login + + + ) : ( + + )} +
+
); } diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/SurveyResultsTabs.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/SurveyResultsTabs.tsx index e4bf51e5c7..b07b3eecce 100644 --- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/SurveyResultsTabs.tsx +++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/SurveyResultsTabs.tsx @@ -23,5 +23,5 @@ export default function SurveyResultsTab({ activeId, environmentId, surveyId }: }, ]; - return ; + return ; } diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/LogicEditor.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/LogicEditor.tsx index 0fbf34f83f..43700dacde 100644 --- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/LogicEditor.tsx +++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/LogicEditor.tsx @@ -17,8 +17,7 @@ import { TooltipProvider, TooltipTrigger, } from "@formbricks/ui"; -import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline"; -import { TrashIcon } from "@heroicons/react/24/solid"; +import { QuestionMarkCircleIcon, TrashIcon } from "@heroicons/react/24/solid"; import { ChevronDown, SplitIcon } from "lucide-react"; import { useMemo } from "react"; import { BsArrowDown, BsArrowReturnRight } from "react-icons/bs"; @@ -129,10 +128,6 @@ export default function LogicEditor({ }, }; - // useEffect(() => { - // console.log(question); - // }, [question]); - const addLogic = () => { const newLogic: Logic[] = !question.logic ? [] : question.logic; newLogic.push({ @@ -217,13 +212,13 @@ export default function LogicEditor({ {question?.logic?.map((logic, logicIdx) => (
-

If this answer

+

If this answer

updateLogic(logicIdx, { destination: e })}> - + {localSurvey.questions.map( @@ -314,7 +309,7 @@ export default function LogicEditor({ ))}
-

All other answers will continue to the next question

+

All other answers will continue to the next question

)} @@ -322,6 +317,7 @@ export default function LogicEditor({
- + - + - + With logic jumps you can skip questions based on the responses users give. diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/MultipleChoiceMultiForm.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/MultipleChoiceMultiForm.tsx index abec9ddca5..e3a175320d 100644 --- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/MultipleChoiceMultiForm.tsx +++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/MultipleChoiceMultiForm.tsx @@ -95,7 +95,7 @@ export default function MultipleChoiceMultiForm({ /> {question.choices && question.choices.length > 2 && ( deleteChoice(choiceIdx)} /> )} diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/MultipleChoiceSingleForm.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/MultipleChoiceSingleForm.tsx index cf1ffa7084..fcc4288024 100644 --- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/MultipleChoiceSingleForm.tsx +++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/MultipleChoiceSingleForm.tsx @@ -18,7 +18,6 @@ export default function MultipleChoiceSingleForm({ updateQuestion, lastQuestion, }: OpenQuestionFormProps): JSX.Element { - // console.log(localSurvey); const updateChoice = (choiceIdx: number, updatedAttributes: any) => { const newChoices = !question.choices ? [] @@ -96,7 +95,7 @@ export default function MultipleChoiceSingleForm({ /> {question.choices && question.choices.length > 2 && ( deleteChoice(choiceIdx)} /> )} diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionCard.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionCard.tsx index e4feb7c53d..f4cc14802a 100644 --- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionCard.tsx +++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionCard.tsx @@ -1,5 +1,6 @@ "use client"; +import LogicEditor from "@/app/environments/[environmentId]/surveys/[surveyId]/edit/LogicEditor"; import { getQuestionTypeName } from "@/lib/questions"; import { cn } from "@formbricks/lib/cn"; import type { Question } from "@formbricks/types/questions"; @@ -26,7 +27,6 @@ import OpenQuestionForm from "./OpenQuestionForm"; import QuestionDropdown from "./QuestionDropdown"; import RatingQuestionForm from "./RatingQuestionForm"; import UpdateQuestionId from "./UpdateQuestionId"; -import LogicEditor from "@/app/environments/[environmentId]/surveys/[surveyId]/edit/LogicEditor"; interface QuestionCardProps { localSurvey: Survey; @@ -35,6 +35,7 @@ interface QuestionCardProps { moveQuestion: (questionIndex: number, up: boolean) => void; updateQuestion: (questionIdx: number, updatedAttributes: any) => void; deleteQuestion: (questionIdx: number) => void; + duplicateQuestion: (questionIdx: number) => void; activeQuestionId: string | null; setActiveQuestionId: (questionId: string | null) => void; lastQuestion: boolean; @@ -46,6 +47,7 @@ export default function QuestionCard({ questionIdx, moveQuestion, updateQuestion, + duplicateQuestion, deleteQuestion, activeQuestionId, setActiveQuestionId, @@ -130,6 +132,7 @@ export default function QuestionCard({ diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionDropdown.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionDropdown.tsx index 685cd93dd9..88a47ea78b 100644 --- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionDropdown.tsx +++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionDropdown.tsx @@ -1,11 +1,18 @@ "use client"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@formbricks/ui"; -import { EllipsisHorizontalIcon, ArrowUpIcon, ArrowDownIcon, TrashIcon } from "@heroicons/react/24/solid"; +import { + EllipsisHorizontalIcon, + ArrowUpIcon, + ArrowDownIcon, + TrashIcon, + DocumentDuplicateIcon, +} from "@heroicons/react/24/solid"; interface QuestionDropdownProps { questionIdx: number; lastQuestion: boolean; + duplicateQuestion: (questionIdx: number) => void; deleteQuestion: (questionIdx: number) => void; moveQuestion: (questionIdx: number, up: boolean) => void; } @@ -13,6 +20,7 @@ interface QuestionDropdownProps { export default function QuestionDropdown({ questionIdx, lastQuestion, + duplicateQuestion, deleteQuestion, moveQuestion, }: QuestionDropdownProps) { @@ -22,14 +30,6 @@ export default function QuestionDropdown({ - { - e.stopPropagation(); - deleteQuestion(questionIdx); - }}> - Delete - { @@ -49,6 +49,22 @@ export default function QuestionDropdown({ Move down + { + e.stopPropagation(); + duplicateQuestion(questionIdx); + }}> + Duplicate + + { + e.stopPropagation(); + deleteQuestion(questionIdx); + }}> + Delete + ); diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionsView.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionsView.tsx index 5aae0ba090..8769b13f1e 100644 --- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionsView.tsx +++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/edit/QuestionsView.tsx @@ -1,13 +1,14 @@ "use client"; import type { Survey } from "@formbricks/types/surveys"; -import { DragDropContext } from "react-beautiful-dnd"; -import AddQuestionButton from "./AddQuestionButton"; -import QuestionCard from "./QuestionCard"; -import { StrictModeDroppable } from "./StrictModeDroppable"; -import EditThankYouCard from "./EditThankYouCard"; import { createId } from "@paralleldrive/cuid2"; import { useMemo } from "react"; +import { DragDropContext } from "react-beautiful-dnd"; +import toast from "react-hot-toast"; +import AddQuestionButton from "./AddQuestionButton"; +import EditThankYouCard from "./EditThankYouCard"; +import QuestionCard from "./QuestionCard"; +import { StrictModeDroppable } from "./StrictModeDroppable"; interface QuestionsViewProps { localSurvey: Survey; @@ -73,6 +74,28 @@ export default function QuestionsView({ setActiveQuestionId(localSurvey.questions[questionIdx - 1].id); } } + toast.success("Question deleted."); + }; + + const duplicateQuestion = (questionIdx: number) => { + const questionToDuplicate = JSON.parse(JSON.stringify(localSurvey.questions[questionIdx])); + const newQuestionId = createId(); + + // create a copy of the question with a new id + const duplicatedQuestion = { + ...questionToDuplicate, + id: newQuestionId, + }; + + // insert the new question right after the original one + const updatedSurvey = JSON.parse(JSON.stringify(localSurvey)); + updatedSurvey.questions.splice(questionIdx + 1, 0, duplicatedQuestion); + + setLocalSurvey(updatedSurvey); + setActiveQuestionId(newQuestionId); + internalQuestionIdMap[newQuestionId] = createId(); + + toast.success("Question duplicated."); }; const addQuestion = (question: any) => { @@ -122,6 +145,7 @@ export default function QuestionsView({ questionIdx={questionIdx} moveQuestion={moveQuestion} updateQuestion={updateQuestion} + duplicateQuestion={duplicateQuestion} deleteQuestion={deleteQuestion} activeQuestionId={activeQuestionId} setActiveQuestionId={setActiveQuestionId} diff --git a/apps/web/components/auth/FormWrapper.tsx b/apps/web/components/auth/FormWrapper.tsx index 8e8df26540..a9d04a636a 100644 --- a/apps/web/components/auth/FormWrapper.tsx +++ b/apps/web/components/auth/FormWrapper.tsx @@ -4,7 +4,7 @@ export default function FormWrapper({ children }: { children: React.ReactNode }) return (
-
+
{children} diff --git a/apps/web/components/auth/SigninForm.tsx b/apps/web/components/auth/SigninForm.tsx index 4af3c2c814..9893daa405 100644 --- a/apps/web/components/auth/SigninForm.tsx +++ b/apps/web/components/auth/SigninForm.tsx @@ -38,7 +38,7 @@ export const SigninForm = () => { return ( <>
-

Log in to your account

+

Log in to your account

{showLogin && ( @@ -98,7 +98,7 @@ export const SigninForm = () => { className="w-full justify-center" loading={loggingIn} disabled={!isButtonEnabled}> - Continue with Email + Login with Email
@@ -114,9 +114,11 @@ export const SigninForm = () => { )}
{process.env.NEXT_PUBLIC_SIGNUP_DISABLED !== "1" && ( -
- - Create new account +
+ New to Formbricks? +
+ + Create an account
)} diff --git a/apps/web/components/auth/SignupForm.tsx b/apps/web/components/auth/SignupForm.tsx index 6454988b94..73bfc769d7 100644 --- a/apps/web/components/auth/SignupForm.tsx +++ b/apps/web/components/auth/SignupForm.tsx @@ -67,7 +67,7 @@ export const SignupForm = () => {
)}
-

Create your Formbricks account

+

Create your Formbricks account

{showLogin && ( @@ -189,9 +189,10 @@ export const SignupForm = () => {
)} -
- Have an account?{" "} - +
+ Have an account? +
+ Log in.
diff --git a/apps/web/components/auth/Testimonial.tsx b/apps/web/components/auth/Testimonial.tsx index 8e5b306e31..a9a506bb4c 100644 --- a/apps/web/components/auth/Testimonial.tsx +++ b/apps/web/components/auth/Testimonial.tsx @@ -5,10 +5,10 @@ import CalComLogo from "@/images/cal-logo-light.svg"; export default function Testimonial() { return ( -
-
+
+
-

+

Versatile in-app surveys. Valuable user insights.

@@ -19,19 +19,19 @@ export default function Testimonial() {
-

All features included

+

All features included

-

Free and open-source

+

Free and open-source

-

No credit card required

+

No credit card required

-
+

We measure the clarity of our docs and learn from churn all on one platform. Great product, very responsive team! diff --git a/apps/web/components/environments/SecondNavBar.tsx b/apps/web/components/environments/SecondNavBar.tsx index 3b3b6aae3b..96b3b32e8c 100644 --- a/apps/web/components/environments/SecondNavBar.tsx +++ b/apps/web/components/environments/SecondNavBar.tsx @@ -1,16 +1,30 @@ import { cn } from "@formbricks/lib/cn"; +import SurveyNavBarName from "@/components/shared/SurveyNavBarName"; import Link from "next/link"; interface SecondNavbarProps { tabs: { id: string; label: string; href: string; icon?: React.ReactNode }[]; activeId: string; + surveyId?: string; + environmentId?: string; } -export default function SecondNavbar({ tabs, activeId, ...props }: SecondNavbarProps) { +export default function SecondNavbar({ + tabs, + activeId, + surveyId, + environmentId, + ...props +}: SecondNavbarProps) { return (

-
-
); diff --git a/apps/web/components/shared/SurveyNavBarName.tsx b/apps/web/components/shared/SurveyNavBarName.tsx new file mode 100644 index 0000000000..7f7527739a --- /dev/null +++ b/apps/web/components/shared/SurveyNavBarName.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { useProduct } from "@/lib/products/products"; +import { useSurvey } from "@/lib/surveys/surveys"; + +interface SurveyNavBarNameProps { + surveyId: string; + environmentId: string; +} + +export default function SurveyNavBarName({ surveyId, environmentId }: SurveyNavBarNameProps) { + const { survey, isLoadingSurvey, isErrorSurvey } = useSurvey(environmentId, surveyId); + const { product, isLoadingProduct, isErrorProduct } = useProduct(environmentId); + + if (isLoadingSurvey || isLoadingProduct) { + return null; + } + + if (isErrorProduct || isErrorSurvey) { + return null; + } + + return ( +
+ {/* */} +

{product.name} /

+ {survey.name} +
+ ); +}