mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-20 19:00:57 -06:00
build free limit into formbricks cloud
This commit is contained in:
@@ -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 && (
|
||||
<div className="bg-brand-light relative isolate flex items-center gap-x-6 overflow-hidden px-6 py-2.5 sm:px-3.5 sm:before:flex-1">
|
||||
<div className="flex flex-wrap items-center gap-x-4 gap-y-2">
|
||||
<p className="text-sm leading-6 text-gray-900">
|
||||
<strong className="font-semibold">Free limit reached</strong>
|
||||
<svg viewBox="0 0 2 2" className="mx-2 inline h-0.5 w-0.5 fill-current" aria-hidden="true">
|
||||
<circle cx={1} cy={1} r={1} />
|
||||
</svg>
|
||||
You can only see {RESPONSES_LIMIT_FREE} of the {count} responses you received.
|
||||
</p>
|
||||
<Link
|
||||
href={`/environments/${environmentId}/settings/billing`}
|
||||
className="flex-none rounded-full bg-white/50 px-3.5 py-1 text-sm font-semibold text-slate-900 shadow-sm hover:bg-white/40 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-900">
|
||||
Upgrade now <span aria-hidden="true">→</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex flex-1"></div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
<ResponsesLimitReachedBanner environmentId={params.environmentId} surveyId={params.surveyId} />
|
||||
<ContentWrapper>
|
||||
<ResponseTimeline environmentId={params.environmentId} surveyId={params.surveyId} />
|
||||
</ContentWrapper>
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<SurveyResultsTabs activeId="summary" environmentId={params.environmentId} surveyId={params.surveyId} />
|
||||
<ResponsesLimitReachedBanner environmentId={params.environmentId} surveyId={params.surveyId} />
|
||||
<ContentWrapper>
|
||||
<SummaryMetadata surveyId={params.surveyId} environmentId={params.environmentId} />
|
||||
<SummaryList environmentId={params.environmentId} surveyId={params.surveyId} />
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -8,7 +8,7 @@ export const useResponses = (environmentId: string, surveyId: string) => {
|
||||
);
|
||||
|
||||
return {
|
||||
responses: data,
|
||||
responsesData: data,
|
||||
isLoadingResponses: isLoading,
|
||||
isErrorResponses: error,
|
||||
mutateRespones: mutate,
|
||||
|
||||
@@ -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
|
||||
|
||||
1
packages/lib/constants.ts
Normal file
1
packages/lib/constants.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const RESPONSES_LIMIT_FREE = 30;
|
||||
Reference in New Issue
Block a user