diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore index a324a42299..aeb7219b23 100644 --- a/apps/docs/.gitignore +++ b/apps/docs/.gitignore @@ -35,3 +35,4 @@ yarn-error.log* next-env.d.ts public/sitemap*.xml +public/robots.txt \ No newline at end of file diff --git a/apps/docs/app/app-surveys/framework-guides/page.mdx b/apps/docs/app/app-surveys/framework-guides/page.mdx index 20994e4141..279db721a1 100644 --- a/apps/docs/app/app-surveys/framework-guides/page.mdx +++ b/apps/docs/app/app-surveys/framework-guides/page.mdx @@ -2,7 +2,6 @@ import { MdxImage } from "@/components/MdxImage"; import { Libraries } from "./components/Libraries"; -import SetupChecklist from "./images/env-id.png"; import ReactApp from "./images/react-in-app-survey-app-popup-form.webp"; import WidgetConnected from "./images/widget-connected.webp"; import WidgetNotConnected from "./images/widget-not-connected.webp"; @@ -27,14 +26,7 @@ for something else, please [join our Discord!](https://formbricks.com/discord) a Before getting started, make sure you have: 1. A web application (behind your user authentication system) in your desired framework is set up and running. -2. A Formbricks account with access to your environment ID and API host. You can find these in the **Setup Checklist** in the Settings: - - +2. A Formbricks account with access to your environment ID and API host. You can find these in the **Setup Checklist** in the Settings. --- diff --git a/apps/docs/app/website-surveys/framework-guides/page.mdx b/apps/docs/app/website-surveys/framework-guides/page.mdx index c291416d31..ea6f568243 100644 --- a/apps/docs/app/website-surveys/framework-guides/page.mdx +++ b/apps/docs/app/website-surveys/framework-guides/page.mdx @@ -2,7 +2,6 @@ import { MdxImage } from "@/components/MdxImage"; import { Libraries } from "./components/Libraries"; -import SetupChecklist from "./images/env-id.png"; import ReactApp from "./images/react-in-app-survey-app-popup-form.webp"; import WidgetConnected from "./images/widget-connected.webp"; import WidgetNotConnected from "./images/widget-not-connected.webp"; @@ -31,14 +30,7 @@ Detailed Website Survey SDK documentation can be found [here](/developer-docs/we Before getting started, make sure you have: 1. A **public-facing** web application in your desired framework is set up and running. -2. A Formbricks account with access to your environment ID and API host. You can find these in the **Setup Checklist** in the Settings: - - +2. A Formbricks account with access to your environment ID and API host. You can find these in the **Setup Checklist** in the Settings. --- diff --git a/apps/docs/components/Footer.tsx b/apps/docs/components/Footer.tsx index 6470e2ee06..2259d829d2 100644 --- a/apps/docs/components/Footer.tsx +++ b/apps/docs/components/Footer.tsx @@ -3,9 +3,11 @@ import { navigation } from "@/lib/navigation"; import Link from "next/link"; import { usePathname } from "next/navigation"; -import { FaDiscord, FaGithub, FaXTwitter } from "react-icons/fa6"; import { Button } from "./Button"; +import { DiscordIcon } from "./icons/DiscordIcon"; +import { GithubIcon } from "./icons/GithubIcon"; +import { TwitterIcon } from "./icons/TwitterIcon"; function PageLink({ label, @@ -98,13 +100,13 @@ function SmallPrint() { Formbricks GmbH © {currentYear}. All rights reserved.

- + Follow us on Twitter - + Follow us on GitHub - + Join our Discord server
diff --git a/apps/docs/components/icons/DiscordIcon.tsx b/apps/docs/components/icons/DiscordIcon.tsx new file mode 100644 index 0000000000..3007444289 --- /dev/null +++ b/apps/docs/components/icons/DiscordIcon.tsx @@ -0,0 +1,15 @@ +export const DiscordIcon: React.FC> = (props) => { + return ( + + + + ); +}; diff --git a/apps/docs/components/icons/GithubIcon.tsx b/apps/docs/components/icons/GithubIcon.tsx new file mode 100644 index 0000000000..21b613925a --- /dev/null +++ b/apps/docs/components/icons/GithubIcon.tsx @@ -0,0 +1,15 @@ +export const GithubIcon: React.FC> = (props) => { + return ( + + + + ); +}; diff --git a/apps/docs/components/icons/TwitterIcon.tsx b/apps/docs/components/icons/TwitterIcon.tsx new file mode 100644 index 0000000000..0731b51ffb --- /dev/null +++ b/apps/docs/components/icons/TwitterIcon.tsx @@ -0,0 +1,15 @@ +export const TwitterIcon: React.FC> = (props) => { + return ( + + + + ); +}; diff --git a/apps/docs/package.json b/apps/docs/package.json index 1a911702f5..6dad3ddb7c 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -1,5 +1,5 @@ { - "name": "@formbricks/formbricks-com", + "name": "@formbricks/docs", "version": "1.0.0", "private": true, "scripts": { @@ -50,7 +50,6 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-highlight-words": "^0.20.0", - "react-icons": "^5.2.1", "react-markdown": "^9.0.1", "react-responsive-embed": "^2.1.0", "remark": "^15.0.1", diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json index d956201bcd..c36afde61e 100644 --- a/apps/docs/tsconfig.json +++ b/apps/docs/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "@formbricks/tsconfig/nextjs.json", "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "../../packages/types/*.d.ts"], - "exclude": ["../../.env"], + "exclude": ["../../.env", "node_modules"], "compilerOptions": { "baseUrl": ".", "paths": { diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/layout.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/layout.tsx new file mode 100644 index 0000000000..1c37ae43da --- /dev/null +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/layout.tsx @@ -0,0 +1,57 @@ +import FormbricksClient from "@/app/(app)/components/FormbricksClient"; +import PosthogIdentify from "@/app/(app)/environments/[environmentId]/components/PosthogIdentify"; +import { ResponseFilterProvider } from "@/app/(app)/environments/[environmentId]/components/ResponseFilterContext"; +import { getServerSession } from "next-auth"; +import { redirect } from "next/navigation"; + +import { authOptions } from "@formbricks/lib/authOptions"; +import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth"; +import { getEnvironment } from "@formbricks/lib/environment/service"; +import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; +import { AuthorizationError } from "@formbricks/types/errors"; +import { DevEnvironmentBanner } from "@formbricks/ui/DevEnvironmentBanner"; +import ToasterClient from "@formbricks/ui/ToasterClient"; + +export default async function EnvLayout({ children, params }) { + const session = await getServerSession(authOptions); + if (!session || !session.user) { + return redirect(`/auth/login`); + } + const hasAccess = await hasUserEnvironmentAccess(session.user.id, params.environmentId); + if (!hasAccess) { + throw new AuthorizationError("Not authorized"); + } + + const team = await getTeamByEnvironmentId(params.environmentId); + if (!team) { + throw new Error("Team not found"); + } + + const environment = await getEnvironment(params.environmentId); + + if (!environment) { + throw new Error("Environment not found"); + } + + return ( + <> + + + + +
+ +
{children}
+
+
+ + ); +} diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions.ts b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/actions.ts similarity index 98% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions.ts rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/actions.ts index c3c943de09..568591b153 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions.ts +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/actions.ts @@ -61,7 +61,7 @@ export const deleteSurveyAction = async (surveyId: string) => { await deleteSurvey(surveyId); }; -export const refetchProduct = async (productId: string): Promise => { +export const refetchProductAction = async (productId: string): Promise => { const session = await getServerSession(authOptions); if (!session) throw new AuthorizationError("Not authorized"); diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/AddActionModal.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AddActionModal.tsx similarity index 85% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/AddActionModal.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AddActionModal.tsx index 800a223d29..a31c3c4970 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/AddActionModal.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AddActionModal.tsx @@ -1,12 +1,12 @@ "use client"; -import { CreateNewActionTab } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/CreateNewActionTab"; -import { SavedActionsTab } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/SavedActionsTab"; - import { TActionClass } from "@formbricks/types/actionClasses"; import { TSurvey } from "@formbricks/types/surveys"; import { ModalWithTabs } from "@formbricks/ui/ModalWithTabs"; +import { CreateNewActionTab } from "./CreateNewActionTab"; +import { SavedActionsTab } from "./SavedActionsTab"; + interface AddActionModalProps { open: boolean; setOpen: React.Dispatch>; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/AddQuestionButton.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AddQuestionButton.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/AddQuestionButton.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AddQuestionButton.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/AddressQuestionForm.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AddressQuestionForm.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/AddressQuestionForm.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AddressQuestionForm.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedSettings.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedSettings.tsx similarity index 88% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedSettings.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedSettings.tsx index eec753d4a5..f62224a873 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedSettings.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AdvancedSettings.tsx @@ -1,7 +1,6 @@ -import LogicEditor from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditor"; - import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys"; +import LogicEditor from "./LogicEditor"; import UpdateQuestionId from "./UpdateQuestionId"; interface AdvancedSettingsProps { diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/AnimatedSurveyBg.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AnimatedSurveyBg.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/AnimatedSurveyBg.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/AnimatedSurveyBg.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/BackgroundStylingCard.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/BackgroundStylingCard.tsx similarity index 98% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/BackgroundStylingCard.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/BackgroundStylingCard.tsx index 4ea94628d3..daa3eee841 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/BackgroundStylingCard.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/BackgroundStylingCard.tsx @@ -23,7 +23,7 @@ interface BackgroundStylingCardProps { isUnsplashConfigured: boolean; } -export default function BackgroundStylingCard({ +export const BackgroundStylingCard = ({ open, setOpen, styling, @@ -33,7 +33,7 @@ export default function BackgroundStylingCard({ disabled, environmentId, isUnsplashConfigured, -}: BackgroundStylingCardProps) { +}: BackgroundStylingCardProps) => { const { bgType, brightness } = styling?.background ?? {}; const handleBgChange = (color: string, type: TSurveyBackgroundBgType) => { @@ -150,4 +150,4 @@ export default function BackgroundStylingCard({ ); -} +}; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/CTAQuestionForm.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/CTAQuestionForm.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/CTAQuestionForm.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/CTAQuestionForm.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/CalQuestionForm.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/CalQuestionForm.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/CalQuestionForm.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/CalQuestionForm.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/CardStylingSettings.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/CardStylingSettings.tsx similarity index 99% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/CardStylingSettings.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/CardStylingSettings.tsx index fd253a1438..5c585ff799 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/CardStylingSettings.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/CardStylingSettings.tsx @@ -27,7 +27,7 @@ type CardStylingSettingsProps = { localProduct: TProduct; }; -const CardStylingSettings = ({ +export const CardStylingSettings = ({ setStyling, styling, isSettingsPage = false, @@ -300,5 +300,3 @@ const CardStylingSettings = ({ ); }; - -export default CardStylingSettings; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/ColorSurveyBg.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ColorSurveyBg.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/ColorSurveyBg.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ColorSurveyBg.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConsentQuestionForm.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConsentQuestionForm.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConsentQuestionForm.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ConsentQuestionForm.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/CreateNewActionTab.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/CreateNewActionTab.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/CreateNewActionTab.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/CreateNewActionTab.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/DateQuestionForm.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/DateQuestionForm.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/DateQuestionForm.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/DateQuestionForm.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditThankYouCard.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditThankYouCard.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditThankYouCard.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditThankYouCard.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditWelcomeCard.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditWelcomeCard.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditWelcomeCard.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/EditWelcomeCard.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/FileUploadQuestionForm.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/FileUploadQuestionForm.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/FileUploadQuestionForm.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/FileUploadQuestionForm.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/FormStylingSettings.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/FormStylingSettings.tsx similarity index 98% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/FormStylingSettings.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/FormStylingSettings.tsx index 1b84437715..2f7dc1ea8f 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/FormStylingSettings.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/FormStylingSettings.tsx @@ -21,7 +21,7 @@ type FormStylingSettingsProps = { disabled?: boolean; }; -const FormStylingSettings = ({ +export const FormStylingSettings = ({ styling, setStyling, open, @@ -201,5 +201,3 @@ const FormStylingSettings = ({ ); }; - -export default FormStylingSettings; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/HiddenFieldsCard.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/HiddenFieldsCard.tsx similarity index 97% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/HiddenFieldsCard.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/HiddenFieldsCard.tsx index 9fbd079a9f..8c68b86d8c 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/HiddenFieldsCard.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/HiddenFieldsCard.tsx @@ -1,6 +1,5 @@ "use client"; -import { validateId } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/lib/validation"; import * as Collapsible from "@radix-ui/react-collapsible"; import { FC, useState } from "react"; import { toast } from "react-hot-toast"; @@ -13,6 +12,8 @@ import { Label } from "@formbricks/ui/Label"; import { Switch } from "@formbricks/ui/Switch"; import { Tag } from "@formbricks/ui/Tag"; +import { validateId } from "../lib/validation"; + interface HiddenFieldsCardProps { localSurvey: TSurvey; setLocalSurvey: (survey: TSurvey) => void; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/HowToSendCard.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/HowToSendCard.tsx similarity index 99% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/HowToSendCard.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/HowToSendCard.tsx index 5c5b783a9f..686a4d1ae2 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/HowToSendCard.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/HowToSendCard.tsx @@ -182,7 +182,7 @@ export default function HowToSendCard({ localSurvey, setLocalSurvey, environment

Connect Formbricks diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/ImageSurveyBg.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ImageSurveyBg.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/ImageSurveyBg.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/ImageSurveyBg.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/LoadingSkeleton.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LoadingSkeleton.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/LoadingSkeleton.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LoadingSkeleton.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditor.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditor.tsx similarity index 98% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditor.tsx rename to apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditor.tsx index c0d5354750..f95543143f 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditor.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/LogicEditor.tsx @@ -1,8 +1,8 @@ import { HelpCircle, TrashIcon } from "lucide-react"; import { ChevronDown, SplitIcon } from "lucide-react"; +import { CornerDownRightIcon, MoveDownIcon } from "lucide-react"; import { useMemo } from "react"; import { toast } from "react-hot-toast"; -import { BsArrowDown, BsArrowReturnRight } from "react-icons/bs"; import { getLocalizedValue } from "@formbricks/lib/i18n/utils"; import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone"; @@ -269,7 +269,7 @@ export default function LogicEditor({

{question?.logic?.map((logic, logicIdx) => (
- +

If this answer

= ({ defaultValue={product.name} {...register("name", { required: true })} /> -
) : null} - diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/product/loading.tsx b/apps/web/app/(app)/environments/[environmentId]/product/general/loading.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/settings/product/loading.tsx rename to apps/web/app/(app)/environments/[environmentId]/product/general/loading.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/product/page.tsx b/apps/web/app/(app)/environments/[environmentId]/product/general/page.tsx similarity index 76% rename from apps/web/app/(app)/environments/[environmentId]/settings/product/page.tsx rename to apps/web/app/(app)/environments/[environmentId]/product/general/page.tsx index bf5a4b759a..1781a029af 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/product/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/product/general/page.tsx @@ -1,5 +1,7 @@ +import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation"; import { getServerSession } from "next-auth"; +import { getMultiLanguagePermission } from "@formbricks/ee/lib/service"; import { authOptions } from "@formbricks/lib/authOptions"; import { getEnvironment } from "@formbricks/lib/environment/service"; import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; @@ -7,10 +9,11 @@ import { getAccessFlags } from "@formbricks/lib/membership/utils"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { ErrorComponent } from "@formbricks/ui/ErrorComponent"; +import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; +import { PageHeader } from "@formbricks/ui/PageHeader"; import { SettingsId } from "@formbricks/ui/SettingsId"; -import SettingsCard from "../components/SettingsCard"; -import SettingsTitle from "../components/SettingsTitle"; +import SettingsCard from "../../settings/components/SettingsCard"; import DeleteProduct from "./components/DeleteProduct"; import EditProductName from "./components/EditProductName"; import EditWaitingTime from "./components/EditWaitingTime"; @@ -41,9 +44,18 @@ export default async function ProfileSettingsPage({ params }: { params: { enviro return ; } + const isMultiLanguageAllowed = await getMultiLanguagePermission(team); + return ( -
- + + + + + -
+ ); } diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/language/page.tsx b/apps/web/app/(app)/environments/[environmentId]/product/languages/page.tsx similarity index 67% rename from apps/web/app/(app)/environments/[environmentId]/settings/language/page.tsx rename to apps/web/app/(app)/environments/[environmentId]/product/languages/page.tsx index fd437f46a5..593e524c54 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/language/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/product/languages/page.tsx @@ -1,11 +1,13 @@ +import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation"; import SettingsCard from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard"; -import SettingsTitle from "@/app/(app)/environments/[environmentId]/settings/components/SettingsTitle"; import { notFound } from "next/navigation"; import { getMultiLanguagePermission } from "@formbricks/ee/lib/service"; import EditLanguage from "@formbricks/ee/multiLanguage/components/EditLanguage"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; import { getTeam } from "@formbricks/lib/team/service"; +import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; +import { PageHeader } from "@formbricks/ui/PageHeader"; export default async function LanguageSettingsPage({ params }: { params: { environmentId: string } }) { const product = await getProductByEnvironmentId(params.environmentId); @@ -27,13 +29,19 @@ export default async function LanguageSettingsPage({ params }: { params: { envir } return ( -
- + + + + -
+ ); } diff --git a/apps/web/app/(app)/environments/[environmentId]/product/layout.tsx b/apps/web/app/(app)/environments/[environmentId]/product/layout.tsx new file mode 100644 index 0000000000..b98e875db7 --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/product/layout.tsx @@ -0,0 +1,32 @@ +import { Metadata } from "next"; +import { getServerSession } from "next-auth"; + +import { authOptions } from "@formbricks/lib/authOptions"; +import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; +import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; + +export const metadata: Metadata = { + title: "Config", +}; + +export default async function ConfigLayout({ children, params }) { + const [team, product, session] = await Promise.all([ + getTeamByEnvironmentId(params.environmentId), + getProductByEnvironmentId(params.environmentId), + getServerSession(authOptions), + ]); + + if (!team) { + throw new Error("Team not found"); + } + + if (!product) { + throw new Error("Product not found"); + } + + if (!session) { + throw new Error("Unauthenticated"); + } + + return children; +} diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/actions.ts b/apps/web/app/(app)/environments/[environmentId]/product/look/actions.ts similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/actions.ts rename to apps/web/app/(app)/environments/[environmentId]/product/look/actions.ts diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/components/EditBranding.tsx b/apps/web/app/(app)/environments/[environmentId]/product/look/components/EditBranding.tsx similarity index 97% rename from apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/components/EditBranding.tsx rename to apps/web/app/(app)/environments/[environmentId]/product/look/components/EditBranding.tsx index 8c5014eeee..1cb8e7d9be 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/components/EditBranding.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/product/look/components/EditBranding.tsx @@ -17,12 +17,12 @@ interface EditFormbricksBrandingProps { environmentId: string; } -export function EditFormbricksBranding({ +export const EditFormbricksBranding = ({ type, product, canRemoveBranding, environmentId, -}: EditFormbricksBrandingProps) { +}: EditFormbricksBrandingProps) => { const [isBrandingEnabled, setIsBrandingEnabled] = useState( type === "linkSurvey" ? product.linkSurveyBranding : product.inAppSurveyBranding ); @@ -80,4 +80,4 @@ export function EditFormbricksBranding({ )}
); -} +}; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/components/EditLogo.tsx b/apps/web/app/(app)/environments/[environmentId]/product/look/components/EditLogo.tsx similarity index 99% rename from apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/components/EditLogo.tsx rename to apps/web/app/(app)/environments/[environmentId]/product/look/components/EditLogo.tsx index a203aec1df..6400657dbf 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/components/EditLogo.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/product/look/components/EditLogo.tsx @@ -165,7 +165,7 @@ export const EditLogo = ({ product, environmentId, isViewer }: EditLogoProps) => )} {logoUrl && ( - )} diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/components/EditPlacement.tsx b/apps/web/app/(app)/environments/[environmentId]/product/look/components/EditPlacement.tsx similarity index 95% rename from apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/components/EditPlacement.tsx rename to apps/web/app/(app)/environments/[environmentId]/product/look/components/EditPlacement.tsx index 61db000b8d..476bbcf0c1 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/components/EditPlacement.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/product/look/components/EditPlacement.tsx @@ -1,6 +1,5 @@ "use client"; -import { getPlacementStyle } from "@/app/lib/preview"; import { useState } from "react"; import toast from "react-hot-toast"; @@ -9,6 +8,7 @@ import { TPlacement } from "@formbricks/types/common"; import { TProduct, TProductUpdateInput } from "@formbricks/types/product"; import { Button } from "@formbricks/ui/Button"; import { Label } from "@formbricks/ui/Label"; +import { getPlacementStyle } from "@formbricks/ui/PreviewSurvey/lib/utils"; import { RadioGroup, RadioGroupItem } from "@formbricks/ui/RadioGroup"; import { updateProductAction } from "../actions"; @@ -121,7 +121,12 @@ export function EditPlacement({ product }: EditPlacementProps) { )} - diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/components/ThemeStyling.tsx b/apps/web/app/(app)/environments/[environmentId]/product/look/components/ThemeStyling.tsx similarity index 90% rename from apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/components/ThemeStyling.tsx rename to apps/web/app/(app)/environments/[environmentId]/product/look/components/ThemeStyling.tsx index fa8b8922d9..77ae9b0e6f 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/components/ThemeStyling.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/product/look/components/ThemeStyling.tsx @@ -1,9 +1,9 @@ "use client"; -import { ThemeStylingPreviewSurvey } from "@/app/(app)/environments/[environmentId]/settings/lookandfeel/components/ThemeStylingPreviewSurvey"; -import BackgroundStylingCard from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/BackgroundStylingCard"; -import CardStylingSettings from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/CardStylingSettings"; -import FormStylingSettings from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/FormStylingSettings"; +import { BackgroundStylingCard } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/BackgroundStylingCard"; +import { CardStylingSettings } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/CardStylingSettings"; +import { FormStylingSettings } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/FormStylingSettings"; +import { ThemeStylingPreviewSurvey } from "@/app/(app)/environments/[environmentId]/product/look/components/ThemeStylingPreviewSurvey"; import { RotateCcwIcon } from "lucide-react"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useState } from "react"; @@ -166,7 +166,7 @@ export const ThemeStyling = ({ product, environmentId, colors, isUnsplashConfigu return (
{/* Styling settings */} -
+
@@ -218,11 +218,12 @@ export const ThemeStyling = ({ product, environmentId, colors, isUnsplashConfigu
-
-
@@ -125,7 +125,7 @@ export function DeleteAccount({

Delete your account with all personal data. This cannot be undone!

-
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/profile/components/DisableTwoFactorModal.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/DisableTwoFactorModal.tsx similarity index 98% rename from apps/web/app/(app)/environments/[environmentId]/settings/profile/components/DisableTwoFactorModal.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/DisableTwoFactorModal.tsx index 773c10e10b..d9e62974fb 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/profile/components/DisableTwoFactorModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/DisableTwoFactorModal.tsx @@ -1,6 +1,6 @@ "use client"; -import { disableTwoFactorAuthAction } from "@/app/(app)/environments/[environmentId]/settings/profile/actions"; +import { disableTwoFactorAuthAction } from "@/app/(app)/environments/[environmentId]/settings/(account)/profile/actions"; import { useRouter } from "next/navigation"; import React, { useEffect, useState } from "react"; import { Controller, SubmitHandler, useForm } from "react-hook-form"; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/profile/components/EditAvatar.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/EditAvatar.tsx similarity index 93% rename from apps/web/app/(app)/environments/[environmentId]/settings/profile/components/EditAvatar.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/EditAvatar.tsx index 04598f03bc..707ff9c124 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/profile/components/EditAvatar.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/EditAvatar.tsx @@ -3,7 +3,7 @@ import { removeAvatarAction, updateAvatarAction, -} from "@/app/(app)/environments/[environmentId]/settings/profile/actions"; +} from "@/app/(app)/environments/[environmentId]/settings/(account)/profile/actions"; import { handleFileUpload } from "@/app/lib/fileUpload"; import { Session } from "next-auth"; import { useRouter } from "next/navigation"; @@ -79,8 +79,9 @@ export function EditAvatar({ session, environmentId }: { session: Session; envir
{session?.user?.imageUrl && ( - )} diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/profile/components/EditName.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/EditName.tsx similarity index 98% rename from apps/web/app/(app)/environments/[environmentId]/settings/profile/components/EditName.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/EditName.tsx index d1c04cbfaf..25e474605c 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/profile/components/EditName.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/EditName.tsx @@ -62,6 +62,7 @@ export function EditName({ user }: { user: TUser }) { type="submit" variant="darkCTA" className="mt-4" + size="sm" loading={isSubmitting} disabled={nameValue === "" || isSubmitting}> Update diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/profile/components/EnableTwoFactorModal.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/EnableTwoFactorModal.tsx similarity index 99% rename from apps/web/app/(app)/environments/[environmentId]/settings/profile/components/EnableTwoFactorModal.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/EnableTwoFactorModal.tsx index 726ad52441..eb1f031e1c 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/profile/components/EnableTwoFactorModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/EnableTwoFactorModal.tsx @@ -3,7 +3,7 @@ import { enableTwoFactorAuthAction, setupTwoFactorAuthAction, -} from "@/app/(app)/environments/[environmentId]/settings/profile/actions"; +} from "@/app/(app)/environments/[environmentId]/settings/(account)/profile/actions"; import Image from "next/image"; import { useRouter } from "next/navigation"; import React, { useState } from "react"; diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/profile/loading.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/loading.tsx similarity index 98% rename from apps/web/app/(app)/environments/[environmentId]/settings/profile/loading.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/loading.tsx index 6c335e46b8..dd851eb46f 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/profile/loading.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/loading.tsx @@ -21,7 +21,7 @@ function LoadingCard({ title, description, skeletonLines }) { export default function Loading() { const cards = [ { - title: "Personal Information", + title: "Personal information", description: "Update your personal information", skeletonLines: [ { classes: "h-4 w-28" }, diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/profile/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/page.tsx similarity index 73% rename from apps/web/app/(app)/environments/[environmentId]/settings/profile/page.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/page.tsx index 1550c17f80..fc46e7cd0a 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/profile/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/page.tsx @@ -1,13 +1,15 @@ -import AccountSecurity from "@/app/(app)/environments/[environmentId]/settings/profile/components/AccountSecurity"; +import { AccountSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(account)/components/AccountSettingsNavbar"; +import AccountSecurity from "@/app/(app)/environments/[environmentId]/settings/(account)/profile/components/AccountSecurity"; import { getServerSession } from "next-auth"; import { authOptions } from "@formbricks/lib/authOptions"; import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; import { getUser } from "@formbricks/lib/user/service"; +import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; +import { PageHeader } from "@formbricks/ui/PageHeader"; import { SettingsId } from "@formbricks/ui/SettingsId"; -import SettingsCard from "../components/SettingsCard"; -import SettingsTitle from "../components/SettingsTitle"; +import SettingsCard from "../../components/SettingsCard"; import { DeleteAccount } from "./components/DeleteAccount"; import { EditAvatar } from "./components/EditAvatar"; import { EditName } from "./components/EditName"; @@ -21,11 +23,13 @@ export default async function ProfileSettingsPage({ params }: { params: { enviro const user = session && session.user ? await getUser(session.user.id) : null; return ( - <> + + + + {user && (
- - + @@ -45,6 +49,6 @@ export default async function ProfileSettingsPage({ params }: { params: { enviro
)} - +
); } diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/billing/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/actions.ts similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/settings/billing/actions.ts rename to apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/actions.ts diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/billing/components/PricingTable.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/components/PricingTable.tsx similarity index 97% rename from apps/web/app/(app)/environments/[environmentId]/settings/billing/components/PricingTable.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/components/PricingTable.tsx index 6bdbc2ae1d..06f97afbbb 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/billing/components/PricingTable.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/billing/components/PricingTable.tsx @@ -4,7 +4,7 @@ import { manageSubscriptionAction, removeSubscriptionAction, upgradePlanAction, -} from "@/app/(app)/environments/[environmentId]/settings/billing/actions"; +} from "@/app/(app)/environments/[environmentId]/settings/(team)/billing/actions"; import { useRouter } from "next/navigation"; import { useState } from "react"; import toast from "react-hot-toast"; @@ -176,10 +176,15 @@ export default function PricingTableComponent({
{team.billing.stripeCustomerId ? (
- +
+
+
+ )} + + ); +} diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(team)/layout.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/layout.tsx new file mode 100644 index 0000000000..e65be8b58b --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/layout.tsx @@ -0,0 +1,27 @@ +import { getServerSession } from "next-auth"; + +import { authOptions } from "@formbricks/lib/authOptions"; +import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; +import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; + +export default async function Layout({ children, params }) { + const [team, product, session] = await Promise.all([ + getTeamByEnvironmentId(params.environmentId), + getProductByEnvironmentId(params.environmentId), + getServerSession(authOptions), + ]); + + if (!team) { + throw new Error("Team not found"); + } + + if (!product) { + throw new Error("Product not found"); + } + + if (!session) { + throw new Error("Unauthenticated"); + } + + return <>{children}; +} diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/members/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/actions.ts similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/settings/members/actions.ts rename to apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/actions.ts diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/members/components/AddMemberModal.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/AddMemberModal.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/settings/members/components/AddMemberModal.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/AddMemberModal.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/members/components/BulkInviteTab.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/BulkInviteTab.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/settings/members/components/BulkInviteTab.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/BulkInviteTab.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/members/components/DeleteTeam.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/DeleteTeam.tsx similarity index 98% rename from apps/web/app/(app)/environments/[environmentId]/settings/members/components/DeleteTeam.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/DeleteTeam.tsx index 5deb8e25a4..5288f2bd81 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/members/components/DeleteTeam.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/DeleteTeam.tsx @@ -1,6 +1,6 @@ "use client"; -import { deleteTeamAction } from "@/app/(app)/environments/[environmentId]/settings/members/actions"; +import { deleteTeamAction } from "@/app/(app)/environments/[environmentId]/settings/(team)/members/actions"; import { useRouter } from "next/navigation"; import { Dispatch, SetStateAction, useState } from "react"; import toast from "react-hot-toast"; @@ -45,6 +45,7 @@ export default function DeleteTeam({ team, isDeleteDisabled = false, isUserOwner This action cannot be undone. If it's gone, it's gone.

)} {!isInviteDisabled && isAdminOrOwner && ( )}
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/members/components/EditMemberships/index.ts b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditMemberships/index.ts similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/settings/members/components/EditMemberships/index.ts rename to apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditMemberships/index.ts diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/members/components/EditTeamName.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditTeamName.tsx similarity index 97% rename from apps/web/app/(app)/environments/[environmentId]/settings/members/components/EditTeamName.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditTeamName.tsx index 2e16867400..013d1d81a9 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/members/components/EditTeamName.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditTeamName.tsx @@ -1,6 +1,6 @@ "use client"; -import { updateTeamNameAction } from "@/app/(app)/environments/[environmentId]/settings/members/actions"; +import { updateTeamNameAction } from "@/app/(app)/environments/[environmentId]/settings/(team)/members/actions"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { SubmitHandler, useForm, useWatch } from "react-hook-form"; @@ -86,6 +86,7 @@ export default function EditTeamName({ team, membershipRole }: EditTeamNameProps type="submit" className="mt-4" variant="darkCTA" + size="sm" loading={isUpdatingTeam} disabled={isTeamNameInputEmpty || currentTeamName === previousTeamName}> Update diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/members/components/IndividualInviteTab.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/IndividualInviteTab.tsx similarity index 97% rename from apps/web/app/(app)/environments/[environmentId]/settings/members/components/IndividualInviteTab.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/IndividualInviteTab.tsx index 5b43ed94e6..2b377624e8 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/members/components/IndividualInviteTab.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/IndividualInviteTab.tsx @@ -80,6 +80,7 @@ export const IndividualInviteTab = ({
-
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/members/components/ShareInviteModal.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/ShareInviteModal.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/settings/members/components/ShareInviteModal.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/components/ShareInviteModal.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/members/loading.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/loading.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/settings/members/loading.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/loading.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/members/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/page.tsx similarity index 85% rename from apps/web/app/(app)/environments/[environmentId]/settings/members/page.tsx rename to apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/page.tsx index 9095f8b570..80879af72a 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/members/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(team)/members/page.tsx @@ -1,4 +1,5 @@ -import TeamActions from "@/app/(app)/environments/[environmentId]/settings/members/components/EditMemberships/TeamActions"; +import { TeamSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(team)/components/TeamSettingsNavbar"; +import TeamActions from "@/app/(app)/environments/[environmentId]/settings/(team)/members/components/EditMemberships/TeamActions"; import { getServerSession } from "next-auth"; import { Suspense } from "react"; @@ -8,11 +9,12 @@ import { INVITE_DISABLED, IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants" import { getMembershipByUserIdTeamId, getMembershipsByUserId } from "@formbricks/lib/membership/service"; import { getAccessFlags } from "@formbricks/lib/membership/utils"; import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; +import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; +import { PageHeader } from "@formbricks/ui/PageHeader"; import { SettingsId } from "@formbricks/ui/SettingsId"; import { Skeleton } from "@formbricks/ui/Skeleton"; -import SettingsCard from "../components/SettingsCard"; -import SettingsTitle from "../components/SettingsTitle"; +import SettingsCard from "../../components/SettingsCard"; import DeleteTeam from "./components/DeleteTeam"; import { EditMemberships } from "./components/EditMemberships"; import EditTeamName from "./components/EditTeamName"; @@ -64,8 +66,15 @@ export default async function MembersSettingsPage({ params }: { params: { enviro const isUserAdminOrOwner = isAdmin || isOwner; return ( -
- + + + + {currentUserRole && ( -
+ ); } diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/billing/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/billing/page.tsx deleted file mode 100644 index da026b34ca..0000000000 --- a/apps/web/app/(app)/environments/[environmentId]/settings/billing/page.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { PRICING_APPSURVEYS_FREE_RESPONSES, PRICING_USERTARGETING_FREE_MTU } from "@formbricks/lib/constants"; -import { - getMonthlyActiveTeamPeopleCount, - getMonthlyTeamResponseCount, - getTeamByEnvironmentId, -} from "@formbricks/lib/team/service"; - -import SettingsTitle from "../components/SettingsTitle"; -import PricingTable from "./components/PricingTable"; - -export default async function BillingPage({ params }) { - const team = await getTeamByEnvironmentId(params.environmentId); - if (!team) { - throw new Error("Team not found"); - } - - const [peopleCount, responseCount] = await Promise.all([ - getMonthlyActiveTeamPeopleCount(team.id), - getMonthlyTeamResponseCount(team.id), - ]); - - return ( - <> -
- - -
- - ); -} diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/components/SettingsCard.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/components/SettingsCard.tsx index e79e48d143..0b1db908ff 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/components/SettingsCard.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/components/SettingsCard.tsx @@ -19,8 +19,13 @@ export default function SettingsCard({ className?: string; }) { return ( -
-
+
+

{title}

@@ -30,7 +35,7 @@ export default function SettingsCard({

{description}

-
{children}
+
{children}
); } diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/components/SettingsNavbar.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/components/SettingsNavbar.tsx deleted file mode 100644 index 9e14086756..0000000000 --- a/apps/web/app/(app)/environments/[environmentId]/settings/components/SettingsNavbar.tsx +++ /dev/null @@ -1,324 +0,0 @@ -"use client"; - -import clsx from "clsx"; -import { - BellRingIcon, - BoltIcon, - BrushIcon, - ChevronDownIcon, - CreditCardIcon, - FileCheckIcon, - FileSearch2Icon, - HashIcon, - KeyIcon, - LanguagesIcon, - LinkIcon, - SlidersIcon, - UserCircleIcon, - UsersIcon, -} from "lucide-react"; -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import { useMemo, useState } from "react"; -import { FaDiscord } from "react-icons/fa6"; - -import { getAccessFlags } from "@formbricks/lib/membership/utils"; -import { truncate } from "@formbricks/lib/strings"; -import { TMembershipRole } from "@formbricks/types/memberships"; -import { TProduct } from "@formbricks/types/product"; -import { TTeam } from "@formbricks/types/teams"; -import { Popover, PopoverContent, PopoverTrigger } from "@formbricks/ui/Popover"; - -interface SettingsNavbarProps { - environmentId: string; - isFormbricksCloud: boolean; - team: TTeam; - product: TProduct; - membershipRole?: TMembershipRole; - isMultiLanguageAllowed: boolean; -} - -export default function SettingsNavbar({ - environmentId, - isFormbricksCloud, - team, - product, - membershipRole, - isMultiLanguageAllowed, -}: SettingsNavbarProps) { - const pathname = usePathname(); - const [mobileNavMenuOpen, setMobileNavMenuOpen] = useState(false); - const { isAdmin, isOwner, isViewer } = getAccessFlags(membershipRole); - const isPricingDisabled = !isOwner && !isAdmin; - - interface NavigationLink { - name: string; - href: string; - icon: React.ComponentType; - current?: boolean; - hidden: boolean; - target?: string; - } - - interface NavigationSection { - title: string; - links: NavigationLink[]; - hidden: boolean; - } - - const navigation: NavigationSection[] = useMemo( - () => [ - { - title: "Account", - links: [ - { - name: "Profile", - href: `/environments/${environmentId}/settings/profile`, - icon: UserCircleIcon, - current: pathname?.includes("/profile"), - hidden: false, - }, - { - name: "Notifications", - href: `/environments/${environmentId}/settings/notifications`, - icon: BellRingIcon, - current: pathname?.includes("/notifications"), - hidden: false, - }, - ], - hidden: false, - }, - { - title: "Product", - links: [ - { - name: "Settings", - href: `/environments/${environmentId}/settings/product`, - icon: SlidersIcon, - current: pathname?.includes("/product"), - hidden: false, - }, - { - name: "Look & Feel", - href: `/environments/${environmentId}/settings/lookandfeel`, - icon: BrushIcon, - current: pathname?.includes("/lookandfeel"), - hidden: isViewer, - }, - { - name: "Survey Languages", - href: `/environments/${environmentId}/settings/language`, - icon: LanguagesIcon, - current: pathname?.includes("/language"), - hidden: !isMultiLanguageAllowed, - }, - { - name: "API Keys", - href: `/environments/${environmentId}/settings/api-keys`, - icon: KeyIcon, - current: pathname?.includes("/api-keys"), - hidden: isViewer, - }, - { - name: "Tags", - href: `/environments/${environmentId}/settings/tags`, - icon: HashIcon, - current: pathname?.includes("/tags"), - hidden: isViewer, - }, - ], - hidden: isViewer, - }, - { - title: "Team", - links: [ - { - name: "Members", - href: `/environments/${environmentId}/settings/members`, - icon: UsersIcon, - current: pathname?.includes("/members"), - hidden: false, - }, - { - name: "Billing & Plan", - href: `/environments/${environmentId}/settings/billing`, - icon: CreditCardIcon, - hidden: !isFormbricksCloud || isPricingDisabled, - current: pathname?.includes("/billing"), - }, - { - name: "Enterprise License", - href: `/environments/${environmentId}/settings/enterprise`, - icon: BoltIcon, - hidden: isFormbricksCloud || isPricingDisabled, - current: pathname?.includes("/enterprise"), - }, - ], - hidden: false, - }, - { - title: "Setup", - links: [ - { - name: "Setup Checklist", - href: `/environments/${environmentId}/settings/setup`, - icon: FileCheckIcon, - current: pathname?.includes("/setup"), - hidden: false, - }, - { - name: "Documentation", - href: "https://formbricks.com/docs", - icon: FileSearch2Icon, - target: "_blank", - hidden: false, - }, - { - name: "Join Discord", - href: "https://formbricks.com/discord", - icon: FaDiscord, - target: "_blank", - hidden: false, - }, - ], - hidden: false, - }, - { - title: "Compliance", - links: [ - { - name: "GDPR & CCPA", - href: "https://formbricks.com/gdpr", - icon: LinkIcon, - target: "_blank", - hidden: !isFormbricksCloud, - }, - { - name: "Privacy", - href: "https://formbricks.com/privacy", - icon: LinkIcon, - target: "_blank", - hidden: !isFormbricksCloud, - }, - { - name: "Terms", - href: "https://formbricks.com/terms", - icon: LinkIcon, - target: "_blank", - hidden: !isFormbricksCloud, - }, - { - name: "License", - href: "https://github.com/formbricks/formbricks/blob/main/LICENSE", - icon: LinkIcon, - target: "_blank", - hidden: false, - }, - ], - hidden: false, - }, - ], - [environmentId, pathname, isViewer, isMultiLanguageAllowed, isFormbricksCloud, isPricingDisabled] - ); - - if (!navigation) return null; - - return ( - <> -
- -
- - {/* Mobile Menu */} -
- - setMobileNavMenuOpen(!mobileNavMenuOpen)}> - - Settings - - - -
- {navigation.map((item) => ( -
-

- {item.title}{" "} - {item.title === "Product" && product?.name && ( - ({truncate(product?.name, 10)}) - )} - {item.title === "Team" && team?.name && ( - ({truncate(team?.name, 14)}) - )} -

-
- {item.links - .filter((l) => !l.hidden) - .map((link) => ( - setMobileNavMenuOpen(false)} - className={clsx( - link.current - ? "bg-slate-100 text-slate-900" - : "text-slate-900 hover:bg-slate-50 ", - "group flex items-center whitespace-nowrap rounded-md px-1 py-1 pl-2 text-sm font-medium " - )}> -
-
- ))} -
-
-
-
- - ); -} diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/enterprise/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/enterprise/page.tsx deleted file mode 100644 index f0a4f68c21..0000000000 --- a/apps/web/app/(app)/environments/[environmentId]/settings/enterprise/page.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import { CheckIcon } from "lucide-react"; -import { getServerSession } from "next-auth"; -import { notFound } from "next/navigation"; - -import { getIsEnterpriseEdition } from "@formbricks/ee/lib/service"; -import { authOptions } from "@formbricks/lib/authOptions"; -import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; -import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; -import { getAccessFlags } from "@formbricks/lib/membership/utils"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; -import { Button } from "@formbricks/ui/Button"; - -import SettingsTitle from "../components/SettingsTitle"; - -export default async function EnterpriseLicensePage({ params }) { - if (IS_FORMBRICKS_CLOUD) { - notFound(); - } - - const session = await getServerSession(authOptions); - - const team = await getTeamByEnvironmentId(params.environmentId); - - if (!session) { - throw new Error("Unauthorized"); - } - - if (!team) { - throw new Error("Team not found"); - } - - const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id); - const { isAdmin, isOwner } = getAccessFlags(currentUserMembership?.role); - const isPricingDisabled = !isOwner && !isAdmin; - - if (isPricingDisabled) { - notFound(); - } - - const isEnterpriseEdition = await getIsEnterpriseEdition(); - - const paidFeatures = [ - { - title: "Team Roles (Admin, Editor, Developer, etc.)", - comingSoon: false, - onRequest: false, - }, - { - title: "Advanced Targeting and Segmentation (In-app Surveys)", - comingSoon: false, - onRequest: false, - }, - { - title: "Multi-Language Surveys", - comingSoon: false, - onRequest: false, - }, - { - title: "Audit Logs", - comingSoon: false, - onRequest: true, - }, - { - title: "SAML SSO", - comingSoon: false, - onRequest: true, - }, - { - title: "Service Level Agreement", - comingSoon: false, - onRequest: true, - }, - { - title: "SOC2, HIPAA, ISO 27001 Compliance check", - comingSoon: false, - onRequest: true, - }, - { - title: "Extensive Whitelabeling", - comingSoon: false, - onRequest: true, - }, - { - title: "Custom Feature Development", - comingSoon: false, - onRequest: true, - }, - ]; - - return ( - <> -
- - ); -} diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/layout.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/layout.tsx deleted file mode 100644 index 4dcdac2509..0000000000 --- a/apps/web/app/(app)/environments/[environmentId]/settings/layout.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { Metadata } from "next"; -import { getServerSession } from "next-auth"; - -import { getMultiLanguagePermission } from "@formbricks/ee/lib/service"; -import { authOptions } from "@formbricks/lib/authOptions"; -import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; -import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; -import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; -import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; - -import SettingsNavbar from "./components/SettingsNavbar"; - -export const metadata: Metadata = { - title: "Settings", -}; - -export default async function SettingsLayout({ children, params }) { - const [team, product, session] = await Promise.all([ - getTeamByEnvironmentId(params.environmentId), - getProductByEnvironmentId(params.environmentId), - getServerSession(authOptions), - ]); - - if (!team) { - throw new Error("Team not found"); - } - - if (!product) { - throw new Error("Product not found"); - } - - if (!session) { - throw new Error("Unauthenticated"); - } - - const isMultiLanguageAllowed = await getMultiLanguagePermission(team); - - const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id); - - return ( - <> -
- -
-
-
{children}
-
-
-
- - ); -} diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/setup/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/setup/page.tsx deleted file mode 100644 index 4889073dfe..0000000000 --- a/apps/web/app/(app)/environments/[environmentId]/settings/setup/page.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import WidgetStatusIndicator from "@/app/(app)/environments/[environmentId]/components/WidgetStatusIndicator"; - -import { IS_FORMBRICKS_CLOUD, WEBAPP_URL } from "@formbricks/lib/constants"; -import EnvironmentNotice from "@formbricks/ui/EnvironmentNotice"; - -import SettingsCard from "../components/SettingsCard"; -import SettingsTitle from "../components/SettingsTitle"; -import EnvironmentIdField from "./components/EnvironmentIdField"; -import SetupInstructions from "./components/SetupInstructions"; - -export default async function ProfileSettingsPage({ params }) { - return ( - <> -
- - - - - - - - - - - -
- - ); -} diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/EmptyInAppSurveys.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/EmptyInAppSurveys.tsx index d39d6f8a97..7eb4b7fcc3 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/EmptyInAppSurveys.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/EmptyInAppSurveys.tsx @@ -23,7 +23,7 @@ export const EmptyAppSurveys = ({ environment, surveyType = "app" }: TEmptyAppSu Connect your {surveyType} with Formbricks to run {surveyType} surveys.

- + diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/SurveyAnalysisNavigation.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/SurveyAnalysisNavigation.tsx new file mode 100644 index 0000000000..608cbe7a16 --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/SurveyAnalysisNavigation.tsx @@ -0,0 +1,53 @@ +"use client"; + +import revalidateSurveyIdPath from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/actions"; +import { InboxIcon, PresentationIcon } from "lucide-react"; +import { useParams, usePathname } from "next/navigation"; + +import { SecondaryNavigation } from "@formbricks/ui/SecondaryNavigation"; + +interface SurveyAnalysisNavigationProps { + environmentId: string; + surveyId: string; + responseCount: number | null; + activeId: string; +} + +export const SurveyAnalysisNavigation = ({ + environmentId, + surveyId, + responseCount, + activeId, +}: SurveyAnalysisNavigationProps) => { + const pathname = usePathname(); + const params = useParams(); + const sharingKey = params.sharingKey as string; + const isSharingPage = !!sharingKey; + + const url = isSharingPage ? `/share/${sharingKey}` : `/environments/${environmentId}/surveys/${surveyId}`; + + const navigation = [ + { + id: "summary", + label: "Summary", + icon: , + href: `${url}/summary?referer=true`, + current: pathname?.includes("/summary"), + onClick: () => { + revalidateSurveyIdPath(environmentId, surveyId); + }, + }, + { + id: "responses", + label: `Responses ${responseCount !== null ? `(${responseCount})` : ""}`, + icon: , + href: `${url}/responses?referer=true`, + current: pathname?.includes("/responses"), + onClick: () => { + revalidateSurveyIdPath(environmentId, surveyId); + }, + }, + ]; + + return ; +}; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/SurveyResultsTabs.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/SurveyResultsTabs.tsx deleted file mode 100644 index 724939de13..0000000000 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/SurveyResultsTabs.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import revalidateSurveyIdPath from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/actions"; -import { InboxIcon, PresentationIcon } from "lucide-react"; -import Link from "next/link"; -import { useParams } from "next/navigation"; - -import { cn } from "@formbricks/lib/cn"; - -interface SurveyResultsTabProps { - activeId: string; - environmentId: string; - surveyId: string; - responseCount: number | null; -} - -export const SurveyResultsTabs = ({ - activeId, - environmentId, - surveyId, - responseCount, -}: SurveyResultsTabProps) => { - const params = useParams(); - const sharingKey = params.sharingKey as string; - const isSharingPage = !!sharingKey; - - const url = isSharingPage ? `/share/${sharingKey}` : `/environments/${environmentId}/surveys/${surveyId}`; - - const tabs = [ - { - id: "summary", - label: "Summary", - icon: , - href: `${url}/summary?referer=true`, - }, - { - id: "responses", - label: `Responses ${responseCount !== null ? `(${responseCount})` : ""}`, - icon: , - href: `${url}/responses?referer=true`, - }, - ]; - - return ( -
-
- -
-
- ); -}; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/layout.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/layout.tsx index e6bb44a7a0..c7f90dd67b 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/layout.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/layout.tsx @@ -12,9 +12,9 @@ type Props = { export const generateMetadata = async ({ params }: Props): Promise => { const session = await getServerSession(authOptions); const survey = await getSurvey(params.surveyId); + const responseCount = await getResponseCountBySurveyId(params.surveyId); if (session) { - const responseCount = await getResponseCountBySurveyId(params.surveyId); return { title: `${responseCount} Responses | ${survey?.name} Results`, }; @@ -24,8 +24,6 @@ export const generateMetadata = async ({ params }: Props): Promise => }; }; -const SurveyLayout = ({ children }) => { - return
{children}
; -}; - -export default SurveyLayout; +export default async function SurveyLayout({ children }) { + return <>{children}; +} diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponsePage.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponsePage.tsx index 935bc5b26a..a2cb460ec3 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponsePage.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponsePage.tsx @@ -5,11 +5,9 @@ import { getResponseCountAction, getResponsesAction, } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/actions"; -import { SurveyResultsTabs } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/SurveyResultsTabs"; import ResponseTimeline from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTimeline"; import { CustomFilter } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter"; import { ResultsShareButton } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/ResultsShareButton"; -import { SummaryHeader } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SummaryHeader"; import { getFormattedFilters } from "@/app/lib/surveys/surveys"; import { getResponseCountBySurveySharingKeyAction, @@ -20,24 +18,19 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { checkForRecallInHeadline } from "@formbricks/lib/utils/recall"; import { TEnvironment } from "@formbricks/types/environment"; -import { TMembershipRole } from "@formbricks/types/memberships"; -import { TProduct } from "@formbricks/types/product"; import { TResponse } from "@formbricks/types/responses"; import { TSurvey } from "@formbricks/types/surveys"; import { TTag } from "@formbricks/types/tags"; import { TUser } from "@formbricks/types/user"; -import { ContentWrapper } from "@formbricks/ui/ContentWrapper"; interface ResponsePageProps { environment: TEnvironment; survey: TSurvey; surveyId: string; webAppUrl: string; - product: TProduct; user?: TUser; environmentTags: TTag[]; responsesPerPage: number; - membershipRole?: TMembershipRole; totalResponseCount: number; } @@ -46,11 +39,9 @@ const ResponsePage = ({ survey, surveyId, webAppUrl, - product, user, environmentTags, responsesPerPage, - membershipRole, totalResponseCount, }: ResponsePageProps) => { const params = useParams(); @@ -164,26 +155,11 @@ const ResponsePage = ({ }, [filters]); return ( - - + <>
{!isSharingPage && }
- -
+ ); }; 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 6876adb3e6..bacb156cfb 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 @@ -1,16 +1,21 @@ +import { SurveyAnalysisNavigation } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/SurveyAnalysisNavigation"; import ResponsePage from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponsePage"; +import { SurveyAnalysisCTA } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SurveyAnalysisCTA"; import { getServerSession } from "next-auth"; import { authOptions } from "@formbricks/lib/authOptions"; import { RESPONSES_PER_PAGE, WEBAPP_URL } from "@formbricks/lib/constants"; import { getEnvironment } from "@formbricks/lib/environment/service"; import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; +import { getAccessFlags } from "@formbricks/lib/membership/utils"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; import { getResponseCountBySurveyId } from "@formbricks/lib/response/service"; import { getSurvey } from "@formbricks/lib/survey/service"; import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service"; import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { getUser } from "@formbricks/lib/user/service"; +import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; +import { PageHeader } from "@formbricks/ui/PageHeader"; export default async function Page({ params }) { const session = await getServerSession(authOptions); @@ -45,23 +50,40 @@ export default async function Page({ params }) { } const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id); - const totalResponseCount = await getResponseCountBySurveyId(params.surveyId); + const { isViewer } = getAccessFlags(currentUserMembership?.role); + return ( - <> + + + }> + + - + ); } diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/AddressSummary.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/AddressSummary.tsx index 40f2750fc2..5ace48a133 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/AddressSummary.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/AddressSummary.tsx @@ -1,6 +1,6 @@ import Link from "next/link"; -import { getPersonIdentifier } from "@formbricks/lib/person/util"; +import { getPersonIdentifier } from "@formbricks/lib/person/utils"; import { timeSince } from "@formbricks/lib/time"; import { TSurveyQuestionSummaryAddress } from "@formbricks/types/surveys"; import { AddressResponse } from "@formbricks/ui/AddressResponse"; @@ -15,9 +15,9 @@ interface AddressSummaryProps { export const AddressSummary = ({ questionSummary, environmentId }: AddressSummaryProps) => { return ( -
+
-
+
User
Response
@@ -28,7 +28,7 @@ export const AddressSummary = ({ questionSummary, environmentId }: AddressSummar return (
+ className="grid grid-cols-4 items-center border-b border-slate-100 py-2 text-sm text-slate-800 last:border-transparent md:text-base">
{response.person ? ( { return ( -
+
{ } /> -
+

CTR

@@ -49,7 +49,7 @@ export const CTASummary = ({ questionSummary }: CTASummaryProps) => { {questionSummary.ctr.count} {questionSummary.ctr.count === 1 ? "Click" : "Clicks"}

- +
); diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/CalSummary.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/CalSummary.tsx index 59e0a9e60d..e3cdcbfa8c 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/CalSummary.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/CalSummary.tsx @@ -1,4 +1,4 @@ -import { convertFloatToNDecimal } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/util"; +import { convertFloatToNDecimal } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/utils"; import { TSurveyQuestionSummaryCal } from "@formbricks/types/surveys"; import { ProgressBar } from "@formbricks/ui/ProgressBar"; @@ -12,9 +12,9 @@ interface CalSummaryProps { export const CalSummary = ({ questionSummary }: CalSummaryProps) => { return ( -
+
-
+
@@ -29,7 +29,7 @@ export const CalSummary = ({ questionSummary }: CalSummaryProps) => { {questionSummary.booked.count} {questionSummary.booked.count === 1 ? "response" : "responses"}

- +
@@ -45,7 +45,7 @@ export const CalSummary = ({ questionSummary }: CalSummaryProps) => { {questionSummary.skipped.count} {questionSummary.skipped.count === 1 ? "response" : "responses"}

- +
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/ConsentSummary.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/ConsentSummary.tsx index 5000acaad1..49abc8cbc9 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/ConsentSummary.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/ConsentSummary.tsx @@ -1,7 +1,7 @@ import { TSurveyQuestionSummaryConsent } from "@formbricks/types/surveys"; import { ProgressBar } from "@formbricks/ui/ProgressBar"; -import { convertFloatToNDecimal } from "../lib/util"; +import { convertFloatToNDecimal } from "../lib/utils"; import { QuestionSummaryHeader } from "./QuestionSummaryHeader"; interface ConsentSummaryProps { @@ -10,9 +10,9 @@ interface ConsentSummaryProps { export const ConsentSummary = ({ questionSummary }: ConsentSummaryProps) => { return ( -
+
-
+
@@ -28,7 +28,7 @@ export const ConsentSummary = ({ questionSummary }: ConsentSummaryProps) => { {questionSummary.accepted.count === 1 ? "response" : "responses"}

- +
@@ -45,7 +45,7 @@ export const ConsentSummary = ({ questionSummary }: ConsentSummaryProps) => { {questionSummary.dismissed.count === 1 ? "response" : "responses"}

- +
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/DateQuestionSummary.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/DateQuestionSummary.tsx index 721c44f49c..de04cc4fe1 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/DateQuestionSummary.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/DateQuestionSummary.tsx @@ -1,7 +1,7 @@ import Link from "next/link"; import { useState } from "react"; -import { getPersonIdentifier } from "@formbricks/lib/person/util"; +import { getPersonIdentifier } from "@formbricks/lib/person/utils"; import { timeSince } from "@formbricks/lib/time"; import { formatDateWithOrdinal } from "@formbricks/lib/utils/datetime"; import { TSurveyQuestionSummaryDate } from "@formbricks/types/surveys"; @@ -26,9 +26,9 @@ export const DateQuestionSummary = ({ questionSummary, environmentId }: DateQues }; return ( -
+
-
+
User
Response
@@ -38,7 +38,7 @@ export const DateQuestionSummary = ({ questionSummary, environmentId }: DateQues {questionSummary.samples.slice(0, visibleResponses).map((response) => (
+ className="grid grid-cols-4 items-center border-b border-slate-100 py-2 text-sm text-slate-800 last:border-transparent md:text-base">
{response.person ? ( +
-
+
User
Response
@@ -39,7 +39,7 @@ export const FileUploadSummary = ({ questionSummary, environmentId }: FileUpload {questionSummary.files.slice(0, visibleResponses).map((response) => (
+ className="grid grid-cols-4 items-center border-b border-slate-100 py-2 text-sm text-slate-800 last:border-transparent md:text-base">
{response.person ? ( +

{questionSummary.id}

diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/MatrixQuestionSummary.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/MatrixQuestionSummary.tsx index 07a67928f7..229f9881af 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/MatrixQuestionSummary.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/MatrixQuestionSummary.tsx @@ -26,9 +26,9 @@ export const MatrixQuestionSummary = ({ questionSummary }: MatrixQuestionSummary const columns = questionSummary.data[0] ? Object.keys(questionSummary.data[0].columnPercentages) : []; return ( -
+
-
+
{/* Summary Table */} diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/MultipleChoiceSummary.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/MultipleChoiceSummary.tsx index efcdfc07c0..918962574a 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/MultipleChoiceSummary.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/MultipleChoiceSummary.tsx @@ -1,13 +1,13 @@ import Link from "next/link"; import { useState } from "react"; -import { getPersonIdentifier } from "@formbricks/lib/person/util"; +import { getPersonIdentifier } from "@formbricks/lib/person/utils"; import { TSurveyQuestionSummaryMultipleChoice, TSurveyType } from "@formbricks/types/surveys"; import { PersonAvatar } from "@formbricks/ui/Avatars"; import { Button } from "@formbricks/ui/Button"; import { ProgressBar } from "@formbricks/ui/ProgressBar"; -import { convertFloatToNDecimal } from "../lib/util"; +import { convertFloatToNDecimal } from "../lib/utils"; import { QuestionSummaryHeader } from "./QuestionSummaryHeader"; interface MultipleChoiceSummaryProps { @@ -44,9 +44,9 @@ export const MultipleChoiceSummary = ({ }; return ( -
+
-
+
{results.map((result, resultsIdx) => (
@@ -64,7 +64,7 @@ export const MultipleChoiceSummary = ({ {result.count} {result.count === 1 ? "response" : "responses"}

- + {result.others && result.others.length > 0 && (
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/NPSSummary.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/NPSSummary.tsx index 5bc827cc9f..7a2ea4484e 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/NPSSummary.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/NPSSummary.tsx @@ -1,7 +1,7 @@ import { TSurveyQuestionSummaryNps } from "@formbricks/types/surveys"; import { HalfCircle, ProgressBar } from "@formbricks/ui/ProgressBar"; -import { convertFloatToNDecimal } from "../lib/util"; +import { convertFloatToNDecimal } from "../lib/utils"; import { QuestionSummaryHeader } from "./QuestionSummaryHeader"; interface NPSSummaryProps { @@ -10,9 +10,9 @@ interface NPSSummaryProps { export const NPSSummary = ({ questionSummary }: NPSSummaryProps) => { return ( -
+
-
+
{["promoters", "passives", "detractors"].map((group) => (
@@ -28,7 +28,7 @@ export const NPSSummary = ({ questionSummary }: NPSSummaryProps) => { {questionSummary[group].count} {questionSummary[group].count === 1 ? "response" : "responses"}

- +
))}
@@ -53,7 +53,7 @@ export const NPSSummary = ({ questionSummary }: NPSSummaryProps) => {
)} -
+
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/OpenTextSummary.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/OpenTextSummary.tsx index 3afb484562..8385c2388b 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/OpenTextSummary.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/OpenTextSummary.tsx @@ -1,7 +1,7 @@ import Link from "next/link"; import { useState } from "react"; -import { getPersonIdentifier } from "@formbricks/lib/person/util"; +import { getPersonIdentifier } from "@formbricks/lib/person/utils"; import { timeSince } from "@formbricks/lib/time"; import { TSurveyQuestionSummaryOpenText } from "@formbricks/types/surveys"; import { PersonAvatar } from "@formbricks/ui/Avatars"; @@ -25,9 +25,9 @@ export const OpenTextSummary = ({ questionSummary, environmentId }: OpenTextSumm }; return ( -
+
-
+
User
Response
@@ -37,7 +37,7 @@ export const OpenTextSummary = ({ questionSummary, environmentId }: OpenTextSumm {questionSummary.samples.slice(0, visibleResponses).map((response) => (
+ className="grid grid-cols-4 items-center border-b border-slate-100 py-2 text-sm text-slate-800 last:border-transparent md:text-base">
{response.person ? ( b.count - a.count); return ( -
+
-
+
{results.map((result) => (
@@ -40,7 +40,7 @@ export const PictureChoiceSummary = ({ questionSummary }: PictureChoiceSummaryPr {result.count} {result.count === 1 ? "response" : "responses"}

- +
))}
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/RatingSummary.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/RatingSummary.tsx index 231fecd583..32fc0888b5 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/RatingSummary.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/RatingSummary.tsx @@ -1,4 +1,4 @@ -import { convertFloatToNDecimal } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/util"; +import { convertFloatToNDecimal } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/utils"; import { TSurveyQuestionSummaryRating } from "@formbricks/types/surveys"; import { ProgressBar } from "@formbricks/ui/ProgressBar"; @@ -12,9 +12,9 @@ interface RatingSummaryProps { export const RatingSummary = ({ questionSummary }: RatingSummaryProps) => { return ( -
+
-
+
{questionSummary.choices.map((result) => (
@@ -36,7 +36,7 @@ export const RatingSummary = ({ questionSummary }: RatingSummaryProps) => { {result.count} {result.count === 1 ? "response" : "responses"}

- +
))}
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryDropOffs.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryDropOffs.tsx index 959eb7cb46..38028f8cc3 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryDropOffs.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryDropOffs.tsx @@ -9,8 +9,8 @@ interface SummaryDropOffsProps { export const SummaryDropOffs = ({ dropOff }: SummaryDropOffsProps) => { return ( -
-
+
+
Questions
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryMetadata.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryMetadata.tsx index 31fc0edfc9..a696555cc6 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryMetadata.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryMetadata.tsx @@ -1,12 +1,9 @@ import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"; -import { timeSinceConditionally } from "@formbricks/lib/time"; -import { TSurvey, TSurveySummary } from "@formbricks/types/surveys"; -import { Button } from "@formbricks/ui/Button"; +import { TSurveySummary } from "@formbricks/types/surveys"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@formbricks/ui/Tooltip"; interface SummaryMetadataProps { - survey: TSurvey; setShowDropOffs: React.Dispatch>; showDropOffs: boolean; surveySummary: TSurveySummary["meta"]; @@ -16,7 +13,7 @@ const StatCard = ({ label, percentage, value, tooltipText }) => ( -
+

{label} {percentage && percentage !== "NaN%" && ( @@ -48,12 +45,7 @@ function formatTime(ttc) { return formattedValue; } -export const SummaryMetadata = ({ - survey, - setShowDropOffs, - showDropOffs, - surveySummary, -}: SummaryMetadataProps) => { +export const SummaryMetadata = ({ setShowDropOffs, showDropOffs, surveySummary }: SummaryMetadataProps) => { const { completedPercentage, completedResponses, @@ -66,52 +58,64 @@ export const SummaryMetadata = ({ } = surveySummary; return ( -

-
-
- - : displayCount} - tooltipText="Number of times the survey has been viewed." - /> - - : totalResponses} - tooltipText="Number of times the survey has been started." - /> - - : completedResponses} - tooltipText="Number of times the survey has been completed." - /> - - : dropOffCount} - tooltipText="Number of times the survey has been started but not completed." - /> - - : `${formatTime(ttcAverage)}`} - tooltipText="Average time to complete the survey." - /> -
-
-
- Last updated: {timeSinceConditionally(survey.updatedAt.toString())} -
- -
+
+
+ - : displayCount} + tooltipText="Number of times the survey has been viewed." + /> + - : totalResponses} + tooltipText="Number of times the survey has been started." + /> + - : completedResponses} + tooltipText="Number of times the survey has been completed." + /> + + + + +
setShowDropOffs(!showDropOffs)} + className="group flex h-full w-full cursor-pointer flex-col justify-between space-y-2 rounded-lg border border-slate-200 bg-white p-4 text-left shadow-sm"> + + Drop-Offs + {`${Math.round(dropOffPercentage)}%` !== "NaN%" && ( + {`${Math.round(dropOffPercentage)}%`} + )} + +
+ + {dropOffCount === 0 ? - : dropOffCount} + + + {showDropOffs ? ( + + ) : ( + + )} + +
+
+
+ +

Number of times the survey has been started but not completed.

+
+
+
+ - : `${formatTime(ttcAverage)}`} + tooltipText="Average time to complete the survey." + />
); diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryPage.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryPage.tsx index 1c51441ef0..21fd77c5f7 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryPage.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryPage.tsx @@ -5,11 +5,9 @@ import { getResponseCountAction, getSurveySummaryAction, } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/actions"; -import { SurveyResultsTabs } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/SurveyResultsTabs"; import { SummaryDropOffs } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryDropOffs"; import { CustomFilter } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter"; import { ResultsShareButton } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/ResultsShareButton"; -import { SummaryHeader } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SummaryHeader"; import { getFormattedFilters } from "@/app/lib/surveys/surveys"; import { getResponseCountBySurveySharingKeyAction, @@ -20,12 +18,8 @@ import { useEffect, useMemo, useState } from "react"; import { checkForRecallInHeadline } from "@formbricks/lib/utils/recall"; import { TEnvironment } from "@formbricks/types/environment"; -import { TMembershipRole } from "@formbricks/types/memberships"; -import { TProduct } from "@formbricks/types/product"; -import { TSurveySummary } from "@formbricks/types/surveys"; -import { TSurvey } from "@formbricks/types/surveys"; +import { TSurvey, TSurveySummary } from "@formbricks/types/surveys"; import { TUser } from "@formbricks/types/user"; -import { ContentWrapper } from "@formbricks/ui/ContentWrapper"; import { SummaryList } from "./SummaryList"; import { SummaryMetadata } from "./SummaryMetadata"; @@ -50,9 +44,7 @@ interface SummaryPageProps { survey: TSurvey; surveyId: string; webAppUrl: string; - product: TProduct; user?: TUser; - membershipRole?: TMembershipRole; totalResponseCount: number; } @@ -60,10 +52,8 @@ const SummaryPage = ({ environment, survey, surveyId, - product, webAppUrl, user, - membershipRole, totalResponseCount, }: SummaryPageProps) => { const params = useParams(); @@ -125,33 +115,17 @@ const SummaryPage = ({ }, [searchParams, resetState]); return ( - - -
- - {!isSharingPage && } -
- + <> {showDropOffs && } +
+ + {!isSharingPage && } +
-
+ ); }; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SurveyAnalysisCTA.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SurveyAnalysisCTA.tsx new file mode 100644 index 0000000000..36bee6fe1a --- /dev/null +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SurveyAnalysisCTA.tsx @@ -0,0 +1,71 @@ +"use client"; + +import { ShareEmbedSurvey } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/ShareEmbedSurvey"; +import { SuccessMessage } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SuccessMessage"; +import { SurveyStatusDropdown } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SurveyStatusDropdown"; +import { ShareIcon, SquarePenIcon } from "lucide-react"; +import { useState } from "react"; + +import { TEnvironment } from "@formbricks/types/environment"; +import { TSurvey } from "@formbricks/types/surveys"; +import { TUser } from "@formbricks/types/user"; +import { Badge } from "@formbricks/ui/Badge"; +import { Button } from "@formbricks/ui/Button"; + +export const SurveyAnalysisCTA = ({ + survey, + environment, + isViewer, + webAppUrl, + user, +}: { + survey: TSurvey; + environment: TEnvironment; + isViewer: boolean; + webAppUrl: string; + user: TUser; +}) => { + const [showShareSurveyModal, setShowShareSurveyModal] = useState(false); + + return ( +
+ {survey.resultShareKey && ( + + )} + {(environment.widgetSetupCompleted || survey.type === "link") && survey.status !== "draft" ? ( + + ) : null} + {survey.type === "link" && ( + + )} + {!isViewer && ( + + )} + {showShareSurveyModal && user && ( + + )} + + {user && } +
+ ); +}; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/util.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/utils.ts similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/util.ts rename to apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/utils.ts 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 f4b16d6d22..63cb9b418d 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 @@ -1,4 +1,6 @@ +import { SurveyAnalysisNavigation } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/SurveyAnalysisNavigation"; import SummaryPage from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryPage"; +import { SurveyAnalysisCTA } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SurveyAnalysisCTA"; import { getServerSession } from "next-auth"; import { notFound } from "next/navigation"; @@ -6,11 +8,14 @@ import { authOptions } from "@formbricks/lib/authOptions"; import { WEBAPP_URL } from "@formbricks/lib/constants"; import { getEnvironment } from "@formbricks/lib/environment/service"; import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; +import { getAccessFlags } from "@formbricks/lib/membership/utils"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; import { getResponseCountBySurveyId } from "@formbricks/lib/response/service"; import { getSurvey } from "@formbricks/lib/survey/service"; import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; import { getUser } from "@formbricks/lib/user/service"; +import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; +import { PageHeader } from "@formbricks/ui/PageHeader"; export default async function Page({ params }) { const session = await getServerSession(authOptions); @@ -53,18 +58,36 @@ export default async function Page({ params }) { const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id); const totalResponseCount = await getResponseCountBySurveyId(params.surveyId); + const { isViewer } = getAccessFlags(currentUserMembership?.role); + return ( - <> + + + }> + + - + ); } diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/actions.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/actions.ts index 6227944320..c9a677df94 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/actions.ts @@ -8,10 +8,12 @@ import { getResponseMeta, getResponsePersonAttributes, } from "@formbricks/lib/response/service"; -import { canUserAccessSurvey } from "@formbricks/lib/survey/auth"; +import { canUserAccessSurvey, verifyUserRoleAccess } from "@formbricks/lib/survey/auth"; +import { updateSurvey } from "@formbricks/lib/survey/service"; import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service"; import { AuthorizationError } from "@formbricks/types/errors"; import { TResponseFilterCriteria } from "@formbricks/types/responses"; +import { TSurvey } from "@formbricks/types/surveys"; export async function getResponsesDownloadUrlAction( surveyId: string, @@ -42,3 +44,16 @@ export async function getSurveyFilterDataAction(surveyId: string, environmentId: return { environmentTags: tags, attributes, meta }; } + +export async function updateSurveyAction(survey: TSurvey): Promise { + const session = await getServerSession(authOptions); + if (!session) throw new AuthorizationError("Not authorized"); + + const isAuthorized = await canUserAccessSurvey(session.user.id, survey.id); + if (!isAuthorized) throw new AuthorizationError("Not authorized"); + + const { hasCreateOrUpdateAccess } = await verifyUserRoleAccess(survey.environmentId, session.user.id); + if (!hasCreateOrUpdateAccess) throw new AuthorizationError("Not authorized"); + + return await updateSurvey(survey); +} diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter.tsx index b3797f1ec9..e276efef20 100755 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter.tsx @@ -192,7 +192,7 @@ export const CustomFilter = ({ survey }: CustomFilterProps) => { return ( <> -
+
{ setIsFilterDropDownOpen(value); }}> -
+
{filterRange === FilterDropDownLabels.CUSTOM_RANGE ? `${dateRange?.from ? format(dateRange?.from, "dd LLL") : "Select first date"} - ${ diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/ResultsShareButton.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/ResultsShareButton.tsx index 02546adb92..7cf2481838 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/ResultsShareButton.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/ResultsShareButton.tsx @@ -5,8 +5,7 @@ import { generateResultShareUrlAction, getResultShareUrlAction, } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/actions"; -import { CopyIcon, GlobeIcon, LinkIcon } from "lucide-react"; -import { DownloadIcon } from "lucide-react"; +import { CopyIcon, DownloadIcon, GlobeIcon, LinkIcon } from "lucide-react"; import { useEffect, useState } from "react"; import toast from "react-hot-toast"; @@ -89,7 +88,7 @@ export const ResultsShareButton = ({ survey, webAppUrl, user }: ResultsShareButt } }; return ( -
+
{ - const params = useParams(); - const sharingKey = params.sharingKey as string; - const isSharingPage = !!sharingKey; - - const router = useRouter(); - - const isCloseOnDateEnabled = survey.closeOnDate !== null; - const closeOnDate = survey.closeOnDate ? new Date(survey.closeOnDate) : null; - const isStatusChangeDisabled = (isCloseOnDateEnabled && closeOnDate && closeOnDate < new Date()) ?? false; - const { isViewer } = getAccessFlags(membershipRole); - const [showShareSurveyModal, setShowShareSurveyModal] = useState(false); - - return ( -
-
-
-

{survey.name}

- {survey.resultShareKey && !isSharingPage && ( - - )} -
- {product.name} -
- {!isSharingPage && ( - <> -
- {!isViewer && - (environment.widgetSetupCompleted || survey.type === "link") && - survey.status !== "draft" ? ( - - ) : null} - {survey.type === "link" && ( - - )} - {!isViewer && ( - - )} -
-
- - - - - - {survey.type === "link" && user && ( - <> - - - - )} - {(environment?.widgetSetupCompleted || survey.type === "link") && - survey?.status !== "draft" ? ( - <> - - -
- {(survey.type === "link" || environment.widgetSetupCompleted) && ( - - )} - - {survey.status === "inProgress" && "In-progress"} - {survey.status === "paused" && "Paused"} - {survey.status === "completed" && "Completed"} - -
-
- - - { - const castedValue = value as "draft" | "inProgress" | "paused" | "completed"; - updateSurveyAction({ ...survey, status: castedValue }) - .then(() => { - toast.success( - value === "inProgress" - ? "Survey live" - : value === "paused" - ? "Survey paused" - : value === "completed" - ? "Survey completed" - : "" - ); - router.refresh(); - }) - .catch((error) => { - toast.error(`Error: ${error.message}`); - }); - }}> - - In-progress - - - - Paused - - - - Completed - - - - -
- - - ) : null} - -
-
-
- {user && ( - - )} - {showShareSurveyModal && user && ( - - )} - - )} -
- ); -}; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SurveyStatusDropdown.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SurveyStatusDropdown.tsx index 730ca75501..199279fffb 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SurveyStatusDropdown.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SurveyStatusDropdown.tsx @@ -1,4 +1,3 @@ -import { updateSurveyAction } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/actions"; import { CheckCircle2Icon, PauseCircleIcon, PlayCircleIcon } from "lucide-react"; import toast from "react-hot-toast"; @@ -8,6 +7,8 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { SurveyStatusIndicator } from "@formbricks/ui/SurveyStatusIndicator"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@formbricks/ui/Tooltip"; +import { updateSurveyAction } from "../actions"; + interface SurveyStatusDropdownProps { environment: TEnvironment; updateLocalSurveyStatus?: (status: TSurvey["status"]) => void; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/loading.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/loading.tsx deleted file mode 100644 index 20ccf8fb6f..0000000000 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/loading.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { LoadingSkeleton } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/LoadingSkeleton"; - -export default function Loading() { - return ; -} diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyStarter.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyStarter.tsx index 111ac22dcd..9c1e0c3f8e 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyStarter.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/components/SurveyStarter.tsx @@ -1,18 +1,9 @@ "use client"; -import { TemplateList } from "@/app/(app)/environments/[environmentId]/surveys/templates/TemplateList"; -import { useRouter } from "next/navigation"; -import { useState } from "react"; -import { toast } from "react-hot-toast"; - import type { TEnvironment } from "@formbricks/types/environment"; import type { TProduct } from "@formbricks/types/product"; -import { TSurveyInput } from "@formbricks/types/surveys"; -import { TTemplate } from "@formbricks/types/templates"; import { TUser } from "@formbricks/types/user"; -import LoadingSpinner from "@formbricks/ui/LoadingSpinner"; - -import { createSurveyAction } from "../actions"; +import { TemplateList } from "@formbricks/ui/TemplateList"; export default function SurveyStarter({ environmentId, @@ -25,47 +16,21 @@ export default function SurveyStarter({ product: TProduct; user: TUser; }) { - const [isCreateSurveyLoading, setIsCreateSurveyLoading] = useState(false); - const router = useRouter(); - - const newSurveyFromTemplate = async (template: TTemplate) => { - setIsCreateSurveyLoading(true); - const surveyType = environment?.widgetSetupCompleted ? "app" : "link"; - const augmentedTemplate: TSurveyInput = { - ...template.preset, - type: surveyType, - createdBy: user.id, - }; - try { - const survey = await createSurveyAction(environmentId, augmentedTemplate); - router.push(`/environments/${environmentId}/surveys/${survey.id}/edit`); - } catch (e) { - toast.error("An error occured creating a new survey"); - setIsCreateSurveyLoading(false); - } - }; return ( -
- {isCreateSurveyLoading ? ( - - ) : ( - <> -
-

- You're all set! Time to create your first survey. -

-
- { + <> +

+ You're all set! Time to create your first survey. +

+ + { newSurveyFromTemplate(template); - }} - environment={environment} - product={product} - user={user} - /> - - )} -
+ }} */ + environment={environment} + product={product} + user={user} + /> + ); } diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/page.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/page.tsx index 08b621f500..320bc392c9 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/page.tsx @@ -1,18 +1,19 @@ -import WidgetStatusIndicator from "@/app/(app)/environments/[environmentId]/components/WidgetStatusIndicator"; import SurveyStarter from "@/app/(app)/environments/[environmentId]/surveys/components/SurveyStarter"; +import { PlusIcon } from "lucide-react"; import { Metadata } from "next"; import { getServerSession } from "next-auth"; import { authOptions } from "@formbricks/lib/authOptions"; import { SURVEYS_PER_PAGE, WEBAPP_URL } from "@formbricks/lib/constants"; -import { getEnvironment } from "@formbricks/lib/environment/service"; -import { getEnvironments } from "@formbricks/lib/environment/service"; +import { getEnvironment, getEnvironments } from "@formbricks/lib/environment/service"; import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service"; import { getAccessFlags } from "@formbricks/lib/membership/utils"; import { getProductByEnvironmentId } from "@formbricks/lib/product/service"; import { getSurveyCount } from "@formbricks/lib/survey/service"; import { getTeamByEnvironmentId } from "@formbricks/lib/team/service"; -import { ContentWrapper } from "@formbricks/ui/ContentWrapper"; +import { Button } from "@formbricks/ui/Button"; +import { PageContentWrapper } from "@formbricks/ui/PageContentWrapper"; +import { PageHeader } from "@formbricks/ui/PageHeader"; import { SurveysList } from "@formbricks/ui/SurveysList"; export const metadata: Metadata = { @@ -48,17 +49,30 @@ export default async function SurveysPage({ params }) { const environments = await getEnvironments(product.id); const otherEnvironment = environments.find((e) => e.type !== environment.type)!; + const CreateSurveyButton = ( + + ); + return ( - + {surveyCount > 0 ? ( - + <> + + + ) : ( )} - - - + ); } diff --git a/apps/web/app/(app)/onboarding/components/inapp/SetupInstructions.tsx b/apps/web/app/(app)/onboarding/components/inapp/SetupInstructions.tsx index 155891e477..4f9c578011 100644 --- a/apps/web/app/(app)/onboarding/components/inapp/SetupInstructions.tsx +++ b/apps/web/app/(app)/onboarding/components/inapp/SetupInstructions.tsx @@ -3,15 +3,15 @@ import "prismjs/themes/prism.css"; import { useState } from "react"; import toast from "react-hot-toast"; -import { IoLogoHtml5, IoLogoNpm } from "react-icons/io5"; import { cn } from "@formbricks/lib/cn"; import { Button } from "@formbricks/ui/Button"; import CodeBlock from "@formbricks/ui/CodeBlock"; +import { Html5Icon, NpmIcon } from "@formbricks/ui/icons"; const tabs = [ - { id: "html", label: "HTML", icon: }, - { id: "npm", label: "NPM", icon: }, + { id: "html", label: "HTML", icon: }, + { id: "npm", label: "NPM", icon: }, ]; interface SetupInstructionsOnboardingProps { diff --git a/apps/web/app/(app)/onboarding/components/link/CreateFirstSurvey.tsx b/apps/web/app/(app)/onboarding/components/link/CreateFirstSurvey.tsx index f1356f4241..6c5913896e 100644 --- a/apps/web/app/(app)/onboarding/components/link/CreateFirstSurvey.tsx +++ b/apps/web/app/(app)/onboarding/components/link/CreateFirstSurvey.tsx @@ -1,9 +1,5 @@ "use client"; -import { - customSurvey, - templates, -} from "@/app/(app)/environments/[environmentId]/surveys/templates/templates"; import OnboardingTitle from "@/app/(app)/onboarding/components/OnboardingTitle"; import ChurnImage from "@/images/onboarding-churn.png"; import FeedbackImage from "@/images/onboarding-collect-feedback.png"; @@ -14,6 +10,7 @@ import { useRouter } from "next/navigation"; import { useState } from "react"; import { toast } from "react-hot-toast"; +import { customSurvey, templates } from "@formbricks/lib/templates"; import { TTemplate } from "@formbricks/types/templates"; import { Button } from "@formbricks/ui/Button"; import { OptionCard } from "@formbricks/ui/OptionCard"; diff --git a/apps/web/app/(auth)/auth/components/AzureButton.tsx b/apps/web/app/(auth)/auth/components/AzureButton.tsx index 1cc80da8c9..1caf9e0d83 100644 --- a/apps/web/app/(auth)/auth/components/AzureButton.tsx +++ b/apps/web/app/(auth)/auth/components/AzureButton.tsx @@ -1,8 +1,8 @@ import { signIn } from "next-auth/react"; import { useCallback, useEffect } from "react"; -import { FaMicrosoft } from "react-icons/fa"; import { Button } from "@formbricks/ui/Button"; +import { MicrosoftIcon } from "@formbricks/ui/icons"; export const AzureButton = ({ text = "Continue with Azure", @@ -29,7 +29,7 @@ export const AzureButton = ({ return ( -
+ { handleCreateSegment(); }}> - Create Segment + Create segment
diff --git a/packages/ee/multiLanguage/components/EditLanguage.tsx b/packages/ee/multiLanguage/components/EditLanguage.tsx index df59874342..51a2dbfcf6 100644 --- a/packages/ee/multiLanguage/components/EditLanguage.tsx +++ b/packages/ee/multiLanguage/components/EditLanguage.tsx @@ -152,7 +152,7 @@ export default function EditLanguage({ product, environmentId }: EditLanguagePro ) : null; return ( -
+
{languages.length > 0 ? ( <> @@ -231,15 +231,15 @@ const EditSaveButtons: React.FC<{ }> = ({ isEditing, onEdit, onSave, onCancel }) => isEditing ? (
- -
) : ( - ); diff --git a/packages/ee/multiLanguage/components/MultiLanguageCard.tsx b/packages/ee/multiLanguage/components/MultiLanguageCard.tsx index fc057b1a68..1ebb524b8b 100644 --- a/packages/ee/multiLanguage/components/MultiLanguageCard.tsx +++ b/packages/ee/multiLanguage/components/MultiLanguageCard.tsx @@ -268,7 +268,7 @@ export const MultiLanguageCard: FC = ({
)} - + diff --git a/packages/js/index.html b/packages/js/index.html index 96161294f2..2331286069 100644 --- a/packages/js/index.html +++ b/packages/js/index.html @@ -7,7 +7,7 @@ e.parentNode.insertBefore(t, e), setTimeout(function () { formbricks.init({ - environmentId: "clvc0nye3003bubfl568et5f8", + environmentId: "clvzbzhhi000d10cpk0vtopz4", apiHost: "http://localhost:3000", }); }, 500); diff --git a/packages/lib/auth/util.ts b/packages/lib/auth/utils.ts similarity index 100% rename from packages/lib/auth/util.ts rename to packages/lib/auth/utils.ts diff --git a/packages/lib/authOptions.ts b/packages/lib/authOptions.ts index 888dc8fdad..6338a07cae 100644 --- a/packages/lib/authOptions.ts +++ b/packages/lib/authOptions.ts @@ -8,7 +8,7 @@ import GoogleProvider from "next-auth/providers/google"; import { prisma } from "@formbricks/database"; import { createAccount } from "./account/service"; -import { verifyPassword } from "./auth/util"; +import { verifyPassword } from "./auth/utils"; import { AZUREAD_CLIENT_ID, AZUREAD_CLIENT_SECRET, diff --git a/packages/lib/person/util.ts b/packages/lib/person/utils.ts similarity index 100% rename from packages/lib/person/util.ts rename to packages/lib/person/utils.ts diff --git a/packages/lib/response/service.ts b/packages/lib/response/service.ts index ccafe217e7..c4516a213e 100644 --- a/packages/lib/response/service.ts +++ b/packages/lib/response/service.ts @@ -30,16 +30,6 @@ import { ITEMS_PER_PAGE, WEBAPP_URL } from "../constants"; import { displayCache } from "../display/cache"; import { deleteDisplayByResponseId, getDisplayCountBySurveyId } from "../display/service"; import { createPerson, getPerson, getPersonByUserId } from "../person/service"; -import { - buildWhereClause, - calculateTtcTotal, - extractSurveyDetails, - getQuestionWiseSummary, - getResponsesFileName, - getResponsesJson, - getSurveySummaryDropOff, - getSurveySummaryMeta, -} from "../response/util"; import { responseNoteCache } from "../responseNote/cache"; import { getResponseNotes } from "../responseNote/service"; import { putFile } from "../storage/service"; @@ -49,6 +39,16 @@ import { convertToCsv, convertToXlsxBuffer } from "../utils/fileConversion"; import { checkForRecallInHeadline } from "../utils/recall"; import { validateInputs } from "../utils/validate"; import { responseCache } from "./cache"; +import { + buildWhereClause, + calculateTtcTotal, + extractSurveyDetails, + getQuestionWiseSummary, + getResponsesFileName, + getResponsesJson, + getSurveySummaryDropOff, + getSurveySummaryMeta, +} from "./utils"; const RESPONSES_PER_PAGE = 10; diff --git a/packages/lib/response/tests/response.test.ts b/packages/lib/response/tests/response.test.ts index 8a874138cd..7518e2da1f 100644 --- a/packages/lib/response/tests/response.test.ts +++ b/packages/lib/response/tests/response.test.ts @@ -50,7 +50,7 @@ import { getSurveySummary, updateResponse, } from "../service"; -import { buildWhereClause } from "../util"; +import { buildWhereClause } from "../utils"; import { constantsForTests } from "./constants"; const expectedResponseWithoutPerson: TResponse = { diff --git a/packages/lib/response/util.ts b/packages/lib/response/utils.ts similarity index 100% rename from packages/lib/response/util.ts rename to packages/lib/response/utils.ts diff --git a/packages/lib/survey/service.ts b/packages/lib/survey/service.ts index c7663eba09..6afbc55467 100644 --- a/packages/lib/survey/service.ts +++ b/packages/lib/survey/service.ts @@ -34,7 +34,7 @@ import { subscribeTeamMembersToSurveyResponses } from "../team/service"; import { diffInDays } from "../utils/datetime"; import { validateInputs } from "../utils/validate"; import { surveyCache } from "./cache"; -import { anySurveyHasFilters, buildOrderByClause, buildWhereClause, transformPrismaSurvey } from "./util"; +import { anySurveyHasFilters, buildOrderByClause, buildWhereClause, transformPrismaSurvey } from "./utils"; interface TriggerUpdate { create?: Array<{ actionClassId: string }>; diff --git a/packages/lib/survey/util.ts b/packages/lib/survey/utils.ts similarity index 100% rename from packages/lib/survey/util.ts rename to packages/lib/survey/utils.ts diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts b/packages/lib/templates.ts similarity index 93% rename from apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts rename to packages/lib/templates.ts index 76ace623f3..23fc907458 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts +++ b/packages/lib/templates.ts @@ -2,7 +2,6 @@ import { createId } from "@paralleldrive/cuid2"; import { TActionClass } from "@formbricks/types/actionClasses"; import { - TSurvey, TSurveyCTAQuestion, TSurveyDisplayOption, TSurveyHiddenFields, @@ -10,11 +9,13 @@ import { TSurveyOpenTextQuestion, TSurveyQuestionType, TSurveyStatus, + TSurveyThankYouCard, TSurveyType, + TSurveyWelcomeCard, } from "@formbricks/types/surveys"; import { TTemplate } from "@formbricks/types/templates"; -const thankYouCardDefault = { +const thankYouCardDefault: TSurveyThankYouCard = { enabled: true, headline: { default: "Thank you!" }, subheader: { default: "We appreciate your feedback." }, @@ -27,7 +28,7 @@ const hiddenFieldsDefault: TSurveyHiddenFields = { fieldIds: [], }; -const welcomeCardDefault = { +const welcomeCardDefault: TSurveyWelcomeCard = { enabled: false, headline: { default: "Welcome!" }, html: { default: "Thanks for providing your feedback - let's go!" }, @@ -35,11 +36,20 @@ const welcomeCardDefault = { showResponseCount: false, }; +const surveyDefault: TTemplate["preset"] = { + name: "New Survey", + welcomeCard: welcomeCardDefault, + thankYouCard: thankYouCardDefault, + hiddenFields: hiddenFieldsDefault, + questions: [], +}; + export const testTemplate: TTemplate = { name: "Test template", category: "Product Experience", description: "Test template consisting of all questions", preset: { + ...surveyDefault, name: "Test template", questions: [ { @@ -337,11 +347,6 @@ export const testTemplate: TTemplate = { label: { default: "I agree to the terms and conditions" }, }, ], - thankYouCard: thankYouCardDefault, - welcomeCard: welcomeCardDefault, - hiddenFields: { - enabled: false, - }, }, }; @@ -352,8 +357,8 @@ export const templates: TTemplate[] = [ description: "Measure PMF by assessing how disappointed users would be if your product disappeared.", preset: { + ...surveyDefault, name: "Product Market Fit (Superhuman)", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -444,8 +449,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -454,8 +457,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_user_adoption", "improve_user_retention"], description: "Learn more about who signed up to your product and why.", preset: { + ...surveyDefault, name: "Onboarding Segmentation", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -548,8 +551,6 @@ export const templates: TTemplate[] = [ ], }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -558,8 +559,8 @@ export const templates: TTemplate[] = [ objectives: ["sharpen_marketing_messaging", "improve_user_retention"], description: "Find out why people cancel their subscriptions. These insights are pure gold!", preset: { + ...surveyDefault, name: "Churn Survey", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -631,8 +632,6 @@ export const templates: TTemplate[] = [ dismissButtonLabel: { default: "Skip" }, }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -642,8 +641,8 @@ export const templates: TTemplate[] = [ description: "The EAS is a riff off the NPS but asking for actual past behaviour instead of lofty intentions.", preset: { + ...surveyDefault, name: "Earned Advocacy Score (EAS)", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -695,8 +694,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -705,8 +702,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_user_adoption", "increase_conversion", "improve_user_retention"], description: "Find out why people stopped their trial. These insights help you improve your funnel.", preset: { + ...surveyDefault, name: "Improve Trial Conversion", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -802,8 +799,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -813,8 +808,8 @@ export const templates: TTemplate[] = [ objectives: ["support_sales"], description: "Invite users who love your product to review it publicly.", preset: { + ...surveyDefault, name: "Review Prompt", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -849,8 +844,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -860,8 +853,8 @@ export const templates: TTemplate[] = [ objectives: ["improve_user_retention"], description: "Invite a specific subset of your users to schedule an interview with your product team.", preset: { + ...surveyDefault, name: "Interview Prompt", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -874,8 +867,6 @@ export const templates: TTemplate[] = [ required: false, }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -884,8 +875,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_user_adoption", "increase_conversion"], description: "Identify weaknesses in your onboarding flow to increase user activation.", preset: { + ...surveyDefault, name: "Onboarding Drop-Off Reasons", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -972,8 +963,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -982,8 +971,8 @@ export const templates: TTemplate[] = [ objectives: ["sharpen_marketing_messaging", "improve_user_retention"], description: "Find out what users like and don't like about your product or offering.", preset: { + ...surveyDefault, name: "Uncover Strengths & Weaknesses", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1022,8 +1011,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -1031,8 +1018,8 @@ export const templates: TTemplate[] = [ category: "Product Experience", description: "Measure PMF by assessing how disappointed users would be if your product disappeared.", preset: { + ...surveyDefault, name: "Product Market Fit Survey (Short)", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1065,8 +1052,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -1076,8 +1061,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_conversion", "sharpen_marketing_messaging"], description: "How did you first hear about us?", preset: { + ...surveyDefault, name: "Marketing Attribution", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1110,8 +1095,6 @@ export const templates: TTemplate[] = [ ], }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -1121,8 +1104,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_conversion", "improve_user_retention"], description: "Find out what goes through peoples minds when changing their subscriptions.", preset: { + ...surveyDefault, name: "Changing subscription experience", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1175,8 +1158,6 @@ export const templates: TTemplate[] = [ ], }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, @@ -1188,8 +1169,8 @@ export const templates: TTemplate[] = [ description: "Better understand if your messaging creates the right expectations of the value your product provides.", preset: { + ...surveyDefault, name: "Identify Customer Goals", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1217,8 +1198,6 @@ export const templates: TTemplate[] = [ ], }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -1228,8 +1207,8 @@ export const templates: TTemplate[] = [ objectives: ["improve_user_retention"], description: "Follow up with users who just used a specific feature.", preset: { + ...surveyDefault, name: "Feature Chaser", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1255,8 +1234,6 @@ export const templates: TTemplate[] = [ required: true, }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -1266,8 +1243,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_user_adoption"], description: "Follow up with users who ran into one of your Fake Door experiments.", preset: { + ...surveyDefault, name: "Fake Door Follow-Up", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1305,8 +1282,6 @@ export const templates: TTemplate[] = [ ], }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -1316,8 +1291,8 @@ export const templates: TTemplate[] = [ objectives: ["improve_user_retention"], description: "Give your users the chance to seamlessly share what's on their minds.", preset: { + ...surveyDefault, name: "Feedback Box", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1372,8 +1347,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -1383,8 +1356,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_user_adoption"], description: "Evaluate how easily users can add integrations to your product. Find blind spots.", preset: { + ...surveyDefault, name: "Integration Usage Survey", - welcomeCard: welcomeCardDefault, questions: [ { id: "s6ss6znzxdwjod1hv16fow4w", @@ -1414,8 +1387,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -1425,8 +1396,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_user_adoption", "increase_conversion"], description: "Find out which integrations your users would like to see next.", preset: { + ...surveyDefault, name: "New Integration Survey", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1458,8 +1429,6 @@ export const templates: TTemplate[] = [ ], }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -1469,8 +1438,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_user_adoption", "improve_user_retention"], description: "Measure how clear each page of your developer documentation is.", preset: { + ...surveyDefault, name: "{{productName}} Docs Feedback", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1504,8 +1473,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -1515,8 +1482,8 @@ export const templates: TTemplate[] = [ objectives: ["support_sales"], description: "Measure the Net Promoter Score of your product or service.", preset: { + ...surveyDefault, name: "NPS Survey", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1534,8 +1501,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -1544,8 +1509,8 @@ export const templates: TTemplate[] = [ objectives: ["support_sales"], description: "Measure the Customer Satisfaction Score of your product.", preset: { + ...surveyDefault, name: "{{productName}} CSAT", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1576,8 +1541,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -1586,8 +1549,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_user_adoption", "improve_user_retention"], description: "Gather comprehensive feedback on your product or service.", preset: { + ...surveyDefault, name: "Feedback Survey", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1664,8 +1627,6 @@ export const templates: TTemplate[] = [ placeholder: { default: "example@email.com" }, }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -1675,8 +1636,8 @@ export const templates: TTemplate[] = [ objectives: ["support_sales", "sharpen_marketing_messaging"], description: "Find out how much time your product saves your user. Use it to upsell.", preset: { + ...surveyDefault, name: "Identify upsell opportunities", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1704,8 +1665,6 @@ export const templates: TTemplate[] = [ ], }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, @@ -1716,8 +1675,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_user_adoption"], description: "Identify features your users need most and least.", preset: { + ...surveyDefault, name: "Feature Prioritization", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1755,8 +1714,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -1766,8 +1723,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_user_adoption", "improve_user_retention"], description: "Evaluate the satisfaction of specific features of your product.", preset: { + ...surveyDefault, name: "Gauge Feature Satisfaction", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1798,8 +1755,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_conversion", "sharpen_marketing_messaging"], description: "Identify users dropping off your marketing site. Improve your messaging.", preset: { + ...surveyDefault, name: "Marketing Site Clarity", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1839,8 +1796,6 @@ export const templates: TTemplate[] = [ buttonExternal: true, }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -1850,8 +1805,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_user_adoption", "improve_user_retention"], description: "Determine how easy it is to use a feature.", preset: { + ...surveyDefault, name: "Customer Effort Score (CES)", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1872,8 +1827,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, @@ -1884,8 +1837,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_conversion"], description: "Let customers rate the checkout experience to tweak conversion.", preset: { + ...surveyDefault, name: "Rate Checkout Experience", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1916,8 +1869,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -1927,8 +1878,8 @@ export const templates: TTemplate[] = [ objectives: ["improve_user_retention"], description: "Measure how relevant your search results are.", preset: { + ...surveyDefault, name: "Measure Search Experience", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -1959,8 +1910,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -1970,8 +1919,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_conversion"], description: "Measure if your content marketing pieces hit right.", preset: { + ...surveyDefault, name: "Evaluate Content Quality", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -2002,8 +1951,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -2013,8 +1960,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_user_adoption", "improve_user_retention"], description: "See if people get their 'Job To Be Done' done. Successful people are better customers.", preset: { + ...surveyDefault, name: "Measure Task Accomplishment", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -2078,8 +2025,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -2089,8 +2034,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_conversion"], description: "Offer a discount to gather insights about sign up barriers.", preset: { + ...surveyDefault, name: "{{productName}} Sign Up Barriers", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -2155,7 +2100,6 @@ export const templates: TTemplate[] = [ placeholder: { default: "Type your answer here..." }, inputType: "text", }, - { id: "j7jkpolm5xl7u0zt3g0e4z7d", type: TSurveyQuestionType.OpenText, @@ -2206,8 +2150,6 @@ export const templates: TTemplate[] = [ dismissButtonLabel: { default: "Skip for now" }, }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -2217,8 +2159,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_user_adoption"], description: "Identify the ONE thing your users want the most and build it.", preset: { + ...surveyDefault, name: "{{productName}} Roadmap Input", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -2243,8 +2185,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -2254,8 +2194,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_conversion", "increase_user_adoption"], description: "Find out how close your visitors are to buy or subscribe.", preset: { + ...surveyDefault, name: "Purchase Intention Survey", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -2294,8 +2234,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -2304,8 +2242,8 @@ export const templates: TTemplate[] = [ objectives: ["increase_conversion", "sharpen_marketing_messaging"], description: "Find out how your subscribers like your newsletter content.", preset: { + ...surveyDefault, name: "Improve Newsletter Content", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -2348,8 +2286,6 @@ export const templates: TTemplate[] = [ dismissButtonLabel: { default: "Find your own friends" }, }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -2359,8 +2295,8 @@ export const templates: TTemplate[] = [ objectives: ["improve_user_retention", "increase_user_adoption"], description: "Survey users about product or feature ideas. Get feedback rapidly.", preset: { + ...surveyDefault, name: "Evaluate a Product Idea", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -2453,8 +2389,6 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, { @@ -2464,8 +2398,8 @@ export const templates: TTemplate[] = [ objectives: ["improve_user_retention", "increase_user_adoption"], description: "Identify reasons for low engagement to improve user adoption.", preset: { + ...surveyDefault, name: "Reasons for Low Engagement", - welcomeCard: welcomeCardDefault, questions: [ { id: "aq9dafe9nxe0kpm67b1os2z9", @@ -2546,43 +2480,16 @@ export const templates: TTemplate[] = [ inputType: "text", }, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }, - - /* { - name: "TEMPLATE MASTER", - - category: "X", - objectives: ["X"], - description: "X", - preset: { - name: "X", - welcomeCard: welcomeCardDefault, -questions: [ - - { - id: createId(), - type: "X", - headline: "X", - required: false, - lowerLabel: "Not likely", - upperLabel: "Very likely", - }, - ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, - }, - }, */ ]; export const customSurvey = { name: "Start from scratch", description: "Create a survey without template.", preset: { + ...surveyDefault, name: "New Survey", - welcomeCard: welcomeCardDefault, questions: [ { id: createId(), @@ -2594,49 +2501,9 @@ export const customSurvey = { inputType: "text", } as TSurveyOpenTextQuestion, ], - thankYouCard: thankYouCardDefault, - hiddenFields: hiddenFieldsDefault, }, }; -export const minimalSurvey: TSurvey = { - id: "someUniqueId1", - createdAt: new Date(), - updatedAt: new Date(), - name: "Minimal Survey", - type: "app", - environmentId: "someEnvId1", - createdBy: null, - status: "draft", - displayOption: "displayOnce", - autoClose: null, - triggers: [], - redirectUrl: null, - recontactDays: null, - welcomeCard: welcomeCardDefault, - questions: [], - thankYouCard: { - enabled: false, - }, - hiddenFields: { - enabled: false, - }, - delay: 0, // No delay - displayPercentage: null, - autoComplete: null, - runOnDate: null, - closeOnDate: null, - surveyClosedMessage: { - enabled: false, - }, - productOverwrites: null, - singleUse: null, - styling: null, - resultShareKey: null, - segment: null, - languages: [], -}; - export const getExampleSurveyTemplate = (webAppUrl: string, trigger: TActionClass): TSurveyInput => ({ ...customSurvey.preset, questions: customSurvey.preset.questions.map( diff --git a/packages/tailwind-config/tailwind.config.js b/packages/tailwind-config/tailwind.config.js index 2e6df32452..6507ee7674 100644 --- a/packages/tailwind-config/tailwind.config.js +++ b/packages/tailwind-config/tailwind.config.js @@ -85,6 +85,13 @@ module.exports = { to: { height: 0 }, }, }, + width: { + "sidebar-expanded": "4rem", + "sidebar-collapsed": "14rem", + }, + transitionProperty: { + width: "width", + }, maxWidth: { "8xl": "88rem", }, diff --git a/packages/ui/Button/index.tsx b/packages/ui/Button/index.tsx index f0fc2f8042..c621febc7d 100644 --- a/packages/ui/Button/index.tsx +++ b/packages/ui/Button/index.tsx @@ -4,6 +4,8 @@ import React, { AnchorHTMLAttributes, ButtonHTMLAttributes, forwardRef } from "r import { cn } from "@formbricks/lib/cn"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../Tooltip"; + type SVGComponent = React.FunctionComponent> | LucideIcon; export type ButtonBaseProps = { @@ -17,6 +19,9 @@ export type ButtonBaseProps = { EndIcon?: SVGComponent | React.ComponentType>; endIconClassName?: string; shallow?: boolean; + tooltip?: string; + tooltipSide?: "top" | "right" | "bottom" | "left"; + tooltipOffset?: number; }; type ButtonBasePropsWithTarget = ButtonBaseProps & { target?: string }; @@ -41,6 +46,8 @@ export const Button: React.ForwardRefExoticComponent< endIconClassName, EndIcon, shallow, + tooltipSide = "top", + tooltipOffset = 4, // attributes propagated from `HTMLAnchorProps` or `HTMLButtonProps` ...passThroughProps } = props; @@ -150,6 +157,39 @@ export const Button: React.ForwardRefExoticComponent< {element} ) : ( - element + + {element} + ); }); + +const Wrapper = ({ + children, + tooltip, + tooltipSide = "top", + tooltipOffset = 0, +}: { + tooltip?: string; + children: React.ReactNode; + tooltipSide?: "top" | "right" | "bottom" | "left"; + tooltipOffset?: number; +}) => { + if (!tooltip) { + return <>{children}; + } + + return ( + + + {children} + + {tooltip} + + + + ); +}; diff --git a/packages/ui/Card/index.tsx b/packages/ui/Card/index.tsx index 45c8e64e17..76ce680d82 100644 --- a/packages/ui/Card/index.tsx +++ b/packages/ui/Card/index.tsx @@ -29,9 +29,9 @@ export const Card: React.FC = ({ connected, statusText, }) => ( -
+
{connected != undefined && statusText != undefined && ( -
+
{connected === true ? ( diff --git a/packages/ui/CodeBlock/index.tsx b/packages/ui/CodeBlock/index.tsx index 02306d5a30..1c030fd05f 100644 --- a/packages/ui/CodeBlock/index.tsx +++ b/packages/ui/CodeBlock/index.tsx @@ -32,13 +32,14 @@ const CodeBlock: React.FC = ({ return (
{showCopyToClipboard && ( -
+
{ const childText = children?.toString() || ""; navigator.clipboard.writeText(childText); toast.success("Copied to clipboard"); }} + className="h-4 w-4" />
)} diff --git a/packages/ui/ContentWrapper/index.tsx b/packages/ui/ContentWrapper/index.tsx deleted file mode 100644 index 6ad9e43936..0000000000 --- a/packages/ui/ContentWrapper/index.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { cn } from "@formbricks/lib/cn"; - -interface ContentWrapperProps { - children: React.ReactNode; - className?: string; -} - -export const ContentWrapper = ({ children, className }: ContentWrapperProps) => { - return
{children}
; -}; diff --git a/packages/ui/DeleteDialog/index.tsx b/packages/ui/DeleteDialog/index.tsx index 22e47a1b14..f595449706 100644 --- a/packages/ui/DeleteDialog/index.tsx +++ b/packages/ui/DeleteDialog/index.tsx @@ -17,7 +17,7 @@ interface DeleteDialogProps { disabled?: boolean; } -export function DeleteDialog({ +export const DeleteDialog = ({ open, setOpen, deleteWhat, @@ -29,7 +29,7 @@ export function DeleteDialog({ onSave, children, disabled, -}: DeleteDialogProps) { +}: DeleteDialogProps) => { return (

{text || "Are you sure? This action cannot be undone."}

@@ -52,4 +52,4 @@ export function DeleteDialog({
); -} +}; diff --git a/packages/ui/DevEnvironmentBanner/index.tsx b/packages/ui/DevEnvironmentBanner/index.tsx new file mode 100644 index 0000000000..421d985241 --- /dev/null +++ b/packages/ui/DevEnvironmentBanner/index.tsx @@ -0,0 +1,17 @@ +import { TEnvironment } from "@formbricks/types/environment"; + +interface DevEnvironmentBannerProps { + environment: TEnvironment; +} + +export const DevEnvironmentBanner = ({ environment }: DevEnvironmentBannerProps) => { + return ( + <> + {environment.type === "development" && ( +
+ You're in an development environment. Set it up to test surveys, actions and attributes. +
+ )} + + ); +}; diff --git a/packages/ui/DropdownMenu/index.tsx b/packages/ui/DropdownMenu/index.tsx index 45ee381412..d22f77c4c4 100644 --- a/packages/ui/DropdownMenu/index.tsx +++ b/packages/ui/DropdownMenu/index.tsx @@ -192,18 +192,18 @@ DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; export { DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, DropdownMenuCheckboxItem, - DropdownMenuRadioItem, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, DropdownMenuLabel, + DropdownMenuPortal, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, - DropdownMenuGroup, - DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, - DropdownMenuRadioGroup, + DropdownMenuTrigger, }; diff --git a/packages/ui/EmptySpaceFiller/index.tsx b/packages/ui/EmptySpaceFiller/index.tsx index 815f0ff639..61d2ddfcb8 100644 --- a/packages/ui/EmptySpaceFiller/index.tsx +++ b/packages/ui/EmptySpaceFiller/index.tsx @@ -22,16 +22,14 @@ const EmptySpaceFiller: React.FC = ({ }) => { if (type === "table") { return ( -
-
-
-
- +
+
+
{!environment.widgetSetupCompleted && !noWidgetRequired && ( + href={`/environments/${environment.id}/product/setup`}> Install Formbricks Widget. Go to Setup Checklist 👉 @@ -41,7 +39,7 @@ const EmptySpaceFiller: React.FC = ({ "Waiting for a response 🧘‍♂️"}
-
+
); @@ -60,7 +58,7 @@ const EmptySpaceFiller: React.FC = ({ {!environment.widgetSetupCompleted && !noWidgetRequired && ( + href={`/environments/${environment.id}/product/setup`}> Install Formbricks Widget. Go to Setup Checklist 👉 @@ -91,7 +89,7 @@ const EmptySpaceFiller: React.FC = ({ {!environment.widgetSetupCompleted && !noWidgetRequired && ( + href={`/environments/${environment.id}/product/setup`}> Install Formbricks Widget. Go to Setup Checklist 👉 @@ -109,7 +107,7 @@ const EmptySpaceFiller: React.FC = ({ if (type === "summary") { return ( -
+
@@ -139,7 +137,7 @@ const EmptySpaceFiller: React.FC = ({ {!environment.widgetSetupCompleted && !noWidgetRequired && ( + href={`/environments/${environment.id}/product/setup`}> Install Formbricks Widget. Go to Setup Checklist 👉 diff --git a/packages/ui/EnvironmentNotice/index.tsx b/packages/ui/EnvironmentNotice/index.tsx index 283122babb..7273b6e847 100644 --- a/packages/ui/EnvironmentNotice/index.tsx +++ b/packages/ui/EnvironmentNotice/index.tsx @@ -18,18 +18,16 @@ export default async function EnvironmentNotice({ environmentId, subPageUrl }: E const otherEnvironmentId = environments.filter((e) => e.id !== environment.id)[0].id; return ( -
-
- -

- {`You're currently in the ${environment.type} environment.`} - - Switch to {environment.type === "production" ? "Development" : "Production"} now. - -

-
+
+ +

+ {`You're currently in the ${environment.type} environment.`} + + Switch to {environment.type === "production" ? "Development" : "Production"} now. + +

); } diff --git a/packages/ui/FileUploadResponse/index.tsx b/packages/ui/FileUploadResponse/index.tsx index 911c748428..2320e3d325 100644 --- a/packages/ui/FileUploadResponse/index.tsx +++ b/packages/ui/FileUploadResponse/index.tsx @@ -19,7 +19,7 @@ export const FileUploadResponse = ({ selected }: FileUploadResponseProps) => { const fileName = getOriginalFileNameFromUrl(fileUrl); return ( -
+
diff --git a/packages/ui/GoBackButton/index.tsx b/packages/ui/GoBackButton/index.tsx index 2f3835d02b..b9de661096 100644 --- a/packages/ui/GoBackButton/index.tsx +++ b/packages/ui/GoBackButton/index.tsx @@ -1,14 +1,17 @@ "use client"; +import { ArrowLeftIcon } from "lucide-react"; import { useRouter } from "next/navigation"; -import { BackIcon } from "../icons"; +import { Button } from "../Button"; -export default function GoBackButton({ url }: { url?: string }) { +export const GoBackButton = ({ url }: { url?: string }) => { const router = useRouter(); return ( - + ); -} +}; diff --git a/apps/web/app/s/[surveyId]/components/MediaBackground.tsx b/packages/ui/MediaBackground/index.tsx similarity index 100% rename from apps/web/app/s/[surveyId]/components/MediaBackground.tsx rename to packages/ui/MediaBackground/index.tsx diff --git a/packages/ui/PageContentWrapper/index.tsx b/packages/ui/PageContentWrapper/index.tsx new file mode 100644 index 0000000000..e4d9c32346 --- /dev/null +++ b/packages/ui/PageContentWrapper/index.tsx @@ -0,0 +1,10 @@ +import { cn } from "@formbricks/lib/cn"; + +interface PageContentWrapperProps { + children: React.ReactNode; + className?: string; +} + +export const PageContentWrapper = ({ children, className }: PageContentWrapperProps) => { + return
{children}
; +}; diff --git a/packages/ui/PageHeader/index.tsx b/packages/ui/PageHeader/index.tsx new file mode 100644 index 0000000000..77005ef9eb --- /dev/null +++ b/packages/ui/PageHeader/index.tsx @@ -0,0 +1,19 @@ +import { cn } from "@formbricks/lib/cn"; + +interface PageHeaderProps { + pageTitle: string; + cta?: React.ReactNode; + children?: React.ReactNode; +} + +export const PageHeader = ({ cta, pageTitle, children }: PageHeaderProps) => { + return ( +
+
+

{pageTitle}

+ {cta} +
+ {children} +
+ ); +}; diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/components/Modal.tsx b/packages/ui/PreviewSurvey/components/Modal.tsx similarity index 98% rename from apps/web/app/(app)/environments/[environmentId]/surveys/components/Modal.tsx rename to packages/ui/PreviewSurvey/components/Modal.tsx index 17add2fa8b..4123b17aa0 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/components/Modal.tsx +++ b/packages/ui/PreviewSurvey/components/Modal.tsx @@ -1,9 +1,10 @@ -import { getPlacementStyle } from "@/app/lib/preview"; import { ReactNode, useEffect, useRef, useState } from "react"; import { cn } from "@formbricks/lib/cn"; import { TPlacement } from "@formbricks/types/common"; +import { getPlacementStyle } from "../lib/utils"; + export default function Modal({ children, isOpen, diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/components/TabOption.tsx b/packages/ui/PreviewSurvey/components/TabOption.tsx similarity index 100% rename from apps/web/app/(app)/environments/[environmentId]/surveys/components/TabOption.tsx rename to packages/ui/PreviewSurvey/components/TabOption.tsx diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/components/PreviewSurvey.tsx b/packages/ui/PreviewSurvey/index.tsx similarity index 96% rename from apps/web/app/(app)/environments/[environmentId]/surveys/components/PreviewSurvey.tsx rename to packages/ui/PreviewSurvey/index.tsx index 0fc1068785..3c43269a90 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/components/PreviewSurvey.tsx +++ b/packages/ui/PreviewSurvey/index.tsx @@ -1,8 +1,5 @@ "use client"; -import Modal from "@/app/(app)/environments/[environmentId]/surveys/components/Modal"; -import TabOption from "@/app/(app)/environments/[environmentId]/surveys/components/TabOption"; -import { MediaBackground } from "@/app/s/[surveyId]/components/MediaBackground"; import { Variants, motion } from "framer-motion"; import { ExpandIcon, MonitorIcon, ShrinkIcon, SmartphoneIcon } from "lucide-react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; @@ -12,9 +9,13 @@ import type { TProduct } from "@formbricks/types/product"; import { TProductStyling } from "@formbricks/types/product"; import { TUploadFileConfig } from "@formbricks/types/storage"; import { TSurvey, TSurveyStyling } from "@formbricks/types/surveys"; -import { ClientLogo } from "@formbricks/ui/ClientLogo"; -import { ResetProgressButton } from "@formbricks/ui/ResetProgressButton"; -import { SurveyInline } from "@formbricks/ui/Survey"; + +import { ClientLogo } from "../ClientLogo"; +import { MediaBackground } from "../MediaBackground"; +import { ResetProgressButton } from "../ResetProgressButton"; +import { SurveyInline } from "../Survey"; +import Modal from "./components/Modal"; +import TabOption from "./components/TabOption"; type TPreviewType = "modal" | "fullwidth" | "email"; @@ -387,3 +388,5 @@ export const PreviewSurvey = ({
); }; + +export { getPlacementStyle } from "./lib/utils"; diff --git a/apps/web/app/lib/preview.ts b/packages/ui/PreviewSurvey/lib/utils.ts similarity index 100% rename from apps/web/app/lib/preview.ts rename to packages/ui/PreviewSurvey/lib/utils.ts diff --git a/packages/ui/SecondaryNavigation/index.tsx b/packages/ui/SecondaryNavigation/index.tsx new file mode 100644 index 0000000000..5d7448477d --- /dev/null +++ b/packages/ui/SecondaryNavigation/index.tsx @@ -0,0 +1,35 @@ +import Link from "next/link"; + +import { cn } from "@formbricks/lib/cn"; + +interface SecondaryNavbarProps { + navigation: { id: string; label: string; href: string; icon?: React.ReactNode; hidden?: boolean }[]; + activeId: string; +} + +export const SecondaryNavigation = ({ navigation, activeId, ...props }: SecondaryNavbarProps) => { + return ( +
+
+ +
+
+
+ ); +}; diff --git a/packages/ui/SimpleLayout/index.tsx b/packages/ui/SimpleLayout/index.tsx new file mode 100644 index 0000000000..bdb4b265e0 --- /dev/null +++ b/packages/ui/SimpleLayout/index.tsx @@ -0,0 +1,11 @@ +interface SimpleLayoutProps { + children: React.ReactNode; +} + +export const SimpleLayout = ({ children }: SimpleLayoutProps) => { + return ( +
+
{children}
+
+ ); +}; diff --git a/packages/ui/SingleResponseCard/components/QuestionSkip.tsx b/packages/ui/SingleResponseCard/components/QuestionSkip.tsx index 3b9e43dd19..c2605d3d39 100644 --- a/packages/ui/SingleResponseCard/components/QuestionSkip.tsx +++ b/packages/ui/SingleResponseCard/components/QuestionSkip.tsx @@ -88,7 +88,9 @@ export default function QuestionSkip({
-

Survey Closed

+

+ Survey closed +

{skippedQuestions && skippedQuestions.map((questionId) => { return ( diff --git a/packages/ui/SingleResponseCard/components/ResponseTagsWrapper.tsx b/packages/ui/SingleResponseCard/components/ResponseTagsWrapper.tsx index 5a96c20251..ab9f9264aa 100644 --- a/packages/ui/SingleResponseCard/components/ResponseTagsWrapper.tsx +++ b/packages/ui/SingleResponseCard/components/ResponseTagsWrapper.tsx @@ -58,7 +58,7 @@ const ResponseTagsWrapper: React.FC = ({ }, [tagIdToHighlight]); return ( -
+
{!isViewer && ( -
+
)} {orientation === "grid" && ( -
+
{surveys.map((survey) => { return ( void; + onTemplateClick?: (template: TTemplate) => void; environment: TEnvironment; product: TProduct; templateSearch?: string; @@ -32,7 +32,7 @@ const RECOMMENDED_CATEGORY_NAME = "For you"; export const TemplateList = ({ environmentId, user, - onTemplateClick, + onTemplateClick = () => {}, product, environment, templateSearch, @@ -65,7 +65,7 @@ export const TemplateList = ({ setSelectedFilter(activeFilter); }, [user, templateSearch]); - const addSurvey = async (activeTemplate) => { + const addSurvey = async (activeTemplate: TTemplate) => { setLoading(true); const surveyType = environment?.widgetSetupCompleted ? "app" : "link"; const augmentedTemplate: TSurveyInput = { @@ -160,7 +160,7 @@ export const TemplateList = ({ key={template.name} className={cn( activeTemplate?.name === template.name && "ring-2 ring-slate-400", - "duration-120 group relative cursor-pointer rounded-lg bg-white p-6 shadow transition-all duration-150 hover:scale-105" + "duration-120 group relative cursor-pointer rounded-lg bg-white p-6 shadow transition-all duration-150 hover:ring-2 hover:ring-slate-300" )}>
{ + if (!product) return question; + const newQuestion = structuredClone(question); + const defaultLanguageCode = "default"; + if (newQuestion.headline) { + newQuestion.headline[defaultLanguageCode] = getLocalizedValue( + newQuestion.headline, + defaultLanguageCode + ).replace("{{productName}}", product.name); + } + if (newQuestion.subheader) { + newQuestion.subheader[defaultLanguageCode] = getLocalizedValue( + newQuestion.subheader, + defaultLanguageCode + )?.replace("{{productName}}", product.name); + } + return newQuestion; +}; + +// replace all occurences of productName with the actual product name in the current template +export const replacePresetPlaceholders = (template: TTemplate, product: any) => { + const preset = structuredClone(template.preset); + preset.name = preset.name.replace("{{productName}}", product.name); + preset.questions = preset.questions.map((question) => { + return replaceQuestionPresetPlaceholders(question, product); + }); + return { ...template, preset }; +}; diff --git a/packages/ui/icons/GithubIcon.tsx b/packages/ui/icons/GithubIcon.tsx new file mode 100644 index 0000000000..21b613925a --- /dev/null +++ b/packages/ui/icons/GithubIcon.tsx @@ -0,0 +1,15 @@ +export const GithubIcon: React.FC> = (props) => { + return ( + + + + ); +}; diff --git a/packages/ui/icons/GoogleIcon.tsx b/packages/ui/icons/GoogleIcon.tsx new file mode 100644 index 0000000000..153a9f174a --- /dev/null +++ b/packages/ui/icons/GoogleIcon.tsx @@ -0,0 +1,15 @@ +export const GoogleIcon: React.FC> = (props) => { + return ( + + + + ); +}; diff --git a/packages/ui/icons/Html5Icon.tsx b/packages/ui/icons/Html5Icon.tsx new file mode 100644 index 0000000000..d98d458884 --- /dev/null +++ b/packages/ui/icons/Html5Icon.tsx @@ -0,0 +1,15 @@ +export const Html5Icon: React.FC> = (props) => { + return ( + + + + ); +}; diff --git a/packages/ui/icons/MicrosoftIcon.tsx b/packages/ui/icons/MicrosoftIcon.tsx new file mode 100644 index 0000000000..ac9f76753f --- /dev/null +++ b/packages/ui/icons/MicrosoftIcon.tsx @@ -0,0 +1,15 @@ +export const MicrosoftIcon: React.FC> = (props) => { + return ( + + + + ); +}; diff --git a/packages/ui/icons/NpmIcon.tsx b/packages/ui/icons/NpmIcon.tsx new file mode 100644 index 0000000000..f3ae8cec28 --- /dev/null +++ b/packages/ui/icons/NpmIcon.tsx @@ -0,0 +1,16 @@ +export const NpmIcon: React.FC> = (props) => { + return ( + + + + + ); +}; diff --git a/packages/ui/icons/SlackIcon.tsx b/packages/ui/icons/SlackIcon.tsx new file mode 100644 index 0000000000..b0329443bf --- /dev/null +++ b/packages/ui/icons/SlackIcon.tsx @@ -0,0 +1,24 @@ +export const SlackIcon: React.FC> = (props) => { + return ( + + + + + + + + + + + ); +}; diff --git a/packages/ui/icons/index.tsx b/packages/ui/icons/index.tsx index 6ec67f3aec..4dd84cd19d 100644 --- a/packages/ui/icons/index.tsx +++ b/packages/ui/icons/index.tsx @@ -52,3 +52,9 @@ export { UserGroupIcon } from "./UserGroupIcon"; export { UserSearchGlasIcon } from "./UserSearchGlasIcon"; export { VeryDisappointedIcon } from "./VeryDisappointedIcon"; export { VideoTabletAdjustIcon } from "./VideoTabletAdjustIcon"; +export { NpmIcon } from "./NpmIcon"; +export { Html5Icon } from "./Html5Icon"; +export { SlackIcon } from "./SlackIcon"; +export { MicrosoftIcon } from "./MicrosoftIcon"; +export { GithubIcon } from "./GithubIcon"; +export { GoogleIcon } from "./GoogleIcon"; diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json index 757a8dd6e7..261feee946 100644 --- a/packages/ui/tsconfig.json +++ b/packages/ui/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "@formbricks/tsconfig/react-library.json", - "include": [".", "../types/*.d.ts"], + "include": [".", "../types/*.d.ts", "../lib/templates.ts"], "exclude": ["build", "node_modules"], "compilerOptions": { "lib": ["ES2021.String"] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 474f02330e..7e19029da4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -181,9 +181,6 @@ importers: react-highlight-words: specifier: ^0.20.0 version: 0.20.0(react@18.3.1) - react-icons: - specifier: ^5.2.1 - version: 5.2.1(react@18.3.1) react-markdown: specifier: ^9.0.1 version: 9.0.1(@types/react@18.3.1)(react@18.3.1) @@ -362,7 +359,7 @@ importers: version: 0.0.17(@types/react@18.3.1)(react@18.3.1) '@sentry/nextjs': specifier: ^7.113.0 - version: 7.113.0(encoding@0.1.13)(next@14.2.3)(react@18.3.1)(webpack@5.91.0) + version: 7.114.0(encoding@0.1.13)(next@14.2.3)(react@18.3.1)(webpack@5.91.0) '@vercel/og': specifier: ^0.6.2 version: 0.6.2 @@ -416,7 +413,7 @@ importers: version: 5.4.1 posthog-js: specifier: ^1.131.0 - version: 1.131.1 + version: 1.131.3 prismjs: specifier: ^1.29.0 version: 1.29.0 @@ -438,9 +435,6 @@ importers: react-hot-toast: specifier: ^2.4.1 version: 2.4.1(csstype@3.1.3)(react-dom@18.3.1)(react@18.3.1) - react-icons: - specifier: ^5.2.1 - version: 5.2.1(react@18.3.1) redis: specifier: ^4.6.13 version: 4.6.13 @@ -541,10 +535,10 @@ importers: version: 10.9.2(@types/node@20.12.10)(typescript@5.4.5) zod: specifier: ^3.23.7 - version: 3.23.7 + version: 3.23.8 zod-prisma: specifier: ^0.5.4 - version: 0.5.4(prisma@5.13.0)(zod@3.23.7) + version: 0.5.4(prisma@5.13.0)(zod@3.23.8) packages/ee: dependencies: @@ -701,7 +695,7 @@ importers: version: 2.2.2 '@t3-oss/env-nextjs': specifier: ^0.10.1 - version: 0.10.1(typescript@5.4.5)(zod@3.23.7) + version: 0.10.1(typescript@5.4.5)(zod@3.23.8) '@ungap/structured-clone': specifier: ^1.2.0 version: 1.2.0 @@ -873,7 +867,7 @@ importers: dependencies: zod: specifier: ^3.23.7 - version: 3.23.7 + version: 3.23.8 devDependencies: '@formbricks/tsconfig': specifier: workspace:* @@ -7168,46 +7162,46 @@ packages: selderee: 0.11.0 dev: false - /@sentry-internal/feedback@7.113.0: - resolution: {integrity: sha512-eEmL8QXauUnM3FXGv0GT29RpL0Jo0pkn/uMu3aqjhQo7JKNqUGVYIUxJxiGWbVMbDXqPQ7L66bjjMS3FR1GM2g==} + /@sentry-internal/feedback@7.114.0: + resolution: {integrity: sha512-kUiLRUDZuh10QE9JbSVVLgqxFoD9eDPOzT0MmzlPuas8JlTmJuV4FtSANNcqctd5mBuLt2ebNXH0MhRMwyae4A==} engines: {node: '>=12'} dependencies: - '@sentry/core': 7.113.0 - '@sentry/types': 7.113.0 - '@sentry/utils': 7.113.0 + '@sentry/core': 7.114.0 + '@sentry/types': 7.114.0 + '@sentry/utils': 7.114.0 dev: false - /@sentry-internal/replay-canvas@7.113.0: - resolution: {integrity: sha512-K8uA42aobNF/BAXf14el15iSAi9fonLBUrjZi6nPDq7zaA8rPvfcTL797hwCbqkETz2zDf52Jz7I3WFCshDoUw==} + /@sentry-internal/replay-canvas@7.114.0: + resolution: {integrity: sha512-6rTiqmKi/FYtesdM2TM2U+rh6BytdPjLP65KTUodtxohJ+r/3m+termj2o4BhIYPE1YYOZNmbZfwebkuQPmWeg==} engines: {node: '>=12'} dependencies: - '@sentry/core': 7.113.0 - '@sentry/replay': 7.113.0 - '@sentry/types': 7.113.0 - '@sentry/utils': 7.113.0 + '@sentry/core': 7.114.0 + '@sentry/replay': 7.114.0 + '@sentry/types': 7.114.0 + '@sentry/utils': 7.114.0 dev: false - /@sentry-internal/tracing@7.113.0: - resolution: {integrity: sha512-8MDnYENRMnEfQjvN4gkFYFaaBSiMFSU/6SQZfY9pLI3V105z6JQ4D0PGMAUVowXilwNZVpKNYohE7XByuhEC7Q==} + /@sentry-internal/tracing@7.114.0: + resolution: {integrity: sha512-dOuvfJN7G+3YqLlUY4HIjyWHaRP8vbOgF+OsE5w2l7ZEn1rMAaUbPntAR8AF9GBA6j2zWNoSo8e7GjbJxVofSg==} engines: {node: '>=8'} dependencies: - '@sentry/core': 7.113.0 - '@sentry/types': 7.113.0 - '@sentry/utils': 7.113.0 + '@sentry/core': 7.114.0 + '@sentry/types': 7.114.0 + '@sentry/utils': 7.114.0 dev: false - /@sentry/browser@7.113.0: - resolution: {integrity: sha512-PdyVHPOprwoxGfKGsP2dXDWO0MBDW1eyP7EZlfZvM1A4hjk6ZRNfCv30g+TrqX4hiZDKzyqN3+AdP7N/J2IX0Q==} + /@sentry/browser@7.114.0: + resolution: {integrity: sha512-ijJ0vOEY6U9JJADVYGkUbLrAbpGSQgA4zV+KW3tcsBLX9M1jaWq4BV1PWHdzDPPDhy4OgfOjIfaMb5BSPn1U+g==} engines: {node: '>=8'} dependencies: - '@sentry-internal/feedback': 7.113.0 - '@sentry-internal/replay-canvas': 7.113.0 - '@sentry-internal/tracing': 7.113.0 - '@sentry/core': 7.113.0 - '@sentry/integrations': 7.113.0 - '@sentry/replay': 7.113.0 - '@sentry/types': 7.113.0 - '@sentry/utils': 7.113.0 + '@sentry-internal/feedback': 7.114.0 + '@sentry-internal/replay-canvas': 7.114.0 + '@sentry-internal/tracing': 7.114.0 + '@sentry/core': 7.114.0 + '@sentry/integrations': 7.114.0 + '@sentry/replay': 7.114.0 + '@sentry/types': 7.114.0 + '@sentry/utils': 7.114.0 dev: false /@sentry/cli@1.77.3(encoding@0.1.13): @@ -7227,26 +7221,26 @@ packages: - supports-color dev: false - /@sentry/core@7.113.0: - resolution: {integrity: sha512-pg75y3C5PG2+ur27A0Re37YTCEnX0liiEU7EOxWDGutH17x3ySwlYqLQmZsFZTSnvzv7t3MGsNZ8nT5O0746YA==} + /@sentry/core@7.114.0: + resolution: {integrity: sha512-YnanVlmulkjgZiVZ9BfY9k6I082n+C+LbZo52MTvx3FY6RE5iyiPMpaOh67oXEZRWcYQEGm+bKruRxLVP6RlbA==} engines: {node: '>=8'} dependencies: - '@sentry/types': 7.113.0 - '@sentry/utils': 7.113.0 + '@sentry/types': 7.114.0 + '@sentry/utils': 7.114.0 dev: false - /@sentry/integrations@7.113.0: - resolution: {integrity: sha512-w0sspGBQ+6+V/9bgCkpuM3CGwTYoQEVeTW6iNebFKbtN7MrM3XsGAM9I2cW1jVxFZROqCBPFtd2cs5n0j14aAg==} + /@sentry/integrations@7.114.0: + resolution: {integrity: sha512-BJIBWXGKeIH0ifd7goxOS29fBA8BkEgVVCahs6xIOXBjX1IRS6PmX0zYx/GP23nQTfhJiubv2XPzoYOlZZmDxg==} engines: {node: '>=8'} dependencies: - '@sentry/core': 7.113.0 - '@sentry/types': 7.113.0 - '@sentry/utils': 7.113.0 + '@sentry/core': 7.114.0 + '@sentry/types': 7.114.0 + '@sentry/utils': 7.114.0 localforage: 1.10.0 dev: false - /@sentry/nextjs@7.113.0(encoding@0.1.13)(next@14.2.3)(react@18.3.1)(webpack@5.91.0): - resolution: {integrity: sha512-lI5iJfbAC3dSakwq5+/JP58mLftxlDPPeY5ttcIuSHmNV/oobETJFTbWRIojUWYYn0E+Eea2OSdY5jPqxI7+iA==} + /@sentry/nextjs@7.114.0(encoding@0.1.13)(next@14.2.3)(react@18.3.1)(webpack@5.91.0): + resolution: {integrity: sha512-QRqE+YTVG3btTPVhOfiq0XmHp0dG4A0C/R+ssR/pdfOBr4EfEEav0hlTlqvk9BV0u6naJ5TOvBZ6Fy41rkYYrQ==} engines: {node: '>=8'} peerDependencies: next: ^10.0.8 || ^11.0 || ^12.0 || ^13.0 || ^14.0 @@ -7257,13 +7251,13 @@ packages: optional: true dependencies: '@rollup/plugin-commonjs': 24.0.0(rollup@2.78.0) - '@sentry/core': 7.113.0 - '@sentry/integrations': 7.113.0 - '@sentry/node': 7.113.0 - '@sentry/react': 7.113.0(react@18.3.1) - '@sentry/types': 7.113.0 - '@sentry/utils': 7.113.0 - '@sentry/vercel-edge': 7.113.0 + '@sentry/core': 7.114.0 + '@sentry/integrations': 7.114.0 + '@sentry/node': 7.114.0 + '@sentry/react': 7.114.0(react@18.3.1) + '@sentry/types': 7.114.0 + '@sentry/utils': 7.114.0 + '@sentry/vercel-edge': 7.114.0 '@sentry/webpack-plugin': 1.21.0(encoding@0.1.13) chalk: 3.0.0 next: 14.2.3(@opentelemetry/api@1.8.0)(@playwright/test@1.44.0)(react-dom@18.3.1)(react@18.3.1) @@ -7277,62 +7271,62 @@ packages: - supports-color dev: false - /@sentry/node@7.113.0: - resolution: {integrity: sha512-Vam4Ia0I9fhVw8GJOzcLP7MiiHJSKl8L9LzLMMLG3+2/dFnDQOyS7sOfk3GqgpwzqPiusP9vFu7CFSX7EMQbTg==} + /@sentry/node@7.114.0: + resolution: {integrity: sha512-cqvi+OHV1Hj64mIGHoZtLgwrh1BG6ntcRjDLlVNMqml5rdTRD3TvG21579FtlqHlwZpbpF7K5xkwl8e5KL2hGw==} engines: {node: '>=8'} dependencies: - '@sentry-internal/tracing': 7.113.0 - '@sentry/core': 7.113.0 - '@sentry/integrations': 7.113.0 - '@sentry/types': 7.113.0 - '@sentry/utils': 7.113.0 + '@sentry-internal/tracing': 7.114.0 + '@sentry/core': 7.114.0 + '@sentry/integrations': 7.114.0 + '@sentry/types': 7.114.0 + '@sentry/utils': 7.114.0 dev: false - /@sentry/react@7.113.0(react@18.3.1): - resolution: {integrity: sha512-+zVPz+h5Wydq4ntekw3/dXq5jeHIpZoQ2iqhB96PA9Y94JIq178i/xIP204S1h6rN7cmWAqtR93vnPKdxnlUbQ==} + /@sentry/react@7.114.0(react@18.3.1): + resolution: {integrity: sha512-zVPtvSy00Al25Z21f5GNzo3rd/TKS+iOX9wQwLrUZAxyf9RwBxKATLVJNJPkf8dQml6Qx+lfr0BHIlVcr1a1SQ==} engines: {node: '>=8'} peerDependencies: react: 15.x || 16.x || 17.x || 18.x dependencies: - '@sentry/browser': 7.113.0 - '@sentry/core': 7.113.0 - '@sentry/types': 7.113.0 - '@sentry/utils': 7.113.0 + '@sentry/browser': 7.114.0 + '@sentry/core': 7.114.0 + '@sentry/types': 7.114.0 + '@sentry/utils': 7.114.0 hoist-non-react-statics: 3.3.2 react: 18.3.1 dev: false - /@sentry/replay@7.113.0: - resolution: {integrity: sha512-UD2IaphOWKFdeGR+ZiaNAQ+wFsnwbJK6PNwcW6cHmWKv9COlKufpFt06lviaqFZ8jmNrM4H+r+R8YVTrqCuxgg==} + /@sentry/replay@7.114.0: + resolution: {integrity: sha512-UvEajoLIX9n2poeW3R4Ybz7D0FgCGXoFr/x/33rdUEMIdTypknxjJWxg6fJngIduzwrlrvWpvP8QiZXczYQy2Q==} engines: {node: '>=12'} dependencies: - '@sentry-internal/tracing': 7.113.0 - '@sentry/core': 7.113.0 - '@sentry/types': 7.113.0 - '@sentry/utils': 7.113.0 + '@sentry-internal/tracing': 7.114.0 + '@sentry/core': 7.114.0 + '@sentry/types': 7.114.0 + '@sentry/utils': 7.114.0 dev: false - /@sentry/types@7.113.0: - resolution: {integrity: sha512-PJbTbvkcPu/LuRwwXB1He8m+GjDDLKBtu3lWg5xOZaF5IRdXQU2xwtdXXsjge4PZR00tF7MO7X8ZynTgWbYaew==} + /@sentry/types@7.114.0: + resolution: {integrity: sha512-tsqkkyL3eJtptmPtT0m9W/bPLkU7ILY7nvwpi1hahA5jrM7ppoU0IMaQWAgTD+U3rzFH40IdXNBFb8Gnqcva4w==} engines: {node: '>=8'} dev: false - /@sentry/utils@7.113.0: - resolution: {integrity: sha512-nzKsErwmze1mmEsbW2AwL2oB+I5v6cDEJY4sdfLekA4qZbYZ8pV5iWza6IRl4XfzGTE1qpkZmEjPU9eyo0yvYw==} + /@sentry/utils@7.114.0: + resolution: {integrity: sha512-319N90McVpupQ6vws4+tfCy/03AdtsU0MurIE4+W5cubHME08HtiEWlfacvAxX+yuKFhvdsO4K4BB/dj54ideg==} engines: {node: '>=8'} dependencies: - '@sentry/types': 7.113.0 + '@sentry/types': 7.114.0 dev: false - /@sentry/vercel-edge@7.113.0: - resolution: {integrity: sha512-cHbo+v7ECRNluVWMJZqsNelf3JrV5Qw/aRH4Dw74IdErCYROAQx1pEuE7BnM3rIEgOQAMR/J/Fu7GFGecSbzTA==} + /@sentry/vercel-edge@7.114.0: + resolution: {integrity: sha512-EYMC8qXtJeZmsb+fPSmOO7BBezwTZjsI3S8JYtK+zeQgXWEVGI8UPlpkqkBiM29fHqCJ2nrLMyoWCOLG7ZwfhA==} engines: {node: '>=8'} dependencies: - '@sentry-internal/tracing': 7.113.0 - '@sentry/core': 7.113.0 - '@sentry/integrations': 7.113.0 - '@sentry/types': 7.113.0 - '@sentry/utils': 7.113.0 + '@sentry-internal/tracing': 7.114.0 + '@sentry/core': 7.114.0 + '@sentry/integrations': 7.114.0 + '@sentry/types': 7.114.0 + '@sentry/utils': 7.114.0 dev: false /@sentry/webpack-plugin@1.21.0(encoding@0.1.13): @@ -8559,7 +8553,7 @@ packages: '@swc/counter': 0.1.3 dev: false - /@t3-oss/env-core@0.10.1(typescript@5.4.5)(zod@3.23.7): + /@t3-oss/env-core@0.10.1(typescript@5.4.5)(zod@3.23.8): resolution: {integrity: sha512-GcKZiCfWks5CTxhezn9k5zWX3sMDIYf6Kaxy2Gx9YEQftFcz8hDRN56hcbylyAO3t4jQnQ5ifLawINsNgCDpOg==} peerDependencies: typescript: '>=5.0.0' @@ -8569,10 +8563,10 @@ packages: optional: true dependencies: typescript: 5.4.5 - zod: 3.23.7 + zod: 3.23.8 dev: false - /@t3-oss/env-nextjs@0.10.1(typescript@5.4.5)(zod@3.23.7): + /@t3-oss/env-nextjs@0.10.1(typescript@5.4.5)(zod@3.23.8): resolution: {integrity: sha512-iy2qqJLnFh1RjEWno2ZeyTu0ufomkXruUsOZludzDIroUabVvHsrSjtkHqwHp1/pgPUzN3yBRHMILW162X7x2Q==} peerDependencies: typescript: '>=5.0.0' @@ -8581,9 +8575,9 @@ packages: typescript: optional: true dependencies: - '@t3-oss/env-core': 0.10.1(typescript@5.4.5)(zod@3.23.7) + '@t3-oss/env-core': 0.10.1(typescript@5.4.5)(zod@3.23.8) typescript: 5.4.5 - zod: 3.23.7 + zod: 3.23.8 dev: false /@tailwindcss/forms@0.5.7(tailwindcss@3.4.3): @@ -16391,8 +16385,8 @@ packages: xtend: 4.0.2 dev: false - /posthog-js@1.131.1: - resolution: {integrity: sha512-XE+RIYY5QlmcwWuRvOOvFa2Hqb5RDG40Kg5KwCIzvsE9GY8Hi0L7XUVAXPZe6yJPRtYDPNtDjrEeHJEcZYPkMg==} + /posthog-js@1.131.3: + resolution: {integrity: sha512-ds/TADDS+rT/WgUyeW4cJ+X+fX+O1KdkOyssNI/tP90PrFf0IJsck5B42YOLhfz87U2vgTyBaKHkdlMgWuOFog==} dependencies: fflate: 0.4.8 preact: 10.21.0 @@ -17039,14 +17033,6 @@ packages: - csstype dev: false - /react-icons@5.2.1(react@18.3.1): - resolution: {integrity: sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==} - peerDependencies: - react: '*' - dependencies: - react: 18.3.1 - dev: false - /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -20185,7 +20171,7 @@ packages: readable-stream: 3.6.2 dev: true - /zod-prisma@0.5.4(prisma@5.13.0)(zod@3.23.7): + /zod-prisma@0.5.4(prisma@5.13.0)(zod@3.23.8): resolution: {integrity: sha512-5Ca4Qd1a1jy1T/NqCEpbr0c+EsbjJfJ/7euEHob3zDvtUK2rTuD1Rc/vfzH8q8PtaR2TZbysD88NHmrLwpv3Xg==} engines: {node: '>=14'} hasBin: true @@ -20201,11 +20187,11 @@ packages: parenthesis: 3.1.8 prisma: 5.13.0 ts-morph: 13.0.3 - zod: 3.23.7 + zod: 3.23.8 dev: true - /zod@3.23.7: - resolution: {integrity: sha512-NBeIoqbtOiUMomACV/y+V3Qfs9+Okr18vR5c/5pHClPpufWOrsx8TENboDPe265lFdfewX2yBtNTLPvnmCxwog==} + /zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} /zustand@4.5.2(@types/react@18.3.1)(react@18.3.1): resolution: {integrity: sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==}