diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/ResponsesLimitReachedBanner.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/ResponsesLimitReachedBanner.tsx
new file mode 100644
index 0000000000..cc7f431e0e
--- /dev/null
+++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/ResponsesLimitReachedBanner.tsx
@@ -0,0 +1,35 @@
+"use client";
+
+import { RESPONSES_LIMIT_FREE } from "@formbricks/lib/constants";
+import { useResponses } from "@/lib/responses/responses";
+import Link from "next/Link";
+
+export default function ResponsesLimitReachedBanner({ environmentId, surveyId }) {
+ const { responsesData } = useResponses(environmentId, surveyId);
+ const reachedLimit = responsesData?.reachedLimit;
+ const count = responsesData?.count;
+
+ return (
+ <>
+ {reachedLimit && (
+
+
+
+ Free limit reached
+
+ You can only see {RESPONSES_LIMIT_FREE} of the {count} responses you received.
+
+
+ Upgrade now
→
+
+
+
+
+ )}
+ >
+ );
+}
diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/responses/ResponseTimeline.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/responses/ResponseTimeline.tsx
index cc89353979..27a725076b 100644
--- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/responses/ResponseTimeline.tsx
+++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/responses/ResponseTimeline.tsx
@@ -9,9 +9,11 @@ import { useMemo } from "react";
import SingleResponse from "./SingleResponse";
export default function ResponseTimeline({ environmentId, surveyId }) {
- const { responses, isLoadingResponses, isErrorResponses } = useResponses(environmentId, surveyId);
+ const { responsesData, isLoadingResponses, isErrorResponses } = useResponses(environmentId, surveyId);
const { survey, isLoadingSurvey, isErrorSurvey } = useSurvey(environmentId, surveyId);
+ const responses = responsesData?.responses;
+
const matchQandA = useMemo(() => {
if (survey && responses) {
// Create a mapping of question IDs to their headlines
diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/responses/page.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/responses/page.tsx
index d1dbc4a6ab..7b61784871 100644
--- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/responses/page.tsx
+++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/responses/page.tsx
@@ -1,6 +1,7 @@
import ContentWrapper from "@/components/shared/ContentWrapper";
import SurveyResultsTabs from "../SurveyResultsTabs";
import ResponseTimeline from "./ResponseTimeline";
+import ResponsesLimitReachedBanner from "../ResponsesLimitReachedBanner";
export default function ResponsesPage({ params }) {
return (
@@ -10,6 +11,7 @@ export default function ResponsesPage({ params }) {
environmentId={params.environmentId}
surveyId={params.surveyId}
/>
+
diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/SummaryList.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/SummaryList.tsx
index f775638d81..908bb136a8 100644
--- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/SummaryList.tsx
+++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/SummaryList.tsx
@@ -14,11 +14,13 @@ import MultipleChoiceSummary from "./MultipleChoiceSummary";
import OpenTextSummary from "./OpenTextSummary";
export default function SummaryList({ environmentId, surveyId }) {
- const { responses, isLoadingResponses, isErrorResponses } = useResponses(environmentId, surveyId);
+ const { responsesData, isLoadingResponses, isErrorResponses } = useResponses(environmentId, surveyId);
const { survey, isLoadingSurvey, isErrorSurvey } = useSurvey(environmentId, surveyId);
const [confetti, setConfetti] = useState(false);
const searchParams = useSearchParams();
+ const responses = responsesData?.responses;
+
useEffect(() => {
if (searchParams) {
const newSurveyParam = searchParams.get("success");
diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/SummaryMetadata.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/SummaryMetadata.tsx
index 938fb24c44..fe59f8e1dc 100644
--- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/SummaryMetadata.tsx
+++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/SummaryMetadata.tsx
@@ -17,10 +17,12 @@ import { useMemo } from "react";
import SurveyStatusDropdown from "@/components/shared/SurveyStatusDropdown";
export default function SummaryMetadata({ surveyId, environmentId }) {
- const { responses, isLoadingResponses, isErrorResponses } = useResponses(environmentId, surveyId);
+ const { responsesData, isLoadingResponses, isErrorResponses } = useResponses(environmentId, surveyId);
const { survey, isLoadingSurvey, isErrorSurvey } = useSurvey(environmentId, surveyId);
const { environment, isLoadingEnvironment, isErrorEnvironment } = useEnvironment(environmentId);
+ const responses = responsesData?.responses;
+
const completionRate = useMemo(() => {
if (!responses) return 0;
return (responses.filter((r) => r.finished).length / responses.length) * 100;
diff --git a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/page.tsx b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/page.tsx
index 1e5c6098ed..706fcfc250 100644
--- a/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/page.tsx
+++ b/apps/web/app/environments/[environmentId]/surveys/[surveyId]/summary/page.tsx
@@ -2,11 +2,13 @@ import ContentWrapper from "@/components/shared/ContentWrapper";
import SurveyResultsTabs from "../SurveyResultsTabs";
import SummaryList from "./SummaryList";
import SummaryMetadata from "./SummaryMetadata";
+import ResponsesLimitReachedBanner from "../ResponsesLimitReachedBanner";
export default function SummaryPage({ params }) {
return (
<>
+
diff --git a/apps/web/lib/api/apiHelper.ts b/apps/web/lib/api/apiHelper.ts
index d1f100b6e1..7bbde00a0e 100644
--- a/apps/web/lib/api/apiHelper.ts
+++ b/apps/web/lib/api/apiHelper.ts
@@ -53,6 +53,36 @@ export const hasUserEnvironmentAccess = async (user, environmentId) => {
return false;
};
+export const getPlan = async (req, res) => {
+ if (req.headers["x-api-key"]) {
+ const apiKey = req.headers["x-api-key"].toString();
+ const apiKeyData = await prisma.apiKey.findUnique({
+ where: {
+ hashedKey: hashApiKey(apiKey),
+ },
+ select: {
+ environment: {
+ select: {
+ product: {
+ select: {
+ team: {
+ select: {
+ plan: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ });
+ return apiKeyData?.environment.product.team.plan || "free";
+ } else {
+ const user = await getSessionUser(req, res);
+ return user ? user.plan : "free";
+ }
+};
+
export const hasApiEnvironmentAccess = async (apiKey, environmentId) => {
// write function to check if the API Key has access to the environment
const apiKeyData = await prisma.apiKey.findUnique({
diff --git a/apps/web/lib/responses/responses.ts b/apps/web/lib/responses/responses.ts
index 22aa87feca..b5313aab99 100644
--- a/apps/web/lib/responses/responses.ts
+++ b/apps/web/lib/responses/responses.ts
@@ -8,7 +8,7 @@ export const useResponses = (environmentId: string, surveyId: string) => {
);
return {
- responses: data,
+ responsesData: data,
isLoadingResponses: isLoading,
isErrorResponses: error,
mutateRespones: mutate,
diff --git a/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/responses/index.ts b/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/responses/index.ts
index 17c7cb1cf4..b6a0245080 100644
--- a/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/responses/index.ts
+++ b/apps/web/pages/api/v1/environments/[environmentId]/surveys/[surveyId]/responses/index.ts
@@ -1,5 +1,6 @@
-import { hasEnvironmentAccess } from "@/lib/api/apiHelper";
+import { getPlan, hasEnvironmentAccess } from "@/lib/api/apiHelper";
import { prisma } from "@formbricks/database";
+import { RESPONSES_LIMIT_FREE } from "@formbricks/lib/constants";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
@@ -33,7 +34,18 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
},
],
});
- return res.json(responses);
+
+ if (process.env.NEXT_PUBLIC_IS_FORMBRICKS_CLOUD === "1") {
+ const plan = await getPlan(req, res);
+ if (plan === "free" && responses.length > RESPONSES_LIMIT_FREE) {
+ return res.json({
+ count: responses.length,
+ responses: responses.slice(responses.length - RESPONSES_LIMIT_FREE, responses.length), // get last 30 from array
+ reachedLimit: true,
+ });
+ }
+ }
+ return res.json({ count: responses.length, responses, reachedLimit: false });
}
// Unknown HTTP Method
diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts
new file mode 100644
index 0000000000..5ac2b4704b
--- /dev/null
+++ b/packages/lib/constants.ts
@@ -0,0 +1 @@
+export const RESPONSES_LIMIT_FREE = 30;