Merge branch 'formbricks:main' into gitpod-doc

This commit is contained in:
Anjy Gupta
2023-10-04 23:10:20 +05:30
committed by GitHub
103 changed files with 542 additions and 154 deletions

View File

@@ -49,7 +49,7 @@ Server actions are used to perform server actions in client components. For exam
## Use service abstraction instead of direct database calls
We utilize [prisma](https://www.prisma.io/) as our Object-Relational Mapping (ORM) tool to interact with the database. This implies that when you need to fetch or modify data in the database, you will be utilizing prisma. All prisma calls should be written in the services folder `packages/lib/services`, and before creating a new service, please ensure that one does not already exist.
We utilize [prisma](https://www.prisma.io/) as our Object-Relational Mapping (ORM) tool to interact with the database. This implies that when you need to fetch or modify data in the database, you will be utilizing prisma. All prisma calls should be written in the services folder `packages/lib`, and before creating a new service, please ensure that one does not already exist.
## Handle authentication and CORS in management APIs

View File

@@ -157,8 +157,8 @@ To remove the integration with Google Account,
For the above, we ask for:
1. **User Email**: To identify you (that's it, nothing else, we're opensource, see this in our codebase [here](https://github.com/formbricks/formbricks/blob/main/apps/web/app/api/google-sheet/callback/route.ts#L47C17-L47C25))
1. **Google Drive API**: To list all your google sheets (that's it, nothing else, we're opensource, see this method in our codebase [here](https://github.com/formbricks/formbricks/blob/main/packages/lib/services/googleSheet.ts#L13))
1. **Google Spreadsheet API**: To write to the spreadsheet you select (that's it, nothing else, we're opensource, see this method in our codebase [here](https://github.com/formbricks/formbricks/blob/main/packages/lib/services/googleSheet.ts#L70))
1. **Google Drive API**: To list all your google sheets (that's it, nothing else, we're opensource, see this method in our codebase [here](https://github.com/formbricks/formbricks/blob/main/packages/lib/googleSheet/service.ts#L13))
1. **Google Spreadsheet API**: To write to the spreadsheet you select (that's it, nothing else, we're opensource, see this method in our codebase [here](https://github.com/formbricks/formbricks/blob/main/packages/lib/googleSheet/service.ts#L70))
<Note>
We do not store any other information of yours! We value Privacy more than you and rest assured you're safe

View File

@@ -2,9 +2,9 @@ export const revalidate = REVALIDATION_INTERVAL;
import Navigation from "@/app/(app)/environments/[environmentId]/Navigation";
import { IS_FORMBRICKS_CLOUD, REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { getEnvironment, getEnvironments } from "@formbricks/lib/services/environment";
import { getProducts } from "@formbricks/lib/services/product";
import { getTeamByEnvironmentId, getTeamsByUserId } from "@formbricks/lib/services/team";
import { getEnvironment, getEnvironments } from "@formbricks/lib/environment/service";
import { getProducts } from "@formbricks/lib/product/service";
import { getTeamByEnvironmentId, getTeamsByUserId } from "@formbricks/lib/team/service";
import { ErrorComponent } from "@formbricks/ui";
import type { Session } from "next-auth";

View File

@@ -3,9 +3,9 @@
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { prisma } from "@formbricks/database";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { createMembership } from "@formbricks/lib/services/membership";
import { createProduct } from "@formbricks/lib/services/product";
import { createTeam, getTeamByEnvironmentId } from "@formbricks/lib/services/team";
import { createMembership } from "@formbricks/lib/membership/service";
import { createProduct } from "@formbricks/lib/product/service";
import { createTeam, getTeamByEnvironmentId } from "@formbricks/lib/team/service";
import { canUserAccessSurvey } from "@formbricks/lib/survey/auth";
import { deleteSurvey, getSurvey } from "@formbricks/lib/survey/service";
import { AuthorizationError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
@@ -79,6 +79,9 @@ export async function duplicateSurveyAction(environmentId: string, surveyId: str
singleUse: existingSurvey.singleUse
? JSON.parse(JSON.stringify(existingSurvey.singleUse))
: prismaClient.JsonNull,
productOverwrites: existingSurvey.productOverwrites
? JSON.parse(JSON.stringify(existingSurvey.productOverwrites))
: prismaClient.JsonNull,
verifyEmail: existingSurvey.verifyEmail
? JSON.parse(JSON.stringify(existingSurvey.verifyEmail))
: prismaClient.JsonNull,
@@ -228,6 +231,7 @@ export async function copyToOtherEnvironmentAction(
},
surveyClosedMessage: existingSurvey.surveyClosedMessage ?? prismaClient.JsonNull,
singleUse: existingSurvey.singleUse ?? prismaClient.JsonNull,
productOverwrites: existingSurvey.productOverwrites ?? prismaClient.JsonNull,
verifyEmail: existingSurvey.verifyEmail ?? prismaClient.JsonNull,
},
});

View File

@@ -1,7 +1,7 @@
"use server";
import { getSpreadSheets } from "@formbricks/lib/services/googleSheet";
import { createOrUpdateIntegration, deleteIntegration } from "@formbricks/lib/services/integrations";
import { getSpreadSheets } from "@formbricks/lib/googleSheet/service";
import { createOrUpdateIntegration, deleteIntegration } from "@formbricks/lib/integration/service";
import { TGoogleSheetIntegration } from "@formbricks/types/v1/integrations";
export async function upsertIntegrationAction(

View File

@@ -1,7 +1,7 @@
import GoogleSheetWrapper from "@/app/(app)/environments/[environmentId]/integrations/google-sheets/GoogleSheetWrapper";
import GoBackButton from "@/components/shared/GoBackButton";
import { getSpreadSheets } from "@formbricks/lib/services/googleSheet";
import { getIntegrations } from "@formbricks/lib/services/integrations";
import { getSpreadSheets } from "@formbricks/lib/googleSheet/service";
import { getIntegrations } from "@formbricks/lib/integration/service";
import { getSurveys } from "@formbricks/lib/survey/service";
import { TGoogleSheetIntegration, TGoogleSpreadsheet } from "@formbricks/types/v1/integrations";
import {
@@ -10,7 +10,7 @@ import {
GOOGLE_SHEETS_CLIENT_SECRET,
GOOGLE_SHEETS_REDIRECT_URL,
} from "@formbricks/lib/constants";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getEnvironment } from "@formbricks/lib/environment/service";
export default async function GoogleSheet({ params }) {
const enabled = !!(GOOGLE_SHEETS_CLIENT_ID && GOOGLE_SHEETS_CLIENT_SECRET && GOOGLE_SHEETS_REDIRECT_URL);

View File

@@ -6,9 +6,9 @@ import n8nLogo from "@/images/n8n.png";
import MakeLogo from "@/images/make-small.png";
import { Card } from "@formbricks/ui";
import Image from "next/image";
import { getCountOfWebhooksBasedOnSource } from "@formbricks/lib/services/webhook";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getIntegrations } from "@formbricks/lib/services/integrations";
import { getCountOfWebhooksBasedOnSource } from "@formbricks/lib/webhook/service";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getIntegrations } from "@formbricks/lib/integration/service";
export default async function IntegrationsPage({ params }) {
const environmentId = params.environmentId;

View File

@@ -1,6 +1,6 @@
"use server";
import { createWebhook, deleteWebhook, updateWebhook } from "@formbricks/lib/services/webhook";
import { createWebhook, deleteWebhook, updateWebhook } from "@formbricks/lib/webhook/service";
import { TWebhook, TWebhookInput } from "@formbricks/types/v1/webhooks";
export const createWebhookAction = async (

View File

@@ -5,9 +5,9 @@ import WebhookTable from "@/app/(app)/environments/[environmentId]/integrations/
import WebhookTableHeading from "@/app/(app)/environments/[environmentId]/integrations/webhooks/WebhookTableHeading";
import GoBackButton from "@/components/shared/GoBackButton";
import { getSurveys } from "@formbricks/lib/survey/service";
import { getWebhooks } from "@formbricks/lib/services/webhook";
import { getWebhooks } from "@formbricks/lib/webhook/service";
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getEnvironment } from "@formbricks/lib/environment/service";
export default async function CustomWebhookPage({ params }) {
const [webhooksUnsorted, surveys, environment] = await Promise.all([

View File

@@ -1,6 +1,6 @@
import ActivityTimeline from "@/app/(app)/environments/[environmentId]/people/[personId]/(activitySection)/ActivityTimeline";
import { getActivityTimeline } from "@formbricks/lib/services/activity";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getActivityTimeline } from "@formbricks/lib/activity/service";
import { getEnvironment } from "@formbricks/lib/environment/service";
export default async function ActivitySection({
environmentId,

View File

@@ -3,9 +3,9 @@ export const revalidate = REVALIDATION_INTERVAL;
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { capitalizeFirstLetter } from "@/lib/utils";
import { getPerson } from "@formbricks/lib/services/person";
import { getPerson } from "@formbricks/lib/person/service";
import { getResponsesByPersonId } from "@formbricks/lib/response/service";
import { getSessionCount } from "@formbricks/lib/services/session";
import { getSessionCount } from "@formbricks/lib/session/service";
export default async function AttributesSection({ personId }: { personId: string }) {
const person = await getPerson(personId);

View File

@@ -1,6 +1,6 @@
import GoBackButton from "@/components/shared/GoBackButton";
import { DeletePersonButton } from "./DeletePersonButton";
import { getPerson } from "@formbricks/lib/services/person";
import { getPerson } from "@formbricks/lib/person/service";
interface HeadingSectionProps {
environmentId: string;

View File

@@ -1,6 +1,6 @@
"use server";
import { deletePerson } from "@formbricks/lib/services/person";
import { deletePerson } from "@formbricks/lib/person/service";
export const deletePersonAction = async (personId: string) => {
await deletePerson(personId);

View File

@@ -5,7 +5,7 @@ import AttributesSection from "@/app/(app)/environments/[environmentId]/people/[
import ResponseSection from "@/app/(app)/environments/[environmentId]/people/[personId]/(responseSection)/ResponseSection";
import HeadingSection from "@/app/(app)/environments/[environmentId]/people/[personId]/HeadingSection";
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getEnvironment } from "@formbricks/lib/environment/service";
export default async function PersonPage({ params }) {
const environment = await getEnvironment(params.environmentId);

View File

@@ -3,8 +3,8 @@ export const revalidate = REVALIDATION_INTERVAL;
import EmptySpaceFiller from "@/components/shared/EmptySpaceFiller";
import { truncateMiddle } from "@/lib/utils";
import { PEOPLE_PER_PAGE, REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getPeople, getPeopleCount } from "@formbricks/lib/services/person";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getPeople, getPeopleCount } from "@formbricks/lib/person/service";
import { TPerson } from "@formbricks/types/v1/people";
import { Pagination, PersonAvatar } from "@formbricks/ui";
import Link from "next/link";

View File

@@ -1,7 +1,7 @@
import EditApiKeys from "./EditApiKeys";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { getApiKeys } from "@formbricks/lib/apiKey/service";
import { getEnvironments } from "@formbricks/lib/services/environment";
import { getEnvironments } from "@formbricks/lib/environment/service";
export default async function ApiKeyList({
environmentId,

View File

@@ -5,7 +5,7 @@ import SettingsCard from "../SettingsCard";
import SettingsTitle from "../SettingsTitle";
import ApiKeyList from "./ApiKeyList";
import EnvironmentNotice from "@/components/shared/EnvironmentNotice";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getEnvironment } from "@formbricks/lib/environment/service";
export default async function ProfileSettingsPage({ params }) {
const environment = await getEnvironment(params.environmentId);

View File

@@ -4,7 +4,7 @@ import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { getTeamByEnvironmentId } from "@formbricks/lib/services/team";
import { getTeamByEnvironmentId } from "@formbricks/lib/team/service";
import { getServerSession } from "next-auth";
import { notFound } from "next/navigation";
import SettingsTitle from "../SettingsTitle";

View File

@@ -1,8 +1,8 @@
import { Metadata } from "next";
import SettingsNavbar from "./SettingsNavbar";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { getTeamByEnvironmentId } from "@formbricks/lib/services/team";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getTeamByEnvironmentId } from "@formbricks/lib/team/service";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
export const metadata: Metadata = {
title: "Settings",

View File

@@ -2,11 +2,11 @@
import { cn } from "@formbricks/lib/cn";
import { Button, Label, RadioGroup, RadioGroupItem } from "@formbricks/ui";
import { useState } from "react";
import toast from "react-hot-toast";
import { getPlacementStyle } from "@/lib/preview";
import { PlacementType } from "@formbricks/types/js";
import { TProduct, TProductUpdateInput } from "@formbricks/types/v1/product";
import { useState } from "react";
import toast from "react-hot-toast";
import { updateProductAction } from "./actions";
const placements = [
@@ -35,7 +35,9 @@ export function EditPlacement({ product }: EditPlacementProps) {
darkOverlay: overlay === "darkOverlay",
clickOutsideClose: clickOutside === "allow",
};
await updateProductAction(product.id, inputProduct);
toast.success("Placement updated successfully.");
} catch (error) {
toast.error(`Error: ${error.message}`);

View File

@@ -1,6 +1,6 @@
"use server";
import { updateProduct } from "@formbricks/lib/services/product";
import { updateProduct } from "@formbricks/lib/product/service";
import { TProductUpdateInput } from "@formbricks/types/v1/product";
export async function updateProductAction(productId: string, inputProduct: Partial<TProductUpdateInput>) {

View File

@@ -1,6 +1,6 @@
export const revalidate = REVALIDATION_INTERVAL;
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import SettingsCard from "../SettingsCard";
import SettingsTitle from "../SettingsTitle";
@@ -15,6 +15,7 @@ export default async function ProfileSettingsPage({ params }: { params: { enviro
if (!product) {
throw new Error("Product not found");
}
return (
<div>
<SettingsTitle title="Look & Feel" />

View File

@@ -1,8 +1,8 @@
import { TTeam } from "@formbricks/types/v1/teams";
import React from "react";
import MembersInfo from "@/app/(app)/environments/[environmentId]/settings/members/EditMemberships/MembersInfo";
import { getMembersByTeamId } from "@formbricks/lib/services/membership";
import { getInvitesByTeamId } from "@formbricks/lib/services/invite";
import { getMembersByTeamId } from "@formbricks/lib/membership/service";
import { getInvitesByTeamId } from "@formbricks/lib/invite/service";
import { TMembership } from "@formbricks/types/v1/memberships";
type EditMembershipsProps = {

View File

@@ -9,15 +9,15 @@ import {
inviteUser,
resendInvite,
updateInvite,
} from "@formbricks/lib/services/invite";
} from "@formbricks/lib/invite/service";
import {
deleteMembership,
getMembershipsByUserId,
getMembershipByUserIdTeamId,
transferOwnership,
updateMembership,
} from "@formbricks/lib/services/membership";
import { deleteTeam, updateTeam } from "@formbricks/lib/services/team";
} from "@formbricks/lib/membership/service";
import { deleteTeam, updateTeam } from "@formbricks/lib/team/service";
import { TInviteUpdateInput } from "@formbricks/types/v1/invites";
import { TMembershipRole, TMembershipUpdateInput } from "@formbricks/types/v1/memberships";
import { getServerSession } from "next-auth";

View File

@@ -1,7 +1,7 @@
import TeamActions from "@/app/(app)/environments/[environmentId]/settings/members/EditMemberships/TeamActions";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { getMembershipsByUserId, getMembershipByUserIdTeamId } from "@formbricks/lib/services/membership";
import { getTeamByEnvironmentId } from "@formbricks/lib/services/team";
import { getMembershipsByUserId, getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service";
import { getTeamByEnvironmentId } from "@formbricks/lib/team/service";
import { Skeleton } from "@formbricks/ui";
import { getServerSession } from "next-auth";
import { Suspense } from "react";

View File

@@ -1,10 +1,10 @@
import { getProducts } from "@formbricks/lib/services/product";
import { getTeamByEnvironmentId } from "@formbricks/lib/services/team";
import { getProducts } from "@formbricks/lib/product/service";
import { getTeamByEnvironmentId } from "@formbricks/lib/team/service";
import { TProduct } from "@formbricks/types/v1/product";
import DeleteProductRender from "@/app/(app)/environments/[environmentId]/settings/product/DeleteProductRender";
import { getServerSession } from "next-auth";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { getMembershipByUserIdTeamId } from "@formbricks/lib/services/membership";
import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service";
type DeleteProductProps = {
environmentId: string;

View File

@@ -1,13 +1,13 @@
"use server";
import { deleteProduct, getProducts, updateProduct } from "@formbricks/lib/services/product";
import { deleteProduct, getProducts, updateProduct } from "@formbricks/lib/product/service";
import { TProduct, TProductUpdateInput } from "@formbricks/types/v1/product";
import { getServerSession } from "next-auth";
import { AuthenticationError, AuthorizationError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { TEnvironment } from "@formbricks/types/v1/environment";
import { getTeamByEnvironmentId } from "@formbricks/lib/services/team";
import { getMembershipByUserIdTeamId } from "@formbricks/lib/services/membership";
import { getTeamByEnvironmentId } from "@formbricks/lib/team/service";
import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
export const updateProductAction = async (

View File

@@ -1,4 +1,4 @@
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import SettingsCard from "../SettingsCard";
import SettingsTitle from "../SettingsTitle";
@@ -6,7 +6,7 @@ import SettingsTitle from "../SettingsTitle";
import EditProductName from "./EditProductName";
import EditWaitingTime from "./EditWaitingTime";
import DeleteProduct from "./DeleteProduct";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getEnvironment } from "@formbricks/lib/environment/service";
export default async function ProfileSettingsPage({ params }: { params: { environmentId: string } }) {
const [, product] = await Promise.all([

View File

@@ -1,7 +1,7 @@
"use server";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { updateProfile, deleteProfile } from "@formbricks/lib/services/profile";
import { updateProfile, deleteProfile } from "@formbricks/lib/profile/service";
import { TProfileUpdateInput } from "@formbricks/types/v1/profile";
import { getServerSession } from "next-auth";
import { AuthorizationError } from "@formbricks/types/v1/errors";

View File

@@ -8,7 +8,7 @@ import SettingsTitle from "../SettingsTitle";
import { DeleteAccount } from "./DeleteAccount";
import { EditName } from "./EditName";
import { EditAvatar } from "./EditAvatar";
import { getProfile } from "@formbricks/lib/services/profile";
import { getProfile } from "@formbricks/lib/profile/service";
export default async function ProfileSettingsPage() {
const session = await getServerSession(authOptions);

View File

@@ -1,6 +1,6 @@
"use server";
import { updateEnvironment } from "@formbricks/lib/services/environment";
import { updateEnvironment } from "@formbricks/lib/environment/service";
import { TEnvironment, TEnvironmentUpdateInput } from "@formbricks/types/v1/environment";
export async function updateEnvironmentAction(

View File

@@ -5,7 +5,7 @@ import EnvironmentNotice from "@/components/shared/EnvironmentNotice";
import WidgetStatusIndicator from "@/components/shared/WidgetStatusIndicator";
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { getActionsByEnvironmentId } from "@formbricks/lib/action/service";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { ErrorComponent } from "@formbricks/ui";
import SettingsCard from "../SettingsCard";
import SettingsTitle from "../SettingsTitle";

View File

@@ -1,8 +1,8 @@
import EditTagsWrapper from "./EditTagsWrapper";
import SettingsTitle from "../SettingsTitle";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service";
import { getTagsOnResponsesCount } from "@formbricks/lib/services/tagOnResponse";
import { getTagsOnResponsesCount } from "@formbricks/lib/tagOnResponse/service";
export default async function MembersSettingsPage({ params }) {
const environment = await getEnvironment(params.environmentId);

View File

@@ -11,20 +11,23 @@ import { ArrowPathRoundedSquareIcon } from "@heroicons/react/24/outline";
import { ComputerDesktopIcon, DevicePhoneMobileIcon } from "@heroicons/react/24/solid";
import { useEffect, useRef, useState } from "react";
type TPreviewType = "modal" | "fullwidth" | "email";
interface PreviewSurveyProps {
survey: TSurvey | Survey;
setActiveQuestionId: (id: string | null) => void;
activeQuestionId?: string | null;
previewType?: "modal" | "fullwidth" | "email";
previewType?: TPreviewType;
product: TProduct;
environment: TEnvironment;
}
let surveyNameTemp;
export default function PreviewSurvey({
survey,
setActiveQuestionId,
activeQuestionId,
survey,
previewType,
product,
environment,
@@ -34,6 +37,18 @@ export default function PreviewSurvey({
const [previewMode, setPreviewMode] = useState("desktop");
const ContentRef = useRef<HTMLDivElement | null>(null);
const { productOverwrites } = survey || {};
const {
brandColor: surveyBrandColor,
highlightBorderColor: surveyHighlightBorderColor,
placement: surveyPlacement,
} = productOverwrites || {};
const brandColor = surveyBrandColor || product.brandColor;
const placement = surveyPlacement || product.placement;
const highlightBorderColor = surveyHighlightBorderColor || product.highlightBorderColor;
useEffect(() => {
// close modal if there are no questions left
if (survey.type === "web" && !survey.thankYouCard.enabled) {
@@ -52,6 +67,8 @@ export default function PreviewSurvey({
resetQuestionProgress();
surveyNameTemp = survey.name;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [survey]);
function resetQuestionProgress() {
@@ -94,12 +111,12 @@ export default function PreviewSurvey({
{previewType === "modal" ? (
<Modal
isOpen={isModalOpen}
placement={product.placement}
highlightBorderColor={product.highlightBorderColor}
placement={placement}
highlightBorderColor={highlightBorderColor}
previewMode="mobile">
<SurveyInline
survey={survey}
brandColor={product.brandColor}
brandColor={brandColor}
activeQuestionId={activeQuestionId || undefined}
formbricksSignature={product.formbricksSignature}
onActiveQuestionChange={setActiveQuestionId}
@@ -114,7 +131,7 @@ export default function PreviewSurvey({
<div className="w-full max-w-md px-4">
<SurveyInline
survey={survey}
brandColor={product.brandColor}
brandColor={brandColor}
activeQuestionId={activeQuestionId || undefined}
formbricksSignature={product.formbricksSignature}
onActiveQuestionChange={setActiveQuestionId}
@@ -143,12 +160,12 @@ export default function PreviewSurvey({
{previewType === "modal" ? (
<Modal
isOpen={isModalOpen}
placement={product.placement}
highlightBorderColor={product.highlightBorderColor}
placement={placement}
highlightBorderColor={highlightBorderColor}
previewMode="desktop">
<SurveyInline
survey={survey}
brandColor={product.brandColor}
brandColor={brandColor}
activeQuestionId={activeQuestionId || undefined}
formbricksSignature={product.formbricksSignature}
onActiveQuestionChange={setActiveQuestionId}
@@ -161,7 +178,7 @@ export default function PreviewSurvey({
<div className="w-full max-w-md">
<SurveyInline
survey={survey}
brandColor={product.brandColor}
brandColor={brandColor}
activeQuestionId={activeQuestionId || undefined}
formbricksSignature={product.formbricksSignature}
onActiveQuestionChange={setActiveQuestionId}

View File

@@ -3,8 +3,8 @@ import SurveyDropDownMenu from "@/app/(app)/environments/[environmentId]/surveys
import SurveyStarter from "@/app/(app)/environments/[environmentId]/surveys/SurveyStarter";
import SurveyStatusIndicator from "@/components/shared/SurveyStatusIndicator";
import { SURVEY_BASE_URL } from "@formbricks/lib/constants";
import { getEnvironment, getEnvironments } from "@formbricks/lib/services/environment";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getEnvironment, getEnvironments } from "@formbricks/lib/environment/service";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { getSurveys } from "@formbricks/lib/survey/service";
import type { TEnvironment } from "@formbricks/types/v1/environment";
import { Badge } from "@formbricks/ui";

View File

@@ -2,7 +2,7 @@ import { RESPONSES_LIMIT_FREE } from "@formbricks/lib/constants";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { getSurveyWithAnalytics } from "@formbricks/lib/survey/service";
import { getSurveyResponses } from "@formbricks/lib/response/service";
import { getTeamByEnvironmentId } from "@formbricks/lib/services/team";
import { getTeamByEnvironmentId } from "@formbricks/lib/team/service";
export const getAnalysisData = async (surveyId: string, environmentId: string) => {
const [survey, team, allResponses] = await Promise.all([

View File

@@ -1,9 +1,9 @@
"use server";
import { deleteResponse } from "@formbricks/lib/response/service";
import { updateResponseNote, resolveResponseNote } from "@formbricks/lib/services/responseNote";
import { updateResponseNote, resolveResponseNote } from "@formbricks/lib/responseNote/service";
import { createTag } from "@formbricks/lib/tag/service";
import { addTagToRespone, deleteTagOnResponse } from "@formbricks/lib/services/tagOnResponse";
import { addTagToRespone, deleteTagOnResponse } from "@formbricks/lib/tagOnResponse/service";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { getServerSession } from "next-auth";

View File

@@ -6,8 +6,8 @@ import { getAnalysisData } from "@/app/(app)/environments/[environmentId]/survey
import { getServerSession } from "next-auth";
import { REVALIDATION_INTERVAL, SURVEY_BASE_URL } from "@formbricks/lib/constants";
import ResponsesLimitReachedBanner from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/ResponsesLimitReachedBanner";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service";
export default async function Page({ params }) {

View File

@@ -5,8 +5,8 @@ import { getAnalysisData } from "@/app/(app)/environments/[environmentId]/survey
import SummaryPage from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryPage";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { REVALIDATION_INTERVAL, SURVEY_BASE_URL } from "@formbricks/lib/constants";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service";
import { getServerSession } from "next-auth";
import { generateSurveySingleUseId } from "@/lib/singleUseSurveys";

View File

@@ -0,0 +1,101 @@
"use client";
import { cn } from "@formbricks/lib/cn";
import { Label, RadioGroup, RadioGroupItem } from "@formbricks/ui";
import { getPlacementStyle } from "@/lib/preview";
import { PlacementType } from "@formbricks/types/js";
const placements = [
{ name: "Bottom Right", value: "bottomRight", disabled: false },
{ name: "Top Right", value: "topRight", disabled: false },
{ name: "Top Left", value: "topLeft", disabled: false },
{ name: "Bottom Left", value: "bottomLeft", disabled: false },
{ name: "Centered Modal", value: "center", disabled: false },
];
type TPlacementProps = {
currentPlacement: PlacementType;
setCurrentPlacement: (placement: PlacementType) => void;
setOverlay: (overlay: string) => void;
overlay: string;
setClickOutside: (clickOutside: boolean) => void;
clickOutside: boolean;
};
export default function Placement({
setCurrentPlacement,
currentPlacement,
setOverlay,
overlay,
setClickOutside,
clickOutside,
}: TPlacementProps) {
return (
<>
<div className="flex">
<RadioGroup onValueChange={(e) => setCurrentPlacement(e as PlacementType)} value={currentPlacement}>
{placements.map((placement) => (
<div key={placement.value} className="flex items-center space-x-2 whitespace-nowrap">
<RadioGroupItem id={placement.value} value={placement.value} disabled={placement.disabled} />
<Label
htmlFor={placement.value}
className={cn(placement.disabled ? "cursor-not-allowed text-slate-500" : "text-slate-900")}>
{placement.name}
</Label>
</div>
))}
</RadioGroup>
<div className="relative ml-8 h-40 w-full rounded bg-slate-200">
<div
className={cn(
"absolute h-16 w-16 rounded bg-slate-700",
getPlacementStyle(currentPlacement)
)}></div>
</div>
</div>
{currentPlacement === "center" && (
<>
<div className="mt-6 space-y-2">
<Label className="font-semibold">Centered modal overlay color</Label>
<RadioGroup
onValueChange={(overlay) => setOverlay(overlay)}
value={overlay}
className="flex space-x-4">
<div className="flex items-center space-x-2 whitespace-nowrap">
<RadioGroupItem id="lightOverlay" value="light" />
<Label htmlFor="lightOverlay" className="text-slate-900">
Light Overlay
</Label>
</div>
<div className="flex items-center space-x-2 whitespace-nowrap">
<RadioGroupItem id="darkOverlay" value="dark" />
<Label htmlFor="darkOverlay" className="text-slate-900">
Dark Overlay
</Label>
</div>
</RadioGroup>
</div>
<div className="mt-6 space-y-2">
<Label className="font-semibold">Allow users to exit by clicking outside the study</Label>
<RadioGroup
onValueChange={(value) => setClickOutside(value === "allow")}
value={clickOutside ? "allow" : "disallow"}
className="flex space-x-4">
<div className="flex items-center space-x-2 whitespace-nowrap">
<RadioGroupItem id="disallow" value="disallow" />
<Label htmlFor="disallow" className="text-slate-900">
Don&apos;t Allow
</Label>
</div>
<div className="flex items-center space-x-2 whitespace-nowrap">
<RadioGroupItem id="allow" value="allow" />
<Label htmlFor="allow" className="text-slate-900">
Allow
</Label>
</div>
</RadioGroup>
</div>
</>
)}
</>
);
}

View File

@@ -3,6 +3,7 @@ import RecontactOptionsCard from "./RecontactOptionsCard";
import ResponseOptionsCard from "./ResponseOptionsCard";
import WhenToSendCard from "./WhenToSendCard";
import WhoToSendCard from "./WhoToSendCard";
import StylingCard from "./StylingCard";
import { TSurveyWithAnalytics } from "@formbricks/types/v1/surveys";
import { TEnvironment } from "@formbricks/types/v1/environment";
import { TActionClass } from "@formbricks/types/v1/actionClasses";
@@ -54,6 +55,8 @@ export default function SettingsView({
setLocalSurvey={setLocalSurvey}
environmentId={environment.id}
/>
<StylingCard localSurvey={localSurvey} setLocalSurvey={setLocalSurvey} />
</div>
);
}

View File

@@ -0,0 +1,215 @@
"use client";
import Placement from "./Placement";
import { PlacementType } from "@formbricks/types/js";
import { TSurveyWithAnalytics } from "@formbricks/types/v1/surveys";
import { ColorPicker, Label, Switch } from "@formbricks/ui";
import { CheckCircleIcon } from "@heroicons/react/24/solid";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useState } from "react";
interface StylingCardProps {
localSurvey: TSurveyWithAnalytics;
setLocalSurvey: React.Dispatch<React.SetStateAction<TSurveyWithAnalytics>>;
}
export default function StylingCard({ localSurvey, setLocalSurvey }: StylingCardProps) {
const [open, setOpen] = useState(false);
const { type, productOverwrites } = localSurvey;
const { brandColor, clickOutside, darkOverlay, placement, highlightBorderColor } = productOverwrites ?? {};
const togglePlacement = () => {
setLocalSurvey({
...localSurvey,
productOverwrites: {
...localSurvey.productOverwrites,
placement: !!placement ? null : "bottomRight",
},
});
};
const toggleBrandColor = () => {
setLocalSurvey({
...localSurvey,
productOverwrites: {
...localSurvey.productOverwrites,
brandColor: !!brandColor ? null : "#64748b",
},
});
};
const toggleHighlightBorderColor = () => {
setLocalSurvey({
...localSurvey,
productOverwrites: {
...localSurvey.productOverwrites,
highlightBorderColor: !!highlightBorderColor ? null : "#64748b",
},
});
};
const handleColorChange = (color: string) => {
setLocalSurvey({
...localSurvey,
productOverwrites: {
...localSurvey.productOverwrites,
brandColor: color,
},
});
};
const handleBorderColorChange = (color: string) => {
setLocalSurvey({
...localSurvey,
productOverwrites: {
...localSurvey.productOverwrites,
highlightBorderColor: color,
},
});
};
const handlePlacementChange = (placement: PlacementType) => {
setLocalSurvey({
...localSurvey,
productOverwrites: {
...localSurvey.productOverwrites,
placement,
},
});
};
const handleOverlay = (overlayType: string) => {
const darkOverlay = overlayType === "dark";
setLocalSurvey({
...localSurvey,
productOverwrites: {
...localSurvey.productOverwrites,
darkOverlay,
},
});
};
const handleClickOutside = (clickOutside: boolean) => {
setLocalSurvey({
...localSurvey,
productOverwrites: {
...localSurvey.productOverwrites,
clickOutside,
},
});
};
return (
<Collapsible.Root
open={open}
onOpenChange={setOpen}
className="w-full rounded-lg border border-slate-300 bg-white">
<Collapsible.CollapsibleTrigger asChild className="h-full w-full cursor-pointer">
<div className="inline-flex px-4 py-4">
<div className="flex items-center pl-2 pr-5">
<CheckCircleIcon className="h-8 w-8 text-green-400" />
</div>
<div>
<p className="font-semibold text-slate-800">Styling</p>
<p className="mt-1 truncate text-sm text-slate-500">Overwrite global styling settings</p>
</div>
</div>
</Collapsible.CollapsibleTrigger>
<Collapsible.CollapsibleContent>
<hr className="py-1 text-slate-600" />
<div className="p-3">
{/* Brand Color */}
<div className="p-3">
<div className="ml-2 flex items-center space-x-1">
<Switch id="autoComplete" checked={!!brandColor} onCheckedChange={toggleBrandColor} />
<Label htmlFor="autoComplete" className="cursor-pointer">
<div className="ml-2">
<h3 className="text-sm font-semibold text-slate-700">Overwrite Brand Color</h3>
<p className="text-xs font-normal text-slate-500">Change the main color for this survey.</p>
</div>
</Label>
</div>
{brandColor && (
<div className="ml-2 mt-4 rounded-lg border bg-slate-50 p-4">
<div className="w-full max-w-xs">
<Label htmlFor="brandcolor">Color (HEX)</Label>
<ColorPicker color={brandColor} onChange={handleColorChange} />
</div>
</div>
)}
</div>
{/* positioning */}
{type !== "link" && (
<div className="p-3 ">
<div className="ml-2 flex items-center space-x-1">
<Switch id="surveyDeadline" checked={!!placement} onCheckedChange={togglePlacement} />
<Label htmlFor="surveyDeadline" className="cursor-pointer">
<div className="ml-2">
<h3 className="text-sm font-semibold text-slate-700">Overwrite Placement</h3>
<p className="text-xs font-normal text-slate-500">Change the placement of this survey.</p>
</div>
</Label>
</div>
{placement && (
<div className="ml-2 mt-4 flex items-center space-x-1 pb-4">
<div className="flex w-full cursor-pointer items-center rounded-lg border bg-slate-50 p-4">
<div className="w-full items-center">
<Placement
currentPlacement={placement}
setCurrentPlacement={handlePlacementChange}
setOverlay={handleOverlay}
overlay={darkOverlay ? "dark" : "light"}
setClickOutside={handleClickOutside}
clickOutside={!!clickOutside}
/>
</div>
</div>
</div>
)}
</div>
)}
{/* Highlight border */}
{type !== "link" && (
<div className="p-3 ">
<div className="ml-2 flex items-center space-x-1">
<Switch
id="autoComplete"
checked={!!highlightBorderColor}
onCheckedChange={toggleHighlightBorderColor}
/>
<Label htmlFor="autoComplete" className="cursor-pointer">
<div className="ml-2">
<h3 className="text-sm font-semibold text-slate-700">Overwrite border highlight</h3>
<p className="text-xs font-normal text-slate-500">
Change the border highlight for this survey.
</p>
</div>
</Label>
</div>
{!!highlightBorderColor && (
<div className="ml-2 mt-4 rounded-lg border bg-slate-50 p-4">
<div className="flex items-center space-x-2">
<Switch
id="highlightBorder"
checked={!!highlightBorderColor}
onCheckedChange={toggleHighlightBorderColor}
/>
<h2 className="text-sm font-medium text-slate-800">Show highlight border</h2>
</div>
{!!highlightBorderColor && (
<div className="mt-6 w-full max-w-xs">
<Label htmlFor="brandcolor">Color (HEX)</Label>
<ColorPicker color={highlightBorderColor || ""} onChange={handleBorderColorChange} />
</div>
)}
</div>
)}
</div>
)}
</div>
</Collapsible.CollapsibleContent>
</Collapsible.Root>
);
}

View File

@@ -51,6 +51,8 @@ export default function SurveyEditor({
if (localSurvey?.questions?.length && localSurvey.questions.length > 0) {
setActiveQuestionId(localSurvey.questions[0].id);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [localSurvey?.type]);
if (!localSurvey) {

View File

@@ -3,8 +3,8 @@ import React from "react";
import { FORMBRICKS_ENCRYPTION_KEY, REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import SurveyEditor from "./SurveyEditor";
import { getSurveyWithAnalytics } from "@formbricks/lib/survey/service";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getActionClasses } from "@formbricks/lib/actionClass/service";
import { getAttributeClasses } from "@formbricks/lib/attributeClass/service";
import { ErrorComponent } from "@formbricks/ui";

View File

@@ -5,7 +5,7 @@ import ContentWrapper from "@/components/shared/ContentWrapper";
import WidgetStatusIndicator from "@/components/shared/WidgetStatusIndicator";
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { getActionsByEnvironmentId } from "@formbricks/lib/action/service";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { Metadata } from "next";
import SurveysList from "./SurveyList";

View File

@@ -1,6 +1,6 @@
import TemplateContainerWithPreview from "./TemplateContainer";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
export default async function SurveyTemplatesPage({ params }) {
const environmentId = params.environmentId;

View File

@@ -2062,5 +2062,6 @@ export const minimalSurvey: TSurvey = {
surveyClosedMessage: {
enabled: false,
},
productOverwrites: null,
singleUse: null,
};

View File

@@ -1,8 +1,8 @@
"use server";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { updateProduct } from "@formbricks/lib/services/product";
import { updateProfile } from "@formbricks/lib/services/profile";
import { updateProduct } from "@formbricks/lib/product/service";
import { updateProfile } from "@formbricks/lib/profile/service";
import { TProductUpdateInput } from "@formbricks/types/v1/product";
import { TProfileUpdateInput } from "@formbricks/types/v1/profile";
import { getServerSession } from "next-auth";

View File

@@ -2,9 +2,9 @@ export const revalidate = REVALIDATION_INTERVAL;
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
import { getFirstEnvironmentByUserId } from "@formbricks/lib/services/environment";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getProfile } from "@formbricks/lib/services/profile";
import { getFirstEnvironmentByUserId } from "@formbricks/lib/environment/service";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { getProfile } from "@formbricks/lib/profile/service";
import { getServerSession } from "next-auth";
import Onboarding from "./components/Onboarding";
@@ -13,8 +13,14 @@ export default async function OnboardingPage() {
if (!session) {
throw new Error("No session found");
}
const environment = await getFirstEnvironmentByUserId(session?.user.id);
const profile = await getProfile(session?.user.id!);
const userId = session?.user.id;
const environment = await getFirstEnvironmentByUserId(userId);
if (!environment) {
throw new Error("No environment found for user");
}
const profile = await getProfile(userId);
const product = await getProductByEnvironmentId(environment?.id!);
if (!environment || !profile || !product) {

View File

@@ -1,7 +1,7 @@
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { hasTeamAccess } from "@/lib/api/apiHelper";
import { getEnvironments } from "@formbricks/lib/services/environment";
import { getProduct } from "@formbricks/lib/services/product";
import { getEnvironments } from "@formbricks/lib/environment/service";
import { getProduct } from "@formbricks/lib/product/service";
import { AuthenticationError, AuthorizationError } from "@formbricks/types/v1/errors";
import { getServerSession } from "next-auth";
import { notFound, redirect } from "next/navigation";

View File

@@ -1,7 +1,7 @@
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { hasTeamAccess } from "@/lib/api/apiHelper";
import { getEnvironments } from "@formbricks/lib/services/environment";
import { getProducts } from "@formbricks/lib/services/product";
import { getEnvironments } from "@formbricks/lib/environment/service";
import { getProducts } from "@formbricks/lib/product/service";
import { AuthenticationError, AuthorizationError } from "@formbricks/types/v1/errors";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";

View File

@@ -3,7 +3,7 @@ import { verifyPassword } from "@/lib/auth";
import { prisma } from "@formbricks/database";
import { EMAIL_VERIFICATION_DISABLED, INTERNAL_SECRET, WEBAPP_URL } from "@formbricks/lib/constants";
import { verifyToken } from "@formbricks/lib/jwt";
import { getProfileByEmail } from "@formbricks/lib/services/profile";
import { getProfileByEmail } from "@formbricks/lib/profile/service";
import type { IdentityProvider } from "@prisma/client";
import type { NextAuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";

View File

@@ -1,4 +1,4 @@
import { writeData } from "@formbricks/lib/services/googleSheet";
import { writeData } from "@formbricks/lib/googleSheet/service";
import { getSurvey } from "@formbricks/lib/survey/service";
import { TGoogleSheetIntegration, TIntegration } from "@formbricks/types/v1/integrations";
import { TPipelineInput } from "@formbricks/types/v1/pipelines";

View File

@@ -4,7 +4,7 @@ import { InvalidInputError } from "@formbricks/types/v1/errors";
import { capturePosthogEvent } from "@formbricks/lib/posthogServer";
import { createDisplay } from "@formbricks/lib/display/service";
import { getSurvey } from "@formbricks/lib/survey/service";
import { getTeamDetails } from "@formbricks/lib/services/teamDetails";
import { getTeamDetails } from "@formbricks/lib/teamDetail/service";
import { TDisplay, ZDisplayInput } from "@formbricks/types/v1/displays";
import { NextResponse } from "next/server";

View File

@@ -5,7 +5,7 @@ import { InvalidInputError } from "@formbricks/types/v1/errors";
import { capturePosthogEvent } from "@formbricks/lib/posthogServer";
import { getSurvey } from "@formbricks/lib/survey/service";
import { createResponse } from "@formbricks/lib/response/service";
import { getTeamDetails } from "@formbricks/lib/services/teamDetails";
import { getTeamDetails } from "@formbricks/lib/teamDetail/service";
import { TResponse, TResponseInput, ZResponseInput } from "@formbricks/types/v1/responses";
import { NextResponse } from "next/server";
import { UAParser } from "ua-parser-js";

View File

@@ -2,7 +2,7 @@ import { getUpdatedState } from "@/app/api/v1/js/sync/lib/sync";
import { responses } from "@/lib/api/response";
import { transformErrorToDetails } from "@/lib/api/validator";
import { createAttributeClass, getAttributeClassByNameCached } from "@formbricks/lib/attributeClass/service";
import { getPersonCached, updatePersonAttribute } from "@formbricks/lib/services/person";
import { getPersonCached, updatePersonAttribute } from "@formbricks/lib/person/service";
import { ZJsPeopleAttributeInput } from "@formbricks/types/v1/js";
import { NextResponse } from "next/server";

View File

@@ -2,7 +2,7 @@ import { getUpdatedState } from "@/app/api/v1/js/sync/lib/sync";
import { responses } from "@/lib/api/response";
import { transformErrorToDetails } from "@/lib/api/validator";
import { prisma } from "@formbricks/database";
import { deletePerson, selectPerson, transformPrismaPerson } from "@formbricks/lib/services/person";
import { deletePerson, selectPerson, transformPrismaPerson } from "@formbricks/lib/person/service";
import { ZJsPeopleUserIdInput } from "@formbricks/types/v1/js";
import { revalidateTag } from "next/cache";
import { NextResponse } from "next/server";

View File

@@ -1,10 +1,10 @@
import { getSurveysCached } from "@/app/api/v1/js/surveys";
import { MAU_LIMIT } from "@formbricks/lib/constants";
import { getActionClasses } from "@formbricks/lib/actionClass/service";
import { getEnvironment } from "@formbricks/lib/services/environment";
import { createPerson, getMonthlyActivePeopleCount, getPersonCached } from "@formbricks/lib/services/person";
import { getProductByEnvironmentIdCached } from "@formbricks/lib/services/product";
import { createSession, extendSession, getSessionCached } from "@formbricks/lib/services/session";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { createPerson, getMonthlyActivePeopleCount, getPersonCached } from "@formbricks/lib/person/service";
import { getProductByEnvironmentIdCached } from "@formbricks/lib/product/service";
import { createSession, extendSession, getSessionCached } from "@formbricks/lib/session/service";
import { captureTelemetry } from "@formbricks/lib/telemetry";
import { TEnvironment } from "@formbricks/types/v1/environment";
import { TJsState } from "@formbricks/types/v1/js";

View File

@@ -1,6 +1,6 @@
import { authenticateRequest, handleErrorResponse } from "@/app/api/v1/auth";
import { responses } from "@/lib/api/response";
import { deletePerson, getPerson } from "@formbricks/lib/services/person";
import { deletePerson, getPerson } from "@formbricks/lib/person/service";
import { TAuthenticationApiKey } from "@formbricks/types/v1/auth";
import { TPerson } from "@formbricks/types/v1/people";
import { NextResponse } from "next/server";

View File

@@ -1,6 +1,6 @@
import { authenticateRequest } from "@/app/api/v1/auth";
import { responses } from "@/lib/api/response";
import { getPeople } from "@formbricks/lib/services/person";
import { getPeople } from "@formbricks/lib/person/service";
import { DatabaseError } from "@formbricks/types/v1/errors";
import { TPerson } from "@formbricks/types/v1/people";

View File

@@ -1,5 +1,5 @@
import { INTERNAL_SECRET } from "@formbricks/lib/constants";
import { createDemoProduct } from "@formbricks/lib/services/team";
import { createDemoProduct } from "@formbricks/lib/team/service";
import { NextResponse } from "next/server";
import { headers } from "next/headers";
import { responses } from "@/lib/api/response";

View File

@@ -2,11 +2,11 @@ import { sendInviteAcceptedEmail, sendVerificationEmail } from "@/lib/email";
import { prisma } from "@formbricks/database";
import { EMAIL_VERIFICATION_DISABLED, INVITE_DISABLED, SIGNUP_ENABLED } from "@formbricks/lib/constants";
import { verifyInviteToken } from "@formbricks/lib/jwt";
import { deleteInvite } from "@formbricks/lib/services/invite";
import { createMembership } from "@formbricks/lib/services/membership";
import { createProduct } from "@formbricks/lib/services/product";
import { createProfile } from "@formbricks/lib/services/profile";
import { createTeam } from "@formbricks/lib/services/team";
import { deleteInvite } from "@formbricks/lib/invite/service";
import { createMembership } from "@formbricks/lib/membership/service";
import { createProduct } from "@formbricks/lib/product/service";
import { createProfile } from "@formbricks/lib/profile/service";
import { createTeam } from "@formbricks/lib/team/service";
import { NextResponse } from "next/server";
export async function POST(request: Request) {

View File

@@ -1,6 +1,6 @@
import { responses } from "@/lib/api/response";
import { getApiKeyFromKey } from "@formbricks/lib/apiKey/service";
import { deleteWebhook, getWebhook } from "@formbricks/lib/services/webhook";
import { deleteWebhook, getWebhook } from "@formbricks/lib/webhook/service";
import { headers } from "next/headers";
export async function GET(_: Request, { params }: { params: { webhookId: string } }) {

View File

@@ -2,7 +2,7 @@ import { responses } from "@/lib/api/response";
import { transformErrorToDetails } from "@/lib/api/validator";
import { getApiKeyFromKey } from "@formbricks/lib/apiKey/service";
import { DatabaseError, InvalidInputError } from "@formbricks/types/v1/errors";
import { createWebhook, getWebhooks } from "@formbricks/lib/services/webhook";
import { createWebhook, getWebhooks } from "@formbricks/lib/webhook/service";
import { ZWebhookInput } from "@formbricks/types/v1/webhooks";
import { headers } from "next/headers";
import { NextResponse } from "next/server";

View File

@@ -1,6 +1,6 @@
import ClientLogout from "@/app/ClientLogout";
import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { getFirstEnvironmentByUserId } from "@formbricks/lib/services/environment";
import { getFirstEnvironmentByUserId } from "@formbricks/lib/environment/service";
import type { Session } from "next-auth";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";

View File

@@ -46,6 +46,8 @@ export default function LinkSurvey({
? getPrefillResponseData(survey.questions[0], survey, prefillAnswer)
: undefined;
const brandColor = survey.productOverwrites?.brandColor || product.brandColor;
const responseQueue = useMemo(
() =>
new ResponseQueue(
@@ -110,7 +112,7 @@ export default function LinkSurvey({
)}
<SurveyInline
survey={survey}
brandColor={product.brandColor}
brandColor={brandColor}
formbricksSignature={product.formbricksSignature}
onDisplay={async () => {
if (!isPreview) {

View File

@@ -3,8 +3,8 @@ export const revalidate = REVALIDATION_INTERVAL;
import LinkSurvey from "@/app/s/[surveyId]/LinkSurvey";
import SurveyInactive from "@/app/s/[surveyId]/SurveyInactive";
import { REVALIDATION_INTERVAL, WEBAPP_URL } from "@formbricks/lib/constants";
import { getOrCreatePersonByUserId } from "@formbricks/lib/services/person";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getOrCreatePersonByUserId } from "@formbricks/lib/person/service";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { getSurvey } from "@formbricks/lib/survey/service";
import { getEmailVerificationStatus } from "./helpers";
import { checkValidity } from "@/app/s/[surveyId]/prefilling";

View File

@@ -1,7 +1,7 @@
import { cn } from "@formbricks/lib/cn";
import SurveyNavBarName from "@/components/shared/SurveyNavBarName";
import Link from "next/link";
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { getSurvey } from "@formbricks/lib/survey/service";
interface SecondNavbarProps {

View File

@@ -1,4 +1,4 @@
import { getEnvironments } from "@formbricks/lib/services/environment";
import { getEnvironments } from "@formbricks/lib/environment/service";
import { TEnvironment } from "@formbricks/types/v1/environment";
import { LightBulbIcon } from "@heroicons/react/24/outline";
import { headers } from "next/headers";

View File

@@ -1,6 +1,6 @@
import { getSessionUser, hasEnvironmentAccess } from "@/lib/api/apiHelper";
import { prisma } from "@formbricks/database";
import { createProduct } from "@formbricks/lib/services/product";
import { createProduct } from "@formbricks/lib/product/service";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handle(req: NextApiRequest, res: NextApiResponse) {

View File

@@ -147,6 +147,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
},
surveyClosedMessage: existingSurvey.surveyClosedMessage ?? prismaClient.JsonNull,
singleUse: existingSurvey.singleUse ?? prismaClient.JsonNull,
productOverwrites: existingSurvey.productOverwrites ?? prismaClient.JsonNull,
verifyEmail: existingSurvey.verifyEmail ?? prismaClient.JsonNull,
},
});

View File

@@ -67,6 +67,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
},
surveyClosedMessage: existingSurvey.surveyClosedMessage ?? prismaClient.JsonNull,
singleUse: existingSurvey.singleUse ?? prismaClient.JsonNull,
productOverwrites: existingSurvey.productOverwrites ?? prismaClient.JsonNull,
verifyEmail: existingSurvey.verifyEmail ?? prismaClient.JsonNull,
},
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -3,6 +3,7 @@ import { TIntegrationConfig } from "@formbricks/types/v1/integrations";
import { TResponsePersonAttributes, TResponseData, TResponseMeta } from "@formbricks/types/v1/responses";
import {
TSurveyClosedMessage,
TSurveyProductOverwrites,
TSurveyQuestions,
TSurveySingleUse,
TSurveyThankYouCard,
@@ -20,6 +21,7 @@ declare global {
export type ResponsePersonAttributes = TResponsePersonAttributes;
export type SurveyQuestions = TSurveyQuestions;
export type SurveyThankYouCard = TSurveyThankYouCard;
export type SurveyProductOverwrites = TSurveyProductOverwrites;
export type SurveyClosedMessage = TSurveyClosedMessage;
export type SurveySingleUse = TSurveySingleUse;
export type SurveyVerifyEmail = TSurveyVerifyEmail;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Survey" ADD COLUMN "productOverwrites" JSONB;

View File

@@ -251,10 +251,16 @@ model Survey {
surveyClosedMessage Json?
/// @zod.custom(imports.ZSurveySingleUse)
/// [SurveySingleUse]
singleUse Json? @default("{\"enabled\": false, \"isEncrypted\": true}")
/// @zod.custom(imports.ZSurveyProductOverwrites)
/// [SurveyProductOverwrites]
productOverwrites Json?
/// @zod.custom(imports.ZSurveySingleUse)
/// [SurveySingleUse]
singleUse Json? @default("{\"enabled\": false, \"isEncrypted\": true}")
/// @zod.custom(imports.ZSurveyVerifyEmail)
/// [SurveyVerifyEmail]
verifyEmail Json?
verifyEmail Json?
}
model Event {

View File

@@ -10,6 +10,7 @@ export {
ZSurveyQuestions,
ZSurveyThankYouCard,
ZSurveyClosedMessage,
ZSurveyProductOverwrites,
ZSurveyVerifyEmail,
ZSurveySingleUse,
} from "@formbricks/types/v1/surveys";

View File

@@ -42,15 +42,22 @@ export const renderWidget = (survey: TSurvey) => {
surveyState
);
const productOverwrites = survey.productOverwrites ?? {};
const brandColor = productOverwrites.brandColor ?? product.brandColor;
const highlightBorderColor = productOverwrites.highlightBorderColor ?? product.highlightBorderColor;
const clickOutside = productOverwrites.clickOutside ?? product.clickOutsideClose;
const darkOverlay = productOverwrites.darkOverlay ?? product.darkOverlay;
const placement = productOverwrites.placement ?? product.placement;
setTimeout(() => {
renderSurveyModal({
survey: survey,
brandColor: product.brandColor,
brandColor,
formbricksSignature: product.formbricksSignature,
clickOutside: product.clickOutsideClose,
darkOverlay: product.darkOverlay,
highlightBorderColor: product.highlightBorderColor,
placement: product.placement,
clickOutside,
darkOverlay,
highlightBorderColor,
placement,
onDisplay: async () => {
const { id } = await createDisplay(
{

View File

@@ -12,7 +12,7 @@ import { TJsActionInput } from "@formbricks/types/v1/js";
import { revalidateTag } from "next/cache";
import { EventType } from "@prisma/client";
import { getActionClassCacheTag, getActionClassCached } from "../actionClass/service";
import { getSessionCached } from "../services/session";
import { getSessionCached } from "../session/service";
export const getActionsByEnvironmentId = cache(
async (environmentId: string, limit?: number): Promise<TAction[]> => {

View File

@@ -13,7 +13,7 @@ import { Prisma } from "@prisma/client";
import { revalidateTag } from "next/cache";
import { cache } from "react";
import { validateInputs } from "../utils/validate";
import { transformPrismaPerson } from "../services/person";
import { transformPrismaPerson } from "../person/service";
const selectDisplay = {
id: true,

View File

@@ -136,9 +136,8 @@ export const updateEnvironment = async (
export const getFirstEnvironmentByUserId = async (userId: string): Promise<TEnvironment | null> => {
validateInputs([userId, ZId]);
let environmentPrisma;
try {
environmentPrisma = await prisma.environment.findFirst({
return await prisma.environment.findFirst({
where: {
type: "production",
product: {
@@ -159,16 +158,6 @@ export const getFirstEnvironmentByUserId = async (userId: string): Promise<TEnvi
throw error;
}
try {
const environment = ZEnvironment.parse(environmentPrisma);
return environment;
} catch (error) {
if (error instanceof z.ZodError) {
console.error(JSON.stringify(error.errors, null, 2));
}
throw new ValidationError("Data validation of environment failed");
}
};
export const createEnvironment = async (

View File

@@ -11,7 +11,7 @@ import { cache } from "react";
import { z } from "zod";
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
import { validateInputs } from "../utils/validate";
import { createEnvironment, getEnvironmentCacheTag, getEnvironmentsCacheTag } from "./environment";
import { createEnvironment, getEnvironmentCacheTag, getEnvironmentsCacheTag } from "../environment/service";
export const getProductsCacheTag = (teamId: string): string => `teams-${teamId}-products`;
const getProductCacheTag = (environmentId: string): string => `environments-${environmentId}-product`;

View File

@@ -13,7 +13,7 @@ import {
import { MembershipRole, Prisma } from "@prisma/client";
import { unstable_cache, revalidateTag } from "next/cache";
import { validateInputs } from "../utils/validate";
import { deleteTeam } from "./team";
import { deleteTeam } from "../team/service";
import { z } from "zod";
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";

View File

@@ -14,7 +14,7 @@ import { TTag } from "@formbricks/types/v1/tags";
import { Prisma } from "@prisma/client";
import { z } from "zod";
import { cache } from "react";
import { getPerson, transformPrismaPerson } from "../services/person";
import { getPerson, transformPrismaPerson } from "../person/service";
import { captureTelemetry } from "../telemetry";
import { validateInputs } from "../utils/validate";
import { ZId } from "@formbricks/types/v1/environment";

View File

@@ -42,6 +42,7 @@ export const selectSurvey = {
autoComplete: true,
verifyEmail: true,
redirectUrl: true,
productOverwrites: true,
surveyClosedMessage: true,
singleUse: true,
triggers: {

View File

@@ -5,7 +5,7 @@ import { unstable_cache } from "next/cache";
import { ZId } from "@formbricks/types/v1/environment";
import { canUserAccessResponse } from "../response/auth";
import { canUserAccessTag } from "../tag/auth";
import { getTagOnResponseCacheTag } from "../services/tagOnResponse";
import { getTagOnResponseCacheTag } from "./service";
import { SERVICES_REVALIDATION_INTERVAL } from "../constants";
export const canUserAccessTagOnResponse = async (

View File

@@ -1,3 +1,4 @@
import { PlacementType } from "./js";
import { Question } from "./questions";
export interface ThankYouCard {
@@ -22,6 +23,14 @@ export interface VerifyEmail {
subheading?: string;
}
export interface SurveyProductOverwrites {
brandColor: string;
highlightBorderColor: string | null;
placement: PlacementType;
clickOutside: boolean;
darkOverlay: boolean;
}
export interface Survey {
id: string;
createdAt: string;
@@ -47,6 +56,7 @@ export interface Survey {
closeOnDate: Date | null;
singleUse: SurveySingleUse | null;
_count: { responses: number | null } | null;
productOverwrites: SurveyProductOverwrites | null;
}
export interface AttributeFilter {

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