Compare commits

..

23 Commits

Author SHA1 Message Date
Piyush Gupta
65a5d5b8e7 fix: consistency 2024-11-26 11:13:29 +05:30
Johannes
4214e94778 fix icon bar 2024-11-25 12:48:12 -08:00
Johannes
7139ec5276 Merge branch 'main' of https://github.com/formbricks/formbricks into feat/icon-bar 2024-11-25 11:37:52 -08:00
Piyush Gupta
b83b54eee1 fix: improved AI insights generation prompt (#4330) 2024-11-25 11:12:01 +00:00
Piyush Gupta
eb2621f72a fix: license checks in server actions (#4274)
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
2024-11-25 09:27:46 +00:00
Dhruwang Jariwala
44980d21a9 fix: vercel build (#4351) 2024-11-22 10:16:41 +00:00
Dhruwang Jariwala
e1d2e1357b fix: increase share rate limit (#4345) 2024-11-22 08:07:18 +00:00
Piyush Gupta
f80c7d03e2 fix: next js 15 stale experimental flags (#4350) 2024-11-22 07:55:42 +00:00
Dhruwang Jariwala
4ca6ee358b fix: created at to integrations (#4343)
Co-authored-by: Piyush Gupta <piyushguptaa2z123@gmail.com>
2024-11-22 06:19:54 +00:00
Dhruwang Jariwala
9dad06222d chore: move ui components to modules (#4342)
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
2024-11-21 17:58:15 +00:00
Anshuman Pandey
37ef6be4c3 feat: survey follow ups (#4247)
Co-authored-by: Johannes <johannes@formbricks.com>
Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
2024-11-21 06:50:37 +00:00
Matti Nannt
f0a4fad878 chore: add github issue task template (#4340) 2024-11-20 12:42:01 +01:00
Matti Nannt
06ae035e11 chore: update github issue template (#4339) 2024-11-20 12:33:11 +01:00
Piyush Gupta
91b5177bdb fix: variable docs (#4338) 2024-11-20 10:07:03 +00:00
Dhruwang Jariwala
30bd427985 docs: redis docs (#4316) 2024-11-20 08:16:47 +00:00
Piyush Gupta
a92762ff47 feat: allowed assigning of variable to open text number question (#4334) 2024-11-20 07:43:21 +00:00
Piyush Gupta
0a5c98aba0 chore: removed experience page (#4320) 2024-11-20 07:23:37 +00:00
Piyush Gupta
6f041bf693 fix: license cache issue (#4337) 2024-11-20 07:21:57 +00:00
Dhruwang Jariwala
23c9dc304a chore: Billing to new module structure (#4308) 2024-11-19 11:00:39 +00:00
Piyush Gupta
97377fe8bd feat: updated org limits data migration (#4329) 2024-11-19 10:10:26 +00:00
Piyush Gupta
36cf16ce90 chore: move ee services to new module structure (#4317) 2024-11-19 08:17:21 +00:00
Piyush Gupta
ab3ef63097 fix: only one jump to question is allowed (#4332) 2024-11-19 04:33:58 +00:00
aryamantodkar
44bbb59c8f feat: created icon bar component in survey overview
feat: created icon bar component in survey overview

chore: next version upgrade (#4291)

fix: recall in slack integration (#4304)

fix: survey release and close bug for first date of the month (#4311)

Co-authored-by: Dhruwang Jariwala <67850763+Dhruwang@users.noreply.github.com>

chore: make redis an enterprise feature (#4314)

feat: logic fallback option added (#4306)

Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>

feat: created icon bar component in survey overview
2024-11-16 07:44:54 +05:30
710 changed files with 5031 additions and 2918 deletions

View File

@@ -1,7 +1,6 @@
name: Bug report
description: "Found a bug? Please fill out the sections below. \U0001F44D"
labels:
- bug
type: bug
body:
- type: textarea
id: issue-summary

View File

@@ -1,7 +1,6 @@
name: Feature request
description: "Suggest an idea for this project \U0001F680"
labels:
- enhancement
type: feature
body:
- type: textarea
id: problem-description

11
.github/ISSUE_TEMPLATE/task.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
name: Task (internal)
description: "Template for creating a task. Used by the Formbricks Team only \U0001f4e5"
type: task
body:
- type: textarea
id: task-summary
attributes:
label: Task description
description: A clear detailed-rich description of the task.
validations:
required: true

3
apps/demo/globals.css Normal file
View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -12,7 +12,6 @@
},
"dependencies": {
"@formbricks/js": "workspace:*",
"@formbricks/ui": "workspace:*",
"lucide-react": "0.452.0",
"next": "15.0.3",
"react": "19.0.0-rc-ed15d500-20241110",

View File

@@ -1,6 +1,6 @@
import type { AppProps } from "next/app";
import Head from "next/head";
import "@formbricks/ui/globals.css";
import "../globals.css";
const App = ({ Component, pageProps }: AppProps) => {
return (

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -127,10 +127,10 @@ Locate that file. We are using the [Tailwind Template “Syntax”](https://tail
<CodeGroup title="Entire Widget">
```tsx
import { Button } from "@/modules/ui/components/Button";
import { Popover, PopoverContent, PopoverTrigger } from "@/modules/ui/popover";
import { useRouter } from "next/router";
import { useState } from "react";
import { Button } from "@formbricks/ui/components/Button";
import { Popover, PopoverContent, PopoverTrigger } from "@formbricks/ui/components/Popover";
import { handleFeedbackSubmit, updateFeedback } from "../../lib/handleFeedbackSubmit";
export const DocsFeedback = () => {

View File

@@ -1,83 +0,0 @@
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@formbricks/ui/components/Accordion";
import { FaqJsonLdComponent } from "./FAQPageJsonLd";
const FAQ_DATA = [
{
question: "What is an environment ID?",
answer: () => (
<>
The environment ID is a unique identifier associated with each Environment in Formbricks,
distinguishing between different setups like production, development, etc.
</>
),
},
{
question: "How can I implement authentication for the Formbricks API?",
answer: () => (
<>
Formbricks provides 2 types of API keys for each environment ie development and production. You can
generate, view, and manage these keys in the Settings section on the Admin dashboard. Include the API
key in your requests to authenticate and gain access to Formbricks functionalities.
</>
),
},
{
question: "Can I run the deployment shell script on any server?",
answer: () => (
<>
You can run it on any machine you own as long as its running a <b> Linux Ubuntu </b> distribution. And
to forward the requests, make sure you have an <b>A record</b> setup for your domain pointing to the
server.
</>
),
},
{
question: "Can I self-host Formbricks?",
answer: () => (
<>
Absolutely! We provide an option for users to host Formbricks on their own server, ensuring even more
control over data and compliance. And the best part? Self-hosting is available for free, always. For
documentation on self hosting, click{" "}
<a href="/self-hosting/deployment" className="text-brand-dark dark:text-brand-light">
here
</a>
.
</>
),
},
{
question: "How can I change Button texts in my survey?",
answer: () => (
<>
For the question that you want to change the button text, click on the <b>Show Advanced Settings</b>{" "}
toggle and change the button label in the <b>Button Text</b> field.
</>
),
},
];
export const faqJsonLdData = FAQ_DATA.map((faq) => ({
questionName: faq.question,
acceptedAnswerText: faq.answer(),
}));
export const FAQ = () => {
return (
<>
<FaqJsonLdComponent data={faqJsonLdData} />
<Accordion type="single" collapsible>
{FAQ_DATA.map((faq, index) => (
<AccordionItem key={`item-${index}`} value={`item-${index + 1}`} className="not-prose mb-0 mt-0">
<AccordionTrigger>{faq.question}</AccordionTrigger>
<AccordionContent>{faq.answer()}</AccordionContent>
</AccordionItem>
))}
</Accordion>
</>
);
};

View File

@@ -1,12 +0,0 @@
"use client";
import { FAQPageJsonLd } from "next-seo";
export const FaqJsonLdComponent = ({ data }) => {
const faqEntities = data.map(({ question, answer }) => ({
questionName: question,
acceptedAnswerText: answer,
}));
return <FAQPageJsonLd mainEntity={faqEntities} />;
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,89 @@
import { MdxImage } from "@/components/MdxImage";
import CreatedVariables from "./images/created-variables.webp";
import InputVariables from "./images/input-variables.webp";
import LogicWithVariables from "./images/logic-with-variables.webp";
import VariablesCard from "./images/variables-card.webp";
import VariablesUsage from "./images/variables-usage.webp";
export const metadata = {
title: "Variables",
description: "Add variabeles to your surveys to transform your survey into a quiz",
};
# Variables
Variables are a powerful feature in Formbricks that allows you to keep track of data variables when user fills a form. This feature is especially useful when you want to use your survey as a quiz.
## Types of Variables
There are two types of variables you can add to your survey:
1. **Text**: You can add text variables to your survey to capture text data.
2. **Number**: You can add number variables to your survey to capture number data.
## How to Add Variables
1. Edit the survey you want to add variables to & switch to the Questions tab and scroll down to the bottom of the page. You will see a section called **Variables**.
<MdxImage
src={VariablesCard}
alt="Variables card"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
2. Now click on it to add a new variable ID. You can add as many variables as you want. You can also choose the type of variable you want to add along with the default value.
<MdxImage
src={InputVariables}
alt="add variables"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
<MdxImage
src={CreatedVariables}
alt="created variables"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
## Use case:
- **Quiz**: You can use variables to create a quiz. For example, you can add a variable `score` and update it every time a user answers a question. You can also use the variable for recall to show the final score to the user.
- **Personalisation**: You can use variables to store user data and personalise the survey experience. For example, you can add a variable `full_name` and update it every time a user fills in their first name and last name. You can use the variable to personalise the survey experience by addressing the user with their full name.
## How is it different from Hidden Fields?
Variables are different from hidden fields in the following ways:
1. **Setting**: Hidden fields can be set through query parameters or `formbricks.init`, but the variables can only be set either during creation or dynamically by using logic actions.
2. **Updating**: Hidden fields cannot be set again, but the value of variables can be updated while the user fills the survey.
3. **Type**: Hidden fields can only store text data, but variables can store both text and number data.
## How to use Variables
1. Once you have added the variables to your survey, you'll be able to access them in the logic editor. You can use the variables to create logic actions and conditions.
<MdxImage
src={VariablesUsage}
alt="Variables usage"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
2. You can create logic based on the variables and questions in your survey and can update the variables based on the user's response.
<MdxImage
src={LogicWithVariables}
alt="Logic with variables"
quality="100"
className="max-w-full rounded-lg sm:max-w-3xl"
/>
<Note>
To know more about how to use logic in Formbricks, check out the [Conditional
Logic](/global/conditional-logic).
</Note>

View File

@@ -0,0 +1,54 @@
export const metadata = {
title: "Cluster Setup for Formbricks",
description: "Learn how to set up Formbricks in a multi-instance cluster configuration for high availability and scalability.",
};
#### Self-Hosting
# Cluster Setup
<Note>
Running Formbricks in a multi-instance cluster configuration is an Enterprise Edition feature and requires an enterprise license key.
</Note>
## Overview
Running Formbricks as a cluster of multiple instances offers several key advantages:
- **High Availability**: Ensure your surveys remain accessible even if some instances fail
- **Load Distribution**: Handle higher traffic by distributing requests across multiple instances
- **Scalability**: Easily scale horizontally by adding more instances as your needs grow
- **Zero-Downtime Updates**: Rolling updates without service interruption
## Requirements
To run Formbricks in a cluster setup, you'll need:
- Enterprise Edition license key
- Shared PostgreSQL database
- Shared Redis cache for session management and caching
- Load balancer to distribute traffic
## Redis Configuration
<Note>
Redis integration is an Enterprise Edition feature and requires an enterprise license key.
</Note>
Configure Redis by adding the following environment variables to your instances:
```sh {{ title: 'env file' }}
REDIS_URL=redis://your-redis-host:6379
REDIS_HTTP_URL=http://your-redis-host:8000
```
## Coming Soon
We're working on comprehensive Kubernetes deployment instructions to make cluster setup even easier. This will include:
- Kubernetes manifests
- Helm charts
- Detailed scaling configurations
- Production-ready deployment examples
Stay tuned for updates.

View File

@@ -54,6 +54,7 @@ export const navigation: Array<NavGroup> = [
title: "Add Image/Video to Question",
href: "/global/add-image-or-video-question",
},
{ title: "Variables", href: "/global/variables" },
],
},
],
@@ -85,6 +86,7 @@ export const navigation: Array<NavGroup> = [
title: "Add Image/Video to Question",
href: "/global/add-image-or-video-question",
},
{ title: "Variables", href: "/global/variables" },
],
},
],
@@ -141,6 +143,7 @@ export const navigation: Array<NavGroup> = [
{ title: "Configuration", href: "/self-hosting/configuration" },
{ title: "Integrations", href: "/self-hosting/integrations" },
{ title: "License", href: "/self-hosting/license" },
{ title: "Cluster Setup", href: "/self-hosting/cluster-setup" },
],
},
{

View File

@@ -16,7 +16,7 @@ const withMDX = nextMDX({
const nextConfig = {
basePath: "/docs",
pageExtensions: ["js", "jsx", "ts", "tsx", "mdx"],
transpilePackages: ["@formbricks/ui", "@formbricks/lib"],
transpilePackages: ["@formbricks/lib"],
images: {
remotePatterns: [
{

View File

@@ -18,7 +18,6 @@
"@docsearch/react": "3.6.2",
"@formbricks/lib": "workspace:*",
"@formbricks/types": "workspace:*",
"@formbricks/ui": "workspace:*",
"@headlessui/react": "2.1.9",
"@headlessui/tailwindcss": "0.2.1",
"@mapbox/rehype-prism": "0.9.0",

View File

@@ -1,5 +1,4 @@
import headlessuiPlugin from "@headlessui/tailwindcss";
import forms from "@tailwindcss/forms";
import typographyPlugin from "@tailwindcss/typography";
import { type Config } from "tailwindcss";
import defaultTheme from "tailwindcss/defaultTheme";
@@ -82,5 +81,5 @@ export default {
},
},
},
plugins: [typographyPlugin, headlessuiPlugin, forms],
plugins: [typographyPlugin, headlessuiPlugin],
} satisfies Config;

View File

@@ -10,11 +10,7 @@ const getAbsolutePath = (value: string) => {
};
const config: StorybookConfig = {
stories: [
"../src/**/*.mdx",
"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)",
"../../../packages/ui/**/stories.@(js|jsx|mjs|ts|tsx)",
],
stories: ["../src/**/*.mdx", "../../web/modules/ui/**/stories.@(js|jsx|mjs|ts|tsx)"],
addons: [
getAbsolutePath("@storybook/addon-onboarding"),
getAbsolutePath("@storybook/addon-links"),

View File

@@ -1,5 +1,5 @@
import type { Preview } from "@storybook/react";
import "../../../packages/ui/globals.css";
import "@formbricks/web/modules/ui/globals.css";
const preview: Preview = {
parameters: {

View File

@@ -11,7 +11,6 @@
"clean": "rimraf .turbo node_modules dist storybook-static"
},
"dependencies": {
"@formbricks/ui": "workspace:*",
"eslint-plugin-react-refresh": "0.4.12",
"react": "19.0.0-rc-ed15d500-20241110",
"react-dom": "19.0.0-rc-ed15d500-20241110"

View File

@@ -1,7 +1,7 @@
/** @type {import('tailwindcss').Config} */
import base from "../../packages/ui/tailwind.config";
import base from "../web/tailwind.config";
export default {
...base,
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}", "../../packages/ui/**/*.{js,ts,jsx,tsx}"],
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}", "../web/modules/ui/**/*.{js,ts,jsx,tsx}"],
};

View File

@@ -1,4 +1,5 @@
import react from "@vitejs/plugin-react";
import path from "path";
import { defineConfig } from "vite";
// https://vitejs.dev/config/
@@ -7,4 +8,9 @@ export default defineConfig({
define: {
"process.env": {},
},
resolve: {
alias: {
"@": path.resolve(__dirname, "../web"),
},
},
});

View File

@@ -1,5 +1,6 @@
"use client";
import { Button } from "@/modules/ui/components/button";
import { ArrowRight } from "lucide-react";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
@@ -7,7 +8,6 @@ import { useEffect } from "react";
import { cn } from "@formbricks/lib/cn";
import { TEnvironment } from "@formbricks/types/environment";
import { TProductConfigChannel } from "@formbricks/types/product";
import { Button } from "@formbricks/ui/components/Button";
import { OnboardingSetupInstructions } from "./OnboardingSetupInstructions";
interface ConnectWithFormbricksProps {

View File

@@ -1,6 +1,9 @@
"use client";
import { inviteOrganizationMemberAction } from "@/app/(app)/(onboarding)/organizations/actions";
import { Button } from "@/modules/ui/components/button";
import { FormControl, FormError, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
import { Input } from "@/modules/ui/components/input";
import { zodResolver } from "@hookform/resolvers/zod";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
@@ -8,9 +11,6 @@ import { FormProvider, useForm } from "react-hook-form";
import { toast } from "react-hot-toast";
import { z } from "zod";
import { TOrganization } from "@formbricks/types/organizations";
import { Button } from "@formbricks/ui/components/Button";
import { FormControl, FormError, FormField, FormItem, FormLabel } from "@formbricks/ui/components/Form";
import { Input } from "@formbricks/ui/components/Input";
interface InviteOrganizationMemberProps {
organization: TOrganization;

View File

@@ -1,14 +1,14 @@
"use client";
import { Button } from "@/modules/ui/components/button";
import { CodeBlock } from "@/modules/ui/components/code-block";
import { Html5Icon, NpmIcon } from "@/modules/ui/components/icons";
import { TabBar } from "@/modules/ui/components/tab-bar";
import { useTranslations } from "next-intl";
import "prismjs/themes/prism.css";
import { useState } from "react";
import toast from "react-hot-toast";
import { TProductConfigChannel } from "@formbricks/types/product";
import { Button } from "@formbricks/ui/components/Button";
import { CodeBlock } from "@formbricks/ui/components/CodeBlock";
import { TabBar } from "@formbricks/ui/components/TabBar";
import { Html5Icon, NpmIcon } from "@formbricks/ui/components/icons";
const tabs = [
{ id: "html", label: "HTML", icon: <Html5Icon /> },

View File

@@ -1,4 +1,6 @@
import { InviteOrganizationMember } from "@/app/(app)/(onboarding)/environments/[environmentId]/connect/components/InviteOrganizationMember";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { XIcon } from "lucide-react";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
@@ -6,8 +8,6 @@ import { notFound, redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
import { Button } from "@formbricks/ui/components/Button";
import { Header } from "@formbricks/ui/components/Header";
interface InvitePageProps {
params: Promise<{

View File

@@ -1,11 +1,11 @@
import { ConnectWithFormbricks } from "@/app/(app)/(onboarding)/environments/[environmentId]/connect/components/ConnectWithFormbricks";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { XIcon } from "lucide-react";
import { getTranslations } from "next-intl/server";
import { WEBAPP_URL } from "@formbricks/lib/constants";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { Button } from "@formbricks/ui/components/Button";
import { Header } from "@formbricks/ui/components/Header";
interface ConnectPageProps {
params: Promise<{

View File

@@ -1,5 +1,7 @@
import { XMTemplateList } from "@/app/(app)/(onboarding)/environments/[environmentId]/xm-templates/components/XMTemplateList";
import { getOrganizationIdFromEnvironmentId } from "@/lib/utils/helper";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { XIcon } from "lucide-react";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
@@ -7,8 +9,6 @@ import { authOptions } from "@formbricks/lib/authOptions";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getProductByEnvironmentId, getUserProducts } from "@formbricks/lib/product/service";
import { getUser } from "@formbricks/lib/user/service";
import { Button } from "@formbricks/ui/components/Button";
import { Header } from "@formbricks/ui/components/Header";
interface XMTemplatePageProps {
params: Promise<{

View File

@@ -3,19 +3,7 @@
import { formbricksLogout } from "@/app/lib/formbricks";
import FBLogo from "@/images/formbricks-wordmark.svg";
import { CreateOrganizationModal } from "@/modules/organization/components/CreateOrganizationModal";
import { ArrowUpRightIcon, ChevronRightIcon, LogOutIcon, PlusIcon } from "lucide-react";
import { signOut } from "next-auth/react";
import { useTranslations } from "next-intl";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useMemo, useState } from "react";
import { AiOutlineDiscord } from "react-icons/ai";
import { cn } from "@formbricks/lib/cn";
import { capitalizeFirstLetter } from "@formbricks/lib/utils/strings";
import { TOrganization } from "@formbricks/types/organizations";
import { TUser } from "@formbricks/types/user";
import { ProfileAvatar } from "@formbricks/ui/components/Avatars";
import { ProfileAvatar } from "@/modules/ui/components/avatars";
import {
DropdownMenu,
DropdownMenuContent,
@@ -28,7 +16,19 @@ import {
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@formbricks/ui/components/DropdownMenu";
} from "@/modules/ui/components/dropdown-menu";
import { ArrowUpRightIcon, ChevronRightIcon, LogOutIcon, PlusIcon } from "lucide-react";
import { signOut } from "next-auth/react";
import { useTranslations } from "next-intl";
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useMemo, useState } from "react";
import { AiOutlineDiscord } from "react-icons/ai";
import { cn } from "@formbricks/lib/cn";
import { capitalizeFirstLetter } from "@formbricks/lib/utils/strings";
import { TOrganization } from "@formbricks/types/organizations";
import { TUser } from "@formbricks/types/user";
interface LandingSidebarProps {
isMultiOrgEnabled: boolean;

View File

@@ -1,12 +1,12 @@
import { LandingSidebar } from "@/app/(app)/(onboarding)/organizations/[organizationId]/landing/components/landing-sidebar";
import { getEnterpriseLicense } from "@/modules/ee/license-check/lib/utils";
import { Header } from "@/modules/ui/components/header";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { notFound, redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getOrganization, getOrganizationsByUserId } from "@formbricks/lib/organization/service";
import { getUser } from "@formbricks/lib/user/service";
import { Header } from "@formbricks/ui/components/Header";
const Page = async (props) => {
const params = await props.params;

View File

@@ -1,4 +1,5 @@
import { PosthogIdentify } from "@/app/(app)/environments/[environmentId]/components/PosthogIdentify";
import { ToasterClient } from "@/modules/ui/components/toaster-client";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
@@ -7,7 +8,6 @@ import { canUserAccessOrganization } from "@formbricks/lib/organization/auth";
import { getOrganization } from "@formbricks/lib/organization/service";
import { getUser } from "@formbricks/lib/user/service";
import { AuthorizationError } from "@formbricks/types/errors";
import { ToasterClient } from "@formbricks/ui/components/ToasterClient";
const ProductOnboardingLayout = async (props) => {
const params = await props.params;

View File

@@ -1,12 +1,12 @@
import { OnboardingOptionsContainer } from "@/app/(app)/(onboarding)/organizations/components/OnboardingOptionsContainer";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { GlobeIcon, GlobeLockIcon, LinkIcon, XIcon } from "lucide-react";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getUserProducts } from "@formbricks/lib/product/service";
import { Button } from "@formbricks/ui/components/Button";
import { Header } from "@formbricks/ui/components/Header";
interface ChannelPageProps {
params: Promise<{

View File

@@ -1,12 +1,12 @@
import { OnboardingOptionsContainer } from "@/app/(app)/(onboarding)/organizations/components/OnboardingOptionsContainer";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { HeartIcon, ListTodoIcon, XIcon } from "lucide-react";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getUserProducts } from "@formbricks/lib/product/service";
import { Button } from "@formbricks/ui/components/Button";
import { Header } from "@formbricks/ui/components/Header";
interface ModePageProps {
params: Promise<{

View File

@@ -4,6 +4,20 @@ import { createProductAction } from "@/app/(app)/environments/[environmentId]/ac
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { TOrganizationTeam } from "@/modules/ee/teams/product-teams/types/teams";
import { CreateTeamModal } from "@/modules/ee/teams/team-list/components/create-team-modal";
import { Button } from "@/modules/ui/components/button";
import { ColorPicker } from "@/modules/ui/components/color-picker";
import {
FormControl,
FormDescription,
FormError,
FormField,
FormItem,
FormLabel,
FormProvider,
} from "@/modules/ui/components/form";
import { Input } from "@/modules/ui/components/input";
import { MultiSelect } from "@/modules/ui/components/multi-select";
import { SurveyInline } from "@/modules/ui/components/survey";
import { zodResolver } from "@hookform/resolvers/zod";
import { useTranslations } from "next-intl";
import Image from "next/image";
@@ -20,20 +34,6 @@ import {
TProductUpdateInput,
ZProductUpdateInput,
} from "@formbricks/types/product";
import { Button } from "@formbricks/ui/components/Button";
import { ColorPicker } from "@formbricks/ui/components/ColorPicker";
import {
FormControl,
FormDescription,
FormError,
FormField,
FormItem,
FormLabel,
FormProvider,
} from "@formbricks/ui/components/Form";
import { Input } from "@formbricks/ui/components/Input";
import { MultiSelect } from "@formbricks/ui/components/MultiSelect";
import { SurveyInline } from "@formbricks/ui/components/Survey";
interface ProductSettingsProps {
organizationId: string;

View File

@@ -2,6 +2,8 @@ import { getTeamsByOrganizationId } from "@/app/(app)/(onboarding)/lib/onboardin
import { getCustomHeadline } from "@/app/(app)/(onboarding)/lib/utils";
import { ProductSettings } from "@/app/(app)/(onboarding)/organizations/[organizationId]/products/new/settings/components/ProductSettings";
import { getRoleManagementPermission } from "@/modules/ee/license-check/lib/utils";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { XIcon } from "lucide-react";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
@@ -12,8 +14,6 @@ import { getOrganization } from "@formbricks/lib/organization/service";
import { getUserProducts } from "@formbricks/lib/product/service";
import { getUserLocale } from "@formbricks/lib/user/service";
import { TProductConfigChannel, TProductConfigIndustry, TProductMode } from "@formbricks/types/product";
import { Button } from "@formbricks/ui/components/Button";
import { Header } from "@formbricks/ui/components/Header";
interface ProductSettingsPageProps {
params: Promise<{

View File

@@ -1,7 +1,7 @@
import { OptionCard } from "@/modules/ui/components/option-card";
import { LucideProps } from "lucide-react";
import Link from "next/link";
import { ForwardRefExoticComponent, RefAttributes } from "react";
import { OptionCard } from "@formbricks/ui/components/OptionCard";
interface OnboardingOptionsContainerProps {
options: {

View File

@@ -1,6 +1,8 @@
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 { DevEnvironmentBanner } from "@/modules/ui/components/dev-environment-banner";
import { ToasterClient } from "@/modules/ui/components/toaster-client";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
@@ -10,8 +12,6 @@ import { getEnvironment } from "@formbricks/lib/environment/service";
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
import { getUser } from "@formbricks/lib/user/service";
import { AuthorizationError } from "@formbricks/types/errors";
import { DevEnvironmentBanner } from "@formbricks/ui/components/DevEnvironmentBanner";
import { ToasterClient } from "@formbricks/ui/components/ToasterClient";
const SurveyEditorEnvironmentLayout = async (props) => {
const params = await props.params;

View File

@@ -12,9 +12,12 @@ import {
getProductIdFromSurveyId,
} from "@/lib/utils/helper";
import { getSegment, getSurvey } from "@/lib/utils/services";
import { getSurveyFollowUpsPermission } from "@/modules/ee/license-check/lib/utils";
import { checkMultiLanguagePermission } from "@/modules/ee/multi-language-surveys/lib/actions";
import { z } from "zod";
import { createActionClass } from "@formbricks/lib/actionClass/service";
import { UNSPLASH_ACCESS_KEY, UNSPLASH_ALLOWED_DOMAINS } from "@formbricks/lib/constants";
import { getOrganization } from "@formbricks/lib/organization/service";
import { getProduct } from "@formbricks/lib/product/service";
import {
cloneSegment,
@@ -26,15 +29,37 @@ import { surveyCache } from "@formbricks/lib/survey/cache";
import { loadNewSegmentInSurvey, updateSurvey } from "@formbricks/lib/survey/service";
import { ZActionClassInput } from "@formbricks/types/action-classes";
import { ZId } from "@formbricks/types/common";
import { OperationNotAllowedError, ResourceNotFoundError } from "@formbricks/types/errors";
import { ZBaseFilters, ZSegmentFilters, ZSegmentUpdateInput } from "@formbricks/types/segment";
import { ZSurvey } from "@formbricks/types/surveys/types";
/**
* Checks if survey follow-ups are enabled for the given organization.
*
* @param { string } organizationId The ID of the organization to check.
* @returns { Promise<void> } A promise that resolves if the permission is granted.
* @throws { ResourceNotFoundError } If the organization is not found.
* @throws { OperationNotAllowedError } If survey follow-ups are not enabled for the organization.
*/
const checkSurveyFollowUpsPermission = async (organizationId: string): Promise<void> => {
const organization = await getOrganization(organizationId);
if (!organization) {
throw new ResourceNotFoundError("Organization", organizationId);
}
const isSurveyFollowUpsEnabled = await getSurveyFollowUpsPermission(organization);
if (!isSurveyFollowUpsEnabled) {
throw new OperationNotAllowedError("Survey follow ups are not enabled for this organization");
}
};
export const updateSurveyAction = authenticatedActionClient
.schema(ZSurvey)
.action(async ({ ctx, parsedInput }) => {
const organizationId = await getOrganizationIdFromSurveyId(parsedInput.id);
await checkAuthorizationUpdated({
userId: ctx.user.id,
organizationId: await getOrganizationIdFromSurveyId(parsedInput.id),
organizationId,
access: [
{
type: "organization",
@@ -48,6 +73,14 @@ export const updateSurveyAction = authenticatedActionClient
],
});
if (parsedInput.followUps?.length) {
await checkSurveyFollowUpsPermission(organizationId);
}
if (parsedInput.languages?.length) {
await checkMultiLanguagePermission(organizationId);
}
return await updateSurvey(parsedInput);
});

View File

@@ -1,9 +1,9 @@
"use client";
import { ModalWithTabs } from "@/modules/ui/components/modal-with-tabs";
import { useTranslations } from "next-intl";
import { TActionClass } from "@formbricks/types/action-classes";
import { TSurvey } from "@formbricks/types/surveys/types";
import { ModalWithTabs } from "@formbricks/ui/components/ModalWithTabs";
import { CreateNewActionTab } from "./CreateNewActionTab";
import { SavedActionsTab } from "./SavedActionsTab";

View File

@@ -1,6 +1,8 @@
"use client";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { Button } from "@/modules/ui/components/button";
import { QuestionToggleTable } from "@/modules/ui/components/question-toggle-table";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { PlusIcon } from "lucide-react";
import { useTranslations } from "next-intl";
@@ -9,8 +11,6 @@ import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/uti
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TSurvey, TSurveyAddressQuestion } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { Button } from "@formbricks/ui/components/Button";
import { QuestionToggleTable } from "@formbricks/ui/components/QuestionToggleTable";
interface AddressQuestionFormProps {
localSurvey: TSurvey;

View File

@@ -1,5 +1,8 @@
"use client";
import { Badge } from "@/modules/ui/components/badge";
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
import { Slider } from "@/modules/ui/components/slider";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { CheckIcon } from "lucide-react";
@@ -8,9 +11,6 @@ import { UseFormReturn } from "react-hook-form";
import { cn } from "@formbricks/lib/cn";
import { TProductStyling } from "@formbricks/types/product";
import { TSurveyStyling } from "@formbricks/types/surveys/types";
import { Badge } from "@formbricks/ui/components/Badge";
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@formbricks/ui/components/Form";
import { Slider } from "@formbricks/ui/components/Slider";
import { SurveyBgSelectorTab } from "./SurveyBgSelectorTab";
interface BackgroundStylingCardProps {

View File

@@ -2,15 +2,15 @@
import { LocalizedEditor } from "@/modules/ee/multi-language-surveys/components/localized-editor";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { Input } from "@/modules/ui/components/input";
import { Label } from "@/modules/ui/components/label";
import { OptionsSwitch } from "@/modules/ui/components/options-switch";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useTranslations } from "next-intl";
import { type JSX, useState } from "react";
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TSurvey, TSurveyCTAQuestion } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { Input } from "@formbricks/ui/components/Input";
import { Label } from "@formbricks/ui/components/Label";
import { OptionsSwitch } from "@formbricks/ui/components/OptionsSwitch";
const options = [
{

View File

@@ -1,4 +1,8 @@
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { AdvancedOptionToggle } from "@/modules/ui/components/advanced-option-toggle";
import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
import { Label } from "@/modules/ui/components/label";
import { PlusIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { type JSX, useEffect, useState } from "react";
@@ -6,10 +10,6 @@ import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/uti
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TSurvey, TSurveyCalQuestion } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { AdvancedOptionToggle } from "@formbricks/ui/components/AdvancedOptionToggle";
import { Button } from "@formbricks/ui/components/Button";
import { Input } from "@formbricks/ui/components/Input";
import { Label } from "@formbricks/ui/components/Label";
interface CalQuestionFormProps {
localSurvey: TSurvey;

View File

@@ -1,5 +1,11 @@
"use client";
import { Badge } from "@/modules/ui/components/badge";
import { CardArrangementTabs } from "@/modules/ui/components/card-arrangement-tabs";
import { ColorPicker } from "@/modules/ui/components/color-picker";
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
import { Slider } from "@/modules/ui/components/slider";
import { Switch } from "@/modules/ui/components/switch";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { CheckIcon } from "lucide-react";
@@ -10,12 +16,6 @@ import { cn } from "@formbricks/lib/cn";
import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants";
import { TProduct, TProductStyling } from "@formbricks/types/product";
import { TSurveyStyling, TSurveyType } from "@formbricks/types/surveys/types";
import { Badge } from "@formbricks/ui/components/Badge";
import { CardArrangementTabs } from "@formbricks/ui/components/CardArrangementTabs";
import { ColorPicker } from "@formbricks/ui/components/ColorPicker";
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@formbricks/ui/components/Form";
import { Slider } from "@formbricks/ui/components/Slider";
import { Switch } from "@formbricks/ui/components/Switch";
type CardStylingSettingsProps = {
open: boolean;

View File

@@ -1,5 +1,5 @@
import { ColorPicker } from "@/modules/ui/components/color-picker";
import { useState } from "react";
import { ColorPicker } from "@formbricks/ui/components/ColorPicker";
interface ColorSurveyBgProps {
handleBgChange: (bg: string, bgType: string) => void;

View File

@@ -3,6 +3,14 @@ import {
getDefaultOperatorForQuestion,
replaceEndingCardHeadlineRecall,
} from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
import { Button } from "@/modules/ui/components/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/modules/ui/components/dropdown-menu";
import { Label } from "@/modules/ui/components/label";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { createId } from "@paralleldrive/cuid2";
import {
@@ -20,14 +28,6 @@ import { duplicateLogicItem } from "@formbricks/lib/surveyLogic/utils";
import { replaceHeadlineRecall } from "@formbricks/lib/utils/recall";
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TSurvey, TSurveyLogic, TSurveyQuestion } from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/components/Button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@formbricks/ui/components/DropdownMenu";
import { Label } from "@formbricks/ui/components/Label";
interface ConditionalLogicProps {
localSurvey: TSurvey;

View File

@@ -2,12 +2,12 @@
import { LocalizedEditor } from "@/modules/ee/multi-language-surveys/components/localized-editor";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { Label } from "@/modules/ui/components/label";
import { useTranslations } from "next-intl";
import { type JSX, useState } from "react";
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TSurvey, TSurveyConsentQuestion } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { Label } from "@formbricks/ui/components/Label";
interface ConsentQuestionFormProps {
localSurvey: TSurvey;

View File

@@ -1,6 +1,8 @@
"use client";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { Button } from "@/modules/ui/components/button";
import { QuestionToggleTable } from "@/modules/ui/components/question-toggle-table";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { PlusIcon } from "lucide-react";
import { useTranslations } from "next-intl";
@@ -9,8 +11,6 @@ import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/uti
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TSurvey, TSurveyContactInfoQuestion } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { Button } from "@formbricks/ui/components/Button";
import { QuestionToggleTable } from "@formbricks/ui/components/QuestionToggleTable";
interface ContactInfoQuestionFormProps {
localSurvey: TSurvey;

View File

@@ -1,4 +1,11 @@
import { isValidCssSelector } from "@/app/lib/actionClass/actionClass";
import { Button } from "@/modules/ui/components/button";
import { CodeActionForm } from "@/modules/ui/components/code-action-form";
import { FormControl, FormError, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
import { Input } from "@/modules/ui/components/input";
import { Label } from "@/modules/ui/components/label";
import { NoCodeActionForm } from "@/modules/ui/components/no-code-action-form";
import { TabToggle } from "@/modules/ui/components/tab-toggle";
import { zodResolver } from "@hookform/resolvers/zod";
import { useTranslations } from "next-intl";
import { useMemo } from "react";
@@ -12,13 +19,6 @@ import {
ZActionClassInput,
} from "@formbricks/types/action-classes";
import { TSurvey } from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/components/Button";
import { FormControl, FormError, FormField, FormItem, FormLabel } from "@formbricks/ui/components/Form";
import { Input } from "@formbricks/ui/components/Input";
import { Label } from "@formbricks/ui/components/Label";
import { TabToggle } from "@formbricks/ui/components/TabToggle";
import { CodeActionForm } from "@formbricks/ui/components/organisms/CodeActionForm";
import { NoCodeActionForm } from "@formbricks/ui/components/organisms/NoCodeActionForm";
import { createActionClassAction } from "../actions";
interface CreateNewActionTabProps {

View File

@@ -1,4 +1,7 @@
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { Button } from "@/modules/ui/components/button";
import { Label } from "@/modules/ui/components/label";
import { OptionsSwitch } from "@/modules/ui/components/options-switch";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { PlusIcon } from "lucide-react";
import { useTranslations } from "next-intl";
@@ -7,9 +10,6 @@ import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/uti
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TSurvey, TSurveyDateQuestion } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { Button } from "@formbricks/ui/components/Button";
import { Label } from "@formbricks/ui/components/Label";
import { OptionsSwitch } from "@formbricks/ui/components/OptionsSwitch";
interface IDateQuestionFormProps {
localSurvey: TSurvey;

View File

@@ -7,12 +7,16 @@ import {
findEndingCardUsedInLogic,
formatTextWithSlashes,
} from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
import { ConfirmationModal } from "@/modules/ui/components/confirmation-modal";
import { OptionsSwitch } from "@/modules/ui/components/options-switch";
import { TooltipRenderer } from "@/modules/ui/components/tooltip";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { createId } from "@paralleldrive/cuid2";
import * as Collapsible from "@radix-ui/react-collapsible";
import { GripIcon, Handshake, Undo2 } from "lucide-react";
import { useTranslations } from "next-intl";
import { useState } from "react";
import toast from "react-hot-toast";
import { cn } from "@formbricks/lib/cn";
import { recallToHeadline } from "@formbricks/lib/utils/recall";
@@ -25,8 +29,6 @@ import {
TSurveyRedirectUrlCard,
} from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { OptionsSwitch } from "@formbricks/ui/components/OptionsSwitch";
import { TooltipRenderer } from "@formbricks/ui/components/Tooltip";
interface EditEndingCardProps {
localSurvey: TSurvey;
@@ -65,6 +67,8 @@ export const EditEndingCard = ({
? plan === "free" && endingCard.type !== "redirectToUrl"
: false;
const [openDeleteConfirmationModal, setOpenDeleteConfirmationModal] = useState(false);
const endingCardTypes = [
{ value: "endScreen", label: t("environments.surveys.edit.ending_card") },
{
@@ -77,6 +81,7 @@ export const EditEndingCard = ({
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: endingCard.id,
});
let open = activeQuestionId === endingCard.id;
const setOpen = (e) => {
@@ -97,6 +102,16 @@ export const EditEndingCard = ({
};
const deleteEndingCard = () => {
const isEndingCardUsedInFollowUps = localSurvey.followUps.some((followUp) => {
if (followUp.trigger.type === "endings") {
if (followUp.trigger.properties?.endingIds?.includes(endingCard.id)) {
return true;
}
}
return false;
});
// checking if this ending card is used in logic
const quesIdx = findEndingCardUsedInLogic(localSurvey, endingCard.id);
@@ -105,6 +120,11 @@ export const EditEndingCard = ({
return;
}
if (isEndingCardUsedInFollowUps) {
setOpenDeleteConfirmationModal(true);
return;
}
setLocalSurvey((prevSurvey) => {
const updatedEndings = prevSurvey.endings.filter((_, index) => index !== endingCardIndex);
return { ...prevSurvey, endings: updatedEndings };
@@ -265,6 +285,37 @@ export const EditEndingCard = ({
)}
</Collapsible.CollapsibleContent>
</Collapsible.Root>
<ConfirmationModal
buttonText={t("common.delete")}
onConfirm={() => {
setLocalSurvey((prevSurvey) => {
const updatedEndings = prevSurvey.endings.filter((_, index) => index !== endingCardIndex);
const surveyFollowUps = prevSurvey.followUps.map((f) => {
if (f.trigger.properties?.endingIds?.includes(endingCard.id)) {
return {
...f,
trigger: {
...f.trigger,
properties: {
...f.trigger.properties,
endingIds: f.trigger.properties.endingIds.filter((id) => id !== endingCard.id),
},
},
};
}
return f;
});
return { ...prevSurvey, endings: updatedEndings, followUps: surveyFollowUps };
});
}}
open={openDeleteConfirmationModal}
setOpen={setOpenDeleteConfirmationModal}
text={t("environments.surveys.edit.follow_ups_ending_card_delete_modal_text")}
title={t("environments.surveys.edit.follow_ups_ending_card_delete_modal_title")}
/>
</div>
);
};

View File

@@ -2,6 +2,9 @@
import { LocalizedEditor } from "@/modules/ee/multi-language-surveys/components/localized-editor";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { FileInput } from "@/modules/ui/components/file-input";
import { Label } from "@/modules/ui/components/label";
import { Switch } from "@/modules/ui/components/switch";
import * as Collapsible from "@radix-ui/react-collapsible";
import { Hand } from "lucide-react";
import { useTranslations } from "next-intl";
@@ -11,9 +14,6 @@ import { cn } from "@formbricks/lib/cn";
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TSurvey, TSurveyQuestionId, TSurveyWelcomeCard } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { FileInput } from "@formbricks/ui/components/FileInput";
import { Label } from "@formbricks/ui/components/Label";
import { Switch } from "@formbricks/ui/components/Switch";
interface EditWelcomeCardProps {
localSurvey: TSurvey;

View File

@@ -1,5 +1,15 @@
"use client";
import { ConfirmationModal } from "@/modules/ui/components/confirmation-modal";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/modules/ui/components/dropdown-menu";
import { createId } from "@paralleldrive/cuid2";
import { ArrowDownIcon, ArrowUpIcon, CopyIcon, EllipsisIcon, TrashIcon } from "lucide-react";
import { useTranslations } from "next-intl";
@@ -19,16 +29,6 @@ import {
TSurveyQuestionTypeEnum,
TSurveyRedirectUrlCard,
} from "@formbricks/types/surveys/types";
import { ConfirmationModal } from "@formbricks/ui/components/ConfirmationModal";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@formbricks/ui/components/DropdownMenu";
interface EditorCardMenuProps {
survey: TSurvey;
@@ -184,7 +184,6 @@ export const EditorCardMenu = ({
deleteCard(cardIdx);
}}
/>
<DropdownMenu>
<DropdownMenuTrigger>
<EllipsisIcon className="h-4 w-4 text-slate-500 hover:text-slate-600" />
@@ -286,7 +285,6 @@ export const EditorCardMenu = ({
</div>
</DropdownMenuContent>
</DropdownMenu>
<ConfirmationModal
open={logicWarningModal}
setOpen={setLogicWarningModal}

View File

@@ -1,15 +1,15 @@
"use client";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { Input } from "@/modules/ui/components/input";
import { Label } from "@/modules/ui/components/label";
import { Switch } from "@/modules/ui/components/switch";
import { useTranslations } from "next-intl";
import { useState } from "react";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TSurvey, TSurveyEndScreenCard } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { Input } from "@formbricks/ui/components/Input";
import { Label } from "@formbricks/ui/components/Label";
import { Switch } from "@formbricks/ui/components/Switch";
interface EndScreenFormProps {
localSurvey: TSurvey;

View File

@@ -1,6 +1,9 @@
"use client";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { AdvancedOptionToggle } from "@/modules/ui/components/advanced-option-toggle";
import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
import { useGetBillingInfo } from "@/modules/utils/hooks/useGetBillingInfo";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { PlusIcon, XCircleIcon } from "lucide-react";
@@ -15,9 +18,6 @@ import { TAllowedFileExtension, ZAllowedFileExtension } from "@formbricks/types/
import { TProduct } from "@formbricks/types/product";
import { TSurvey, TSurveyFileUploadQuestion } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { AdvancedOptionToggle } from "@formbricks/ui/components/AdvancedOptionToggle";
import { Button } from "@formbricks/ui/components/Button";
import { Input } from "@formbricks/ui/components/Input";
interface FileUploadFormProps {
localSurvey: TSurvey;

View File

@@ -1,5 +1,8 @@
"use client";
import { Button } from "@/modules/ui/components/button";
import { ColorPicker } from "@/modules/ui/components/color-picker";
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { CheckIcon, SparklesIcon } from "lucide-react";
@@ -11,9 +14,6 @@ import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants";
import { mixColor } from "@formbricks/lib/utils/colors";
import { TProductStyling } from "@formbricks/types/product";
import { TSurveyStyling } from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/components/Button";
import { ColorPicker } from "@formbricks/ui/components/ColorPicker";
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@formbricks/ui/components/Form";
type FormStylingSettingsProps = {
open: boolean;

View File

@@ -1,6 +1,11 @@
"use client";
import { findHiddenFieldUsedInLogic } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
import { Label } from "@/modules/ui/components/label";
import { Switch } from "@/modules/ui/components/switch";
import { Tag } from "@/modules/ui/components/tag";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { EyeOff } from "lucide-react";
@@ -11,11 +16,6 @@ import { cn } from "@formbricks/lib/cn";
import { extractRecallInfo } from "@formbricks/lib/utils/recall";
import { TSurvey, TSurveyHiddenFields, TSurveyQuestionId } from "@formbricks/types/surveys/types";
import { validateId } from "@formbricks/types/surveys/validation";
import { Button } from "@formbricks/ui/components/Button";
import { Input } from "@formbricks/ui/components/Input";
import { Label } from "@formbricks/ui/components/Label";
import { Switch } from "@formbricks/ui/components/Switch";
import { Tag } from "@formbricks/ui/components/Tag";
interface HiddenFieldsCardProps {
localSurvey: TSurvey;
@@ -84,6 +84,17 @@ export const HiddenFieldsCard = ({
return;
}
const isHiddenFieldUsedInFollowUp = localSurvey.followUps
.filter((f) => !f.deleted)
.some((followUp) => {
return followUp.action.properties.to === fieldId;
});
if (isHiddenFieldUsedInFollowUp) {
toast.error(t("environments.surveys.edit.follow_ups_hidden_field_error"));
return;
}
updateSurvey(
{
enabled: true,

View File

@@ -1,5 +1,8 @@
"use client";
import { Badge } from "@/modules/ui/components/badge";
import { Label } from "@/modules/ui/components/label";
import { RadioGroup, RadioGroupItem } from "@/modules/ui/components/radio-group";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { AlertCircleIcon, CheckIcon, LinkIcon, MonitorIcon } from "lucide-react";
@@ -11,9 +14,6 @@ import { getDefaultEndingCard } from "@formbricks/lib/templates";
import { TEnvironment } from "@formbricks/types/environment";
import { TSegment } from "@formbricks/types/segment";
import { TSurvey, TSurveyType } from "@formbricks/types/surveys/types";
import { Badge } from "@formbricks/ui/components/Badge";
import { Label } from "@formbricks/ui/components/Label";
import { RadioGroup, RadioGroupItem } from "@formbricks/ui/components/RadioGroup";
interface HowToSendCardProps {
localSurvey: TSurvey;

View File

@@ -1,4 +1,4 @@
import { FileInput } from "@formbricks/ui/components/FileInput";
import { FileInput } from "@/modules/ui/components/file-input";
interface UploadImageSurveyBgProps {
environmentId: string;

View File

@@ -1,18 +1,18 @@
import { LogicEditorActions } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorActions";
import { LogicEditorConditions } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditorConditions";
import { ArrowRightIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { ReactElement, useMemo } from "react";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { QUESTIONS_ICON_MAP } from "@formbricks/lib/utils/questions";
import { TSurvey, TSurveyLogic, TSurveyQuestion } from "@formbricks/types/surveys/types";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@formbricks/ui/components/Select";
} from "@/modules/ui/components/select";
import { ArrowRightIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { ReactElement, useMemo } from "react";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { QUESTIONS_ICON_MAP } from "@formbricks/lib/utils/questions";
import { TSurvey, TSurveyLogic, TSurveyQuestion } from "@formbricks/types/surveys/types";
interface LogicEditorProps {
localSurvey: TSurvey;

View File

@@ -4,7 +4,15 @@ import {
getActionTargetOptions,
getActionValueOptions,
getActionVariableOptions,
hasJumpToQuestionAction,
} from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/modules/ui/components/dropdown-menu";
import { InputCombobox } from "@/modules/ui/components/input-combo-box";
import { createId } from "@paralleldrive/cuid2";
import { CopyIcon, CornerDownRightIcon, EllipsisVerticalIcon, PlusIcon, TrashIcon } from "lucide-react";
import { useTranslations } from "next-intl";
@@ -19,13 +27,6 @@ import {
TSurveyLogicAction,
TSurveyQuestion,
} from "@formbricks/types/surveys/types";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@formbricks/ui/components/DropdownMenu";
import { InputCombobox } from "@formbricks/ui/components/InputCombobox";
interface LogicEditorActions {
localSurvey: TSurvey;
@@ -60,7 +61,11 @@ export function LogicEditorActions({
actionsClone.splice(actionIdx, 1);
break;
case "addBelow":
actionsClone.splice(actionIdx + 1, 0, { id: createId(), objective: "jumpToQuestion", target: "" });
actionsClone.splice(actionIdx + 1, 0, {
id: createId(),
objective: hasJumpToQuestionAction(logicItem.actions) ? "requireAnswer" : "jumpToQuestion",
target: "",
});
break;
case "duplicate":
actionsClone.splice(actionIdx + 1, 0, { ...actionsClone[actionIdx], id: createId() });
@@ -88,6 +93,11 @@ export function LogicEditorActions({
handleActionsChange("update", actionIdx, actionBody);
};
const filteredObjectiveOptions = actionObjectiveOptions.filter(
(option) => option.value !== "jumpToQuestion"
);
const jumpToQuestionActionIdx = actions.findIndex((action) => action.objective === "jumpToQuestion");
return (
<div className="flex grow gap-2">
<CornerDownRightIcon className="mt-3 h-4 w-4 shrink-0" />
@@ -102,7 +112,11 @@ export function LogicEditorActions({
id={`action-${idx}-objective`}
key={`objective-${action.id}`}
showSearch={false}
options={actionObjectiveOptions}
options={
jumpToQuestionActionIdx === -1 || idx === jumpToQuestionActionIdx
? actionObjectiveOptions
: filteredObjectiveOptions
}
value={action.objective}
onChangeValue={(val: TActionObjective) => {
handleObjectiveChange(idx, val);

View File

@@ -4,6 +4,13 @@ import {
getDefaultOperatorForQuestion,
getMatchValueProps,
} from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/modules/ui/components/dropdown-menu";
import { InputCombobox, TComboboxOption } from "@/modules/ui/components/input-combo-box";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { createId } from "@paralleldrive/cuid2";
import { CopyIcon, EllipsisVerticalIcon, PlusIcon, TrashIcon, WorkflowIcon } from "lucide-react";
@@ -27,13 +34,6 @@ import {
TSurveyLogicConditionsOperator,
TSurveyQuestion,
} from "@formbricks/types/surveys/types";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@formbricks/ui/components/DropdownMenu";
import { InputCombobox, TComboboxOption } from "@formbricks/ui/components/InputCombobox";
interface LogicEditorConditionsProps {
conditions: TConditionGroup;

View File

@@ -1,6 +1,9 @@
"use client";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { Button } from "@/modules/ui/components/button";
import { Label } from "@/modules/ui/components/label";
import { ShuffleOptionSelect } from "@/modules/ui/components/shuffle-option-select";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { PlusIcon, TrashIcon } from "lucide-react";
import { useTranslations } from "next-intl";
@@ -9,9 +12,6 @@ import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/uti
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TI18nString, TSurvey, TSurveyMatrixQuestion } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { Button } from "@formbricks/ui/components/Button";
import { Label } from "@formbricks/ui/components/Label";
import { ShuffleOptionSelect } from "@formbricks/ui/components/ShuffleOptionSelect";
import { isLabelValidForAllLanguages } from "../lib/validation";
interface MatrixQuestionFormProps {

View File

@@ -2,6 +2,9 @@
import { findOptionUsedInLogic } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { Button } from "@/modules/ui/components/button";
import { Label } from "@/modules/ui/components/label";
import { ShuffleOptionSelect } from "@/modules/ui/components/shuffle-option-select";
import { DndContext } from "@dnd-kit/core";
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
import { useAutoAnimate } from "@formkit/auto-animate/react";
@@ -20,9 +23,6 @@ import {
TSurveyQuestionTypeEnum,
} from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { Button } from "@formbricks/ui/components/Button";
import { Label } from "@formbricks/ui/components/Label";
import { ShuffleOptionSelect } from "@formbricks/ui/components/ShuffleOptionSelect";
import { QuestionOptionChoice } from "./QuestionOptionChoice";
interface MultipleChoiceQuestionFormProps {

View File

@@ -1,6 +1,8 @@
"use client";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { AdvancedOptionToggle } from "@/modules/ui/components/advanced-option-toggle";
import { Button } from "@/modules/ui/components/button";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { PlusIcon } from "lucide-react";
import { useTranslations } from "next-intl";
@@ -9,8 +11,6 @@ import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/uti
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TSurvey, TSurveyNPSQuestion } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { AdvancedOptionToggle } from "@formbricks/ui/components/AdvancedOptionToggle";
import { Button } from "@formbricks/ui/components/Button";
interface NPSQuestionFormProps {
localSurvey: TSurvey;

View File

@@ -1,6 +1,9 @@
"use client";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { Button } from "@/modules/ui/components/button";
import { Label } from "@/modules/ui/components/label";
import { OptionsSwitch } from "@/modules/ui/components/options-switch";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { HashIcon, LinkIcon, MailIcon, MessageSquareTextIcon, PhoneIcon, PlusIcon } from "lucide-react";
import { useTranslations } from "next-intl";
@@ -13,9 +16,6 @@ import {
TSurveyOpenTextQuestionInputType,
} from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { Button } from "@formbricks/ui/components/Button";
import { Label } from "@formbricks/ui/components/Label";
import { OptionsSwitch } from "@formbricks/ui/components/OptionsSwitch";
const questionTypes = [
{ value: "text", label: "common.text", icon: <MessageSquareTextIcon className="h-4 w-4" /> },

View File

@@ -1,4 +1,8 @@
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { Button } from "@/modules/ui/components/button";
import { FileInput } from "@/modules/ui/components/file-input";
import { Label } from "@/modules/ui/components/label";
import { Switch } from "@/modules/ui/components/switch";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { createId } from "@paralleldrive/cuid2";
import { PlusIcon } from "lucide-react";
@@ -9,10 +13,6 @@ import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/uti
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TSurvey, TSurveyPictureSelectionQuestion } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { Button } from "@formbricks/ui/components/Button";
import { FileInput } from "@formbricks/ui/components/FileInput";
import { Label } from "@formbricks/ui/components/Label";
import { Switch } from "@formbricks/ui/components/Switch";
interface PictureSelectionFormProps {
localSurvey: TSurvey;

View File

@@ -1,11 +1,11 @@
"use client";
import { Label } from "@/modules/ui/components/label";
import { getPlacementStyle } from "@/modules/ui/components/preview-survey/lib/utils";
import { RadioGroup, RadioGroupItem } from "@/modules/ui/components/radio-group";
import { useTranslations } from "next-intl";
import { cn } from "@formbricks/lib/cn";
import { TPlacement } from "@formbricks/types/common";
import { Label } from "@formbricks/ui/components/Label";
import { getPlacementStyle } from "@formbricks/ui/components/PreviewSurvey/lib/utils";
import { RadioGroup, RadioGroupItem } from "@formbricks/ui/components/RadioGroup";
const placements = [
{ name: "common.bottom_right", value: "bottomRight", disabled: false },

View File

@@ -4,6 +4,8 @@ import { ContactInfoQuestionForm } from "@/app/(app)/(survey-editor)/environment
import { RankingQuestionForm } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/RankingQuestionForm";
import { formatTextWithSlashes } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { Label } from "@/modules/ui/components/label";
import { Switch } from "@/modules/ui/components/switch";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { useAutoAnimate } from "@formkit/auto-animate/react";
@@ -24,8 +26,6 @@ import {
TSurveyQuestionTypeEnum,
} from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { Label } from "@formbricks/ui/components/Label";
import { Switch } from "@formbricks/ui/components/Switch";
import { AddressQuestionForm } from "./AddressQuestionForm";
import { AdvancedSettings } from "./AdvancedSettings";
import { CTAQuestionForm } from "./CTAQuestionForm";

View File

@@ -1,4 +1,5 @@
import { PaintbrushIcon, Rows3Icon, SettingsIcon } from "lucide-react";
import { ProBadge } from "@/modules/ui/components/pro-badge";
import { MailIcon, PaintbrushIcon, Rows3Icon, SettingsIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { type JSX, useMemo } from "react";
import { cn } from "@formbricks/lib/cn";
@@ -8,31 +9,15 @@ interface Tab {
id: TSurveyEditorTabs;
label: string;
icon: JSX.Element;
isPro?: boolean;
}
const tabs: Tab[] = [
{
id: "questions",
label: "common.questions",
icon: <Rows3Icon className="h-5 w-5" />,
},
{
id: "styling",
label: "common.styling",
icon: <PaintbrushIcon className="h-5 w-5" />,
},
{
id: "settings",
label: "common.settings",
icon: <SettingsIcon className="h-5 w-5" />,
},
];
interface QuestionsAudienceTabsProps {
activeId: TSurveyEditorTabs;
setActiveId: React.Dispatch<React.SetStateAction<TSurveyEditorTabs>>;
isStylingTabVisible?: boolean;
isCxMode: boolean;
isSurveyFollowUpsAllowed: boolean;
}
export const QuestionsAudienceTabs = ({
@@ -40,7 +25,35 @@ export const QuestionsAudienceTabs = ({
setActiveId,
isStylingTabVisible,
isCxMode,
isSurveyFollowUpsAllowed = false,
}: QuestionsAudienceTabsProps) => {
const tabs: Tab[] = useMemo(
() => [
{
id: "questions",
label: "common.questions",
icon: <Rows3Icon className="h-5 w-5" />,
},
{
id: "styling",
label: "common.styling",
icon: <PaintbrushIcon className="h-5 w-5" />,
},
{
id: "settings",
label: "common.settings",
icon: <SettingsIcon className="h-5 w-5" />,
},
{
id: "followUps",
label: "environments.surveys.edit.follow_ups",
icon: <MailIcon className="h-5 w-5" />,
isPro: !isSurveyFollowUpsAllowed,
},
],
[isSurveyFollowUpsAllowed]
);
const t = useTranslations();
const tabsComputed = useMemo(() => {
if (isStylingTabVisible) {
@@ -69,6 +82,7 @@ export const QuestionsAudienceTabs = ({
aria-current={tab.id === activeId ? "page" : undefined}>
{tab.icon && <div className="mr-2 h-5 w-5">{tab.icon}</div>}
{t(tab.label)}
{tab.isPro && <ProBadge />}
</button>
))}
</nav>

View File

@@ -282,11 +282,13 @@ export const QuestionsView = ({
}
}
});
updatedSurvey.questions.splice(questionIdx, 1);
const firstEndingCard = localSurvey.endings[0];
setLocalSurvey(updatedSurvey);
delete internalQuestionIdMap[questionId];
if (questionId === activeQuestionIdTemp) {
if (questionIdx <= localSurvey.questions.length && localSurvey.questions.length > 0) {
setActiveQuestionId(localSurvey.questions[questionIdx % localSurvey.questions.length].id);
@@ -294,6 +296,7 @@ export const QuestionsView = ({
setActiveQuestionId(firstEndingCard.id);
}
}
toast.success(t("environments.surveys.edit.question_deleted"));
};

View File

@@ -1,6 +1,9 @@
"use client";
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { Button } from "@/modules/ui/components/button";
import { Label } from "@/modules/ui/components/label";
import { ShuffleOptionSelect } from "@/modules/ui/components/shuffle-option-select";
import { DndContext } from "@dnd-kit/core";
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
import { useAutoAnimate } from "@formkit/auto-animate/react";
@@ -12,9 +15,6 @@ import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/uti
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TI18nString, TSurvey, TSurveyRankingQuestion } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { Button } from "@formbricks/ui/components/Button";
import { Label } from "@formbricks/ui/components/Label";
import { ShuffleOptionSelect } from "@formbricks/ui/components/ShuffleOptionSelect";
import { QuestionOptionChoice } from "./QuestionOptionChoice";
interface RankingQuestionFormProps {

View File

@@ -1,4 +1,7 @@
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
import { AdvancedOptionToggle } from "@/modules/ui/components/advanced-option-toggle";
import { Button } from "@/modules/ui/components/button";
import { Label } from "@/modules/ui/components/label";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { HashIcon, PlusIcon, SmileIcon, StarIcon } from "lucide-react";
import { useTranslations } from "next-intl";
@@ -6,9 +9,6 @@ import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/uti
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TSurvey, TSurveyRatingQuestion } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { AdvancedOptionToggle } from "@formbricks/ui/components/AdvancedOptionToggle";
import { Button } from "@formbricks/ui/components/Button";
import { Label } from "@formbricks/ui/components/Label";
import { Dropdown } from "./RatingTypeDropdown";
interface RatingQuestionFormProps {

View File

@@ -1,5 +1,9 @@
"use client";
import { AdvancedOptionToggle } from "@/modules/ui/components/advanced-option-toggle";
import { Input } from "@/modules/ui/components/input";
import { Label } from "@/modules/ui/components/label";
import { RadioGroup, RadioGroupItem } from "@/modules/ui/components/radio-group";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { CheckIcon } from "lucide-react";
@@ -7,10 +11,6 @@ import { useTranslations } from "next-intl";
import Link from "next/link";
import { useEffect, useState } from "react";
import { TSurvey } from "@formbricks/types/surveys/types";
import { AdvancedOptionToggle } from "@formbricks/ui/components/AdvancedOptionToggle";
import { Input } from "@formbricks/ui/components/Input";
import { Label } from "@formbricks/ui/components/Label";
import { RadioGroup, RadioGroupItem } from "@formbricks/ui/components/RadioGroup";
interface DisplayOption {
id: "displayOnce" | "displayMultiple" | "respondMultiple" | "displaySome";

View File

@@ -1,7 +1,7 @@
import { Input } from "@/modules/ui/components/input";
import { Label } from "@/modules/ui/components/label";
import { useTranslations } from "next-intl";
import { TSurveyRedirectUrlCard } from "@formbricks/types/surveys/types";
import { Input } from "@formbricks/ui/components/Input";
import { Label } from "@formbricks/ui/components/Label";
interface RedirectUrlFormProps {
endingCard: TSurveyRedirectUrlCard;

View File

@@ -1,5 +1,10 @@
"use client";
import { AdvancedOptionToggle } from "@/modules/ui/components/advanced-option-toggle";
import { DatePicker } from "@/modules/ui/components/date-picker";
import { Input } from "@/modules/ui/components/input";
import { Label } from "@/modules/ui/components/label";
import { Switch } from "@/modules/ui/components/switch";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { ArrowUpRight, CheckIcon } from "lucide-react";
@@ -9,11 +14,6 @@ import { KeyboardEventHandler, useEffect, useState } from "react";
import toast from "react-hot-toast";
import { cn } from "@formbricks/lib/cn";
import { TSurvey } from "@formbricks/types/surveys/types";
import { AdvancedOptionToggle } from "@formbricks/ui/components/AdvancedOptionToggle";
import { DatePicker } from "@formbricks/ui/components/DatePicker";
import { Input } from "@formbricks/ui/components/Input";
import { Label } from "@formbricks/ui/components/Label";
import { Switch } from "@formbricks/ui/components/Switch";
interface ResponseOptionsCardProps {
localSurvey: TSurvey;

View File

@@ -1,9 +1,9 @@
import { Input } from "@/modules/ui/components/input";
import { Code2Icon, MousePointerClickIcon, SparklesIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useState } from "react";
import { TActionClass } from "@formbricks/types/action-classes";
import { TSurvey } from "@formbricks/types/surveys/types";
import { Input } from "@formbricks/ui/components/Input";
interface SavedActionsTabProps {
actionClasses: TActionClass[];

View File

@@ -1,3 +1,14 @@
import { AlertDialog } from "@/modules/ui/components/alert-dialog";
import { Button } from "@/modules/ui/components/button";
import {
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormProvider,
} from "@/modules/ui/components/form";
import { Switch } from "@/modules/ui/components/switch";
import { RotateCcwIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";
@@ -7,17 +18,6 @@ import toast from "react-hot-toast";
import { TEnvironment } from "@formbricks/types/environment";
import { TProduct, TProductStyling } from "@formbricks/types/product";
import { TSurvey, TSurveyStyling } from "@formbricks/types/surveys/types";
import { AlertDialog } from "@formbricks/ui/components/AlertDialog";
import { Button } from "@formbricks/ui/components/Button";
import {
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormProvider,
} from "@formbricks/ui/components/Form";
import { Switch } from "@formbricks/ui/components/Switch";
import { BackgroundStylingCard } from "./BackgroundStylingCard";
import { CardStylingSettings } from "./CardStylingSettings";
import { FormStylingSettings } from "./FormStylingSettings";

View File

@@ -1,7 +1,7 @@
import { TabBar } from "@/modules/ui/components/tab-bar";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
import { TabBar } from "@formbricks/ui/components/TabBar";
import { AnimatedSurveyBg } from "./AnimatedSurveyBg";
import { ColorSurveyBg } from "./ColorSurveyBg";
import { UploadImageSurveyBg } from "./ImageSurveyBg";

View File

@@ -1,6 +1,8 @@
"use client";
import { FollowUpsView } from "@/modules/ee/survey-follow-ups/components/follow-ups-view";
import { TTeamPermission } from "@/modules/ee/teams/product-teams/types/teams";
import { PreviewSurvey } from "@/modules/ui/components/preview-survey";
import { useCallback, useEffect, useRef, useState } from "react";
import { extractLanguageCodes, getEnabledLanguages } from "@formbricks/lib/i18n/utils";
import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone";
@@ -14,7 +16,6 @@ import { TProduct } from "@formbricks/types/product";
import { TSegment } from "@formbricks/types/segment";
import { TSurvey, TSurveyEditorTabs, TSurveyStyling } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { PreviewSurvey } from "@formbricks/ui/components/PreviewSurvey";
import { refetchProductAction } from "../actions";
import { LoadingSkeleton } from "./LoadingSkeleton";
import { QuestionsAudienceTabs } from "./QuestionsStylingSettingsTabs";
@@ -40,7 +41,10 @@ interface SurveyEditorProps {
plan: TOrganizationBillingPlan;
isCxMode: boolean;
locale: TUserLocale;
mailFrom: string;
isSurveyFollowUpsAllowed: boolean;
productPermission: TTeamPermission | null;
userEmail: string;
}
export const SurveyEditor = ({
@@ -60,7 +64,10 @@ export const SurveyEditor = ({
plan,
isCxMode = false,
locale,
mailFrom,
isSurveyFollowUpsAllowed = false,
productPermission,
userEmail,
}: SurveyEditorProps) => {
const [activeView, setActiveView] = useState<TSurveyEditorTabs>("questions");
const [activeQuestionId, setActiveQuestionId] = useState<string | null>(null);
@@ -161,6 +168,7 @@ export const SurveyEditor = ({
setActiveId={setActiveView}
isCxMode={isCxMode}
isStylingTabVisible={!!product.styling.allowStyleOverwrite}
isSurveyFollowUpsAllowed={isSurveyFollowUpsAllowed}
/>
{activeView === "questions" && (
@@ -215,6 +223,18 @@ export const SurveyEditor = ({
productPermission={productPermission}
/>
)}
{activeView === "followUps" && (
<FollowUpsView
localSurvey={localSurvey}
setLocalSurvey={setLocalSurvey}
selectedLanguageCode={selectedLanguageCode}
mailFrom={mailFrom}
isSurveyFollowUpsAllowed={isSurveyFollowUpsAllowed}
userEmail={userEmail}
locale={locale}
/>
)}
</main>
<aside className="group hidden flex-1 flex-shrink-0 items-center justify-center overflow-hidden border-l border-slate-200 bg-slate-100 shadow-inner md:flex md:flex-col">

View File

@@ -3,6 +3,10 @@
import { SurveyStatusDropdown } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SurveyStatusDropdown";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { createSegmentAction } from "@/modules/ee/advanced-targeting/lib/actions";
import { AlertDialog } from "@/modules/ui/components/alert-dialog";
import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/modules/ui/components/tooltip";
import { isEqual } from "lodash";
import { AlertTriangleIcon, ArrowLeftIcon, SettingsIcon } from "lucide-react";
import { useTranslations } from "next-intl";
@@ -21,10 +25,6 @@ import {
ZSurveyEndScreenCard,
ZSurveyRedirectUrlCard,
} from "@formbricks/types/surveys/types";
import { AlertDialog } from "@formbricks/ui/components/AlertDialog";
import { Button } from "@formbricks/ui/components/Button";
import { Input } from "@formbricks/ui/components/Input";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@formbricks/ui/components/Tooltip";
import { updateSurveyAction } from "../actions";
import { isSurveyValid } from "../lib/validation";

View File

@@ -1,5 +1,7 @@
"use client";
import { Label } from "@/modules/ui/components/label";
import { Switch } from "@/modules/ui/components/switch";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { CheckIcon } from "lucide-react";
@@ -8,8 +10,6 @@ import Link from "next/link";
import { useState } from "react";
import { TPlacement } from "@formbricks/types/common";
import { TSurvey, TSurveyProductOverwrites } from "@formbricks/types/surveys/types";
import { Label } from "@formbricks/ui/components/Label";
import { Switch } from "@formbricks/ui/components/Switch";
import { Placement } from "./Placement";
interface SurveyPlacementCardProps {

View File

@@ -1,6 +1,17 @@
"use client";
import { findVariableUsedInLogic } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
import { Button } from "@/modules/ui/components/button";
import { FormControl, FormField, FormItem, FormProvider } from "@/modules/ui/components/form";
import { Input } from "@/modules/ui/components/input";
import { Label } from "@/modules/ui/components/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/modules/ui/components/select";
import { createId } from "@paralleldrive/cuid2";
import { TrashIcon } from "lucide-react";
import { useTranslations } from "next-intl";
@@ -9,17 +20,6 @@ import { useForm } from "react-hook-form";
import toast from "react-hot-toast";
import { extractRecallInfo } from "@formbricks/lib/utils/recall";
import { TSurvey, TSurveyVariable } from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/components/Button";
import { FormControl, FormField, FormItem, FormProvider } from "@formbricks/ui/components/Form";
import { Input } from "@formbricks/ui/components/Input";
import { Label } from "@formbricks/ui/components/Label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@formbricks/ui/components/Select";
interface SurveyVariablesCardItemProps {
variable?: TSurveyVariable;

View File

@@ -1,6 +1,16 @@
"use client";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { Alert, AlertDescription } from "@/modules/ui/components/alert";
import { AlertDialog } from "@/modules/ui/components/alert-dialog";
import { BasicAddFilterModal } from "@/modules/ui/components/basic-add-filter-modal";
import { BasicSegmentEditor } from "@/modules/ui/components/basic-segment-editor";
import { Button } from "@/modules/ui/components/button";
import { LoadSegmentModal } from "@/modules/ui/components/load-segment-modal";
import { SaveAsNewSegmentModal } from "@/modules/ui/components/save-as-new-segment-modal";
import { SegmentTitle } from "@/modules/ui/components/segment-title";
import { TargetingIndicator } from "@/modules/ui/components/targeting-indicator";
import { UpgradePlanNotice } from "@/modules/ui/components/upgrade-plan-notice";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import { AlertCircle, CheckIcon, ChevronDownIcon, ChevronUpIcon, PencilIcon } from "lucide-react";
@@ -15,16 +25,6 @@ import { isAdvancedSegment } from "@formbricks/lib/segment/utils";
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TBaseFilter, TSegment, TSegmentCreateInput, TSegmentUpdateInput } from "@formbricks/types/segment";
import { TSurvey } from "@formbricks/types/surveys/types";
import { Alert, AlertDescription } from "@formbricks/ui/components/Alert";
import { AlertDialog } from "@formbricks/ui/components/AlertDialog";
import { BasicAddFilterModal } from "@formbricks/ui/components/BasicAddFilterModal";
import { BasicSegmentEditor } from "@formbricks/ui/components/BasicSegmentEditor";
import { Button } from "@formbricks/ui/components/Button";
import { LoadSegmentModal } from "@formbricks/ui/components/LoadSegmentModal";
import { SaveAsNewSegmentModal } from "@formbricks/ui/components/SaveAsNewSegmentModal";
import { SegmentTitle } from "@formbricks/ui/components/SegmentTitle";
import { TargetingIndicator } from "@formbricks/ui/components/TargetingIndicator";
import { UpgradePlanNotice } from "@formbricks/ui/components/UpgradePlanNotice";
import {
cloneBasicSegmentAction,
createBasicSegmentAction,

View File

@@ -1,5 +1,8 @@
"use client";
import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
import { LoadingSpinner } from "@/modules/ui/components/loading-spinner";
import { debounce } from "lodash";
import { SearchIcon } from "lucide-react";
import { useTranslations } from "next-intl";
@@ -7,9 +10,6 @@ import UnsplashImage from "next/image";
import { useEffect, useRef, useState } from "react";
import toast from "react-hot-toast";
import { TSurveyBackgroundBgType } from "@formbricks/types/surveys/types";
import { Button } from "@formbricks/ui/components/Button";
import { Input } from "@formbricks/ui/components/Input";
import { LoadingSpinner } from "@formbricks/ui/components/LoadingSpinner";
import { getImagesFromUnsplashAction, triggerDownloadUnsplashImageAction } from "../actions";
interface ImageFromUnsplashSurveyBgProps {

View File

@@ -1,13 +1,13 @@
"use client";
import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
import { Label } from "@/modules/ui/components/label";
import { useTranslations } from "next-intl";
import { useState } from "react";
import toast from "react-hot-toast";
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys/types";
import { validateId } from "@formbricks/types/surveys/validation";
import { Button } from "@formbricks/ui/components/Button";
import { Input } from "@formbricks/ui/components/Input";
import { Label } from "@formbricks/ui/components/Label";
interface UpdateQuestionIdProps {
localSurvey: TSurvey;

View File

@@ -2,6 +2,9 @@
import { TTeamPermission } from "@/modules/ee/teams/product-teams/types/teams";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
import { AdvancedOptionToggle } from "@/modules/ui/components/advanced-option-toggle";
import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import * as Collapsible from "@radix-ui/react-collapsible";
import {
@@ -18,9 +21,6 @@ import { getAccessFlags } from "@formbricks/lib/membership/utils";
import { TActionClass } from "@formbricks/types/action-classes";
import { TOrganizationRole } from "@formbricks/types/memberships";
import { TSurvey } from "@formbricks/types/surveys/types";
import { AdvancedOptionToggle } from "@formbricks/ui/components/AdvancedOptionToggle";
import { Button } from "@formbricks/ui/components/Button";
import { Input } from "@formbricks/ui/components/Input";
import { AddActionModal } from "./AddActionModal";
interface WhenToSendCardProps {

View File

@@ -366,6 +366,18 @@ export const logicRules = {
},
],
},
[TSurveyQuestionTypeEnum.ContactInfo]: {
options: [
{
label: "environments.surveys.edit.is_submitted",
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
},
{
label: "environments.surveys.edit.is_skipped",
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
},
],
},
},
["variable.text"]: {
options: [

View File

@@ -0,0 +1,33 @@
import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { cache } from "@formbricks/lib/cache";
import { userCache } from "@formbricks/lib/user/cache";
import { DatabaseError } from "@formbricks/types/errors";
export const getUserEmail = reactCache(
(userId: string): Promise<string | null> =>
cache(
async () => {
try {
const user = await prisma.user.findUnique({ where: { id: userId }, select: { email: true } });
if (!user) {
return null;
}
return user.email;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError(error.message);
}
throw error;
}
},
[`getUserEmail-${userId}`],
{
tags: [userCache.tag.byId(userId)],
}
)()
);

View File

@@ -1,7 +1,9 @@
import { TComboboxGroupedOption, TComboboxOption } from "@/modules/ui/components/input-combo-box";
import { EyeOffIcon, FileDigitIcon, FileType2Icon } from "lucide-react";
import { HTMLInputTypeAttribute } from "react";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { isConditionGroup } from "@formbricks/lib/surveyLogic/utils";
import { translate } from "@formbricks/lib/templates";
import { getQuestionTypes } from "@formbricks/lib/utils/questions";
import { recallToHeadline } from "@formbricks/lib/utils/recall";
import { TAttributeClass } from "@formbricks/types/attribute-classes";
@@ -13,13 +15,14 @@ import {
TSurvey,
TSurveyLogic,
TSurveyLogicAction,
TSurveyLogicActions,
TSurveyLogicConditionsOperator,
TSurveyQuestion,
TSurveyQuestionId,
TSurveyQuestionTypeEnum,
TSurveyVariable,
} from "@formbricks/types/surveys/types";
import { TComboboxGroupedOption, TComboboxOption } from "@formbricks/ui/components/InputCombobox";
import { TUserLocale } from "@formbricks/types/user";
import { TLogicRuleOption, logicRules } from "./logicRuleEngine";
// formats the text to highlight specific parts of the text with slashes
@@ -147,6 +150,10 @@ export const actionObjectiveOptions: TComboboxOption[] = [
{ label: "environments.surveys.edit.jump_to_question", value: "jumpToQuestion" },
];
export const hasJumpToQuestionAction = (actions: TSurveyLogicActions): boolean => {
return actions.some((action) => action.objective === "jumpToQuestion");
};
const getQuestionOperatorOptions = (question: TSurveyQuestion): TComboboxOption[] => {
let options: TLogicRuleOption;
@@ -603,8 +610,10 @@ export const getMatchValueProps = (
options: groupedOptions,
};
} else if (selectedVariable?.type === "number") {
const allowedQuestions = questions.filter((question) =>
[TSurveyQuestionTypeEnum.Rating, TSurveyQuestionTypeEnum.NPS].includes(question.type)
const allowedQuestions = questions.filter(
(question) =>
[TSurveyQuestionTypeEnum.Rating, TSurveyQuestionTypeEnum.NPS].includes(question.type) ||
(question.type === TSurveyQuestionTypeEnum.OpenText && question.inputType === "number")
);
const questionOptions = allowedQuestions.map((question) => {
@@ -940,8 +949,10 @@ export const getActionValueOptions = (
return groupedOptions;
} else if (selectedVariable.type === "number") {
const allowedQuestions = questions.filter((question) =>
[TSurveyQuestionTypeEnum.Rating, TSurveyQuestionTypeEnum.NPS].includes(question.type)
const allowedQuestions = questions.filter(
(question) =>
[TSurveyQuestionTypeEnum.Rating, TSurveyQuestionTypeEnum.NPS].includes(question.type) ||
(question.type === TSurveyQuestionTypeEnum.OpenText && question.inputType === "number")
);
const questionOptions = allowedQuestions.map((question) => {
@@ -1148,6 +1159,10 @@ export const findHiddenFieldUsedInLogic = (survey: TSurvey, hiddenFieldId: strin
return survey.questions.findIndex((question) => question.logic?.some(isUsedInLogicRule));
};
export const getSurveyFollowUpActionDefaultBody = (locale: TUserLocale) => {
return translate("follow_ups_modal_action_body", locale) as string;
};
export const findEndingCardUsedInLogic = (survey: TSurvey, endingCardId: string): number => {
const isUsedInAction = (action: TSurveyLogicAction): boolean => {
return action.objective === "jumpToQuestion" && action.target === endingCardId;

View File

@@ -1,9 +1,12 @@
import { getUserEmail } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/user";
import {
getAdvancedTargetingPermission,
getMultiLanguagePermission,
getSurveyFollowUpsPermission,
} from "@/modules/ee/license-check/lib/utils";
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
import { ErrorComponent } from "@/modules/ui/components/error-component";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { getActionClasses } from "@formbricks/lib/actionClass/service";
@@ -12,6 +15,7 @@ import { authOptions } from "@formbricks/lib/authOptions";
import {
DEFAULT_LOCALE,
IS_FORMBRICKS_CLOUD,
MAIL_FROM,
SURVEY_BG_COLORS,
UNSPLASH_ACCESS_KEY,
} from "@formbricks/lib/constants";
@@ -24,7 +28,6 @@ import { getResponseCountBySurveyId } from "@formbricks/lib/response/service";
import { getSegments } from "@formbricks/lib/segment/service";
import { getSurvey } from "@formbricks/lib/survey/service";
import { getUserLocale } from "@formbricks/lib/user/service";
import { ErrorComponent } from "@formbricks/ui/components/ErrorComponent";
import { SurveyEditor } from "./components/SurveyEditor";
export const generateMetadata = async (props) => {
@@ -60,6 +63,7 @@ const Page = async (props) => {
getServerSession(authOptions),
getSegments(params.environmentId),
]);
if (!session) {
throw new Error(t("common.session_not_found"));
}
@@ -84,6 +88,9 @@ const Page = async (props) => {
const isUserTargetingAllowed = await getAdvancedTargetingPermission(organization);
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization);
const isSurveyFollowUpsAllowed = await getSurveyFollowUpsPermission(organization);
const userEmail = await getUserEmail(session.user.id);
if (
!survey ||
@@ -91,6 +98,7 @@ const Page = async (props) => {
!actionClasses ||
!attributeClasses ||
!product ||
!userEmail ||
isSurveyCreationDeletionDisabled
) {
return <ErrorComponent />;
@@ -117,6 +125,9 @@ const Page = async (props) => {
isUnsplashConfigured={UNSPLASH_ACCESS_KEY ? true : false}
isCxMode={isCxMode}
locale={locale ?? DEFAULT_LOCALE}
mailFrom={MAIL_FROM ?? "hola@formbricks.com"}
isSurveyFollowUpsAllowed={isSurveyFollowUpsAllowed}
userEmail={userEmail}
/>
);
};

View File

@@ -0,0 +1,13 @@
import { z } from "zod";
export const ZCreateSurveyFollowUpFormSchema = z.object({
followUpName: z.string().trim().min(1, "Name is required"),
triggerType: z.enum(["response", "endings"]),
endingIds: z.array(z.string().cuid2()).nullable(),
emailTo: z.string().trim().min(1, "To is required"),
replyTo: z.array(z.string().email()).min(1, "Replies must have at least one email"),
subject: z.string().trim().min(1, "Subject is required"),
body: z.string().trim().min(1, "Body is required"),
});
export type TCreateSurveyFollowUpForm = z.infer<typeof ZCreateSurveyFollowUpFormSchema>;

View File

@@ -39,4 +39,5 @@ export const getMinimalSurvey = (locale: string): TSurvey => ({
isVerifyEmailEnabled: false,
isSingleResponsePerEmailEnabled: false,
variables: [],
followUps: [],
});

View File

@@ -3,13 +3,13 @@
import { authenticatedActionClient } from "@/lib/utils/action-client";
import { checkAuthorizationUpdated } from "@/lib/utils/action-client-middleware";
import { getOrganizationIdFromEnvironmentId, getProductIdFromEnvironmentId } from "@/lib/utils/helper";
import { getIsAIEnabled } from "@/modules/ee/license-check/lib/utils";
import { createId } from "@paralleldrive/cuid2";
import { generateObject } from "ai";
import { z } from "zod";
import { llmModel } from "@formbricks/lib/aiModels";
import { getOrganization } from "@formbricks/lib/organization/service";
import { createSurvey } from "@formbricks/lib/survey/service";
import { getIsAIEnabled } from "@formbricks/lib/utils/ai";
import { ZId, ZString } from "@formbricks/types/common";
import { ZSurveyQuestion } from "@formbricks/types/surveys/types";

View File

@@ -1,9 +1,9 @@
"use client";
import { Button } from "@/modules/ui/components/button";
import { ArrowLeftIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { Button } from "@formbricks/ui/components/Button";
export const BackButton = () => {
const router = useRouter();

View File

@@ -2,12 +2,7 @@
import { createAISurveyAction } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/templates/actions";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { Sparkles } from "lucide-react";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useState } from "react";
import toast from "react-hot-toast";
import { Button } from "@formbricks/ui/components/Button";
import { Button } from "@/modules/ui/components/button";
import {
Card,
CardContent,
@@ -15,8 +10,13 @@ import {
CardFooter,
CardHeader,
CardTitle,
} from "@formbricks/ui/components/Card";
import { Textarea } from "@formbricks/ui/components/Textarea";
} from "@/modules/ui/components/card";
import { Textarea } from "@/modules/ui/components/textarea";
import { Sparkles } from "lucide-react";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useState } from "react";
import toast from "react-hot-toast";
interface FormbricksAICardProps {
environmentId: string;

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