mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-16 19:15:05 -05:00
Compare commits
40 Commits
mertergolu
...
fix/licens
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a020e66008 | ||
|
|
9edf5a6f81 | ||
|
|
7f8549124f | ||
|
|
897b18d5b5 | ||
|
|
1f693689ce | ||
|
|
7bf6ffb2ba | ||
|
|
43b1cb904d | ||
|
|
d2cd155518 | ||
|
|
9e038e326a | ||
|
|
74fdf12e58 | ||
|
|
762a3ca626 | ||
|
|
9465b3fe67 | ||
|
|
91f0d00ba2 | ||
|
|
41f42f4427 | ||
|
|
98181bfe6c | ||
|
|
a8ab4aaf2e | ||
|
|
78dca7a2bf | ||
|
|
844ea40c3a | ||
|
|
7a6dedf452 | ||
|
|
b641b37308 | ||
|
|
8c1f8bfb42 | ||
|
|
1f1563401d | ||
|
|
9fd585ee07 | ||
|
|
609dcabf77 | ||
|
|
80d338c998 | ||
|
|
306784c31b | ||
|
|
bcd68e0f19 | ||
|
|
5764148753 | ||
|
|
e7edfe3ba1 | ||
|
|
da6f54eede | ||
|
|
ade5c3d80e | ||
|
|
cc2600cfba | ||
|
|
9b191ef3e4 | ||
|
|
c450c35baf | ||
|
|
b35b82f4ee | ||
|
|
ea52624ab2 | ||
|
|
0484bccfd1 | ||
|
|
d094f63faa | ||
|
|
dc6bc61442 | ||
|
|
5918c42cf9 |
@@ -1,16 +0,0 @@
|
||||
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
|
||||
ARG VARIANT=20
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
|
||||
|
||||
# [Optional] Uncomment this section to install additional OS packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
|
||||
# [Optional] Uncomment if you want to install an additional version of node using nvm
|
||||
# ARG EXTRA_NODE_VERSION=10
|
||||
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
|
||||
|
||||
# [Optional] Uncomment if you want to install more global node modules
|
||||
# RUN su node -c "npm install -g <your-package-list-here>"
|
||||
|
||||
RUN su node -c "npm install -g pnpm"
|
||||
@@ -1,28 +1,6 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/javascript-node-postgres
|
||||
// Update the VARIANT arg in docker-compose.yml to pick a Node.js version
|
||||
{
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
// Configure properties specific to VS Code.
|
||||
"vscode": {
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": ["dbaeumer.vscode-eslint"]
|
||||
}
|
||||
},
|
||||
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// This can be used to network with other containers or with the host.
|
||||
"forwardPorts": [3000, 5432, 8025],
|
||||
|
||||
"name": "Node.js & PostgreSQL",
|
||||
"postAttachCommand": "pnpm dev --filter=@formbricks/web... --filter=@formbricks/demo...",
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "cp .env.example .env && sed -i '/^ENCRYPTION_KEY=/c\\ENCRYPTION_KEY='$(openssl rand -hex 32) .env && sed -i '/^NEXTAUTH_SECRET=/c\\NEXTAUTH_SECRET='$(openssl rand -hex 32) .env && sed -i '/^CRON_SECRET=/c\\CRON_SECRET='$(openssl rand -hex 32) .env && pnpm install && pnpm db:migrate:dev",
|
||||
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "node",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspace"
|
||||
"features": {},
|
||||
"image": "mcr.microsoft.com/devcontainers/universal:2",
|
||||
"postAttachCommand": "pnpm go",
|
||||
"postCreateCommand": "cp .env.example .env && sed -i '/^ENCRYPTION_KEY=/c\\ENCRYPTION_KEY='$(openssl rand -hex 32) .env && sed -i '/^NEXTAUTH_SECRET=/c\\NEXTAUTH_SECRET='$(openssl rand -hex 32) .env && sed -i '/^CRON_SECRET=/c\\CRON_SECRET='$(openssl rand -hex 32) .env && pnpm install && pnpm db:migrate:dev"
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
# Update 'VARIANT' to pick an LTS version of Node.js: 20, 18, 16, 14.
|
||||
# Append -bullseye or -buster to pin to an OS version.
|
||||
# Use -bullseye variants on local arm64/Apple Silicon.
|
||||
VARIANT: "20"
|
||||
|
||||
volumes:
|
||||
- ..:/workspace:cached
|
||||
|
||||
# Overrides default command so things don't shut down after the process ends.
|
||||
command: sleep infinity
|
||||
|
||||
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
|
||||
network_mode: service:db
|
||||
# Uncomment the next line to use a non-root user for all processes.
|
||||
# user: node
|
||||
|
||||
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
|
||||
# (Adding the "ports" property to this file will not forward from a Codespace.)
|
||||
|
||||
db:
|
||||
image: pgvector/pgvector:pg17
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: formbricks
|
||||
# Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally.
|
||||
# (Adding the "ports" property to this file will not forward from a Codespace.)
|
||||
|
||||
mailhog:
|
||||
image: mailhog/mailhog
|
||||
network_mode: service:app
|
||||
logging:
|
||||
driver:
|
||||
"none" # disable saving logs
|
||||
# ports:
|
||||
# - 8025:8025 # web ui
|
||||
# 1025:1025 # smtp server
|
||||
|
||||
volumes:
|
||||
postgres-data: null
|
||||
7
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
7
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -10,6 +10,13 @@ body:
|
||||
description: A summary of the issue. This needs to be a clear detailed-rich summary.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: issue-expected-behavior
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: other-information
|
||||
attributes:
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -2,7 +2,7 @@ Copyright (c) 2024 Formbricks GmbH
|
||||
|
||||
Portions of this software are licensed as follows:
|
||||
|
||||
- All content that resides under the "packages/ee/", "apps/web/modules/ee" & "apps/web/app/(ee)" directories of this repository, if these directories exist, is licensed under the license defined in "packages/ee/LICENSE".
|
||||
- All content that resides under the "apps/web/modules/ee" directory of this repository, if these directories exist, is licensed under the license defined in "apps/web/modules/ee/LICENSE".
|
||||
- All content that resides under the "packages/js/", "packages/react-native/" and "packages/api/" directories of this repository, if that directories exist, is licensed under the "MIT" license as defined in the "LICENSE" files of these packages.
|
||||
- All third party components incorporated into the Formbricks Software are licensed under the original license provided by the owner of the applicable component.
|
||||
- Content outside of the above mentioned directories or restrictions above is available under the "AGPLv3" license as defined below.
|
||||
|
||||
@@ -228,7 +228,7 @@ The Formbricks core application is licensed under the [AGPLv3 Open Source Licens
|
||||
|
||||
### The Enterprise Edition
|
||||
|
||||
Additional to the AGPL licensed Formbricks core, this repository contains code licensed under an Enterprise license. The [code](https://github.com/formbricks/formbricks/tree/main/packages/ee) and [license](https://github.com/formbricks/formbricks/blob/main/packages/ee/LICENSE) for the enterprise functionality can be found in the `/packages/ee` folder of this repository. This additional functionality is not part of the AGPLv3 licensed Formbricks core and is designed to meet the needs of larger teams and enterprises. This advanced functionality is already included in the Docker images, but you need an [Enterprise License Key](https://formbricks.com/docs/self-hosting/enterprise) to unlock it.
|
||||
Additional to the AGPL licensed Formbricks core, this repository contains code licensed under an Enterprise license. The [code](https://github.com/formbricks/formbricks/tree/main/apps/web/modules/ee) and [license](https://github.com/formbricks/formbricks/blob/main/apps/web/modules/ee/LICENSE) for the enterprise functionality can be found in the `/apps/web/modules/ee` folder of this repository. This additional functionality is not part of the AGPLv3 licensed Formbricks core and is designed to meet the needs of larger teams and enterprises. This advanced functionality is already included in the Docker images, but you need an [Enterprise License Key](https://formbricks.com/docs/self-hosting/enterprise) to unlock it.
|
||||
|
||||
### White-Labeling Formbricks and Other Licensing Needs
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
"@formbricks/react-native": "workspace:*",
|
||||
"expo": "51.0.26",
|
||||
"expo-status-bar": "1.12.1",
|
||||
"react": "18.3.1",
|
||||
"react": "19.0.0-rc-ed15d500-20241110",
|
||||
"react-dom": "19.0.0-rc-ed15d500-20241110",
|
||||
"react-native": "0.74.4",
|
||||
"react-native-webview": "13.8.6"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import type { JSX } from "react";
|
||||
import { Button, LogBox, StyleSheet, Text, View } from "react-native";
|
||||
import Formbricks, { track } from "@formbricks/react-native";
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
"@formbricks/js": "workspace:*",
|
||||
"@formbricks/ui": "workspace:*",
|
||||
"lucide-react": "0.452.0",
|
||||
"next": "14.2.16",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1"
|
||||
"next": "15.0.3",
|
||||
"react": "19.0.0-rc-ed15d500-20241110",
|
||||
"react-dom": "19.0.0-rc-ed15d500-20241110"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/eslint-config": "workspace:*",
|
||||
|
||||
@@ -26,15 +26,20 @@ export const metadata = {
|
||||
|
||||
# n8n Setup
|
||||
|
||||
n8n allows you to build flexible workflows focused on deep data integration. And with sharable templates and a user-friendly UI, the less technical people on your team can collaborate on them too. Unlike other tools, complexity is not a limitation. So you can build whatever you want — without stressing over budget. Hook up Formbricks with n8n and you can send your data to 350+ other apps. Here is how to do it.
|
||||
|
||||
<Note>
|
||||
Nail down your survey? Any changes in the survey cause additional work in the n8n node. It makes
|
||||
sense to first settle on the survey you want to run and then get to setting up n8n.
|
||||
The Formbricks n8n node is currently only available in the n8n self-hosted version as a community node. To
|
||||
install it go to "Settings" -> "Community Nodes" and install @formbricks/n8n-nodes-formbricks.
|
||||
</Note>
|
||||
|
||||
n8n allows you to build flexible workflows focused on deep data integration. And with sharable templates and a user-friendly UI, the less technical people on your team can collaborate on them too. Unlike other tools, complexity is not a limitation. So you can build whatever you want — without stressing over budget. Hook up Formbricks with n8n and you can send your data to 350+ other apps. Here is how to do it.
|
||||
|
||||
## Step 1: Setup your survey incl. `questionId` for every question
|
||||
|
||||
<Note>
|
||||
Nailed down your survey? Any changes in the survey cause additional work in the n8n node. It makes sense to
|
||||
first settle on the survey you want to run and then get to setting up n8n.
|
||||
</Note>
|
||||
|
||||
When setting up the node your life will be easier when you change the `questionId`s of your survey questions. You can only do so **before** you publish your survey.
|
||||
|
||||
<MdxImage
|
||||
|
||||
@@ -5,6 +5,7 @@ import "@/styles/tailwind.css";
|
||||
import glob from "fast-glob";
|
||||
import { type Metadata } from "next";
|
||||
import { Jost } from "next/font/google";
|
||||
import Script from "next/script";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
@@ -27,6 +28,18 @@ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
|
||||
|
||||
return (
|
||||
<html lang="en" className="h-full" suppressHydrationWarning>
|
||||
<head>
|
||||
{process.env.NEXT_PUBLIC_LAYER_API_KEY && (
|
||||
<Script
|
||||
strategy="afterInteractive"
|
||||
src="https://storage.googleapis.com/generic-assets/buildwithlayer-widget-4.js"
|
||||
primary-color="#00C4B8"
|
||||
api-key={process.env.NEXT_PUBLIC_LAYER_API_KEY}
|
||||
walkthrough-enabled="false"
|
||||
design-style="copilot"
|
||||
/>
|
||||
)}
|
||||
</head>
|
||||
<body className={`flex min-h-full bg-white antialiased dark:bg-zinc-900 ${jost.className}`}>
|
||||
<Providers>
|
||||
<div className="w-full">
|
||||
|
||||
@@ -19,7 +19,7 @@ The Formbricks Core source code is licensed under AGPLv3 and available on GitHub
|
||||
|
||||
## Enterprise Edition License
|
||||
|
||||
Additional to the AGPLv3 licensed Formbricks core, the Formbricks repository contains code licensed under our **[Enterprise License](https://github.com/formbricks/formbricks/blob/main/packages/ee/LICENSE)**. This additional functionality is not part of the AGPLv3 licensed Formbricks core and is designed to meet the needs of larger teams and enterprises. This advanced functionality is already included in the Docker images, but you need an **Enterprise License Key** to unlock it. For the pricing, please refer to [Formbricks Pricing](https://formbricks.com/pricing).
|
||||
Additional to the AGPLv3 licensed Formbricks core, the Formbricks repository contains code licensed under our **[Enterprise License](https://github.com/formbricks/formbricks/blob/main/apps/web/modules/ee/LICENSE)**. This additional functionality is not part of the AGPLv3 licensed Formbricks core and is designed to meet the needs of larger teams and enterprises. This advanced functionality is already included in the Docker images, but you need an **Enterprise License Key** to unlock it. For the pricing, please refer to [Formbricks Pricing](https://formbricks.com/pricing).
|
||||
|
||||
### When do I need an Enterprise License?
|
||||
|
||||
@@ -64,7 +64,7 @@ The Formbricks core application is licensed under the **[AGPLv3 Open Source Lice
|
||||
|
||||
### The Enterprise Edition
|
||||
|
||||
Additional to the AGPL licensed Formbricks core, this repository contains code licensed under an Enterprise License. The **[code](https://github.com/formbricks/formbricks/tree/main/packages/ee)** and **[license](https://github.com/formbricks/formbricks/blob/main/packages/ee/LICENSE)** for the enterprise functionality can be found in the `/packages/ee` folder of this repository. This additional functionality is not part of the AGPLv3 licensed Formbricks core and is designed to meet the needs of larger teams and enterprises. This advanced functionality is already included in the Docker images, but you need an **Enterprise License Key** to unlock it.
|
||||
Additional to the AGPL licensed Formbricks core, this repository contains code licensed under an Enterprise license. The **[code](https://github.com/formbricks/formbricks/tree/main/apps/web/modules/ee)** and **[license](https://github.com/formbricks/formbricks/blob/main/apps/web/modules/ee/LICENSE)** for the enterprise functionality can be found in the `/apps/web/modules/ee` folder of this repository. This additional functionality is not part of the AGPLv3 licensed Formbricks core and is designed to meet the needs of larger teams and enterprises. This advanced functionality is already included in the Docker images, but you need an **[Enterprise License Key](https://formbricks.com/docs/self-hosting/enterprise)** to unlock it.
|
||||
|
||||
## White-Labeling Formbricks and Other Licensing Needs
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@mapbox/rehype-prism": "0.9.0",
|
||||
"@mdx-js/loader": "3.0.1",
|
||||
"@mdx-js/react": "3.0.1",
|
||||
"@next/mdx": "14.2.15",
|
||||
"@next/mdx": "15.0.3",
|
||||
"@paralleldrive/cuid2": "2.2.2",
|
||||
"@sindresorhus/slugify": "2.2.1",
|
||||
"@tailwindcss/typography": "0.5.15",
|
||||
@@ -39,7 +39,7 @@
|
||||
"lucide-react": "0.452.0",
|
||||
"mdast-util-to-string": "4.0.0",
|
||||
"mdx-annotations": "0.1.4",
|
||||
"next": "14.2.16",
|
||||
"next": "15.0.3",
|
||||
"next-plausible": "3.12.2",
|
||||
"next-seo": "6.6.0",
|
||||
"next-sitemap": "4.2.3",
|
||||
@@ -47,8 +47,8 @@
|
||||
"node-fetch": "3.3.2",
|
||||
"prism-react-renderer": "2.4.0",
|
||||
"prismjs": "1.29.0",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react": "19.0.0-rc-ed15d500-20241110",
|
||||
"react-dom": "19.0.0-rc-ed15d500-20241110",
|
||||
"react-highlight-words": "0.20.0",
|
||||
"react-markdown": "9.0.1",
|
||||
"react-responsive-embed": "2.1.0",
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
"dependencies": {
|
||||
"@formbricks/ui": "workspace:*",
|
||||
"eslint-plugin-react-refresh": "0.4.12",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1"
|
||||
"react": "19.0.0-rc-ed15d500-20241110",
|
||||
"react-dom": "19.0.0-rc-ed15d500-20241110"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "2.0.2",
|
||||
|
||||
@@ -19,7 +19,11 @@ interface InviteOrganizationMemberProps {
|
||||
|
||||
const ZInviteOrganizationMemberDetails = z.object({
|
||||
email: z.string().email(),
|
||||
inviteMessage: z.string().trim().min(1),
|
||||
inviteMessage: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.refine((value) => !/https?:\/\/|<script/i.test(value), "Invite message cannot contain URLs or scripts"),
|
||||
});
|
||||
type TInviteOrganizationMemberDetails = z.infer<typeof ZInviteOrganizationMemberDetails>;
|
||||
|
||||
|
||||
@@ -10,12 +10,13 @@ import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Header } from "@formbricks/ui/components/Header";
|
||||
|
||||
interface InvitePageProps {
|
||||
params: {
|
||||
params: Promise<{
|
||||
environmentId: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
const Page = async ({ params }: InvitePageProps) => {
|
||||
const Page = async (props: InvitePageProps) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
|
||||
@@ -8,12 +8,13 @@ import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Header } from "@formbricks/ui/components/Header";
|
||||
|
||||
interface ConnectPageProps {
|
||||
params: {
|
||||
params: Promise<{
|
||||
environmentId: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
const Page = async ({ params }: ConnectPageProps) => {
|
||||
const Page = async (props: ConnectPageProps) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const environment = await getEnvironment(params.environmentId);
|
||||
|
||||
|
||||
@@ -4,7 +4,11 @@ import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
|
||||
import { AuthorizationError } from "@formbricks/types/errors";
|
||||
|
||||
const OnboardingLayout = async ({ children, params }) => {
|
||||
const OnboardingLayout = async (props) => {
|
||||
const params = await props.params;
|
||||
|
||||
const { children } = props;
|
||||
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
return redirect(`/auth/login`);
|
||||
|
||||
@@ -11,12 +11,13 @@ import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Header } from "@formbricks/ui/components/Header";
|
||||
|
||||
interface XMTemplatePageProps {
|
||||
params: {
|
||||
params: Promise<{
|
||||
environmentId: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
const Page = async ({ params }: XMTemplatePageProps) => {
|
||||
const Page = async (props: XMTemplatePageProps) => {
|
||||
const params = await props.params;
|
||||
const session = await getServerSession(authOptions);
|
||||
const environment = await getEnvironment(params.environmentId);
|
||||
const t = await getTranslations();
|
||||
|
||||
@@ -11,7 +11,7 @@ import { ZId } from "@formbricks/types/common";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
|
||||
export const getTeamsByOrganizationId = reactCache(
|
||||
(organizationId: string): Promise<TOrganizationTeam[] | null> =>
|
||||
async (organizationId: string): Promise<TOrganizationTeam[] | null> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([organizationId, ZId]);
|
||||
|
||||
@@ -5,7 +5,11 @@ import { getEnvironments } from "@formbricks/lib/environment/service";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getUserProducts } from "@formbricks/lib/product/service";
|
||||
|
||||
const LandingLayout = async ({ children, params }) => {
|
||||
const LandingLayout = async (props) => {
|
||||
const params = await props.params;
|
||||
|
||||
const { children } = props;
|
||||
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
return redirect(`/auth/login`);
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { LandingSidebar } from "@/app/(app)/(onboarding)/organizations/[organizationId]/landing/components/landing-sidebar";
|
||||
import { getEnterpriseLicense } from "@/modules/ee/license-check/lib/utils";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { getEnterpriseLicense } from "@formbricks/ee/lib/service";
|
||||
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 ({ params }) => {
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
|
||||
@@ -9,7 +9,11 @@ import { getUser } from "@formbricks/lib/user/service";
|
||||
import { AuthorizationError } from "@formbricks/types/errors";
|
||||
import { ToasterClient } from "@formbricks/ui/components/ToasterClient";
|
||||
|
||||
const ProductOnboardingLayout = async ({ children, params }) => {
|
||||
const ProductOnboardingLayout = async (props) => {
|
||||
const params = await props.params;
|
||||
|
||||
const { children } = props;
|
||||
|
||||
const t = await getTranslations();
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
|
||||
@@ -4,7 +4,11 @@ import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
|
||||
const OnboardingLayout = async ({ children, params }) => {
|
||||
const OnboardingLayout = async (props) => {
|
||||
const params = await props.params;
|
||||
|
||||
const { children } = props;
|
||||
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
return redirect(`/auth/login`);
|
||||
|
||||
@@ -9,12 +9,13 @@ import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Header } from "@formbricks/ui/components/Header";
|
||||
|
||||
interface ChannelPageProps {
|
||||
params: {
|
||||
params: Promise<{
|
||||
organizationId: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
const Page = async ({ params }: ChannelPageProps) => {
|
||||
const Page = async (props: ChannelPageProps) => {
|
||||
const params = await props.params;
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
return redirect(`/auth/login`);
|
||||
|
||||
@@ -9,12 +9,13 @@ import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Header } from "@formbricks/ui/components/Header";
|
||||
|
||||
interface ModePageProps {
|
||||
params: {
|
||||
params: Promise<{
|
||||
organizationId: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
const Page = async ({ params }: ModePageProps) => {
|
||||
const Page = async (props: ModePageProps) => {
|
||||
const params = await props.params;
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
return redirect(`/auth/login`);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { getTeamsByOrganizationId } from "@/app/(app)/(onboarding)/lib/onboarding";
|
||||
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 { XIcon } from "lucide-react";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getRoleManagementPermission } from "@formbricks/ee/lib/service";
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { DEFAULT_BRAND_COLOR, DEFAULT_LOCALE } from "@formbricks/lib/constants";
|
||||
import { getOrganization } from "@formbricks/lib/organization/service";
|
||||
@@ -16,17 +16,19 @@ import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Header } from "@formbricks/ui/components/Header";
|
||||
|
||||
interface ProductSettingsPageProps {
|
||||
params: {
|
||||
params: Promise<{
|
||||
organizationId: string;
|
||||
};
|
||||
searchParams: {
|
||||
}>;
|
||||
searchParams: Promise<{
|
||||
channel?: TProductConfigChannel;
|
||||
industry?: TProductConfigIndustry;
|
||||
mode?: TProductMode;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
const Page = async ({ params, searchParams }: ProductSettingsPageProps) => {
|
||||
const Page = async (props: ProductSettingsPageProps) => {
|
||||
const searchParams = await props.searchParams;
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
|
||||
@@ -13,7 +13,11 @@ import { AuthorizationError } from "@formbricks/types/errors";
|
||||
import { DevEnvironmentBanner } from "@formbricks/ui/components/DevEnvironmentBanner";
|
||||
import { ToasterClient } from "@formbricks/ui/components/ToasterClient";
|
||||
|
||||
const SurveyEditorEnvironmentLayout = async ({ children, params }) => {
|
||||
const SurveyEditorEnvironmentLayout = async (props) => {
|
||||
const params = await props.params;
|
||||
|
||||
const { children } = props;
|
||||
|
||||
const t = await getTranslations();
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInpu
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect } from "react";
|
||||
import { type JSX, useEffect } from "react";
|
||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TSurvey, TSurveyAddressQuestion } from "@formbricks/types/surveys/types";
|
||||
|
||||
@@ -4,7 +4,7 @@ import { LocalizedEditor } from "@/modules/ee/multi-language-surveys/components/
|
||||
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
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";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect, useState } from "react";
|
||||
import { type JSX, useEffect, useState } from "react";
|
||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TSurvey, TSurveyCalQuestion } from "@formbricks/types/surveys/types";
|
||||
|
||||
@@ -41,8 +41,8 @@ export const CardStylingSettings = ({
|
||||
const surveyTypeDerived = isAppSurvey ? "App" : "Link";
|
||||
const isLogoVisible = !!product.logo?.url;
|
||||
|
||||
const linkCardArrangement = form.watch("cardArrangement.linkSurveys") ?? "simple";
|
||||
const appCardArrangement = form.watch("cardArrangement.appSurveys") ?? "simple";
|
||||
const linkCardArrangement = form.watch("cardArrangement.linkSurveys") ?? "straight";
|
||||
const appCardArrangement = form.watch("cardArrangement.appSurveys") ?? "straight";
|
||||
const roundness = form.watch("roundness") ?? 8;
|
||||
|
||||
const [parent] = useAutoAnimate();
|
||||
|
||||
@@ -87,10 +87,12 @@ export function ConditionalLogic({
|
||||
|
||||
const handleRemoveLogic = (logicItemIdx: number) => {
|
||||
const logicCopy = structuredClone(question.logic ?? []);
|
||||
const isLast = logicCopy.length === 1;
|
||||
logicCopy.splice(logicItemIdx, 1);
|
||||
|
||||
updateQuestion(questionIdx, {
|
||||
logic: logicCopy,
|
||||
logicFallback: isLast ? undefined : question.logicFallback,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { LocalizedEditor } from "@/modules/ee/multi-language-surveys/components/localized-editor";
|
||||
import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInput";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
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";
|
||||
|
||||
@@ -4,7 +4,7 @@ import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInpu
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect } from "react";
|
||||
import { type JSX, useEffect } from "react";
|
||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TSurvey, TSurveyContactInfoQuestion } from "@formbricks/types/surveys/types";
|
||||
|
||||
@@ -2,6 +2,7 @@ import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInpu
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { JSX } from "react";
|
||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TSurvey, TSurveyDateQuestion } from "@formbricks/types/surveys/types";
|
||||
|
||||
@@ -3,13 +3,17 @@
|
||||
import { EditorCardMenu } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditorCardMenu";
|
||||
import { EndScreenForm } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/EndScreenForm";
|
||||
import { RedirectUrlForm } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/RedirectUrlForm";
|
||||
import { formatTextWithSlashes } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
|
||||
import {
|
||||
findEndingCardUsedInLogic,
|
||||
formatTextWithSlashes,
|
||||
} from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/utils";
|
||||
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 toast from "react-hot-toast";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { recallToHeadline } from "@formbricks/lib/utils/recall";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
@@ -93,6 +97,14 @@ export const EditEndingCard = ({
|
||||
};
|
||||
|
||||
const deleteEndingCard = () => {
|
||||
// checking if this ending card is used in logic
|
||||
const quesIdx = findEndingCardUsedInLogic(localSurvey, endingCard.id);
|
||||
|
||||
if (quesIdx !== -1) {
|
||||
toast.error(t("environments.surveys.edit.ending_card_used_in_logic", { questionIndex: quesIdx + 1 }));
|
||||
return;
|
||||
}
|
||||
|
||||
setLocalSurvey((prevSurvey) => {
|
||||
const updatedEndings = prevSurvey.endings.filter((_, index) => index !== endingCardIndex);
|
||||
return { ...prevSurvey, endings: updatedEndings };
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { PlusIcon, XCircleIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import { useMemo, useState } from "react";
|
||||
import { type JSX, useMemo, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||
import { createI18nString } from "@formbricks/lib/i18n/utils";
|
||||
|
||||
@@ -2,7 +2,17 @@ import { LogicEditorActions } from "@/app/(app)/(survey-editor)/environments/[en
|
||||
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";
|
||||
|
||||
interface LogicEditorProps {
|
||||
localSurvey: TSurvey;
|
||||
@@ -24,6 +34,36 @@ export function LogicEditor({
|
||||
isLast,
|
||||
}: LogicEditorProps) {
|
||||
const t = useTranslations();
|
||||
|
||||
const fallbackOptions = useMemo(() => {
|
||||
let options: {
|
||||
icon?: ReactElement;
|
||||
label: string;
|
||||
value: string;
|
||||
}[] = [];
|
||||
|
||||
localSurvey.questions.forEach((ques) => {
|
||||
if (ques.id === question.id) return null;
|
||||
options.push({
|
||||
icon: QUESTIONS_ICON_MAP[ques.type],
|
||||
label: getLocalizedValue(ques.headline, "default"),
|
||||
value: ques.id,
|
||||
});
|
||||
});
|
||||
|
||||
localSurvey.endings.forEach((ending) => {
|
||||
options.push({
|
||||
label:
|
||||
ending.type === "endScreen"
|
||||
? getLocalizedValue(ending.headline, "default") || t("environments.surveys.edit.end_screen_card")
|
||||
: ending.label || t("environments.surveys.edit.redirect_thank_you_card"),
|
||||
value: ending.id,
|
||||
});
|
||||
});
|
||||
|
||||
return options;
|
||||
}, [localSurvey.questions, localSurvey.endings, question.id, t]);
|
||||
|
||||
return (
|
||||
<div className="flex w-full grow flex-col gap-4 overflow-x-auto pb-2 text-sm">
|
||||
<LogicEditorConditions
|
||||
@@ -43,11 +83,36 @@ export function LogicEditor({
|
||||
questionIdx={questionIdx}
|
||||
/>
|
||||
{isLast ? (
|
||||
<div className="flex flex-wrap items-center space-x-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<ArrowRightIcon className="h-4 w-4" />
|
||||
<p className="text-slate-700">
|
||||
{t("environments.surveys.edit.all_other_answers_will_continue_to_the_next_question")}
|
||||
<p className="text-nowrap text-slate-700">
|
||||
{t("environments.surveys.edit.all_other_answers_will_continue_to")}
|
||||
</p>
|
||||
<Select
|
||||
autoComplete="true"
|
||||
defaultValue={question.logicFallback || "defaultSelection"}
|
||||
onValueChange={(val) => {
|
||||
updateQuestion(questionIdx, {
|
||||
logicFallback: val === "defaultSelection" ? undefined : val,
|
||||
});
|
||||
}}>
|
||||
<SelectTrigger className="w-auto bg-white">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem key="fallback_default_selection" value={"defaultSelection"}>
|
||||
{t("environments.surveys.edit.next_question")}
|
||||
</SelectItem>
|
||||
{fallbackOptions.map((option) => (
|
||||
<SelectItem key={`fallback_${option.value}`} value={option.value}>
|
||||
<div className="flex items-center gap-2">
|
||||
{option.icon}
|
||||
{option.label}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -244,7 +244,13 @@ export function LogicEditorConditions({
|
||||
const conditionOperatorOptions = getConditionOperatorOptions(condition, localSurvey);
|
||||
const { show, options, showInput = false, inputType } = getMatchValueProps(condition, localSurvey, t);
|
||||
|
||||
const allowMultiSelect = ["equalsOneOf", "includesAllOf", "includesOneOf"].includes(condition.operator);
|
||||
const allowMultiSelect = [
|
||||
"equalsOneOf",
|
||||
"includesAllOf",
|
||||
"includesOneOf",
|
||||
"doesNotIncludeOneOf",
|
||||
"doesNotIncludeAllOf",
|
||||
].includes(condition.operator);
|
||||
return (
|
||||
<div key={condition.id} className="flex items-center gap-x-2">
|
||||
<div className="w-10 shrink-0">
|
||||
|
||||
@@ -4,6 +4,7 @@ import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInpu
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { PlusIcon, TrashIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { JSX } from "react";
|
||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TI18nString, TSurvey, TSurveyMatrixQuestion } from "@formbricks/types/surveys/types";
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { type JSX, useEffect, useRef, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
|
||||
@@ -4,6 +4,7 @@ import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInpu
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { JSX } from "react";
|
||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TSurvey, TSurveyNPSQuestion } from "@formbricks/types/surveys/types";
|
||||
|
||||
@@ -4,6 +4,7 @@ import { QuestionFormInput } from "@/modules/surveys/components/QuestionFormInpu
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { HashIcon, LinkIcon, MailIcon, MessageSquareTextIcon, PhoneIcon, PlusIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { JSX } from "react";
|
||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { JSX } from "react";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PaintbrushIcon, Rows3Icon, SettingsIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useMemo } from "react";
|
||||
import { type JSX, useMemo } from "react";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { TSurveyEditorTabs } from "@formbricks/types/surveys/types";
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { type JSX, useEffect, useRef, useState } from "react";
|
||||
import { createI18nString, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TI18nString, TSurvey, TSurveyRankingQuestion } from "@formbricks/types/surveys/types";
|
||||
|
||||
@@ -130,21 +130,15 @@ export const ResponseOptionsCard = ({
|
||||
};
|
||||
|
||||
const handleRunOnDateChange = (date: Date) => {
|
||||
const equivalentDate = date?.getDate();
|
||||
date?.setUTCHours(0, 0, 0, 0);
|
||||
date?.setDate(equivalentDate);
|
||||
|
||||
setRunOnDate(date);
|
||||
setLocalSurvey({ ...localSurvey, runOnDate: date ?? null });
|
||||
const utcDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0));
|
||||
setRunOnDate(utcDate);
|
||||
setLocalSurvey({ ...localSurvey, runOnDate: utcDate ?? null });
|
||||
};
|
||||
|
||||
const handleCloseOnDateChange = (date: Date) => {
|
||||
const equivalentDate = date?.getDate();
|
||||
date?.setUTCHours(0, 0, 0, 0);
|
||||
date?.setDate(equivalentDate);
|
||||
|
||||
setCloseOnDate(date);
|
||||
setLocalSurvey({ ...localSurvey, closeOnDate: date ?? null });
|
||||
const utcDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0));
|
||||
setCloseOnDate(utcDate);
|
||||
setLocalSurvey({ ...localSurvey, closeOnDate: utcDate ?? null });
|
||||
};
|
||||
|
||||
const handleClosedSurveyMessageChange = ({
|
||||
|
||||
@@ -2,12 +2,10 @@ import { RotateCcwIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { UseFormReturn, useForm, useWatch } from "react-hook-form";
|
||||
import { UseFormReturn, useForm } from "react-hook-form";
|
||||
import toast from "react-hot-toast";
|
||||
import { COLOR_DEFAULTS } from "@formbricks/lib/styling/constants";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TProduct, TProductStyling } from "@formbricks/types/product";
|
||||
import { TBaseStyling } from "@formbricks/types/styling";
|
||||
import { TSurvey, TSurveyStyling } from "@formbricks/types/surveys/types";
|
||||
import { AlertDialog } from "@formbricks/ui/components/AlertDialog";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
@@ -53,50 +51,8 @@ export const StylingView = ({
|
||||
}: StylingViewProps) => {
|
||||
const t = useTranslations();
|
||||
|
||||
const stylingDefaults: TBaseStyling = useMemo(() => {
|
||||
let stylingDefaults: TBaseStyling;
|
||||
const isOverwriteEnabled = localSurvey.styling?.overwriteThemeStyling ?? false;
|
||||
|
||||
if (isOverwriteEnabled) {
|
||||
const { overwriteThemeStyling, ...baseSurveyStyles } = localSurvey.styling ?? {};
|
||||
stylingDefaults = baseSurveyStyles;
|
||||
} else {
|
||||
const { allowStyleOverwrite, ...baseProductStyles } = product.styling ?? {};
|
||||
stylingDefaults = baseProductStyles;
|
||||
}
|
||||
|
||||
return {
|
||||
brandColor: { light: stylingDefaults.brandColor?.light ?? COLOR_DEFAULTS.brandColor },
|
||||
questionColor: { light: stylingDefaults.questionColor?.light ?? COLOR_DEFAULTS.questionColor },
|
||||
inputColor: { light: stylingDefaults.inputColor?.light ?? COLOR_DEFAULTS.inputColor },
|
||||
inputBorderColor: { light: stylingDefaults.inputBorderColor?.light ?? COLOR_DEFAULTS.inputBorderColor },
|
||||
cardBackgroundColor: {
|
||||
light: stylingDefaults.cardBackgroundColor?.light ?? COLOR_DEFAULTS.cardBackgroundColor,
|
||||
},
|
||||
cardBorderColor: { light: stylingDefaults.cardBorderColor?.light ?? COLOR_DEFAULTS.cardBorderColor },
|
||||
cardShadowColor: { light: stylingDefaults.cardShadowColor?.light ?? COLOR_DEFAULTS.cardShadowColor },
|
||||
highlightBorderColor: stylingDefaults.highlightBorderColor?.light
|
||||
? {
|
||||
light: stylingDefaults.highlightBorderColor.light,
|
||||
}
|
||||
: undefined,
|
||||
isDarkModeEnabled: stylingDefaults.isDarkModeEnabled ?? false,
|
||||
roundness: stylingDefaults.roundness ?? 8,
|
||||
cardArrangement: stylingDefaults.cardArrangement ?? {
|
||||
linkSurveys: "simple",
|
||||
appSurveys: "simple",
|
||||
},
|
||||
background: stylingDefaults.background,
|
||||
hideProgressBar: stylingDefaults.hideProgressBar ?? false,
|
||||
isLogoHidden: stylingDefaults.isLogoHidden ?? false,
|
||||
};
|
||||
}, [localSurvey.styling, product.styling]);
|
||||
|
||||
const form = useForm<TSurveyStyling>({
|
||||
defaultValues: {
|
||||
...localSurvey.styling,
|
||||
...stylingDefaults,
|
||||
},
|
||||
defaultValues: localSurvey.styling ?? product.styling,
|
||||
});
|
||||
|
||||
const overwriteThemeStyling = form.watch("overwriteThemeStyling");
|
||||
@@ -133,20 +89,17 @@ export const StylingView = ({
|
||||
}
|
||||
}, [overwriteThemeStyling]);
|
||||
|
||||
const watchedValues = useWatch({
|
||||
control: form.control,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-expect-error
|
||||
setLocalSurvey((prev) => ({
|
||||
...prev,
|
||||
styling: {
|
||||
...prev.styling,
|
||||
...watchedValues,
|
||||
},
|
||||
}));
|
||||
}, [watchedValues, setLocalSurvey]);
|
||||
form.watch((data: TSurveyStyling) => {
|
||||
setLocalSurvey((prev) => ({
|
||||
...prev,
|
||||
styling: {
|
||||
...prev.styling,
|
||||
...data,
|
||||
},
|
||||
}));
|
||||
});
|
||||
}, [setLocalSurvey]);
|
||||
|
||||
const defaultProductStyling = useMemo(() => {
|
||||
const { styling: productStyling } = product;
|
||||
|
||||
@@ -344,9 +344,7 @@ export const SurveyMenuBar = ({
|
||||
<AlertTriangleIcon className="h-5 w-5 text-amber-400" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side={"top"} className="lg:hidden">
|
||||
<p className="py-2 text-center text-xs text-slate-500 dark:text-slate-400">
|
||||
{t(cautionText)}
|
||||
</p>
|
||||
<p className="py-2 text-center text-xs text-slate-500 dark:text-slate-400">{cautionText}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -116,6 +116,14 @@ export const logicRules = {
|
||||
label: "environments.surveys.edit.does_not_equal",
|
||||
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
|
||||
},
|
||||
{
|
||||
label: "environments.surveys.edit.does_not_include_one_of",
|
||||
value: ZSurveyLogicConditionsOperator.Enum.doesNotIncludeOneOf,
|
||||
},
|
||||
{
|
||||
label: "environments.surveys.edit.does_not_include_all_of",
|
||||
value: ZSurveyLogicConditionsOperator.Enum.doesNotIncludeAllOf,
|
||||
},
|
||||
{
|
||||
label: "environments.surveys.edit.includes_all_of",
|
||||
value: ZSurveyLogicConditionsOperator.Enum.includesAllOf,
|
||||
@@ -144,6 +152,14 @@ export const logicRules = {
|
||||
label: "environments.surveys.edit.does_not_equal",
|
||||
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
|
||||
},
|
||||
{
|
||||
label: "environments.surveys.edit.does_not_include_one_of",
|
||||
value: ZSurveyLogicConditionsOperator.Enum.doesNotIncludeOneOf,
|
||||
},
|
||||
{
|
||||
label: "environments.surveys.edit.does_not_include_all_of",
|
||||
value: ZSurveyLogicConditionsOperator.Enum.doesNotIncludeAllOf,
|
||||
},
|
||||
{
|
||||
label: "environments.surveys.edit.includes_all_of",
|
||||
value: ZSurveyLogicConditionsOperator.Enum.includesAllOf,
|
||||
|
||||
@@ -1060,7 +1060,9 @@ export const findQuestionUsedInLogic = (survey: TSurvey, questionId: TSurveyQues
|
||||
};
|
||||
|
||||
return survey.questions.findIndex(
|
||||
(question) => question.logic && question.id !== questionId && question.logic.some(isUsedInLogicRule)
|
||||
(question) =>
|
||||
question.logicFallback === questionId ||
|
||||
(question.id !== questionId && question.logic?.some(isUsedInLogicRule))
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1145,3 +1147,17 @@ export const findHiddenFieldUsedInLogic = (survey: TSurvey, hiddenFieldId: strin
|
||||
|
||||
return survey.questions.findIndex((question) => question.logic?.some(isUsedInLogicRule));
|
||||
};
|
||||
|
||||
export const findEndingCardUsedInLogic = (survey: TSurvey, endingCardId: string): number => {
|
||||
const isUsedInAction = (action: TSurveyLogicAction): boolean => {
|
||||
return action.objective === "jumpToQuestion" && action.target === endingCardId;
|
||||
};
|
||||
|
||||
const isUsedInLogicRule = (logicRule: TSurveyLogic): boolean => {
|
||||
return logicRule.actions.some(isUsedInAction);
|
||||
};
|
||||
|
||||
return survey.questions.findIndex(
|
||||
(question) => question.logicFallback === endingCardId || question.logic?.some(isUsedInLogicRule)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import {
|
||||
getAdvancedTargetingPermission,
|
||||
getMultiLanguagePermission,
|
||||
} from "@/modules/ee/license-check/lib/utils";
|
||||
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { getAdvancedTargetingPermission, getMultiLanguagePermission } from "@formbricks/ee/lib/service";
|
||||
import { getActionClasses } from "@formbricks/lib/actionClass/service";
|
||||
import { getAttributeClasses } from "@formbricks/lib/attributeClass/service";
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
@@ -24,14 +27,17 @@ import { getUserLocale } from "@formbricks/lib/user/service";
|
||||
import { ErrorComponent } from "@formbricks/ui/components/ErrorComponent";
|
||||
import { SurveyEditor } from "./components/SurveyEditor";
|
||||
|
||||
export const generateMetadata = async ({ params }) => {
|
||||
export const generateMetadata = async (props) => {
|
||||
const params = await props.params;
|
||||
const survey = await getSurvey(params.surveyId);
|
||||
return {
|
||||
title: survey?.name ? `${survey?.name} | Editor` : "Editor",
|
||||
};
|
||||
};
|
||||
|
||||
const Page = async ({ params, searchParams }) => {
|
||||
const Page = async (props) => {
|
||||
const searchParams = await props.searchParams;
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const [
|
||||
survey,
|
||||
@@ -48,7 +54,7 @@ const Page = async ({ params, searchParams }) => {
|
||||
getProductByEnvironmentId(params.environmentId),
|
||||
getEnvironment(params.environmentId),
|
||||
getActionClasses(params.environmentId),
|
||||
getAttributeClasses(params.environmentId),
|
||||
getAttributeClasses(params.environmentId, undefined, { skipArchived: true }),
|
||||
getResponseCountBySurveyId(params.surveyId),
|
||||
getOrganizationByEnvironmentId(params.environmentId),
|
||||
getServerSession(authOptions),
|
||||
|
||||
@@ -14,17 +14,19 @@ import { TTemplateRole } from "@formbricks/types/templates";
|
||||
import { TemplateContainerWithPreview } from "./components/TemplateContainer";
|
||||
|
||||
interface SurveyTemplateProps {
|
||||
params: {
|
||||
params: Promise<{
|
||||
environmentId: string;
|
||||
};
|
||||
searchParams: {
|
||||
}>;
|
||||
searchParams: Promise<{
|
||||
channel?: TProductConfigChannel;
|
||||
industry?: TProductConfigIndustry;
|
||||
role?: TTemplateRole;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
const Page = async ({ params, searchParams }: SurveyTemplateProps) => {
|
||||
const Page = async (props: SurveyTemplateProps) => {
|
||||
const searchParams = await props.searchParams;
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const session = await getServerSession(authOptions);
|
||||
const environmentId = params.environmentId;
|
||||
|
||||
@@ -3,7 +3,8 @@ import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const Page = ({ searchParams }) => {
|
||||
const Page = async (props) => {
|
||||
const searchParams = await props.searchParams;
|
||||
const { environmentId } = searchParams;
|
||||
|
||||
return (
|
||||
|
||||
@@ -20,13 +20,17 @@ export const getSegmentsByAttributeClassAction = authenticatedActionClient
|
||||
userId: ctx.user.id,
|
||||
organizationId: await getOrganizationIdFromEnvironmentId(parsedInput.environmentId),
|
||||
access: [
|
||||
{
|
||||
type: "organization",
|
||||
roles: ["owner", "manager"],
|
||||
},
|
||||
{
|
||||
type: "productTeam",
|
||||
minPermission: "read",
|
||||
productId: await getProductIdFromEnvironmentId(parsedInput.environmentId),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const segments = await getSegmentsByAttributeClassName(
|
||||
parsedInput.environmentId,
|
||||
parsedInput.attributeClass.name
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTranslations } from "next-intl";
|
||||
import { useMemo, useState } from "react";
|
||||
import { TAttributeClass } from "@formbricks/types/attribute-classes";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { Label } from "@formbricks/ui/components/Label";
|
||||
import { Switch } from "@formbricks/ui/components/Switch";
|
||||
import { AttributeDetailModal } from "./AttributeDetailModal";
|
||||
import { AttributeClassDataRow } from "./AttributeRowData";
|
||||
@@ -49,8 +50,15 @@ export const AttributeClassesTable = ({
|
||||
{hasArchived && (
|
||||
<div className="my-4 flex items-center justify-end text-right">
|
||||
<div className="flex items-center text-sm font-medium">
|
||||
{t("environments.attributes.show_archived")}
|
||||
<Switch className="mx-3" checked={showArchived} onCheckedChange={toggleShowArchived} />
|
||||
<Label htmlFor="showArchivedToggle" className="cursor-pointer">
|
||||
{t("environments.attributes.show_archived")}
|
||||
</Label>
|
||||
<Switch
|
||||
id="showArchivedToggle"
|
||||
className="mx-3"
|
||||
checked={showArchived}
|
||||
onCheckedChange={toggleShowArchived}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -21,7 +21,8 @@ export const metadata: Metadata = {
|
||||
title: "Attributes",
|
||||
};
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
let attributeClasses = await getAttributeClasses(params.environmentId);
|
||||
const t = await getTranslations();
|
||||
const product = await getProductByEnvironmentId(params.environmentId);
|
||||
|
||||
@@ -9,7 +9,11 @@ import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/ser
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||
import { AuthorizationError } from "@formbricks/types/errors";
|
||||
|
||||
const ConfigLayout = async ({ children, params }) => {
|
||||
const ConfigLayout = async (props) => {
|
||||
const params = await props.params;
|
||||
|
||||
const { children } = props;
|
||||
|
||||
const t = await getTranslations();
|
||||
const [organization, session] = await Promise.all([
|
||||
getOrganizationByEnvironmentId(params.environmentId),
|
||||
|
||||
@@ -54,7 +54,7 @@ export const ResponseSection = async ({
|
||||
|
||||
const productPermission = await getProductPermissionByUserId(session.user.id, product.id);
|
||||
|
||||
const locale = findMatchingLocale();
|
||||
const locale = await findMatchingLocale();
|
||||
|
||||
return (
|
||||
<ResponseTimeline
|
||||
|
||||
@@ -19,7 +19,8 @@ import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service";
|
||||
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
|
||||
import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const [environment, environmentTags, product, session, organization, person, attributes, attributeClasses] =
|
||||
await Promise.all([
|
||||
|
||||
@@ -16,7 +16,8 @@ import { Button } from "@formbricks/ui/components/Button";
|
||||
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
|
||||
import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
|
||||
const Page = async ({ params }: { params: { environmentId: string } }) => {
|
||||
const Page = async (props: { params: Promise<{ environmentId: string }> }) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@ import { PersonSecondaryNavigation } from "@/app/(app)/environments/[environment
|
||||
import { BasicCreateSegmentModal } from "@/app/(app)/environments/[environmentId]/(people)/segments/components/BasicCreateSegmentModal";
|
||||
import { SegmentTable } from "@/app/(app)/environments/[environmentId]/(people)/segments/components/SegmentTable";
|
||||
import { CreateSegmentModal } from "@/modules/ee/advanced-targeting/components/create-segment-modal";
|
||||
import { getAdvancedTargetingPermission } from "@/modules/ee/license-check/lib/utils";
|
||||
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { getAdvancedTargetingPermission } from "@formbricks/ee/lib/service";
|
||||
import { getAttributeClasses } from "@formbricks/lib/attributeClass/service";
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
@@ -19,12 +19,13 @@ import { getSegments } from "@formbricks/lib/segment/service";
|
||||
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
|
||||
import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const [environment, segments, attributeClasses, organization, product] = await Promise.all([
|
||||
getEnvironment(params.environmentId),
|
||||
getSegments(params.environmentId),
|
||||
getAttributeClasses(params.environmentId),
|
||||
getAttributeClasses(params.environmentId, undefined, { skipArchived: true }),
|
||||
getOrganizationByEnvironmentId(params.environmentId),
|
||||
getProductByEnvironmentId(params.environmentId),
|
||||
]);
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import { authenticatedActionClient } from "@/lib/utils/action-client";
|
||||
import { checkAuthorizationUpdated } from "@/lib/utils/action-client-middleware";
|
||||
import { getIsMultiOrgEnabled } from "@/modules/ee/license-check/lib/utils";
|
||||
import { z } from "zod";
|
||||
import { getIsMultiOrgEnabled } from "@formbricks/ee/lib/service";
|
||||
import { createMembership } from "@formbricks/lib/membership/service";
|
||||
import { createOrganization } from "@formbricks/lib/organization/service";
|
||||
import { createProduct } from "@formbricks/lib/product/service";
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { createActionClassAction } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/actions";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { Code2Icon, MousePointerClickIcon, SparklesIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { convertDateTimeStringShort } from "@formbricks/lib/time";
|
||||
import { capitalizeFirstLetter } from "@formbricks/lib/utils/strings";
|
||||
import { TActionClass } from "@formbricks/types/action-classes";
|
||||
import { TActionClass, TActionClassInput, TActionClassInputCode } from "@formbricks/types/action-classes";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { ErrorComponent } from "@formbricks/ui/components/ErrorComponent";
|
||||
import { Label } from "@formbricks/ui/components/Label";
|
||||
import { LoadingSpinner } from "@formbricks/ui/components/LoadingSpinner";
|
||||
@@ -15,15 +19,25 @@ import { getActiveInactiveSurveysAction } from "../actions";
|
||||
interface ActivityTabProps {
|
||||
actionClass: TActionClass;
|
||||
environmentId: string;
|
||||
environment: TEnvironment;
|
||||
otherEnvActionClasses: TActionClass[];
|
||||
otherEnvironment: TEnvironment;
|
||||
isReadOnly: boolean;
|
||||
}
|
||||
|
||||
export const ActionActivityTab = ({ actionClass, environmentId }: ActivityTabProps) => {
|
||||
export const ActionActivityTab = ({
|
||||
actionClass,
|
||||
otherEnvActionClasses,
|
||||
otherEnvironment,
|
||||
environmentId,
|
||||
environment,
|
||||
isReadOnly,
|
||||
}: ActivityTabProps) => {
|
||||
const t = useTranslations();
|
||||
const [activeSurveys, setActiveSurveys] = useState<string[] | undefined>();
|
||||
const [inactiveSurveys, setInactiveSurveys] = useState<string[] | undefined>();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
|
||||
@@ -32,7 +46,6 @@ export const ActionActivityTab = ({ actionClass, environmentId }: ActivityTabPro
|
||||
const getActiveInactiveSurveysResponse = await getActiveInactiveSurveysAction({
|
||||
actionClassId: actionClass.id,
|
||||
});
|
||||
console.log(getActiveInactiveSurveysResponse, "randike");
|
||||
if (getActiveInactiveSurveysResponse?.data) {
|
||||
setActiveSurveys(getActiveInactiveSurveysResponse.data.activeSurveys);
|
||||
setInactiveSurveys(getActiveInactiveSurveysResponse.data.inactiveSurveys);
|
||||
@@ -46,6 +59,57 @@ export const ActionActivityTab = ({ actionClass, environmentId }: ActivityTabPro
|
||||
updateState();
|
||||
}, [actionClass.id, environmentId]);
|
||||
|
||||
const actionClassNames = useMemo(
|
||||
() => otherEnvActionClasses.map((actionClass) => actionClass.name),
|
||||
[otherEnvActionClasses]
|
||||
);
|
||||
|
||||
const actionClassKeys = useMemo(() => {
|
||||
const codeActionClasses: TActionClassInputCode[] = otherEnvActionClasses.filter(
|
||||
(actionClass) => actionClass.type === "code"
|
||||
) as TActionClassInputCode[];
|
||||
|
||||
return codeActionClasses.map((actionClass) => actionClass.key);
|
||||
}, [otherEnvActionClasses]);
|
||||
|
||||
const copyAction = async (data: TActionClassInput) => {
|
||||
const { type } = data;
|
||||
let copyName = data.name;
|
||||
try {
|
||||
if (isReadOnly) {
|
||||
throw new Error(t("common.you_are_not_authorised_to_perform_this_action"));
|
||||
}
|
||||
|
||||
if (copyName && actionClassNames.includes(copyName)) {
|
||||
while (actionClassNames.includes(copyName)) {
|
||||
copyName += " (copy)";
|
||||
}
|
||||
}
|
||||
|
||||
if (type === "code" && data.key && actionClassKeys.includes(data.key)) {
|
||||
throw new Error(t("environments.actions.action_with_key_already_exists", { key: data.key }));
|
||||
}
|
||||
|
||||
let updatedAction = {
|
||||
...data,
|
||||
name: copyName.trim(),
|
||||
environmentId: otherEnvironment.id,
|
||||
};
|
||||
|
||||
const createActionClassResponse = await createActionClassAction({
|
||||
action: updatedAction as TActionClassInput,
|
||||
});
|
||||
|
||||
if (!createActionClassResponse?.data) {
|
||||
throw new Error(t("environments.actions.action_copy_failed", {}));
|
||||
}
|
||||
|
||||
toast.success(t("environments.actions.action_copied_successfully"));
|
||||
} catch (e: any) {
|
||||
toast.error(e.message);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) return <LoadingSpinner />;
|
||||
if (error) return <ErrorComponent />;
|
||||
|
||||
@@ -99,6 +163,22 @@ export const ActionActivityTab = ({ actionClass, environmentId }: ActivityTabPro
|
||||
<p className="text-sm text-slate-700">{capitalizeFirstLetter(actionClass.type)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="">
|
||||
<Label className="text-xs font-normal text-slate-500">Environment</Label>
|
||||
<div className="items-center-center flex gap-2">
|
||||
<p className="text-xs text-slate-700">
|
||||
{environment.type === "development" ? "Development" : "Production"}
|
||||
</p>
|
||||
<Button
|
||||
onClick={() => {
|
||||
copyAction(actionClass);
|
||||
}}
|
||||
className="m-0 p-0 text-xs font-medium text-black underline underline-offset-4 focus:ring-0 focus:ring-offset-0"
|
||||
variant="minimal">
|
||||
{environment.type === "development" ? "Copy to Production" : "Copy to Development"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { type JSX, useState } from "react";
|
||||
import { TActionClass } from "@formbricks/types/action-classes";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { ActionDetailModal } from "./ActionDetailModal";
|
||||
|
||||
interface ActionClassesTableProps {
|
||||
environmentId: string;
|
||||
actionClasses: TActionClass[];
|
||||
environment: TEnvironment;
|
||||
children: [JSX.Element, JSX.Element[]];
|
||||
isReadOnly: boolean;
|
||||
otherEnvironment: TEnvironment;
|
||||
otherEnvActionClasses: TActionClass[];
|
||||
}
|
||||
|
||||
export const ActionClassesTable = ({
|
||||
environmentId,
|
||||
actionClasses,
|
||||
environment,
|
||||
children: [TableHeading, actionRows],
|
||||
isReadOnly,
|
||||
otherEnvActionClasses,
|
||||
otherEnvironment,
|
||||
}: ActionClassesTableProps) => {
|
||||
const [isActionDetailModalOpen, setActionDetailModalOpen] = useState(false);
|
||||
|
||||
@@ -48,11 +55,14 @@ export const ActionClassesTable = ({
|
||||
{activeActionClass && (
|
||||
<ActionDetailModal
|
||||
environmentId={environmentId}
|
||||
environment={environment}
|
||||
open={isActionDetailModalOpen}
|
||||
setOpen={setActionDetailModalOpen}
|
||||
actionClasses={actionClasses}
|
||||
actionClass={activeActionClass}
|
||||
isReadOnly={isReadOnly}
|
||||
otherEnvActionClasses={otherEnvActionClasses}
|
||||
otherEnvironment={otherEnvironment}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import { Code2Icon, MousePointerClickIcon, SparklesIcon } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { TActionClass } from "@formbricks/types/action-classes";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { ModalWithTabs } from "@formbricks/ui/components/ModalWithTabs";
|
||||
import { ActionActivityTab } from "./ActionActivityTab";
|
||||
import { ActionSettingsTab } from "./ActionSettingsTab";
|
||||
|
||||
interface ActionDetailModalProps {
|
||||
environmentId: string;
|
||||
environment: TEnvironment;
|
||||
open: boolean;
|
||||
setOpen: (v: boolean) => void;
|
||||
actionClass: TActionClass;
|
||||
actionClasses: TActionClass[];
|
||||
isReadOnly: boolean;
|
||||
otherEnvironment: TEnvironment;
|
||||
otherEnvActionClasses: TActionClass[];
|
||||
}
|
||||
|
||||
export const ActionDetailModal = ({
|
||||
@@ -20,13 +24,25 @@ export const ActionDetailModal = ({
|
||||
setOpen,
|
||||
actionClass,
|
||||
actionClasses,
|
||||
environment,
|
||||
isReadOnly,
|
||||
otherEnvActionClasses,
|
||||
otherEnvironment,
|
||||
}: ActionDetailModalProps) => {
|
||||
const t = useTranslations();
|
||||
const tabs = [
|
||||
{
|
||||
title: t("common.activity"),
|
||||
children: <ActionActivityTab actionClass={actionClass} environmentId={environmentId} />,
|
||||
children: (
|
||||
<ActionActivityTab
|
||||
otherEnvActionClasses={otherEnvActionClasses}
|
||||
otherEnvironment={otherEnvironment}
|
||||
isReadOnly={isReadOnly}
|
||||
environment={environment}
|
||||
actionClass={actionClass}
|
||||
environmentId={environmentId}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("common.settings"),
|
||||
|
||||
@@ -10,6 +10,7 @@ import { getTranslations } from "next-intl/server";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getActionClasses } from "@formbricks/lib/actionClass/service";
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { getEnvironments } from "@formbricks/lib/environment/service";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
@@ -22,7 +23,8 @@ export const metadata: Metadata = {
|
||||
title: "Actions",
|
||||
};
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const session = await getServerSession(authOptions);
|
||||
const t = await getTranslations();
|
||||
const [actionClasses, organization, product] = await Promise.all([
|
||||
@@ -30,7 +32,7 @@ const Page = async ({ params }) => {
|
||||
getOrganizationByEnvironmentId(params.environmentId),
|
||||
getProductByEnvironmentId(params.environmentId),
|
||||
]);
|
||||
const locale = findMatchingLocale();
|
||||
const locale = await findMatchingLocale();
|
||||
|
||||
if (!session) {
|
||||
throw new Error(t("common.session_not_found"));
|
||||
@@ -44,6 +46,17 @@ const Page = async ({ params }) => {
|
||||
throw new Error(t("common.product_not_found"));
|
||||
}
|
||||
|
||||
const environments = await getEnvironments(product.id);
|
||||
const currentEnvironment = environments.find((env) => env.id === params.environmentId);
|
||||
|
||||
if (!currentEnvironment) {
|
||||
throw new Error(t("common.environment_not_found"));
|
||||
}
|
||||
|
||||
const otherEnvironment = environments.filter((env) => env.id !== params.environmentId)[0];
|
||||
|
||||
const otherEnvActionClasses = await getActionClasses(otherEnvironment.id);
|
||||
|
||||
const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id);
|
||||
const { isMember, isBilling } = getAccessFlags(currentUserMembership?.role);
|
||||
|
||||
@@ -69,6 +82,9 @@ const Page = async ({ params }) => {
|
||||
<PageContentWrapper>
|
||||
<PageHeader pageTitle={t("common.actions")} cta={!isReadOnly ? renderAddActionButton() : undefined} />
|
||||
<ActionClassesTable
|
||||
environment={currentEnvironment}
|
||||
otherEnvironment={otherEnvironment}
|
||||
otherEnvActionClasses={otherEnvActionClasses}
|
||||
environmentId={params.environmentId}
|
||||
actionClasses={actionClasses}
|
||||
isReadOnly={isReadOnly}>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { MainNavigation } from "@/app/(app)/environments/[environmentId]/components/MainNavigation";
|
||||
import { TopControlBar } from "@/app/(app)/environments/[environmentId]/components/TopControlBar";
|
||||
import { getIsAIEnabled } from "@/app/lib/utils";
|
||||
import { getEnterpriseLicense } from "@/modules/ee/license-check/lib/utils";
|
||||
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import type { Session } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { getEnterpriseLicense } from "@formbricks/ee/lib/service";
|
||||
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import { getEnvironment, getEnvironments } from "@formbricks/lib/environment/service";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
@@ -59,15 +59,13 @@ export const EnvironmentLayout = async ({ environmentId, session, children }: En
|
||||
|
||||
const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id);
|
||||
const membershipRole = currentUserMembership?.role;
|
||||
const { isOwner, isManager } = getAccessFlags(membershipRole);
|
||||
|
||||
const isOwnerOrManager = isOwner || isManager;
|
||||
const { isMember } = getAccessFlags(membershipRole);
|
||||
|
||||
const { features, lastChecked, isPendingDowngrade, active } = await getEnterpriseLicense();
|
||||
|
||||
const productPermission = await getProductPermissionByUserId(session.user.id, environment.productId);
|
||||
|
||||
if (!isOwnerOrManager && !productPermission) {
|
||||
if (isMember && !productPermission) {
|
||||
throw new Error(t("common.product_permission_not_found"));
|
||||
}
|
||||
|
||||
|
||||
@@ -222,7 +222,7 @@ export const MainNavigation = ({
|
||||
{
|
||||
label: t("common.billing"),
|
||||
href: `/environments/${environment.id}/settings/billing`,
|
||||
hidden: !isFormbricksCloud || isPricingDisabled,
|
||||
hidden: !isFormbricksCloud,
|
||||
icon: CreditCardIcon,
|
||||
},
|
||||
{
|
||||
@@ -481,7 +481,11 @@ export const MainNavigation = ({
|
||||
{dropdownNavigation.map(
|
||||
(link) =>
|
||||
!link.hidden && (
|
||||
<Link href={link.href} target={link.target} className="flex w-full items-center">
|
||||
<Link
|
||||
href={link.href}
|
||||
target={link.target}
|
||||
className="flex w-full items-center"
|
||||
key={link.label}>
|
||||
<DropdownMenuItem>
|
||||
<link.icon className="mr-2 h-4 w-4" strokeWidth={1.5} />
|
||||
{link.label}
|
||||
|
||||
@@ -21,7 +21,8 @@ import { GoBackButton } from "@formbricks/ui/components/GoBackButton";
|
||||
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
|
||||
import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const isEnabled = !!AIRTABLE_CLIENT_ID;
|
||||
const [session, surveys, integrations, environment, attributeClasses] = await Promise.all([
|
||||
@@ -53,7 +54,7 @@ const Page = async ({ params }) => {
|
||||
airtableArray = await getAirtableTables(params.environmentId);
|
||||
}
|
||||
|
||||
const locale = findMatchingLocale();
|
||||
const locale = await findMatchingLocale();
|
||||
|
||||
const currentUserMembership = await getMembershipByUserIdOrganizationId(
|
||||
session?.user.id,
|
||||
|
||||
@@ -24,7 +24,8 @@ import { GoBackButton } from "@formbricks/ui/components/GoBackButton";
|
||||
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
|
||||
import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const isEnabled = !!(GOOGLE_SHEETS_CLIENT_ID && GOOGLE_SHEETS_CLIENT_SECRET && GOOGLE_SHEETS_REDIRECT_URL);
|
||||
const [session, surveys, integrations, environment, attributeClasses] = await Promise.all([
|
||||
@@ -51,7 +52,7 @@ const Page = async ({ params }) => {
|
||||
(integration): integration is TIntegrationGoogleSheets => integration.type === "googleSheets"
|
||||
);
|
||||
|
||||
const locale = findMatchingLocale();
|
||||
const locale = await findMatchingLocale();
|
||||
|
||||
const currentUserMembership = await getMembershipByUserIdOrganizationId(
|
||||
session?.user.id,
|
||||
|
||||
@@ -26,7 +26,8 @@ import { GoBackButton } from "@formbricks/ui/components/GoBackButton";
|
||||
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
|
||||
import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const enabled = !!(
|
||||
NOTION_OAUTH_CLIENT_ID &&
|
||||
@@ -80,7 +81,7 @@ const Page = async ({ params }) => {
|
||||
return (
|
||||
<PageContentWrapper>
|
||||
<GoBackButton url={`${WEBAPP_URL}/environments/${params.environmentId}/integrations`} />
|
||||
<PageHeader pageTitle={"environments.integrations.notion.notion_integration"} />
|
||||
<PageHeader pageTitle={t("environments.integrations.notion.notion_integration")} />
|
||||
<NotionWrapper
|
||||
enabled={enabled}
|
||||
surveys={surveys}
|
||||
|
||||
@@ -25,7 +25,8 @@ import { Card } from "@formbricks/ui/components/IntegrationCard";
|
||||
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
|
||||
import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const environmentId = params.environmentId;
|
||||
const t = await getTranslations();
|
||||
const [
|
||||
|
||||
@@ -91,7 +91,7 @@ export const ManageIntegration = ({
|
||||
<span className="mr-4 h-4 w-4 rounded-full bg-green-600"></span>
|
||||
<span className="text-slate-500">
|
||||
{t("environments.integrations.slack.connected_with_team", {
|
||||
team: slackIntegration.config.key.team.name,
|
||||
team: slackIntegration.config.key.team?.name,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -46,7 +46,8 @@ export const SlackWrapper = ({
|
||||
|
||||
if (
|
||||
getSlackChannelsResponse?.serverError &&
|
||||
getSlackChannelsResponse.serverError.includes("missing_scope")
|
||||
(getSlackChannelsResponse.serverError.includes("missing_scope") ||
|
||||
getSlackChannelsResponse.serverError.includes("invalid_auth"))
|
||||
) {
|
||||
setShowReconnectButton(true);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ import { GoBackButton } from "@formbricks/ui/components/GoBackButton";
|
||||
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
|
||||
import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const isEnabled = !!(SLACK_CLIENT_ID && SLACK_CLIENT_SECRET);
|
||||
const t = await getTranslations();
|
||||
const [session, surveys, slackIntegration, environment, attributeClasses] = await Promise.all([
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { WebhookModal } from "@/app/(app)/environments/[environmentId]/integrations/webhooks/components/WebhookDetailModal";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
import { type JSX, useState } from "react";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { TWebhook } from "@formbricks/types/webhooks";
|
||||
|
||||
@@ -18,7 +18,8 @@ import { GoBackButton } from "@formbricks/ui/components/GoBackButton";
|
||||
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
|
||||
import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const [session, organization, webhooksUnsorted, surveys, environment] = await Promise.all([
|
||||
getServerSession(authOptions),
|
||||
|
||||
@@ -15,7 +15,11 @@ import { FormbricksClient } from "../../components/FormbricksClient";
|
||||
import EnvironmentStorageHandler from "./components/EnvironmentStorageHandler";
|
||||
import { PosthogIdentify } from "./components/PosthogIdentify";
|
||||
|
||||
export const EnvLayout = async ({ children, params }) => {
|
||||
export const EnvLayout = async (props) => {
|
||||
const params = await props.params;
|
||||
|
||||
const { children } = props;
|
||||
|
||||
const t = await getTranslations();
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session || !session.user) {
|
||||
|
||||
@@ -7,7 +7,8 @@ import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const session = await getServerSession(authOptions);
|
||||
const t = await getTranslations();
|
||||
const organization = await getOrganizationByEnvironmentId(params.environmentId);
|
||||
|
||||
@@ -2,8 +2,11 @@ import { WidgetStatusIndicator } from "@/app/(app)/environments/[environmentId]/
|
||||
import { EnvironmentIdField } from "@/app/(app)/environments/[environmentId]/product/(setup)/components/EnvironmentIdField";
|
||||
import { SetupInstructions } from "@/app/(app)/environments/[environmentId]/product/(setup)/components/SetupInstructions";
|
||||
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
|
||||
import {
|
||||
getMultiLanguagePermission,
|
||||
getRoleManagementPermission,
|
||||
} from "@/modules/ee/license-check/lib/utils";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { getMultiLanguagePermission, getRoleManagementPermission } from "@formbricks/ee/lib/service";
|
||||
import { WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
@@ -12,7 +15,8 @@ import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper
|
||||
import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
import { SettingsCard } from "../../../settings/components/SettingsCard";
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const [environment, organization] = await Promise.all([
|
||||
getEnvironment(params.environmentId),
|
||||
|
||||
@@ -19,7 +19,7 @@ const LoadingCard = () => {
|
||||
<div className="grid h-12 grid-cols-10 content-center rounded-t-lg bg-slate-100 px-6 text-left text-sm font-semibold text-slate-900">
|
||||
<div className="col-span-4 sm:col-span-2">{t("common.label")}</div>
|
||||
<div className="col-span-4 hidden sm:col-span-5 sm:block">
|
||||
{t("environments.product.api_keys.api_key")}
|
||||
{t("environments.product.api-keys.api_key")}
|
||||
</div>
|
||||
<div className="col-span-4 sm:col-span-2">{t("common.created_at")}</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
|
||||
import {
|
||||
getMultiLanguagePermission,
|
||||
getRoleManagementPermission,
|
||||
} from "@/modules/ee/license-check/lib/utils";
|
||||
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { getMultiLanguagePermission, getRoleManagementPermission } from "@formbricks/ee/lib/service";
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
@@ -17,7 +20,8 @@ import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
import { SettingsCard } from "../../settings/components/SettingsCard";
|
||||
import { ApiKeyList } from "./components/ApiKeyList";
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const [session, environment, organization, product] = await Promise.all([
|
||||
getServerSession(authOptions),
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
|
||||
import {
|
||||
getMultiLanguagePermission,
|
||||
getRoleManagementPermission,
|
||||
} from "@/modules/ee/license-check/lib/utils";
|
||||
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import packageJson from "@/package.json";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { getMultiLanguagePermission, getRoleManagementPermission } from "@formbricks/ee/lib/service";
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
@@ -19,7 +22,8 @@ import { DeleteProduct } from "./components/DeleteProduct";
|
||||
import { EditProductNameForm } from "./components/EditProductNameForm";
|
||||
import { EditWaitingTimeForm } from "./components/EditWaitingTimeForm";
|
||||
|
||||
const Page = async ({ params }: { params: { environmentId: string } }) => {
|
||||
const Page = async (props: { params: Promise<{ environmentId: string }> }) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const [product, session, organization] = await Promise.all([
|
||||
getProductByEnvironmentId(params.environmentId),
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
|
||||
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
|
||||
import {
|
||||
getMultiLanguagePermission,
|
||||
getRoleManagementPermission,
|
||||
} from "@/modules/ee/license-check/lib/utils";
|
||||
import { EditLanguage } from "@/modules/ee/multi-language-surveys/components/edit-language";
|
||||
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getMultiLanguagePermission, getRoleManagementPermission } from "@formbricks/ee/lib/service";
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
@@ -16,7 +19,8 @@ import { getUser } from "@formbricks/lib/user/service";
|
||||
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
|
||||
import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
|
||||
const Page = async ({ params }: { params: { environmentId: string } }) => {
|
||||
const Page = async (props: { params: Promise<{ environmentId: string }> }) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const product = await getProductByEnvironmentId(params.environmentId);
|
||||
|
||||
|
||||
@@ -12,7 +12,11 @@ export const metadata: Metadata = {
|
||||
title: "Config",
|
||||
};
|
||||
|
||||
const ConfigLayout = async ({ children, params }) => {
|
||||
const ConfigLayout = async (props) => {
|
||||
const params = await props.params;
|
||||
|
||||
const { children } = props;
|
||||
|
||||
const t = await getTranslations();
|
||||
|
||||
const [organization, session] = await Promise.all([
|
||||
|
||||
@@ -72,8 +72,8 @@ export const ThemeStyling = ({
|
||||
isDarkModeEnabled: product.styling.isDarkModeEnabled ?? false,
|
||||
roundness: product.styling.roundness ?? 8,
|
||||
cardArrangement: product.styling.cardArrangement ?? {
|
||||
linkSurveys: "simple",
|
||||
appSurveys: "simple",
|
||||
linkSurveys: "straight",
|
||||
appSurveys: "straight",
|
||||
},
|
||||
background: product.styling.background,
|
||||
hideProgressBar: product.styling.hideProgressBar ?? false,
|
||||
@@ -119,8 +119,8 @@ export const ThemeStyling = ({
|
||||
},
|
||||
roundness: 8,
|
||||
cardArrangement: {
|
||||
linkSurveys: "simple",
|
||||
appSurveys: "simple",
|
||||
linkSurveys: "straight",
|
||||
appSurveys: "straight",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -28,9 +28,9 @@ const Loading = () => {
|
||||
<ProductConfigNavigation activeId="look" loading />
|
||||
</PageHeader>
|
||||
<SettingsCard
|
||||
title="environments.product.look.theme"
|
||||
title={t("environments.product.look.theme")}
|
||||
className="max-w-7xl"
|
||||
description="environments.product.look.theme_settings_description">
|
||||
description={t("environments.product.look.theme_settings_description")}>
|
||||
<div className="flex animate-pulse">
|
||||
<div className="w-1/2">
|
||||
<div className="flex flex-col gap-4 pr-6">
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
|
||||
import { EditLogo } from "@/app/(app)/environments/[environmentId]/product/look/components/EditLogo";
|
||||
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import {
|
||||
getMultiLanguagePermission,
|
||||
getRemoveInAppBrandingPermission,
|
||||
getRemoveLinkBrandingPermission,
|
||||
getRoleManagementPermission,
|
||||
} from "@formbricks/ee/lib/service";
|
||||
} from "@/modules/ee/license-check/lib/utils";
|
||||
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { DEFAULT_LOCALE, SURVEY_BG_COLORS, UNSPLASH_ACCESS_KEY } from "@formbricks/lib/constants";
|
||||
@@ -26,7 +26,8 @@ import { EditFormbricksBranding } from "./components/EditBranding";
|
||||
import { EditPlacementForm } from "./components/EditPlacementForm";
|
||||
import { ThemeStyling } from "./components/ThemeStyling";
|
||||
|
||||
const Page = async ({ params }: { params: { environmentId: string } }) => {
|
||||
const Page = async (props: { params: Promise<{ environmentId: string }> }) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const [session, organization, product] = await Promise.all([
|
||||
getServerSession(authOptions),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
const Page = ({ params }) => {
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
return redirect(`/environments/${params.environmentId}/product/general`);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
|
||||
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
|
||||
import {
|
||||
getMultiLanguagePermission,
|
||||
getRoleManagementPermission,
|
||||
} from "@/modules/ee/license-check/lib/utils";
|
||||
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { getMultiLanguagePermission, getRoleManagementPermission } from "@formbricks/ee/lib/service";
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
@@ -17,7 +20,8 @@ import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper
|
||||
import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
import { EditTagsWrapper } from "./components/EditTagsWrapper";
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const environment = await getEnvironment(params.environmentId);
|
||||
if (!environment) {
|
||||
|
||||
@@ -4,7 +4,11 @@ import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||
|
||||
const AccountSettingsLayout = async ({ children, params }) => {
|
||||
const AccountSettingsLayout = async (props) => {
|
||||
const params = await props.params;
|
||||
|
||||
const { children } = props;
|
||||
|
||||
const t = await getTranslations();
|
||||
const [organization, product, session] = await Promise.all([
|
||||
getOrganizationByEnvironmentId(params.environmentId),
|
||||
|
||||
@@ -141,7 +141,9 @@ const getMemberships = async (userId: string): Promise<Membership[]> => {
|
||||
return memberships;
|
||||
};
|
||||
|
||||
const Page = async ({ params, searchParams }) => {
|
||||
const Page = async (props) => {
|
||||
const searchParams = await props.searchParams;
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session) {
|
||||
|
||||
@@ -15,7 +15,8 @@ import { DeleteAccount } from "./components/DeleteAccount";
|
||||
import { EditProfileAvatarForm } from "./components/EditProfileAvatarForm";
|
||||
import { EditProfileDetailsForm } from "./components/EditProfileDetailsForm";
|
||||
|
||||
const Page = async ({ params }: { params: { environmentId: string } }) => {
|
||||
const Page = async (props: { params: Promise<{ environmentId: string }> }) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
const { environmentId } = params;
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
@@ -40,7 +40,7 @@ export const OrganizationSettingsNavbar = ({
|
||||
id: "billing",
|
||||
label: t("common.billing"),
|
||||
href: `/environments/${environmentId}/settings/billing`,
|
||||
hidden: !isFormbricksCloud || isPricingDisabled || loading,
|
||||
hidden: !isFormbricksCloud || loading,
|
||||
current: pathname?.includes("/billing"),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar";
|
||||
import { getEnterpriseLicense, getRoleManagementPermission } from "@/modules/ee/license-check/lib/utils";
|
||||
import { CheckIcon } from "lucide-react";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getEnterpriseLicense, getRoleManagementPermission } from "@formbricks/ee/lib/service";
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
|
||||
@@ -13,7 +13,8 @@ import { Button } from "@formbricks/ui/components/Button";
|
||||
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
|
||||
import { PageHeader } from "@formbricks/ui/components/PageHeader";
|
||||
|
||||
const Page = async ({ params }) => {
|
||||
const Page = async (props) => {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
if (IS_FORMBRICKS_CLOUD) {
|
||||
notFound();
|
||||
|
||||
@@ -7,10 +7,10 @@ import {
|
||||
import { authenticatedActionClient } from "@/lib/utils/action-client";
|
||||
import { checkAuthorizationUpdated } from "@/lib/utils/action-client-middleware";
|
||||
import { getOrganizationIdFromInviteId } from "@/lib/utils/helper";
|
||||
import { getIsMultiOrgEnabled } from "@/modules/ee/license-check/lib/utils";
|
||||
import { sendInviteMemberEmail } from "@/modules/email";
|
||||
import { OrganizationRole } from "@prisma/client";
|
||||
import { z } from "zod";
|
||||
import { getIsMultiOrgEnabled } from "@formbricks/ee/lib/service";
|
||||
import { INVITE_DISABLED, IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import { deleteInvite, getInvite, inviteUser, resendInvite } from "@formbricks/lib/invite/service";
|
||||
import { createInviteToken } from "@formbricks/lib/jwt";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MembersInfo } from "@/app/(app)/environments/[environmentId]/settings/(organization)/general/components/EditMemberships/MembersInfo";
|
||||
import { getMembersByOrganizationId } from "@/app/(app)/environments/[environmentId]/settings/(organization)/general/lib/membership";
|
||||
import { getRoleManagementPermission } from "@/modules/ee/license-check/lib/utils";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { getRoleManagementPermission } from "@formbricks/ee/lib/service";
|
||||
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import { getInvitesByOrganizationId } from "@formbricks/lib/invite/service";
|
||||
import { TMembership } from "@formbricks/types/memberships";
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { AddMemberRole } from "@/modules/ee/role-management/components/add-member-role";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { OrganizationRole } from "@prisma/client";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { TOrganizationRole } from "@formbricks/types/memberships";
|
||||
import { z } from "zod";
|
||||
import { TOrganizationRole, ZOrganizationRole } from "@formbricks/types/memberships";
|
||||
import { ZUserName } from "@formbricks/types/user";
|
||||
import { Alert, AlertDescription } from "@formbricks/ui/components/Alert";
|
||||
import { Button } from "@formbricks/ui/components/Button";
|
||||
import { Input } from "@formbricks/ui/components/Input";
|
||||
@@ -18,6 +21,7 @@ interface IndividualInviteTabProps {
|
||||
isFormbricksCloud: boolean;
|
||||
environmentId: string;
|
||||
}
|
||||
|
||||
export const IndividualInviteTab = ({
|
||||
setOpen,
|
||||
onSubmit,
|
||||
@@ -25,6 +29,13 @@ export const IndividualInviteTab = ({
|
||||
isFormbricksCloud,
|
||||
environmentId,
|
||||
}: IndividualInviteTabProps) => {
|
||||
const ZFormSchema = z.object({
|
||||
name: ZUserName,
|
||||
email: z.string().email("Invalid email address"),
|
||||
role: ZOrganizationRole,
|
||||
});
|
||||
|
||||
type TFormData = z.infer<typeof ZFormSchema>;
|
||||
const t = useTranslations();
|
||||
const {
|
||||
register,
|
||||
@@ -33,17 +44,18 @@ export const IndividualInviteTab = ({
|
||||
reset,
|
||||
control,
|
||||
watch,
|
||||
formState: { isSubmitting },
|
||||
} = useForm<{
|
||||
name: string;
|
||||
email: string;
|
||||
role: TOrganizationRole;
|
||||
}>();
|
||||
formState: { isSubmitting, errors },
|
||||
} = useForm<TFormData>({
|
||||
resolver: zodResolver(ZFormSchema),
|
||||
defaultValues: {
|
||||
role: "owner",
|
||||
},
|
||||
});
|
||||
|
||||
const submitEventClass = async () => {
|
||||
const data = getValues();
|
||||
data.role = data.role || OrganizationRole.owner;
|
||||
await onSubmit([data]);
|
||||
onSubmit([data]);
|
||||
setOpen(false);
|
||||
reset();
|
||||
};
|
||||
@@ -55,9 +67,10 @@ export const IndividualInviteTab = ({
|
||||
<Label htmlFor="memberNameInput">{t("common.full_name")}</Label>
|
||||
<Input
|
||||
id="memberNameInput"
|
||||
placeholder="e.g. Hans Wurst"
|
||||
placeholder="Hans Wurst"
|
||||
{...register("name", { required: true, validate: (value) => value.trim() !== "" })}
|
||||
/>
|
||||
{errors.name && <p className="mt-1 text-sm text-red-500">{errors.name.message}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="memberEmailInput">{t("common.email")}</Label>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user