mirror of
https://github.com/formbricks/formbricks.git
synced 2026-03-10 11:24:37 -05:00
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 <mail@matthiasnannt.com>
This commit is contained in:
@@ -4,13 +4,15 @@ import FormWrapper from "@/components/auth/FormWrapper";
|
||||
|
||||
export default function SignInPage() {
|
||||
return (
|
||||
<div className="grid min-h-screen w-full bg-gradient-to-tr from-slate-200 to-slate-50 lg:grid-cols-2">
|
||||
<div className="hidden lg:flex">
|
||||
<div className="grid min-h-screen w-full bg-gradient-to-tr from-slate-100 to-slate-50 lg:grid-cols-5">
|
||||
<div className="col-span-2 hidden lg:flex">
|
||||
<Testimonial />
|
||||
</div>
|
||||
<FormWrapper>
|
||||
<SigninForm />
|
||||
</FormWrapper>
|
||||
<div className="col-span-3 flex flex-col items-center justify-center">
|
||||
<FormWrapper>
|
||||
<SigninForm />
|
||||
</FormWrapper>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,29 +5,31 @@ import Testimonial from "@/components/auth/Testimonial";
|
||||
|
||||
export default function SignUpPage() {
|
||||
return (
|
||||
<div className="grid min-h-screen w-full bg-gradient-to-tr from-slate-200 to-slate-50 lg:grid-cols-2">
|
||||
<div className="hidden lg:flex">
|
||||
<div className="grid min-h-screen w-full bg-gradient-to-tr from-slate-100 to-slate-50 lg:grid-cols-5">
|
||||
<div className="col-span-2 hidden lg:flex">
|
||||
<Testimonial />
|
||||
</div>
|
||||
<FormWrapper>
|
||||
{process.env.NEXT_PUBLIC_SIGNUP_DISABLED === "1" ? (
|
||||
<>
|
||||
<h1 className="leading-2 mb-4 text-center font-bold">Sign up disabled</h1>
|
||||
<p className="text-center">
|
||||
The account creation is disabled in this instance. Please contact the site administrator to
|
||||
create an account.
|
||||
</p>
|
||||
<hr className="my-4" />
|
||||
<Link
|
||||
href="/"
|
||||
className="mt-5 flex w-full justify-center rounded-md border border-slate-400 bg-white px-4 py-2 text-sm font-medium text-slate-600 shadow-sm hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2">
|
||||
Login
|
||||
</Link>
|
||||
</>
|
||||
) : (
|
||||
<SignupForm />
|
||||
)}
|
||||
</FormWrapper>
|
||||
<div className="col-span-3 flex flex-col items-center justify-center">
|
||||
<FormWrapper>
|
||||
{process.env.NEXT_PUBLIC_SIGNUP_DISABLED === "1" ? (
|
||||
<>
|
||||
<h1 className="leading-2 mb-4 text-center font-bold">Sign up disabled</h1>
|
||||
<p className="text-center">
|
||||
The account creation is disabled in this instance. Please contact the site administrator to
|
||||
create an account.
|
||||
</p>
|
||||
<hr className="my-4" />
|
||||
<Link
|
||||
href="/"
|
||||
className="mt-5 flex w-full justify-center rounded-md border border-slate-400 bg-white px-4 py-2 text-sm font-medium text-slate-600 shadow-sm hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2">
|
||||
Login
|
||||
</Link>
|
||||
</>
|
||||
) : (
|
||||
<SignupForm />
|
||||
)}
|
||||
</FormWrapper>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,5 +23,5 @@ export default function SurveyResultsTab({ activeId, environmentId, surveyId }:
|
||||
},
|
||||
];
|
||||
|
||||
return <SecondNavbar tabs={tabs} activeId={activeId} />;
|
||||
return <SecondNavbar tabs={tabs} activeId={activeId} surveyId={surveyId} environmentId={environmentId} />;
|
||||
}
|
||||
|
||||
@@ -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) => (
|
||||
<div key={logicIdx} className="flex items-center space-x-2 space-y-1 text-sm">
|
||||
<BsArrowReturnRight className="h-4 w-4" />
|
||||
<p>If this answer</p>
|
||||
<p className="text-slate-700">If this answer</p>
|
||||
|
||||
<Select
|
||||
defaultValue={logic.condition}
|
||||
onValueChange={(e) => updateLogic(logicIdx, { condition: e })}>
|
||||
<SelectTrigger className="min-w-fit flex-1">
|
||||
<SelectValue placeholder="select condition" />
|
||||
<SelectValue placeholder="Select condition" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{conditions[question.type].map(
|
||||
@@ -244,7 +239,7 @@ export default function LogicEditor({
|
||||
defaultValue={logic.value}
|
||||
onValueChange={(e) => updateLogic(logicIdx, { value: e })}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="select match type" />
|
||||
<SelectValue placeholder="Select match type" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{logicConditions[logic.condition].values?.map((value) => (
|
||||
@@ -259,7 +254,7 @@ export default function LogicEditor({
|
||||
<DropdownMenuTrigger className="z-10 cursor-pointer" asChild>
|
||||
<div className="flex h-10 w-full items-center justify-between rounded-md border border-slate-300 bg-transparent px-3 py-2 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ">
|
||||
{logic.value?.length === 0 ? (
|
||||
<p className="text-slate-400">select match type</p>
|
||||
<p className="text-slate-400">Select match type</p>
|
||||
) : (
|
||||
<p>{logic.value.join(", ")}</p>
|
||||
)}
|
||||
@@ -285,13 +280,13 @@ export default function LogicEditor({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p>skip to</p>
|
||||
<p className="text-slate-700">skip to</p>
|
||||
|
||||
<Select
|
||||
defaultValue={logic.destination}
|
||||
onValueChange={(e) => updateLogic(logicIdx, { destination: e })}>
|
||||
<SelectTrigger className="w-fit overflow-hidden ">
|
||||
<SelectValue placeholder="select question" />
|
||||
<SelectValue placeholder="Select question" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{localSurvey.questions.map(
|
||||
@@ -314,7 +309,7 @@ export default function LogicEditor({
|
||||
))}
|
||||
<div className="flex flex-wrap items-center space-x-2 py-1 text-sm">
|
||||
<BsArrowDown className="h-4 w-4" />
|
||||
<p>All other answers will continue to the next question</p>
|
||||
<p className="text-slate-700">All other answers will continue to the next question</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -322,6 +317,7 @@ export default function LogicEditor({
|
||||
<div className="mt-2 flex items-center space-x-2">
|
||||
<Button
|
||||
id="logicJumps"
|
||||
className="bg-slate-100 px-6 py-2 hover:bg-slate-50"
|
||||
type="button"
|
||||
name="logicJumps"
|
||||
variant="secondary"
|
||||
@@ -329,12 +325,12 @@ export default function LogicEditor({
|
||||
onClick={() => addLogic()}>
|
||||
Add Logic
|
||||
</Button>
|
||||
<TooltipProvider>
|
||||
<TooltipProvider delayDuration={50}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<QuestionMarkCircleIcon className="h-5 w-5 cursor-default" />
|
||||
<QuestionMarkCircleIcon className="ml-2 inline h-4 w-4 cursor-default text-slate-500" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-[200px]" side="top">
|
||||
<TooltipContent className="max-w-[300px]" side="top">
|
||||
With logic jumps you can skip questions based on the responses users give.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@@ -95,7 +95,7 @@ export default function MultipleChoiceMultiForm({
|
||||
/>
|
||||
{question.choices && question.choices.length > 2 && (
|
||||
<TrashIcon
|
||||
className="ml-2 h-4 w-4 text-slate-400"
|
||||
className="ml-2 h-4 w-4 cursor-pointer text-slate-400 hover:text-slate-500"
|
||||
onClick={() => deleteChoice(choiceIdx)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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 && (
|
||||
<TrashIcon
|
||||
className="ml-2 h-4 w-4 text-slate-400"
|
||||
className="ml-2 h-4 w-4 cursor-pointer text-slate-400 hover:text-slate-500"
|
||||
onClick={() => deleteChoice(choiceIdx)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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({
|
||||
<QuestionDropdown
|
||||
questionIdx={questionIdx}
|
||||
lastQuestion={lastQuestion}
|
||||
duplicateQuestion={duplicateQuestion}
|
||||
deleteQuestion={deleteQuestion}
|
||||
moveQuestion={moveQuestion}
|
||||
/>
|
||||
|
||||
@@ -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({
|
||||
<EllipsisHorizontalIcon className="h-5 w-5 text-slate-600 focus:outline-none active:outline-none" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem
|
||||
className="justify-between"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteQuestion(questionIdx);
|
||||
}}>
|
||||
Delete <TrashIcon className="ml-3 h-4" />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="justify-between"
|
||||
onClick={(e) => {
|
||||
@@ -49,6 +49,22 @@ export default function QuestionDropdown({
|
||||
Move down
|
||||
<ArrowDownIcon className="h-4" />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="justify-between"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
duplicateQuestion(questionIdx);
|
||||
}}>
|
||||
Duplicate <DocumentDuplicateIcon className="ml-3 h-4" />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="justify-between"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteQuestion(questionIdx);
|
||||
}}>
|
||||
Delete <TrashIcon className="ml-3 h-4" />
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -4,7 +4,7 @@ export default function FormWrapper({ children }: { children: React.ReactNode })
|
||||
return (
|
||||
<div className="mx-auto flex flex-1 flex-col justify-center px-4 py-12 sm:px-6 lg:flex-none lg:px-20 xl:px-24">
|
||||
<div className="mx-auto w-full max-w-sm rounded-xl bg-white p-8 shadow-xl lg:w-96">
|
||||
<div className="mb-6 text-center">
|
||||
<div className="mb-8 text-center">
|
||||
<Logo className="mx-auto w-3/4" />
|
||||
</div>
|
||||
{children}
|
||||
|
||||
@@ -38,7 +38,7 @@ export const SigninForm = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="text-center">
|
||||
<p className="mb-8 text-lg text-slate-700">Log in to your account</p>
|
||||
<h1 className="mb-4 text-slate-700">Log in to your account</h1>
|
||||
<div className="space-y-2">
|
||||
<form onSubmit={handleSubmit} ref={formRef} className="space-y-2" onChange={checkFormValidity}>
|
||||
{showLogin && (
|
||||
@@ -98,7 +98,7 @@ export const SigninForm = () => {
|
||||
className="w-full justify-center"
|
||||
loading={loggingIn}
|
||||
disabled={!isButtonEnabled}>
|
||||
Continue with Email
|
||||
Login with Email
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
@@ -114,9 +114,11 @@ export const SigninForm = () => {
|
||||
)}
|
||||
</div>
|
||||
{process.env.NEXT_PUBLIC_SIGNUP_DISABLED !== "1" && (
|
||||
<div className="mt-3 text-center text-xs text-slate-600">
|
||||
<Link href="/auth/signup" className="font-semibold underline">
|
||||
Create new account
|
||||
<div className="mt-9 text-center text-xs ">
|
||||
<span className="leading-5 text-slate-500">New to Formbricks?</span>
|
||||
<br />
|
||||
<Link href="/auth/signup" className="font-semibold text-slate-600 underline hover:text-slate-700">
|
||||
Create an account
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -67,7 +67,7 @@ export const SignupForm = () => {
|
||||
</div>
|
||||
)}
|
||||
<div className="text-center">
|
||||
<p className="mb-8 text-lg text-slate-700">Create your Formbricks account</p>
|
||||
<h1 className="mb-4 text-slate-700">Create your Formbricks account</h1>
|
||||
<div className="space-y-2">
|
||||
<form onSubmit={handleSubmit} ref={formRef} className="space-y-2" onChange={checkFormValidity}>
|
||||
{showLogin && (
|
||||
@@ -189,9 +189,10 @@ export const SignupForm = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-3 text-center text-xs text-slate-600">
|
||||
Have an account?{" "}
|
||||
<Link href="/auth/login" className="font-semibold underline">
|
||||
<div className="mt-9 text-center text-xs ">
|
||||
<span className="leading-5 text-slate-500">Have an account?</span>
|
||||
<br />
|
||||
<Link href="/auth/login" className="font-semibold text-slate-600 underline hover:text-slate-700">
|
||||
Log in.
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -5,10 +5,10 @@ import CalComLogo from "@/images/cal-logo-light.svg";
|
||||
|
||||
export default function Testimonial() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<div className="mb-10 w-3/4 space-y-8 2xl:w-1/2">
|
||||
<div className="flex flex-col items-center justify-center bg-gradient-to-tr from-slate-100 to-slate-300">
|
||||
<div className="3xl:w-2/3 mb-10 space-y-8 px-12 xl:px-20 ">
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold text-slate-900">
|
||||
<h2 className="text-3xl font-bold text-slate-800">
|
||||
Versatile in-app surveys. Valuable user insights.
|
||||
</h2>
|
||||
</div>
|
||||
@@ -19,19 +19,19 @@ export default function Testimonial() {
|
||||
<div className="space-y-2">
|
||||
<div className="flex space-x-2">
|
||||
<CheckCircleIcon className="text-brand-dark h-6 w-6" />
|
||||
<p className="inline text-lg text-slate-900">All features included</p>
|
||||
<p className="inline text-lg text-slate-800">All features included</p>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<CheckCircleIcon className="text-brand-dark h-6 w-6" />
|
||||
<p className="inline text-lg text-slate-900">Free and open-source</p>
|
||||
<p className="inline text-lg text-slate-800">Free and open-source</p>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<CheckCircleIcon className="text-brand-dark h-6 w-6" />
|
||||
<p className="inline text-lg text-slate-900">No credit card required</p>
|
||||
<p className="inline text-lg text-slate-800">No credit card required</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-1 rounded-xl border-slate-200 bg-slate-50 p-6 shadow-sm">
|
||||
<div className="rounded-xl border border-slate-200 bg-gradient-to-tr from-slate-100 to-slate-200 p-8">
|
||||
<p className="italic text-slate-700">
|
||||
We measure the clarity of our docs and learn from churn all on one platform. Great product, very
|
||||
responsive team!
|
||||
|
||||
@@ -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 (
|
||||
<div {...props}>
|
||||
<div className="flex h-14 w-full items-center justify-center border-b bg-white">
|
||||
<nav className="flex h-full items-center space-x-4" aria-label="Tabs">
|
||||
<div className="grid h-14 w-full grid-cols-3 items-center justify-items-stretch border-b bg-white px-4">
|
||||
<div className="justify-self-start">
|
||||
{surveyId && environmentId && (
|
||||
<SurveyNavBarName surveyId={surveyId} environmentId={environmentId} />
|
||||
)}
|
||||
</div>{" "}
|
||||
<nav className="flex h-full items-center space-x-4 justify-self-center" aria-label="Tabs">
|
||||
{tabs.map((tab) => (
|
||||
<Link
|
||||
key={tab.id}
|
||||
@@ -27,6 +41,7 @@ export default function SecondNavbar({ tabs, activeId, ...props }: SecondNavbarP
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
<div className="justify-self-end"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
37
apps/web/components/shared/SurveyNavBarName.tsx
Normal file
37
apps/web/components/shared/SurveyNavBarName.tsx
Normal file
@@ -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 (
|
||||
<div className="hidden items-center space-x-2 whitespace-nowrap md:flex">
|
||||
{/* <Button
|
||||
variant="secondary"
|
||||
StartIcon={ArrowLeftIcon}
|
||||
onClick={() => {
|
||||
router.back();
|
||||
}}>
|
||||
Back
|
||||
</Button> */}
|
||||
<p className="pl-4 font-semibold">{product.name} / </p>
|
||||
<span>{survey.name}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user