mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 16:16:21 -06:00
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:
1
apps/docs/.gitignore
vendored
1
apps/docs/.gitignore
vendored
@@ -35,3 +35,4 @@ yarn-error.log*
|
||||
next-env.d.ts
|
||||
|
||||
public/sitemap*.xml
|
||||
public/robots.txt
|
||||
@@ -2,7 +2,6 @@ import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import { Libraries } from "./components/Libraries";
|
||||
|
||||
import SetupChecklist from "./images/env-id.png";
|
||||
import ReactApp from "./images/react-in-app-survey-app-popup-form.webp";
|
||||
import WidgetConnected from "./images/widget-connected.webp";
|
||||
import WidgetNotConnected from "./images/widget-not-connected.webp";
|
||||
@@ -27,14 +26,7 @@ for something else, please [join our Discord!](https://formbricks.com/discord) a
|
||||
Before getting started, make sure you have:
|
||||
|
||||
1. A web application (behind your user authentication system) in your desired framework is set up and running.
|
||||
2. A Formbricks account with access to your environment ID and API host. You can find these in the **Setup Checklist** in the Settings:
|
||||
|
||||
<MdxImage
|
||||
src={SetupChecklist}
|
||||
alt="Step 2 - Setup Checklist"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
2. A Formbricks account with access to your environment ID and API host. You can find these in the **Setup Checklist** in the Settings.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import { MdxImage } from "@/components/MdxImage";
|
||||
|
||||
import { Libraries } from "./components/Libraries";
|
||||
|
||||
import SetupChecklist from "./images/env-id.png";
|
||||
import ReactApp from "./images/react-in-app-survey-app-popup-form.webp";
|
||||
import WidgetConnected from "./images/widget-connected.webp";
|
||||
import WidgetNotConnected from "./images/widget-not-connected.webp";
|
||||
@@ -31,14 +30,7 @@ Detailed Website Survey SDK documentation can be found [here](/developer-docs/we
|
||||
Before getting started, make sure you have:
|
||||
|
||||
1. A **public-facing** web application in your desired framework is set up and running.
|
||||
2. A Formbricks account with access to your environment ID and API host. You can find these in the **Setup Checklist** in the Settings:
|
||||
|
||||
<MdxImage
|
||||
src={SetupChecklist}
|
||||
alt="Step 2 - Setup Checklist"
|
||||
quality="100"
|
||||
className="max-w-full rounded-lg sm:max-w-3xl"
|
||||
/>
|
||||
2. A Formbricks account with access to your environment ID and API host. You can find these in the **Setup Checklist** in the Settings.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
import { navigation } from "@/lib/navigation";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { FaDiscord, FaGithub, FaXTwitter } from "react-icons/fa6";
|
||||
|
||||
import { Button } from "./Button";
|
||||
import { DiscordIcon } from "./icons/DiscordIcon";
|
||||
import { GithubIcon } from "./icons/GithubIcon";
|
||||
import { TwitterIcon } from "./icons/TwitterIcon";
|
||||
|
||||
function PageLink({
|
||||
label,
|
||||
@@ -98,13 +100,13 @@ function SmallPrint() {
|
||||
Formbricks GmbH © {currentYear}. All rights reserved.
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<SocialLink href="https://twitter.com/formbricks" icon={FaXTwitter}>
|
||||
<SocialLink href="https://twitter.com/formbricks" icon={TwitterIcon}>
|
||||
Follow us on Twitter
|
||||
</SocialLink>
|
||||
<SocialLink href="https://github.com/formbricks/formbricks" icon={FaGithub}>
|
||||
<SocialLink href="https://github.com/formbricks/formbricks" icon={GithubIcon}>
|
||||
Follow us on GitHub
|
||||
</SocialLink>
|
||||
<SocialLink href="https://formbricks.com/discord" icon={FaDiscord}>
|
||||
<SocialLink href="https://formbricks.com/discord" icon={DiscordIcon}>
|
||||
Join our Discord server
|
||||
</SocialLink>
|
||||
</div>
|
||||
|
||||
15
apps/docs/components/icons/DiscordIcon.tsx
Normal file
15
apps/docs/components/icons/DiscordIcon.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
export const DiscordIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
|
||||
return (
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="currentColor"
|
||||
stroke-width="0"
|
||||
viewBox="0 0 640 512"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}>
|
||||
<path d="M524.531,69.836a1.5,1.5,0,0,0-.764-.7A485.065,485.065,0,0,0,404.081,32.03a1.816,1.816,0,0,0-1.923.91,337.461,337.461,0,0,0-14.9,30.6,447.848,447.848,0,0,0-134.426,0,309.541,309.541,0,0,0-15.135-30.6,1.89,1.89,0,0,0-1.924-.91A483.689,483.689,0,0,0,116.085,69.137a1.712,1.712,0,0,0-.788.676C39.068,183.651,18.186,294.69,28.43,404.354a2.016,2.016,0,0,0,.765,1.375A487.666,487.666,0,0,0,176.02,479.918a1.9,1.9,0,0,0,2.063-.676A348.2,348.2,0,0,0,208.12,430.4a1.86,1.86,0,0,0-1.019-2.588,321.173,321.173,0,0,1-45.868-21.853,1.885,1.885,0,0,1-.185-3.126c3.082-2.309,6.166-4.711,9.109-7.137a1.819,1.819,0,0,1,1.9-.256c96.229,43.917,200.41,43.917,295.5,0a1.812,1.812,0,0,1,1.924.233c2.944,2.426,6.027,4.851,9.132,7.16a1.884,1.884,0,0,1-.162,3.126,301.407,301.407,0,0,1-45.89,21.83,1.875,1.875,0,0,0-1,2.611,391.055,391.055,0,0,0,30.014,48.815,1.864,1.864,0,0,0,2.063.7A486.048,486.048,0,0,0,610.7,405.729a1.882,1.882,0,0,0,.765-1.352C623.729,277.594,590.933,167.465,524.531,69.836ZM222.491,337.58c-28.972,0-52.844-26.587-52.844-59.239S193.056,219.1,222.491,219.1c29.665,0,53.306,26.82,52.843,59.239C275.334,310.993,251.924,337.58,222.491,337.58Zm195.38,0c-28.971,0-52.843-26.587-52.843-59.239S388.437,219.1,417.871,219.1c29.667,0,53.307,26.82,52.844,59.239C470.715,310.993,447.538,337.58,417.871,337.58Z"></path>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
15
apps/docs/components/icons/GithubIcon.tsx
Normal file
15
apps/docs/components/icons/GithubIcon.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
export const GithubIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
|
||||
return (
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="currentColor"
|
||||
stroke-width="0"
|
||||
viewBox="0 0 496 512"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}>
|
||||
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
15
apps/docs/components/icons/TwitterIcon.tsx
Normal file
15
apps/docs/components/icons/TwitterIcon.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
export const TwitterIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
|
||||
return (
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="currentColor"
|
||||
stroke-width="0"
|
||||
viewBox="0 0 512 512"
|
||||
height="1em"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}>
|
||||
<path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@formbricks/formbricks-com",
|
||||
"name": "@formbricks/docs",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -50,7 +50,6 @@
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-highlight-words": "^0.20.0",
|
||||
"react-icons": "^5.2.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-responsive-embed": "^2.1.0",
|
||||
"remark": "^15.0.1",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"extends": "@formbricks/tsconfig/nextjs.json",
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "../../packages/types/*.d.ts"],
|
||||
"exclude": ["../../.env"],
|
||||
"exclude": ["../../.env", "node_modules"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
|
||||
@@ -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'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);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user