feat: Formbricks App Redesign (#2581)

Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
This commit is contained in:
Johannes
2024-05-09 17:30:44 +02:00
committed by GitHub
parent 556ee870b1
commit 50407498ec
339 changed files with 3451 additions and 3173 deletions
@@ -0,0 +1,57 @@
import FormbricksClient from "@/app/(app)/components/FormbricksClient";
import PosthogIdentify from "@/app/(app)/environments/[environmentId]/components/PosthogIdentify";
import { ResponseFilterProvider } from "@/app/(app)/environments/[environmentId]/components/ResponseFilterContext";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getTeamByEnvironmentId } from "@formbricks/lib/team/service";
import { AuthorizationError } from "@formbricks/types/errors";
import { DevEnvironmentBanner } from "@formbricks/ui/DevEnvironmentBanner";
import ToasterClient from "@formbricks/ui/ToasterClient";
export default async function EnvLayout({ children, params }) {
const session = await getServerSession(authOptions);
if (!session || !session.user) {
return redirect(`/auth/login`);
}
const hasAccess = await hasUserEnvironmentAccess(session.user.id, params.environmentId);
if (!hasAccess) {
throw new AuthorizationError("Not authorized");
}
const team = await getTeamByEnvironmentId(params.environmentId);
if (!team) {
throw new Error("Team not found");
}
const environment = await getEnvironment(params.environmentId);
if (!environment) {
throw new Error("Environment not found");
}
return (
<>
<ResponseFilterProvider>
<PosthogIdentify
session={session}
environmentId={params.environmentId}
teamId={team.id}
teamName={team.name}
inAppSurveyBillingStatus={team.billing.features.inAppSurvey.status}
linkSurveyBillingStatus={team.billing.features.linkSurvey.status}
userTargetingBillingStatus={team.billing.features.userTargeting.status}
/>
<FormbricksClient session={session} />
<ToasterClient />
<div className="flex h-screen flex-col">
<DevEnvironmentBanner environment={environment} />
<div className="h-full overflow-y-auto bg-slate-50">{children}</div>
</div>
</ResponseFilterProvider>
</>
);
}
@@ -61,7 +61,7 @@ export const deleteSurveyAction = async (surveyId: string) => {
await deleteSurvey(surveyId);
};
export const refetchProduct = async (productId: string): Promise<TProduct | null> => {
export const refetchProductAction = async (productId: string): Promise<TProduct | null> => {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
@@ -1,12 +1,12 @@
"use client";
import { CreateNewActionTab } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/CreateNewActionTab";
import { SavedActionsTab } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/SavedActionsTab";
import { TActionClass } from "@formbricks/types/actionClasses";
import { TSurvey } from "@formbricks/types/surveys";
import { ModalWithTabs } from "@formbricks/ui/ModalWithTabs";
import { CreateNewActionTab } from "./CreateNewActionTab";
import { SavedActionsTab } from "./SavedActionsTab";
interface AddActionModalProps {
open: boolean;
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
@@ -1,7 +1,6 @@
import LogicEditor from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditor";
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys";
import LogicEditor from "./LogicEditor";
import UpdateQuestionId from "./UpdateQuestionId";
interface AdvancedSettingsProps {
@@ -23,7 +23,7 @@ interface BackgroundStylingCardProps {
isUnsplashConfigured: boolean;
}
export default function BackgroundStylingCard({
export const BackgroundStylingCard = ({
open,
setOpen,
styling,
@@ -33,7 +33,7 @@ export default function BackgroundStylingCard({
disabled,
environmentId,
isUnsplashConfigured,
}: BackgroundStylingCardProps) {
}: BackgroundStylingCardProps) => {
const { bgType, brightness } = styling?.background ?? {};
const handleBgChange = (color: string, type: TSurveyBackgroundBgType) => {
@@ -150,4 +150,4 @@ export default function BackgroundStylingCard({
</Collapsible.CollapsibleContent>
</Collapsible.Root>
);
}
};
@@ -27,7 +27,7 @@ type CardStylingSettingsProps = {
localProduct: TProduct;
};
const CardStylingSettings = ({
export const CardStylingSettings = ({
setStyling,
styling,
isSettingsPage = false,
@@ -300,5 +300,3 @@ const CardStylingSettings = ({
</Collapsible.Root>
);
};
export default CardStylingSettings;
@@ -21,7 +21,7 @@ type FormStylingSettingsProps = {
disabled?: boolean;
};
const FormStylingSettings = ({
export const FormStylingSettings = ({
styling,
setStyling,
open,
@@ -201,5 +201,3 @@ const FormStylingSettings = ({
</Collapsible.Root>
);
};
export default FormStylingSettings;
@@ -1,6 +1,5 @@
"use client";
import { validateId } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/lib/validation";
import * as Collapsible from "@radix-ui/react-collapsible";
import { FC, useState } from "react";
import { toast } from "react-hot-toast";
@@ -13,6 +12,8 @@ import { Label } from "@formbricks/ui/Label";
import { Switch } from "@formbricks/ui/Switch";
import { Tag } from "@formbricks/ui/Tag";
import { validateId } from "../lib/validation";
interface HiddenFieldsCardProps {
localSurvey: TSurvey;
setLocalSurvey: (survey: TSurvey) => void;
@@ -182,7 +182,7 @@ export default function HowToSendCard({ localSurvey, setLocalSurvey, environment
</p>
<p className="text-xs font-normal">
<Link
href={`/environments/${environment.id}/settings/setup`}
href={`/environments/${environment.id}/product/setup`}
className="underline hover:text-amber-900"
target="_blank">
Connect Formbricks
@@ -1,8 +1,8 @@
import { HelpCircle, TrashIcon } from "lucide-react";
import { ChevronDown, SplitIcon } from "lucide-react";
import { CornerDownRightIcon, MoveDownIcon } from "lucide-react";
import { useMemo } from "react";
import { toast } from "react-hot-toast";
import { BsArrowDown, BsArrowReturnRight } from "react-icons/bs";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone";
@@ -269,7 +269,7 @@ export default function LogicEditor({
<div className="mt-2 space-y-3">
{question?.logic?.map((logic, logicIdx) => (
<div key={logicIdx} className="flex items-center space-x-2 space-y-1 text-xs xl:text-sm">
<BsArrowReturnRight className="h-4 w-4" />
<CornerDownRightIcon className="h-4 w-4" />
<p className="text-slate-800">If this answer</p>
<Select value={logic.condition} onValueChange={(e) => updateLogic(logicIdx, { condition: e })}>
@@ -385,7 +385,7 @@ export default function LogicEditor({
</div>
))}
<div className="flex flex-wrap items-center space-x-2 py-1 text-sm">
<BsArrowDown className="h-4 w-4" />
<MoveDownIcon className="h-4 w-4" />
<p className="text-slate-700">All other answers will continue to the next question</p>
</div>
</div>
@@ -1,6 +1,5 @@
"use client";
import { isLabelValidForAllLanguages } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/lib/validation";
import { PlusIcon, TrashIcon } from "lucide-react";
import { useState } from "react";
import { toast } from "react-hot-toast";
@@ -11,6 +10,8 @@ import { Button } from "@formbricks/ui/Button";
import { Label } from "@formbricks/ui/Label";
import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput";
import { isLabelValidForAllLanguages } from "../lib/validation";
interface MatrixQuestionFormProps {
localSurvey: TSurvey;
question: TSurveyMatrixQuestion;
@@ -1,6 +1,5 @@
"use client";
import { isLabelValidForAllLanguages } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/lib/validation";
import { createId } from "@paralleldrive/cuid2";
import { PlusIcon, TrashIcon } from "lucide-react";
import { useEffect, useRef, useState } from "react";
@@ -20,6 +19,8 @@ import { Label } from "@formbricks/ui/Label";
import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@formbricks/ui/Select";
import { isLabelValidForAllLanguages } from "../lib/validation";
interface OpenQuestionFormProps {
localSurvey: TSurvey;
question: TSurveyMultipleChoiceSingleQuestion;
@@ -1,10 +1,9 @@
"use client";
import { getPlacementStyle } from "@/app/lib/preview";
import { cn } from "@formbricks/lib/cn";
import { TPlacement } from "@formbricks/types/common";
import { Label } from "@formbricks/ui/Label";
import { getPlacementStyle } from "@formbricks/ui/PreviewSurvey/lib/utils";
import { RadioGroup, RadioGroupItem } from "@formbricks/ui/RadioGroup";
const placements = [
@@ -1,19 +1,5 @@
"use client";
import { AddressQuestionForm } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/AddressQuestionForm";
import { AdvancedSettings } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedSettings";
import { CTAQuestionForm } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/CTAQuestionForm";
import { CalQuestionForm } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/CalQuestionForm";
import { ConsentQuestionForm } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConsentQuestionForm";
import { DateQuestionForm } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/DateQuestionForm";
import { FileUploadQuestionForm } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/FileUploadQuestionForm";
import { MatrixQuestionForm } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/MatrixQuestionForm";
import { MultipleChoiceMultiForm } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/MultipleChoiceMultiForm";
import { MultipleChoiceSingleForm } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/MultipleChoiceSingleForm";
import { NPSQuestionForm } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/NPSQuestionForm";
import { OpenQuestionForm } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/OpenQuestionForm";
import { PictureSelectionForm } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/PictureSelectionForm";
import { RatingQuestionForm } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/RatingQuestionForm";
import { getTSurveyQuestionTypeName } from "@/app/lib/questions";
import * as Collapsible from "@radix-ui/react-collapsible";
import {
@@ -44,7 +30,21 @@ import { Label } from "@formbricks/ui/Label";
import { QuestionFormInput } from "@formbricks/ui/QuestionFormInput";
import { Switch } from "@formbricks/ui/Switch";
import QuestionDropdown from "./QuestionMenu";
import { AddressQuestionForm } from "./AddressQuestionForm";
import { AdvancedSettings } from "./AdvancedSettings";
import { CTAQuestionForm } from "./CTAQuestionForm";
import { CalQuestionForm } from "./CalQuestionForm";
import { ConsentQuestionForm } from "./ConsentQuestionForm";
import { DateQuestionForm } from "./DateQuestionForm";
import { FileUploadQuestionForm } from "./FileUploadQuestionForm";
import { MatrixQuestionForm } from "./MatrixQuestionForm";
import { MultipleChoiceMultiForm } from "./MultipleChoiceMultiForm";
import { MultipleChoiceSingleForm } from "./MultipleChoiceSingleForm";
import { NPSQuestionForm } from "./NPSQuestionForm";
import { OpenQuestionForm } from "./OpenQuestionForm";
import { PictureSelectionForm } from "./PictureSelectionForm";
import { QuestionDropdown } from "./QuestionMenu";
import { RatingQuestionForm } from "./RatingQuestionForm";
interface QuestionCardProps {
localSurvey: TSurvey;
@@ -10,13 +10,13 @@ interface QuestionDropdownProps {
moveQuestion: (questionIdx: number, up: boolean) => void;
}
export default function QuestionActions({
export const QuestionDropdown = ({
questionIdx,
lastQuestion,
duplicateQuestion,
deleteQuestion,
moveQuestion,
}: QuestionDropdownProps) {
}: QuestionDropdownProps) => {
return (
<div className="flex space-x-2">
<ArrowUpIcon
@@ -57,4 +57,4 @@ export default function QuestionActions({
/>
</div>
);
}
};
@@ -1,11 +1,5 @@
"use client";
import HiddenFieldsCard from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/HiddenFieldsCard";
import {
isCardValid,
validateQuestion,
validateSurveyQuestionsInBatch,
} from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/lib/validation";
import { createId } from "@paralleldrive/cuid2";
import { useEffect, useMemo, useState } from "react";
import { DragDropContext } from "react-beautiful-dnd";
@@ -18,9 +12,11 @@ import { checkForEmptyFallBackValue, extractRecallInfo } from "@formbricks/lib/u
import { TProduct } from "@formbricks/types/product";
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys";
import { isCardValid, validateQuestion, validateSurveyQuestionsInBatch } from "../lib/validation";
import AddQuestionButton from "./AddQuestionButton";
import EditThankYouCard from "./EditThankYouCard";
import EditWelcomeCard from "./EditWelcomeCard";
import HiddenFieldsCard from "./HiddenFieldsCard";
import QuestionCard from "./QuestionCard";
import { StrictModeDroppable } from "./StrictModeDroppable";
@@ -148,7 +148,7 @@ export default function RecontactOptionsCard({
This setting overwrites your{" "}
<Link
className="decoration-brand-dark underline"
href={`/environments/${environmentId}/settings/product`}
href={`/environments/${environmentId}/product/general`}
target="_blank">
waiting period
</Link>
@@ -1,5 +1,3 @@
import SurveyPlacementCard from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyPlacementCard";
import { AdvancedTargetingCard } from "@formbricks/ee/advancedTargeting/components/AdvancedTargetingCard";
import { TActionClass } from "@formbricks/types/actionClasses";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
@@ -11,6 +9,7 @@ import { TSurvey } from "@formbricks/types/surveys";
import HowToSendCard from "./HowToSendCard";
import RecontactOptionsCard from "./RecontactOptionsCard";
import ResponseOptionsCard from "./ResponseOptionsCard";
import SurveyPlacementCard from "./SurveyPlacementCard";
import TargetingCard from "./TargetingCard";
import WhenToSendCard from "./WhenToSendCard";
@@ -1,6 +1,3 @@
import BackgroundStylingCard from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/BackgroundStylingCard";
import CardStylingSettings from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/CardStylingSettings";
import FormStylingSettings from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/FormStylingSettings";
import { RotateCcwIcon } from "lucide-react";
import Link from "next/link";
import React, { useEffect, useMemo, useState } from "react";
@@ -13,6 +10,10 @@ import { AlertDialog } from "@formbricks/ui/AlertDialog";
import { Button } from "@formbricks/ui/Button";
import { Switch } from "@formbricks/ui/Switch";
import { BackgroundStylingCard } from "./BackgroundStylingCard";
import { CardStylingSettings } from "./CardStylingSettings";
import { FormStylingSettings } from "./FormStylingSettings";
type StylingViewProps = {
environment: TEnvironment;
product: TProduct;
@@ -1,13 +1,5 @@
"use client";
import { refetchProduct } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions";
import { LoadingSkeleton } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/LoadingSkeleton";
import { QuestionsAudienceTabs } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsStylingSettingsTabs";
import { QuestionsView } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsView";
import { SettingsView } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/SettingsView";
import { StylingView } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/StylingView";
import { SurveyMenuBar } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyMenuBar";
import { PreviewSurvey } from "@/app/(app)/environments/[environmentId]/surveys/components/PreviewSurvey";
import { useCallback, useEffect, useRef, useState } from "react";
import { extractLanguageCodes, getEnabledLanguages } from "@formbricks/lib/i18n/utils";
@@ -20,6 +12,15 @@ import { TMembershipRole } from "@formbricks/types/memberships";
import { TProduct } from "@formbricks/types/product";
import { TSegment } from "@formbricks/types/segment";
import { TSurvey, TSurveyEditorTabs, TSurveyStyling } from "@formbricks/types/surveys";
import { PreviewSurvey } from "@formbricks/ui/PreviewSurvey";
import { refetchProductAction } from "../actions";
import { LoadingSkeleton } from "./LoadingSkeleton";
import { QuestionsAudienceTabs } from "./QuestionsStylingSettingsTabs";
import { QuestionsView } from "./QuestionsView";
import { SettingsView } from "./SettingsView";
import { StylingView } from "./StylingView";
import { SurveyMenuBar } from "./SurveyMenuBar";
interface SurveyEditorProps {
survey: TSurvey;
@@ -64,7 +65,7 @@ export default function SurveyEditor({
const [localStylingChanges, setLocalStylingChanges] = useState<TSurveyStyling | null>(null);
const fetchLatestProduct = useCallback(async () => {
const latestProduct = await refetchProduct(localProduct.id);
const latestProduct = await refetchProductAction(localProduct.id);
if (latestProduct) {
setLocalProduct(latestProduct);
}
@@ -91,7 +92,7 @@ export default function SurveyEditor({
const listener = () => {
if (document.visibilityState === "visible") {
const fetchLatestProduct = async () => {
const latestProduct = await refetchProduct(localProduct.id);
const latestProduct = await refetchProductAction(localProduct.id);
if (latestProduct) {
setLocalProduct(latestProduct);
}
@@ -1,7 +1,6 @@
"use client";
import { SurveyStatusDropdown } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SurveyStatusDropdown";
import { isSurveyValid } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/lib/validation";
import { isEqual } from "lodash";
import { AlertTriangleIcon, ArrowLeftIcon, SettingsIcon } from "lucide-react";
import { useRouter } from "next/navigation";
@@ -19,6 +18,7 @@ import { Input } from "@formbricks/ui/Input";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@formbricks/ui/Tooltip";
import { updateSurveyAction } from "../actions";
import { isSurveyValid } from "../lib/validation";
interface SurveyMenuBarProps {
localSurvey: TSurvey;
@@ -53,7 +53,7 @@ export const SurveyMenuBar = ({
const [isConfirmDialogOpen, setConfirmDialogOpen] = useState(false);
const [isSurveyPublishing, setIsSurveyPublishing] = useState(false);
const [isSurveySaving, setIsSurveySaving] = useState(false);
const cautionText = "This survey received responses, make changes with caution.";
const cautionText = "This survey received responses.";
const faultyQuestions: string[] = [];
@@ -216,13 +216,6 @@ export const SurveyMenuBar = ({
return (
<>
{environment?.type === "development" && (
<nav className="top-0 z-10 w-full border-b border-slate-200 bg-white">
<div className="h-6 w-full bg-[#A33700] p-0.5 text-center text-sm text-white">
You&apos;re in development mode. Use it to test surveys, actions and attributes.
</div>
</nav>
)}
<div className="border-b border-slate-200 bg-white px-5 py-3 sm:flex sm:items-center sm:justify-between">
<div className="flex items-center space-x-2 whitespace-nowrap">
<Button
@@ -1,6 +1,5 @@
"use client";
import Placement from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/Placement";
import * as Collapsible from "@radix-ui/react-collapsible";
import { CheckIcon } from "lucide-react";
import Link from "next/link";
@@ -11,6 +10,8 @@ import { TSurvey, TSurveyProductOverwrites } from "@formbricks/types/surveys";
import { Label } from "@formbricks/ui/Label";
import { Switch } from "@formbricks/ui/Switch";
import Placement from "./Placement";
interface SurveyPlacementCardProps {
localSurvey: TSurvey;
setLocalSurvey: (survey: TSurvey) => void;
@@ -1,6 +1,5 @@
"use client";
import { validateId } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/lib/validation";
import { useState } from "react";
import toast from "react-hot-toast";
@@ -9,6 +8,8 @@ import { Button } from "@formbricks/ui/Button";
import { Input } from "@formbricks/ui/Input";
import { Label } from "@formbricks/ui/Label";
import { validateId } from "../lib/validation";
interface UpdateQuestionIdProps {
localSurvey: TSurvey;
question: TSurveyQuestion;
@@ -131,7 +131,8 @@ export default function WhenToSendCard({
className="w-full rounded-lg border border-slate-300 bg-white">
<Collapsible.CollapsibleTrigger
asChild
className="h-full w-full cursor-pointer rounded-lg hover:bg-slate-50">
className="h-full w-full cursor-pointer rounded-lg hover:bg-slate-50"
id="whenToSendCardTrigger">
<div className="inline-flex px-4 py-4">
<div className="flex items-center pl-2 pr-5">
{containsEmptyTriggers ? (
@@ -0,0 +1,5 @@
import { LoadingSkeleton } from "./components/LoadingSkeleton";
export default function Loading() {
return <LoadingSkeleton />;
}
@@ -0,0 +1,45 @@
import { TSurvey } from "@formbricks/types/surveys";
export const minimalSurvey: TSurvey = {
id: "someUniqueId1",
createdAt: new Date(),
updatedAt: new Date(),
name: "Minimal Survey",
type: "app",
environmentId: "someEnvId1",
createdBy: null,
status: "draft",
displayOption: "displayOnce",
autoClose: null,
triggers: [],
redirectUrl: null,
recontactDays: null,
welcomeCard: {
enabled: false,
headline: { default: "Welcome!" },
html: { default: "Thanks for providing your feedback - let's go!" },
timeToFinish: false,
showResponseCount: false,
},
questions: [],
thankYouCard: {
enabled: false,
},
hiddenFields: {
enabled: false,
},
delay: 0, // No delay
displayPercentage: null,
autoComplete: null,
runOnDate: null,
closeOnDate: null,
surveyClosedMessage: {
enabled: false,
},
productOverwrites: null,
singleUse: null,
styling: null,
resultShareKey: null,
segment: null,
languages: [],
};
@@ -0,0 +1,20 @@
"use client";
import { ArrowLeftIcon } from "lucide-react";
import { useRouter } from "next/navigation";
import { Button } from "@formbricks/ui/Button";
export const BackButton = () => {
const router = useRouter();
return (
<Button
variant="secondary"
StartIcon={ArrowLeftIcon}
onClick={() => {
router.back();
}}>
Back
</Button>
);
};
@@ -0,0 +1,15 @@
"use client";
import { BackButton } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/templates/components/BackButton";
export const MenuBar = () => {
return (
<>
<div className="border-b border-slate-200 bg-white px-5 py-3 sm:flex sm:items-center sm:justify-between">
<div className="flex items-center space-x-2 whitespace-nowrap">
<BackButton />
</div>
</div>
</>
);
};
@@ -1,17 +1,17 @@
"use client";
import { PreviewSurvey } from "@/app/(app)/environments/[environmentId]/surveys/components/PreviewSurvey";
import { TemplateList } from "@/app/(app)/environments/[environmentId]/surveys/templates/TemplateList";
import { replacePresetPlaceholders } from "@/app/lib/templates";
import { useEffect, useState } from "react";
import { MenuBar } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/templates/components/MenuBar";
import { useState } from "react";
import type { TEnvironment } from "@formbricks/types/environment";
import type { TProduct } from "@formbricks/types/product";
import type { TTemplate } from "@formbricks/types/templates";
import { TUser } from "@formbricks/types/user";
import { PreviewSurvey } from "@formbricks/ui/PreviewSurvey";
import { SearchBox } from "@formbricks/ui/SearchBox";
import { TemplateList } from "@formbricks/ui/TemplateList";
import { minimalSurvey, templates } from "./templates";
import { minimalSurvey } from "../../lib/minimalSurvey";
type TemplateContainerWithPreviewProps = {
environmentId: string;
@@ -29,21 +29,15 @@ export default function TemplateContainerWithPreview({
const [activeTemplate, setActiveTemplate] = useState<TTemplate | null>(null);
const [activeQuestionId, setActiveQuestionId] = useState<string | null>(null);
const [templateSearch, setTemplateSearch] = useState<string | null>(null);
useEffect(() => {
if (product && templates?.length) {
const newTemplate = replacePresetPlaceholders(templates[0], product);
setActiveTemplate(newTemplate);
setActiveQuestionId(newTemplate.preset.questions[0].id);
}
}, [product]);
return (
<div className="flex h-full flex-col ">
<div className="flex h-full flex-col">
<MenuBar />
<div className="relative z-0 flex flex-1 overflow-hidden">
<div className="flex-1 flex-col overflow-auto bg-slate-50">
<div className="flex flex-col items-center justify-between md:flex-row md:items-start">
<h1 className="ml-6 mt-6 text-2xl font-bold text-slate-800">Create a new survey</h1>
<div className="ml-6 mt-6 px-6">
<div className="ml-6 mt-6 flex flex-col items-center justify-between md:flex-row md:items-start">
<h1 className="text-2xl font-bold text-slate-800">Create a new survey</h1>
<div className="px-6">
<SearchBox
autoFocus
value={templateSearch ?? ""}
@@ -4,7 +4,7 @@ import { authOptions } from "@formbricks/lib/authOptions";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import TemplateContainerWithPreview from "./TemplateContainer";
import TemplateContainerWithPreview from "./components/TemplateContainer";
export default async function SurveyTemplatesPage({ params }) {
const session = await getServerSession(authOptions);
@@ -4,7 +4,6 @@ import { useEffect, useState } from "react";
import { Button } from "@formbricks/ui/Button";
import { Confetti } from "@formbricks/ui/Confetti";
import { ContentWrapper } from "@formbricks/ui/ContentWrapper";
interface ConfirmationPageProps {
environmentId: string;
@@ -15,25 +14,24 @@ export default function ConfirmationPage({ environmentId }: ConfirmationPageProp
useEffect(() => {
setShowConfetti(true);
}, []);
return (
<div className="h-full w-full">
{showConfetti && <Confetti />}
<ContentWrapper>
<div className="mx-auto max-w-sm py-8 sm:px-6 lg:px-8">
<div className="my-6 sm:flex-auto">
<h1 className="text-center text-xl font-semibold text-slate-900">Upgrade successful</h1>
<p className="mt-2 text-center text-sm text-slate-700">
Thanks a lot for upgrading your Formbricks subscription.
</p>
</div>
<Button
variant="darkCTA"
className="w-full justify-center"
href={`/environments/${environmentId}/settings/billing`}>
Back to billing overview
</Button>
<div className="mx-auto max-w-sm py-8 sm:px-6 lg:px-8">
<div className="my-6 sm:flex-auto">
<h1 className="text-center text-xl font-semibold text-slate-900">Upgrade successful</h1>
<p className="mt-2 text-center text-sm text-slate-700">
Thanks a lot for upgrading your Formbricks subscription.
</p>
</div>
</ContentWrapper>
<Button
variant="darkCTA"
className="w-full justify-center"
href={`/environments/${environmentId}/settings/billing`}>
Back to billing overview
</Button>
</div>
</div>
);
}
@@ -1,9 +1,15 @@
import ConfirmationPage from "./components/ConfirmationPage";
import ConfirmationPage from "@/app/(app)/billing-confirmation/components/ConfirmationPage";
import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper";
export const dynamic = "force-dynamic";
export default function BillingConfirmation({ searchParams }) {
const { environmentId } = searchParams;
return <ConfirmationPage environmentId={environmentId?.toString()} />;
return (
<PageContentWrapper>
<ConfirmationPage environmentId={environmentId?.toString()} />
</PageContentWrapper>
);
}
@@ -1,7 +1,6 @@
"use client";
import { useState } from "react";
import { useMemo } from "react";
import { useMemo, useState } from "react";
import { TAttributeClass } from "@formbricks/types/attributeClasses";
import { Switch } from "@formbricks/ui/Switch";
@@ -9,7 +8,6 @@ import { Switch } from "@formbricks/ui/Switch";
import { AttributeDetailModal } from "./AttributeDetailModal";
import { AttributeClassDataRow } from "./AttributeRowData";
import { AttributeTableHeading } from "./AttributeTableHeading";
import { HowToAddAttributesButton } from "./HowToAddAttributesButton";
import { UploadAttributesModal } from "./UploadAttributesModal";
interface AttributeClassesTableProps {
@@ -45,16 +43,15 @@ export const AttributeClassesTable = ({ attributeClasses }: AttributeClassesTabl
return (
<>
<div className="mb-6 flex items-center justify-end text-right">
{hasArchived && (
{hasArchived && (
<div className="my-4 flex items-center justify-end text-right">
<div className="flex items-center text-sm font-medium">
Show archived
<Switch className="mx-3" checked={showArchived} onCheckedChange={toggleShowArchived} />
</div>
)}
<HowToAddAttributesButton />
</div>
<div className="rounded-lg border border-slate-200">
</div>
)}
<div className="rounded-xl border border-slate-200 bg-white shadow-sm">
<AttributeTableHeading />
<div className="grid-cols-7">
{displayedAttributeClasses.map((attributeClass, index) => (
@@ -5,12 +5,10 @@ import { Badge } from "@formbricks/ui/Badge";
export const AttributeClassDataRow = ({ attributeClass }) => {
return (
<div className="m-2 grid h-16 grid-cols-5 content-center rounded-lg hover:bg-slate-100">
<div className="m-2 grid h-16 grid-cols-5 content-center rounded-lg transition-colors ease-in-out hover:bg-slate-100">
<div className="col-span-5 flex items-center pl-6 text-sm sm:col-span-3">
<div className="flex items-center">
<div className="h-10 w-10 flex-shrink-0">
<TagIcon className="h-8 w-8 flex-shrink-0 text-slate-500" />
</div>
<TagIcon className="h-5 w-5 flex-shrink-0 text-slate-500" />
<div className="ml-4 text-left">
<div className="font-medium text-slate-900">
{attributeClass.name}
@@ -1,7 +1,7 @@
export const AttributeTableHeading = () => {
return (
<>
<div className="grid h-12 grid-cols-5 content-center rounded-lg bg-slate-100 text-left text-sm font-semibold text-slate-900">
<div className="grid h-12 grid-cols-5 content-center border-b border-slate-200 text-left text-sm font-semibold text-slate-900">
<div className="col-span-3 pl-6 ">Name</div>
<div className="hidden text-center sm:block">Created</div>
<div className="hidden text-center sm:block">Last Updated</div>
@@ -1,21 +1,8 @@
import { HelpCircleIcon, TagIcon } from "lucide-react";
import { Button } from "@formbricks/ui/Button";
import { TagIcon } from "lucide-react";
export default function Loading() {
return (
<>
<div className="mb-6 text-right">
<div className="mb-6 flex items-center justify-end text-right">
<Button
variant="secondary"
className="pointer-events-none animate-pulse cursor-not-allowed select-none">
<HelpCircleIcon className="mr-2 h-4 w-4" />
Loading Attributes
</Button>
</div>
</div>
<div className="rounded-lg border border-slate-200">
<div className="grid h-12 grid-cols-5 content-center rounded-lg bg-slate-100 text-left text-sm font-semibold text-slate-900">
<div className="col-span-3 pl-6 ">Name</div>
@@ -0,0 +1,38 @@
import { PeopleSecondaryNavigation } from "@/app/(app)/environments/[environmentId]/(people)/people/components/PeopleSecondaryNavigation";
import { CircleHelpIcon } from "lucide-react";
import { Metadata } from "next";
import { getAttributeClasses } from "@formbricks/lib/attributeClass/service";
import { Button } from "@formbricks/ui/Button";
import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper";
import { PageHeader } from "@formbricks/ui/PageHeader";
import { AttributeClassesTable } from "./components/AttributeClassesTable";
export const metadata: Metadata = {
title: "Attributes",
};
export default async function AttributesPage({ params }) {
let attributeClasses = await getAttributeClasses(params.environmentId);
const HowToAddAttributesButton = (
<Button
size="sm"
href="https://formbricks.com/docs/app-surveys/user-identification#setting-custom-user-attributes"
variant="secondary"
target="_blank"
EndIcon={CircleHelpIcon}>
How to add attributes
</Button>
);
return (
<PageContentWrapper>
<PageHeader pageTitle="People" cta={HowToAddAttributesButton}>
<PeopleSecondaryNavigation activeId="attributes" environmentId={params.environmentId} />
</PageHeader>
<AttributeClassesTable attributeClasses={attributeClasses} />
</PageContentWrapper>
);
}
@@ -1,4 +1,4 @@
import ActivityTimeline from "@/app/(app)/environments/[environmentId]/(peopleAndSegments)/people/[personId]/components/ActivityTimeline";
import ActivityTimeline from "@/app/(app)/environments/[environmentId]/(people)/people/[personId]/components/ActivityTimeline";
import { getActionsByPersonId } from "@formbricks/lib/action/service";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
@@ -1,21 +1,20 @@
"use client";
import { deletePersonAction } from "@/app/(app)/environments/[environmentId]/(peopleAndSegments)/people/[personId]/actions";
import { deletePersonAction } from "@/app/(app)/environments/[environmentId]/(people)/people/[personId]/actions";
import { TrashIcon } from "lucide-react";
import { useRouter } from "next/navigation";
import { useState } from "react";
import toast from "react-hot-toast";
import { TMembershipRole } from "@formbricks/types/memberships";
import { DeleteDialog } from "@formbricks/ui/DeleteDialog";
interface DeletePersonButtonProps {
environmentId: string;
personId: string;
membershipRole?: TMembershipRole;
isViewer: boolean;
}
export function DeletePersonButton({ environmentId, personId }: DeletePersonButtonProps) {
export const DeletePersonButton = ({ environmentId, personId, isViewer }: DeletePersonButtonProps) => {
const router = useRouter();
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
@@ -34,6 +33,11 @@ export function DeletePersonButton({ environmentId, personId }: DeletePersonButt
setIsDeletingPerson(false);
}
};
if (isViewer) {
return null;
}
return (
<>
<button
@@ -51,4 +55,4 @@ export function DeletePersonButton({ environmentId, personId }: DeletePersonButt
/>
</>
);
}
};
@@ -1,4 +1,4 @@
import ResponseTimeline from "@/app/(app)/environments/[environmentId]/(peopleAndSegments)/people/[personId]/components/ResponseTimeline";
import ResponseTimeline from "@/app/(app)/environments/[environmentId]/(people)/people/[personId]/components/ResponseTimeline";
import { getServerSession } from "next-auth";
import { authOptions } from "@formbricks/lib/authOptions";
@@ -1,6 +1,6 @@
"use client";
import ResponseFeed from "@/app/(app)/environments/[environmentId]/(peopleAndSegments)/people/[personId]/components/ResponsesFeed";
import ResponseFeed from "@/app/(app)/environments/[environmentId]/(people)/people/[personId]/components/ResponsesFeed";
import { ArrowDownUpIcon } from "lucide-react";
import { useEffect, useState } from "react";
@@ -1,7 +1,7 @@
import {
ActivityItemIcon,
ActivityItemPopover,
} from "@/app/(app)/environments/[environmentId]/(peopleAndSegments)/people/[personId]/components/ActivityItemComponents";
} from "@/app/(app)/environments/[environmentId]/(people)/people/[personId]/components/ActivityItemComponents";
import { ArrowDownUpIcon } from "lucide-react";
import { TrashIcon } from "lucide-react";
@@ -0,0 +1,76 @@
import ActivitySection from "@/app/(app)/environments/[environmentId]/(people)/people/[personId]/components/ActivitySection";
import AttributesSection from "@/app/(app)/environments/[environmentId]/(people)/people/[personId]/components/AttributesSection";
import { DeletePersonButton } from "@/app/(app)/environments/[environmentId]/(people)/people/[personId]/components/DeletePersonButton";
import ResponseSection from "@/app/(app)/environments/[environmentId]/(people)/people/[personId]/components/ResponseSection";
import { getServerSession } from "next-auth";
import { getAttributes } from "@formbricks/lib/attribute/service";
import { authOptions } from "@formbricks/lib/authOptions";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";
import { getPerson } from "@formbricks/lib/person/service";
import { getPersonIdentifier } from "@formbricks/lib/person/utils";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service";
import { getTeamByEnvironmentId } from "@formbricks/lib/team/service";
import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper";
import { PageHeader } from "@formbricks/ui/PageHeader";
export default async function PersonPage({ params }) {
const [environment, environmentTags, product, session, team, person, attributes] = await Promise.all([
getEnvironment(params.environmentId),
getTagsByEnvironmentId(params.environmentId),
getProductByEnvironmentId(params.environmentId),
getServerSession(authOptions),
getTeamByEnvironmentId(params.environmentId),
getPerson(params.personId),
getAttributes(params.personId),
]);
if (!product) {
throw new Error("Product not found");
}
if (!environment) {
throw new Error("Environment not found");
}
if (!session) {
throw new Error("Session not found");
}
if (!team) {
throw new Error("Team not found");
}
if (!person) {
throw new Error("Person not found");
}
const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id);
const { isViewer } = getAccessFlags(currentUserMembership?.role);
const getDeletePersonButton = () => {
return (
<DeletePersonButton environmentId={environment.id} personId={params.personId} isViewer={isViewer} />
);
};
return (
<PageContentWrapper>
<PageHeader pageTitle={getPersonIdentifier(person, attributes)} cta={getDeletePersonButton()} />
<section className="pb-24 pt-6">
<div className="grid grid-cols-1 gap-x-8 md:grid-cols-4">
<AttributesSection personId={params.personId} />
<ResponseSection
environment={environment}
personId={params.personId}
environmentTags={environmentTags}
/>
<ActivitySection environmentId={params.environmentId} personId={params.personId} />
</div>
</section>
</PageContentWrapper>
);
}
@@ -0,0 +1,28 @@
import { SecondaryNavigation } from "@formbricks/ui/SecondaryNavigation";
interface PeopleSegmentsTabsProps {
activeId: string;
environmentId: string;
}
export const PeopleSecondaryNavigation = ({ activeId, environmentId }: PeopleSegmentsTabsProps) => {
const navigation = [
{
id: "people",
label: "People",
href: `/environments/${environmentId}/people`,
},
{
id: "segments",
label: "Segments",
href: `/environments/${environmentId}/segments`,
},
{
id: "attributes",
label: "Attributes",
href: `/environments/${environmentId}/attributes`,
},
];
return <SecondaryNavigation navigation={navigation} activeId={activeId} />;
};
@@ -2,7 +2,7 @@ import Link from "next/link";
import React from "react";
import { getAttributes } from "@formbricks/lib/attribute/service";
import { getPersonIdentifier } from "@formbricks/lib/person/util";
import { getPersonIdentifier } from "@formbricks/lib/person/utils";
import { TPerson } from "@formbricks/types/people";
import { PersonAvatar } from "@formbricks/ui/Avatars";
@@ -1,14 +1,6 @@
import HowToAddPeopleButton from "@/app/(app)/environments/[environmentId]/components/HowToAddPeopleButton";
export default function Loading() {
return (
<>
<div className="mb-6 text-right">
<div className="mb-6 flex items-center justify-end text-right">
<HowToAddPeopleButton />
</div>
</div>
<div className="rounded-lg border border-slate-200">
<div className="grid h-12 grid-cols-7 content-center rounded-lg bg-slate-100 text-left text-sm font-semibold text-slate-900">
<div className="col-span-3 pl-6">User</div>
@@ -1,10 +1,14 @@
import HowToAddPeopleButton from "@/app/(app)/environments/[environmentId]/components/HowToAddPeopleButton";
import { PeopleSecondaryNavigation } from "@/app/(app)/environments/[environmentId]/(people)/people/components/PeopleSecondaryNavigation";
import { CircleHelpIcon } from "lucide-react";
import { ITEMS_PER_PAGE } from "@formbricks/lib/constants";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getPeople, getPeopleCount } from "@formbricks/lib/person/service";
import { TPerson } from "@formbricks/types/people";
import { Button } from "@formbricks/ui/Button";
import EmptySpaceFiller from "@formbricks/ui/EmptySpaceFiller";
import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper";
import { PageHeader } from "@formbricks/ui/PageHeader";
import { Pagination } from "@formbricks/ui/Pagination";
import { PersonCard } from "./components/PersonCard";
@@ -36,28 +40,38 @@ export default async function PeoplePage({
people = await getPeople(params.environmentId, pageNumber);
}
const HowToAddPeopleButton = (
<Button
size="sm"
href="https://formbricks.com/docs/app-surveys/user-identification"
variant="secondary"
target="_blank"
EndIcon={CircleHelpIcon}>
How to add people
</Button>
);
return (
<>
<div className="mb-6 text-right">
<div className="mb-6 flex items-center justify-end text-right">
<HowToAddPeopleButton />
</div>
</div>
<PageContentWrapper>
<PageHeader pageTitle="People" cta={HowToAddPeopleButton}>
<PeopleSecondaryNavigation activeId="people" environmentId={params.environmentId} />
</PageHeader>
{people.length === 0 ? (
<EmptySpaceFiller
type="table"
environment={environment}
emptyMessage="Your users will appear here as soon as they use your app ⏲️"
noWidgetRequired={true}
/>
) : (
<div className="rounded-lg border border-slate-200">
<div className="grid h-12 grid-cols-7 content-center rounded-lg bg-slate-100 text-left text-sm font-semibold text-slate-900">
<div className="rounded-xl border border-slate-200 bg-white shadow-sm">
<div className="grid h-12 grid-cols-7 content-center border-b border-slate-200 text-left text-sm font-semibold text-slate-900">
<div className="col-span-3 pl-6 ">User</div>
<div className="col-span-2 hidden text-center sm:block">User ID</div>
<div className="col-span-2 hidden text-center sm:block">Email</div>
</div>
{people.map((person) => (
<PersonCard person={person} />
<PersonCard person={person} key={person.id} />
))}
</div>
)}
@@ -69,6 +83,6 @@ export default async function PeoplePage({
itemsPerPage={ITEMS_PER_PAGE}
/>
)}
</>
</PageContentWrapper>
);
}
@@ -0,0 +1,50 @@
"use server";
import { getServerSession } from "next-auth";
import { authOptions } from "@formbricks/lib/authOptions";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { deleteSegment, getSegment, updateSegment } from "@formbricks/lib/segment/service";
import { AuthorizationError } from "@formbricks/types/errors";
import { TSegmentUpdateInput, ZSegmentFilters } from "@formbricks/types/segment";
export const deleteBasicSegmentAction = async (environmentId: string, segmentId: string) => {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const environmentAccess = hasUserEnvironmentAccess(session.user.id, environmentId);
if (!environmentAccess) throw new AuthorizationError("Not authorized");
const foundSegment = await getSegment(segmentId);
if (!foundSegment) {
throw new Error(`Segment with id ${segmentId} not found`);
}
return await deleteSegment(segmentId);
};
export const updateBasicSegmentAction = async (
environmentId: string,
segmentId: string,
data: TSegmentUpdateInput
) => {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const environmentAccess = hasUserEnvironmentAccess(session.user.id, environmentId);
if (!environmentAccess) throw new AuthorizationError("Not authorized");
const { filters } = data;
if (filters) {
const parsedFilters = ZSegmentFilters.safeParse(filters);
if (!parsedFilters.success) {
const errMsg =
parsedFilters.error.issues.find((issue) => issue.code === "custom")?.message || "Invalid filters";
throw new Error(errMsg);
}
}
return await updateSegment(segmentId, data);
};
@@ -1,7 +1,6 @@
"use client";
import { UsersIcon } from "lucide-react";
import { FilterIcon } from "lucide-react";
import { FilterIcon, PlusIcon, UsersIcon } from "lucide-react";
import { useRouter } from "next/navigation";
import { useMemo, useState } from "react";
import toast from "react-hot-toast";
@@ -118,11 +117,9 @@ const BasicCreateSegmentModal = ({
return (
<>
<div className="mb-4 flex justify-end">
<Button variant="darkCTA" onClick={() => setOpen(true)}>
Create Segment
</Button>
</div>
<Button variant="darkCTA" size="sm" onClick={() => setOpen(true)} EndIcon={PlusIcon}>
Create segment
</Button>
<Modal
open={open}
@@ -249,7 +246,7 @@ const BasicCreateSegmentModal = ({
onClick={() => {
handleCreateSegment();
}}>
Create Segment
Create segment
</Button>
</div>
</div>
@@ -1,9 +1,5 @@
"use client";
import {
deleteBasicSegmentAction,
updateBasicSegmentAction,
} from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions";
import { FilterIcon, Trash2 } from "lucide-react";
import { useRouter } from "next/navigation";
import { useMemo, useState } from "react";
@@ -20,6 +16,8 @@ import BasicSegmentEditor from "@formbricks/ui/Targeting/BasicSegmentEditor";
import ConfirmDeleteSegmentModal from "@formbricks/ui/Targeting/ConfirmDeleteSegmentModal";
import { UpgradePlanNotice } from "@formbricks/ui/UpgradePlanNotice";
import { deleteBasicSegmentAction, updateBasicSegmentAction } from "../actions";
type TBasicSegmentSettingsTabProps = {
environmentId: string;
setOpen: (open: boolean) => void;
@@ -1,5 +1,6 @@
import BasicCreateSegmentModal from "@/app/(app)/environments/[environmentId]/(peopleAndSegments)/segments/components/BasicCreateSegmentModal";
import SegmentTable from "@/app/(app)/environments/[environmentId]/(peopleAndSegments)/segments/components/SegmentTable";
import { PeopleSecondaryNavigation } from "@/app/(app)/environments/[environmentId]/(people)/people/components/PeopleSecondaryNavigation";
import BasicCreateSegmentModal from "@/app/(app)/environments/[environmentId]/(people)/segments/components/BasicCreateSegmentModal";
import SegmentTable from "@/app/(app)/environments/[environmentId]/(people)/segments/components/SegmentTable";
import CreateSegmentModal from "@formbricks/ee/advancedTargeting/components/CreateSegmentModal";
import { ACTIONS_TO_EXCLUDE } from "@formbricks/ee/advancedTargeting/lib/constants";
@@ -11,6 +12,8 @@ import { getEnvironment } from "@formbricks/lib/environment/service";
import { getSegments } from "@formbricks/lib/segment/service";
import { getTeamByEnvironmentId } from "@formbricks/lib/team/service";
import EmptySpaceFiller from "@formbricks/ui/EmptySpaceFiller";
import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper";
import { PageHeader } from "@formbricks/ui/PageHeader";
export default async function SegmentsPage({ params }) {
const [environment, segments, attributeClasses, actionClassesFromServer, team] = await Promise.all([
@@ -49,28 +52,33 @@ export default async function SegmentsPage({ params }) {
return true;
});
return (
<>
{isAdvancedTargetingAllowed ? (
<CreateSegmentModal
environmentId={params.environmentId}
actionClasses={actionClasses}
attributeClasses={attributeClasses}
segments={filteredSegments}
/>
) : (
<BasicCreateSegmentModal
attributeClasses={attributeClasses}
environmentId={params.environmentId}
isFormbricksCloud={IS_FORMBRICKS_CLOUD}
/>
)}
const renderCreateSegmentButton = () =>
isAdvancedTargetingAllowed ? (
<CreateSegmentModal
environmentId={params.environmentId}
actionClasses={actionClasses}
attributeClasses={attributeClasses}
segments={filteredSegments}
/>
) : (
<BasicCreateSegmentModal
attributeClasses={attributeClasses}
environmentId={params.environmentId}
isFormbricksCloud={IS_FORMBRICKS_CLOUD}
/>
);
return (
<PageContentWrapper>
<PageHeader pageTitle="People" cta={renderCreateSegmentButton()}>
<PeopleSecondaryNavigation activeId="segments" environmentId={params.environmentId} />
</PageHeader>
{filteredSegments.length === 0 ? (
<EmptySpaceFiller
type="table"
environment={environment}
emptyMessage="No segments yet. Add your first one to get started."
noWidgetRequired={true}
/>
) : (
<SegmentTable
@@ -80,6 +88,6 @@ export default async function SegmentsPage({ params }) {
isAdvancedTargetingAllowed={isAdvancedTargetingAllowed}
/>
)}
</>
</PageContentWrapper>
);
}
@@ -1,61 +0,0 @@
import SurveyNavBarName from "@/app/(app)/environments/[environmentId]/(peopleAndSegments)/attributes/components/SurveyNavBarName";
import Link from "next/link";
import { cn } from "@formbricks/lib/cn";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { getSurvey } from "@formbricks/lib/survey/service";
interface SecondNavbarProps {
tabs: { id: string; label: string; href: string; icon?: React.ReactNode }[];
activeId: string;
surveyId?: string;
environmentId: string;
}
export default async function SecondNavbar({
tabs,
activeId,
surveyId,
environmentId,
...props
}: SecondNavbarProps) {
const product = await getProductByEnvironmentId(environmentId!);
if (!product) {
throw new Error("Product not found");
}
let survey;
if (surveyId) {
survey = await getSurvey(surveyId);
}
return (
<div {...props}>
<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">
{survey && environmentId && (
<SurveyNavBarName surveyName={survey.name} productName={product.name} />
)}
</div>{" "}
<nav className="flex h-full items-center space-x-4 justify-self-center" aria-label="Tabs">
{tabs.map((tab) => (
<Link
key={tab.id}
href={tab.href}
className={cn(
tab.id === activeId
? " border-brand-dark border-b-2 font-semibold text-slate-900"
: "text-slate-500 hover:text-slate-700",
"flex h-full items-center px-3 text-sm font-medium"
)}
aria-current={tab.id === activeId ? "page" : undefined}>
{tab.icon && <div className="mr-2 h-5 w-5">{tab.icon}</div>}
{tab.label}
</Link>
))}
</nav>
<div className="justify-self-end"></div>
</div>
</div>
);
}

Some files were not shown because too many files have changed in this diff Show More