diff --git a/apps/formbricks-com/app/docs/contributing/how-we-code/page.mdx b/apps/formbricks-com/app/docs/contributing/how-we-code/page.mdx index 4ea09e78c6..1e3cf0f4ed 100644 --- a/apps/formbricks-com/app/docs/contributing/how-we-code/page.mdx +++ b/apps/formbricks-com/app/docs/contributing/how-we-code/page.mdx @@ -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 diff --git a/apps/formbricks-com/app/docs/integrations/google-sheets/page.mdx b/apps/formbricks-com/app/docs/integrations/google-sheets/page.mdx index 2f974c3130..7f7db3361b 100644 --- a/apps/formbricks-com/app/docs/integrations/google-sheets/page.mdx +++ b/apps/formbricks-com/app/docs/integrations/google-sheets/page.mdx @@ -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)) We do not store any other information of yours! We value Privacy more than you and rest assured you're safe diff --git a/apps/web/app/(app)/environments/[environmentId]/EnvironmentsNavbar.tsx b/apps/web/app/(app)/environments/[environmentId]/EnvironmentsNavbar.tsx index 8e5296e288..1468ed030d 100644 --- a/apps/web/app/(app)/environments/[environmentId]/EnvironmentsNavbar.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/EnvironmentsNavbar.tsx @@ -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"; diff --git a/apps/web/app/(app)/environments/[environmentId]/actions.ts b/apps/web/app/(app)/environments/[environmentId]/actions.ts index 1be53741c3..ed9c7fc822 100644 --- a/apps/web/app/(app)/environments/[environmentId]/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/actions.ts @@ -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, }, }); diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/google-sheets/actions.ts b/apps/web/app/(app)/environments/[environmentId]/integrations/google-sheets/actions.ts index 1cf2c6c2aa..08e403420f 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/google-sheets/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/google-sheets/actions.ts @@ -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( diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/google-sheets/page.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/google-sheets/page.tsx index a9bc49678f..2d554b6e7f 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/google-sheets/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/google-sheets/page.tsx @@ -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); diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx index 90e8d14a35..d0f47dc020 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/page.tsx @@ -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; diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/actions.ts b/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/actions.ts index 402e6303f0..e62860046c 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/actions.ts @@ -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 ( diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/page.tsx b/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/page.tsx index 3c4839696d..62552e4570 100644 --- a/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/page.tsx @@ -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([ diff --git a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(activitySection)/ActivitySection.tsx b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(activitySection)/ActivitySection.tsx index 402320a1ec..c0ee191534 100644 --- a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(activitySection)/ActivitySection.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(activitySection)/ActivitySection.tsx @@ -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, diff --git a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(attributeSection)/AttributesSection.tsx b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(attributeSection)/AttributesSection.tsx index 3c4da368f8..68e1657130 100644 --- a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(attributeSection)/AttributesSection.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/(attributeSection)/AttributesSection.tsx @@ -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); diff --git a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/HeadingSection.tsx b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/HeadingSection.tsx index a7ccdb7d4e..5cfa9dcf2d 100644 --- a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/HeadingSection.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/HeadingSection.tsx @@ -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; diff --git a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/actions.ts b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/actions.ts index e108d8ed65..7ebeea9a32 100644 --- a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/actions.ts @@ -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); diff --git a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/page.tsx b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/page.tsx index 9bb2fbf8fd..9d8e2df966 100644 --- a/apps/web/app/(app)/environments/[environmentId]/people/[personId]/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/people/[personId]/page.tsx @@ -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); diff --git a/apps/web/app/(app)/environments/[environmentId]/people/page.tsx b/apps/web/app/(app)/environments/[environmentId]/people/page.tsx index 18e234457d..bc48cd39d9 100644 --- a/apps/web/app/(app)/environments/[environmentId]/people/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/people/page.tsx @@ -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"; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/ApiKeyList.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/ApiKeyList.tsx index 0ab082d63c..26531ae960 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/ApiKeyList.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/ApiKeyList.tsx @@ -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, diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/page.tsx index 74d15b10cf..8d405f1f5a 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/page.tsx @@ -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); diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/billing/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/billing/page.tsx index d71594bf91..748debd6e6 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/billing/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/billing/page.tsx @@ -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"; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/layout.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/layout.tsx index 08561f7430..fcf7351fd5 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/layout.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/layout.tsx @@ -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", diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/EditPlacement.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/EditPlacement.tsx index f14b4529df..348fef9813 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/EditPlacement.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/EditPlacement.tsx @@ -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}`); diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/actions.ts index 46da36abac..64dec47677 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/actions.ts @@ -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) { diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/page.tsx index 8c725ae937..1a8d3e59c5 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/page.tsx @@ -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 (
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/members/EditMemberships/EditMemberships.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/members/EditMemberships/EditMemberships.tsx index 682fac982c..006f0490f8 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/members/EditMemberships/EditMemberships.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/members/EditMemberships/EditMemberships.tsx @@ -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 = { diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/members/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/members/actions.ts index c9277e84a1..a5086ba087 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/members/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/members/actions.ts @@ -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"; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/members/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/members/page.tsx index 60255162ec..4039652774 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/members/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/members/page.tsx @@ -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"; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/product/DeleteProduct.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/product/DeleteProduct.tsx index 538441f6cf..b785cbd545 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/product/DeleteProduct.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/product/DeleteProduct.tsx @@ -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; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/product/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/product/actions.ts index 300928c2f0..cffaa22192 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/product/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/product/actions.ts @@ -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 ( diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/product/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/product/page.tsx index 0af1b64da5..97fb952f55 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/product/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/product/page.tsx @@ -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([ diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/profile/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/profile/actions.ts index c1cd181fbc..c5fb462456 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/profile/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/profile/actions.ts @@ -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"; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/profile/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/profile/page.tsx index f3811fc6af..5dbaa31056 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/profile/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/profile/page.tsx @@ -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); diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/setup/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/setup/actions.ts index 196f115cd1..5c64a603ad 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/setup/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/setup/actions.ts @@ -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( diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/setup/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/setup/page.tsx index a850c80ea5..5141a529be 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/setup/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/setup/page.tsx @@ -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"; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/tags/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/tags/page.tsx index b113d9f238..1bac4c3cf8 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/tags/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/tags/page.tsx @@ -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); diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/PreviewSurvey.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/PreviewSurvey.tsx index fd9243d063..55ca0a10a3 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/PreviewSurvey.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/PreviewSurvey.tsx @@ -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(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" ? ( { const [survey, team, allResponses] = await Promise.all([ diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/actions.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/actions.ts index e774c9d3ee..62dd788fef 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/actions.ts @@ -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"; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/page.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/page.tsx index 49bcc05eb5..693db40a42 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/page.tsx @@ -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 }) { diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/page.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/page.tsx index c9a4526191..bec093bcc2 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/page.tsx @@ -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"; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/Placement.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/Placement.tsx new file mode 100644 index 0000000000..a101cc365c --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/Placement.tsx @@ -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 ( + <> +
+ setCurrentPlacement(e as PlacementType)} value={currentPlacement}> + {placements.map((placement) => ( +
+ + +
+ ))} +
+
+
+
+
+ {currentPlacement === "center" && ( + <> +
+ + setOverlay(overlay)} + value={overlay} + className="flex space-x-4"> +
+ + +
+
+ + +
+
+
+
+ + setClickOutside(value === "allow")} + value={clickOutside ? "allow" : "disallow"} + className="flex space-x-4"> +
+ + +
+
+ + +
+
+
+ + )} + + ); +} diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/SettingsView.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/SettingsView.tsx index e31becd710..ce33da42f4 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/SettingsView.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/SettingsView.tsx @@ -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} /> + +
); } diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/StylingCard.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/StylingCard.tsx new file mode 100644 index 0000000000..1cb9d8f9d6 --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/StylingCard.tsx @@ -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>; +} + +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 ( + + +
+
+ +
+
+

Styling

+

Overwrite global styling settings

+
+
+
+ +
+
+ {/* Brand Color */} +
+
+ + +
+ {brandColor && ( +
+
+ + +
+
+ )} +
+ {/* positioning */} + {type !== "link" && ( +
+
+ + +
+ {placement && ( +
+
+
+ +
+
+
+ )} +
+ )} + {/* Highlight border */} + {type !== "link" && ( +
+
+ + +
+ {!!highlightBorderColor && ( +
+
+ +

Show highlight border

+
+ {!!highlightBorderColor && ( +
+ + +
+ )} +
+ )} +
+ )} +
+
+
+ ); +} diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/SurveyEditor.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/SurveyEditor.tsx index ee8991e413..69171d4e36 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/SurveyEditor.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/SurveyEditor.tsx @@ -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) { diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/page.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/page.tsx index 846e0ba128..4080ecece9 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/page.tsx @@ -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"; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/page.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/page.tsx index 18ad886b6f..b010102b8a 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/page.tsx @@ -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"; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/page.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/page.tsx index 9e4c312f3d..9d374f7822 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/page.tsx @@ -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; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts index ee382b6fcb..b32e373d4e 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts @@ -2062,5 +2062,6 @@ export const minimalSurvey: TSurvey = { surveyClosedMessage: { enabled: false, }, + productOverwrites: null, singleUse: null, }; diff --git a/apps/web/app/(app)/onboarding/actions.ts b/apps/web/app/(app)/onboarding/actions.ts index 89537f0007..6608688625 100644 --- a/apps/web/app/(app)/onboarding/actions.ts +++ b/apps/web/app/(app)/onboarding/actions.ts @@ -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"; diff --git a/apps/web/app/(app)/onboarding/page.tsx b/apps/web/app/(app)/onboarding/page.tsx index 56796e13e4..bf9d39247a 100644 --- a/apps/web/app/(app)/onboarding/page.tsx +++ b/apps/web/app/(app)/onboarding/page.tsx @@ -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) { diff --git a/apps/web/app/(redirects)/products/[productId]/route.ts b/apps/web/app/(redirects)/products/[productId]/route.ts index 1ff1bc6506..730cddadd8 100644 --- a/apps/web/app/(redirects)/products/[productId]/route.ts +++ b/apps/web/app/(redirects)/products/[productId]/route.ts @@ -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"; diff --git a/apps/web/app/(redirects)/teams/[teamId]/route.ts b/apps/web/app/(redirects)/teams/[teamId]/route.ts index 7b67901dfe..d392b2582c 100644 --- a/apps/web/app/(redirects)/teams/[teamId]/route.ts +++ b/apps/web/app/(redirects)/teams/[teamId]/route.ts @@ -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"; diff --git a/apps/web/app/api/auth/[...nextauth]/authOptions.ts b/apps/web/app/api/auth/[...nextauth]/authOptions.ts index 5dbdefac8b..29e66a9ca7 100644 --- a/apps/web/app/api/auth/[...nextauth]/authOptions.ts +++ b/apps/web/app/api/auth/[...nextauth]/authOptions.ts @@ -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"; diff --git a/apps/web/app/api/integration/integrations.ts b/apps/web/app/api/integration/integrations.ts index c28c768867..09ba00fb17 100644 --- a/apps/web/app/api/integration/integrations.ts +++ b/apps/web/app/api/integration/integrations.ts @@ -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"; diff --git a/apps/web/app/api/v1/client/displays/route.ts b/apps/web/app/api/v1/client/displays/route.ts index 9ac23ee5b2..d70800e546 100644 --- a/apps/web/app/api/v1/client/displays/route.ts +++ b/apps/web/app/api/v1/client/displays/route.ts @@ -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"; diff --git a/apps/web/app/api/v1/client/responses/route.ts b/apps/web/app/api/v1/client/responses/route.ts index 0def24c540..72595bbde8 100644 --- a/apps/web/app/api/v1/client/responses/route.ts +++ b/apps/web/app/api/v1/client/responses/route.ts @@ -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"; diff --git a/apps/web/app/api/v1/js/people/[personId]/set-attribute/route.ts b/apps/web/app/api/v1/js/people/[personId]/set-attribute/route.ts index 6d959707cc..208692f88d 100644 --- a/apps/web/app/api/v1/js/people/[personId]/set-attribute/route.ts +++ b/apps/web/app/api/v1/js/people/[personId]/set-attribute/route.ts @@ -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"; diff --git a/apps/web/app/api/v1/js/people/[personId]/set-user-id/route.ts b/apps/web/app/api/v1/js/people/[personId]/set-user-id/route.ts index 2d2197ae70..0b8fa446c1 100644 --- a/apps/web/app/api/v1/js/people/[personId]/set-user-id/route.ts +++ b/apps/web/app/api/v1/js/people/[personId]/set-user-id/route.ts @@ -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"; diff --git a/apps/web/app/api/v1/js/sync/lib/sync.ts b/apps/web/app/api/v1/js/sync/lib/sync.ts index e865231f6e..a180fa82af 100644 --- a/apps/web/app/api/v1/js/sync/lib/sync.ts +++ b/apps/web/app/api/v1/js/sync/lib/sync.ts @@ -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"; diff --git a/apps/web/app/api/v1/management/people/[personId]/route.ts b/apps/web/app/api/v1/management/people/[personId]/route.ts index 420d62f77c..f4511bbaa3 100644 --- a/apps/web/app/api/v1/management/people/[personId]/route.ts +++ b/apps/web/app/api/v1/management/people/[personId]/route.ts @@ -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"; diff --git a/apps/web/app/api/v1/management/people/route.ts b/apps/web/app/api/v1/management/people/route.ts index ff36af3654..7be8d4ed7e 100644 --- a/apps/web/app/api/v1/management/people/route.ts +++ b/apps/web/app/api/v1/management/people/route.ts @@ -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"; diff --git a/apps/web/app/api/v1/teams/[teamId]/add_demo_product/route.ts b/apps/web/app/api/v1/teams/[teamId]/add_demo_product/route.ts index f1b2b818af..ca2b18b0f4 100644 --- a/apps/web/app/api/v1/teams/[teamId]/add_demo_product/route.ts +++ b/apps/web/app/api/v1/teams/[teamId]/add_demo_product/route.ts @@ -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"; diff --git a/apps/web/app/api/v1/users/route.ts b/apps/web/app/api/v1/users/route.ts index b4352157eb..bcb482e079 100644 --- a/apps/web/app/api/v1/users/route.ts +++ b/apps/web/app/api/v1/users/route.ts @@ -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) { diff --git a/apps/web/app/api/v1/webhooks/[webhookId]/route.ts b/apps/web/app/api/v1/webhooks/[webhookId]/route.ts index f0d51dcba9..7616554406 100644 --- a/apps/web/app/api/v1/webhooks/[webhookId]/route.ts +++ b/apps/web/app/api/v1/webhooks/[webhookId]/route.ts @@ -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 } }) { diff --git a/apps/web/app/api/v1/webhooks/route.ts b/apps/web/app/api/v1/webhooks/route.ts index ae1fd52d05..cd9924c4ea 100644 --- a/apps/web/app/api/v1/webhooks/route.ts +++ b/apps/web/app/api/v1/webhooks/route.ts @@ -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"; diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index e6489cb9d8..9e23d1606d 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -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"; diff --git a/apps/web/app/s/[surveyId]/LinkSurvey.tsx b/apps/web/app/s/[surveyId]/LinkSurvey.tsx index 242b04d463..a42d618f2f 100644 --- a/apps/web/app/s/[surveyId]/LinkSurvey.tsx +++ b/apps/web/app/s/[surveyId]/LinkSurvey.tsx @@ -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({ )} { if (!isPreview) { diff --git a/apps/web/app/s/[surveyId]/page.tsx b/apps/web/app/s/[surveyId]/page.tsx index 43a4940dc3..7eb4bc1475 100644 --- a/apps/web/app/s/[surveyId]/page.tsx +++ b/apps/web/app/s/[surveyId]/page.tsx @@ -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"; diff --git a/apps/web/components/environments/SecondNavBar.tsx b/apps/web/components/environments/SecondNavBar.tsx index f825f92710..c0c9751a5c 100644 --- a/apps/web/components/environments/SecondNavBar.tsx +++ b/apps/web/components/environments/SecondNavBar.tsx @@ -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 { diff --git a/apps/web/components/shared/EnvironmentNotice.tsx b/apps/web/components/shared/EnvironmentNotice.tsx index 0212673a65..e61d68d7a0 100644 --- a/apps/web/components/shared/EnvironmentNotice.tsx +++ b/apps/web/components/shared/EnvironmentNotice.tsx @@ -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"; diff --git a/apps/web/pages/api/v1/environments/[environmentId]/product/index.ts b/apps/web/pages/api/v1/environments/[environmentId]/product/index.ts index 1c4043240d..cd7888f969 100644 --- a/apps/web/pages/api/v1/environments/[environmentId]/product/index.ts +++ b/apps/web/pages/api/v1/environments/[environmentId]/product/index.ts @@ -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) { diff --git a/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/duplicate/[targetEnvironmentId].ts b/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/duplicate/[targetEnvironmentId].ts index 97e77d3695..24df961675 100644 --- a/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/duplicate/[targetEnvironmentId].ts +++ b/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/duplicate/[targetEnvironmentId].ts @@ -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, }, }); diff --git a/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/duplicate/index.ts b/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/duplicate/index.ts index 55757e850c..858a08e29e 100644 --- a/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/duplicate/index.ts +++ b/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/duplicate/index.ts @@ -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, }, }); diff --git a/apps/web/uploads/cln2o8ist000g19nh5zqpze7e/public/WallPaper 19.png b/apps/web/uploads/cln2o8ist000g19nh5zqpze7e/public/WallPaper 19.png new file mode 100644 index 0000000000..4265c54486 Binary files /dev/null and b/apps/web/uploads/cln2o8ist000g19nh5zqpze7e/public/WallPaper 19.png differ diff --git a/apps/web/uploads/cln2o8ist000g19nh5zqpze7e/public/cpTree.png b/apps/web/uploads/cln2o8ist000g19nh5zqpze7e/public/cpTree.png new file mode 100644 index 0000000000..44471ed920 Binary files /dev/null and b/apps/web/uploads/cln2o8ist000g19nh5zqpze7e/public/cpTree.png differ diff --git a/packages/database/jsonTypes.ts b/packages/database/jsonTypes.ts index d0f5b170be..f751e931ab 100644 --- a/packages/database/jsonTypes.ts +++ b/packages/database/jsonTypes.ts @@ -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; diff --git a/packages/database/migrations/20231003122730_survey_styling_overwrites/migration.sql b/packages/database/migrations/20231003122730_survey_styling_overwrites/migration.sql new file mode 100644 index 0000000000..c4f6a4c876 --- /dev/null +++ b/packages/database/migrations/20231003122730_survey_styling_overwrites/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Survey" ADD COLUMN "productOverwrites" JSONB; diff --git a/packages/database/schema.prisma b/packages/database/schema.prisma index 479dd5816d..48825e745d 100644 --- a/packages/database/schema.prisma +++ b/packages/database/schema.prisma @@ -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 { diff --git a/packages/database/zod-utils.ts b/packages/database/zod-utils.ts index f349b8042a..9106c3f1bb 100644 --- a/packages/database/zod-utils.ts +++ b/packages/database/zod-utils.ts @@ -10,6 +10,7 @@ export { ZSurveyQuestions, ZSurveyThankYouCard, ZSurveyClosedMessage, + ZSurveyProductOverwrites, ZSurveyVerifyEmail, ZSurveySingleUse, } from "@formbricks/types/v1/surveys"; diff --git a/packages/js/src/lib/widget.ts b/packages/js/src/lib/widget.ts index ade5122649..59be662251 100644 --- a/packages/js/src/lib/widget.ts +++ b/packages/js/src/lib/widget.ts @@ -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( { diff --git a/packages/lib/action/service.ts b/packages/lib/action/service.ts index 08038ba032..018aa0bedc 100644 --- a/packages/lib/action/service.ts +++ b/packages/lib/action/service.ts @@ -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 => { diff --git a/packages/lib/services/activity.tsx b/packages/lib/activity/service.ts similarity index 100% rename from packages/lib/services/activity.tsx rename to packages/lib/activity/service.ts diff --git a/packages/lib/display/service.ts b/packages/lib/display/service.ts index 82125f2a81..9d4114f36c 100644 --- a/packages/lib/display/service.ts +++ b/packages/lib/display/service.ts @@ -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, diff --git a/packages/lib/services/environment.ts b/packages/lib/environment/service.ts similarity index 93% rename from packages/lib/services/environment.ts rename to packages/lib/environment/service.ts index 1657d3d6a8..e104e3c74a 100644 --- a/packages/lib/services/environment.ts +++ b/packages/lib/environment/service.ts @@ -136,9 +136,8 @@ export const updateEnvironment = async ( export const getFirstEnvironmentByUserId = async (userId: string): Promise => { 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 `teams-${teamId}-products`; const getProductCacheTag = (environmentId: string): string => `environments-${environmentId}-product`; diff --git a/packages/lib/services/profile.ts b/packages/lib/profile/service.ts similarity index 99% rename from packages/lib/services/profile.ts rename to packages/lib/profile/service.ts index 74ca017421..fd2687f1aa 100644 --- a/packages/lib/services/profile.ts +++ b/packages/lib/profile/service.ts @@ -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"; diff --git a/packages/lib/response/service.ts b/packages/lib/response/service.ts index 188685c653..852fa06a3e 100644 --- a/packages/lib/response/service.ts +++ b/packages/lib/response/service.ts @@ -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"; diff --git a/packages/lib/services/responseNote.ts b/packages/lib/responseNote/service.ts similarity index 100% rename from packages/lib/services/responseNote.ts rename to packages/lib/responseNote/service.ts diff --git a/packages/lib/services/session.ts b/packages/lib/session/service.ts similarity index 100% rename from packages/lib/services/session.ts rename to packages/lib/session/service.ts diff --git a/packages/lib/survey/service.ts b/packages/lib/survey/service.ts index df3e4c3ac7..c77b937d6b 100644 --- a/packages/lib/survey/service.ts +++ b/packages/lib/survey/service.ts @@ -42,6 +42,7 @@ export const selectSurvey = { autoComplete: true, verifyEmail: true, redirectUrl: true, + productOverwrites: true, surveyClosedMessage: true, singleUse: true, triggers: { diff --git a/packages/lib/tagOnResponse/auth.ts b/packages/lib/tagOnResponse/auth.ts index 14819c76da..abf446d2a1 100644 --- a/packages/lib/tagOnResponse/auth.ts +++ b/packages/lib/tagOnResponse/auth.ts @@ -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 ( diff --git a/packages/lib/services/tagOnResponse.ts b/packages/lib/tagOnResponse/service.ts similarity index 100% rename from packages/lib/services/tagOnResponse.ts rename to packages/lib/tagOnResponse/service.ts diff --git a/packages/lib/services/team.ts b/packages/lib/team/service.ts similarity index 100% rename from packages/lib/services/team.ts rename to packages/lib/team/service.ts diff --git a/packages/lib/services/teamDetails.ts b/packages/lib/teamDetail/service.ts similarity index 100% rename from packages/lib/services/teamDetails.ts rename to packages/lib/teamDetail/service.ts diff --git a/packages/lib/services/webhook.ts b/packages/lib/webhook/service.ts similarity index 100% rename from packages/lib/services/webhook.ts rename to packages/lib/webhook/service.ts diff --git a/packages/types/surveys.ts b/packages/types/surveys.ts index a99a8ca0e5..8d1b413cdb 100644 --- a/packages/types/surveys.ts +++ b/packages/types/surveys.ts @@ -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 { diff --git a/packages/types/v1/common.ts b/packages/types/v1/common.ts new file mode 100644 index 0000000000..8ed5e6fbb4 --- /dev/null +++ b/packages/types/v1/common.ts @@ -0,0 +1,4 @@ +import { z } from "zod"; + +export const ZColor = z.string().regex(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/); +export const ZSurveyPlacement = z.enum(["bottomLeft", "bottomRight", "topLeft", "topRight", "center"]); diff --git a/packages/types/v1/product.ts b/packages/types/v1/product.ts index 4f95cec1de..8140414d79 100644 --- a/packages/types/v1/product.ts +++ b/packages/types/v1/product.ts @@ -1,5 +1,6 @@ import { z } from "zod"; import { ZEnvironment } from "./environment"; +import { ZColor, ZSurveyPlacement } from "./common"; export const ZProduct = z.object({ id: z.string().cuid2(), @@ -7,14 +8,11 @@ export const ZProduct = z.object({ updatedAt: z.date(), name: z.string(), teamId: z.string(), - brandColor: z.string().regex(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/), - highlightBorderColor: z - .string() - .regex(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/) - .nullable(), + brandColor: ZColor, + highlightBorderColor: ZColor.nullable(), recontactDays: z.number().int(), formbricksSignature: z.boolean(), - placement: z.enum(["bottomLeft", "bottomRight", "topLeft", "topRight", "center"]), + placement: ZSurveyPlacement, clickOutsideClose: z.boolean(), darkOverlay: z.boolean(), environments: z.array(ZEnvironment), diff --git a/packages/types/v1/surveys.ts b/packages/types/v1/surveys.ts index 41534d9ac5..a09ad8ae7d 100644 --- a/packages/types/v1/surveys.ts +++ b/packages/types/v1/surveys.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { ZActionClass } from "./actionClasses"; import { QuestionType } from "../questions"; +import { ZColor, ZSurveyPlacement } from "./common"; export const ZSurveyThankYouCard = z.object({ enabled: z.boolean(), @@ -8,6 +9,16 @@ export const ZSurveyThankYouCard = z.object({ subheader: z.optional(z.string()), }); +export const ZSurveyProductOverwrites = z.object({ + brandColor: ZColor.nullish(), + highlightBorderColor: ZColor.nullish(), + placement: ZSurveyPlacement.nullish(), + clickOutside: z.boolean().nullish(), + darkOverlay: z.boolean().nullish(), +}); + +export type TSurveyProductOverwrites = z.infer; + export const ZSurveyClosedMessage = z .object({ enabled: z.boolean().optional(), @@ -269,6 +280,7 @@ export const ZSurvey = z.object({ delay: z.number(), autoComplete: z.number().nullable(), closeOnDate: z.date().nullable(), + productOverwrites: ZSurveyProductOverwrites.nullable(), surveyClosedMessage: ZSurveyClosedMessage.nullable(), singleUse: ZSurveySingleUse.nullable(), verifyEmail: ZSurveyVerifyEmail.nullable(),