mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 10:19:51 -06:00
chore: server side pagination for responses (#1869)
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
committed by
GitHub
parent
1d7d07b3c6
commit
a1fa3d6dbb
@@ -1,7 +1,24 @@
|
||||
"use server";
|
||||
|
||||
import { getServerSession } from "next-auth";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
import { authOptions } from "@formbricks/lib/authOptions";
|
||||
import { getResponses } from "@formbricks/lib/response/service";
|
||||
import { canUserAccessSurvey } from "@formbricks/lib/survey/auth";
|
||||
import { AuthorizationError } from "@formbricks/types/errors";
|
||||
import { TResponse } from "@formbricks/types/responses";
|
||||
|
||||
export default async function revalidateSurveyIdPath(environmentId: string, surveyId: string) {
|
||||
revalidatePath(`/environments/${environmentId}/surveys/${surveyId}`);
|
||||
}
|
||||
|
||||
export async function getMoreResponses(surveyId: string, page: number): Promise<TResponse[]> {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session) throw new AuthorizationError("Not authorized");
|
||||
|
||||
const isAuthorized = await canUserAccessSurvey(session.user.id, surveyId);
|
||||
if (!isAuthorized) throw new AuthorizationError("Not authorized");
|
||||
const responses = await getResponses(surveyId, page);
|
||||
return responses;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { getMoreResponses } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/actions";
|
||||
import EmptyInAppSurveys from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/EmptyInAppSurveys";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
@@ -29,22 +30,27 @@ export default function ResponseTimeline({
|
||||
environmentTags,
|
||||
responsesPerPage,
|
||||
}: ResponseTimelineProps) {
|
||||
const [displayedResponses, setDisplayedResponses] = useState<TResponse[]>([]);
|
||||
const loadingRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
setDisplayedResponses(responses.slice(0, responsesPerPage));
|
||||
}, [responses, setDisplayedResponses, responsesPerPage]);
|
||||
const [fetchedResponses, setFetchedResponses] = useState<TResponse[]>(responses);
|
||||
const [page, setPage] = useState(2);
|
||||
const [hasMoreResponses, setHasMoreResponses] = useState<boolean>(responses.length > 0);
|
||||
|
||||
useEffect(() => {
|
||||
const currentLoadingRef = loadingRef.current;
|
||||
|
||||
const loadResponses = async () => {
|
||||
const newResponses = await getMoreResponses(survey.id, page);
|
||||
if (newResponses.length === 0) {
|
||||
setHasMoreResponses(false);
|
||||
} else {
|
||||
setPage(page + 1);
|
||||
}
|
||||
setFetchedResponses((prevResponses) => [...prevResponses, ...newResponses]);
|
||||
};
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
setDisplayedResponses((prevResponses) => [
|
||||
...prevResponses,
|
||||
...responses.slice(prevResponses.length, prevResponses.length + responsesPerPage),
|
||||
]);
|
||||
if (hasMoreResponses) loadResponses();
|
||||
}
|
||||
},
|
||||
{ threshold: 0.8 }
|
||||
@@ -59,13 +65,13 @@ export default function ResponseTimeline({
|
||||
observer.unobserve(currentLoadingRef);
|
||||
}
|
||||
};
|
||||
}, [responses, responsesPerPage]);
|
||||
}, [responses, responsesPerPage, page, survey.id, fetchedResponses.length, hasMoreResponses]);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{survey.type === "web" && displayedResponses.length === 0 && !environment.widgetSetupCompleted ? (
|
||||
{survey.type === "web" && fetchedResponses.length === 0 && !environment.widgetSetupCompleted ? (
|
||||
<EmptyInAppSurveys environment={environment} />
|
||||
) : displayedResponses.length === 0 ? (
|
||||
) : fetchedResponses.length === 0 ? (
|
||||
<EmptySpaceFiller
|
||||
type="response"
|
||||
environment={environment}
|
||||
@@ -73,7 +79,7 @@ export default function ResponseTimeline({
|
||||
/>
|
||||
) : (
|
||||
<div>
|
||||
{displayedResponses.map((response) => {
|
||||
{fetchedResponses.map((response) => {
|
||||
return (
|
||||
<div key={response.id}>
|
||||
<SingleResponseCard
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { getAnalysisData } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/data";
|
||||
import ResponsePage from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponsePage";
|
||||
import { getServerSession } from "next-auth";
|
||||
|
||||
@@ -7,6 +6,8 @@ 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 { getProductByEnvironmentId } from "@formbricks/lib/product/service";
|
||||
import { getResponses } 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";
|
||||
@@ -16,13 +17,18 @@ export default async function Page({ params }) {
|
||||
if (!session) {
|
||||
throw new Error("Unauthorized");
|
||||
}
|
||||
const [{ responses, survey }, environment] = await Promise.all([
|
||||
getAnalysisData(params.surveyId, params.environmentId),
|
||||
const [responses, survey, environment] = await Promise.all([
|
||||
getResponses(params.surveyId, 1),
|
||||
getSurvey(params.surveyId),
|
||||
getEnvironment(params.environmentId),
|
||||
]);
|
||||
|
||||
if (!environment) {
|
||||
throw new Error("Environment not found");
|
||||
}
|
||||
if (!survey) {
|
||||
throw new Error("Survey not found");
|
||||
}
|
||||
const product = await getProductByEnvironmentId(environment.id);
|
||||
if (!product) {
|
||||
throw new Error("Product not found");
|
||||
|
||||
@@ -32,6 +32,8 @@ import { formatDateFields } from "../utils/datetime";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { responseCache } from "./cache";
|
||||
|
||||
const RESPONSES_PER_PAGE = 10;
|
||||
|
||||
export const responseSelection = {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
@@ -398,8 +400,8 @@ export const getResponses = async (surveyId: string, page?: number): Promise<TRe
|
||||
createdAt: "desc",
|
||||
},
|
||||
],
|
||||
take: page ? ITEMS_PER_PAGE : undefined,
|
||||
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
|
||||
take: page ? RESPONSES_PER_PAGE : undefined,
|
||||
skip: page ? RESPONSES_PER_PAGE * (page - 1) : undefined,
|
||||
});
|
||||
|
||||
const transformedResponses: TResponse[] = await Promise.all(
|
||||
@@ -421,7 +423,7 @@ export const getResponses = async (surveyId: string, page?: number): Promise<TRe
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`getResponses-${surveyId}`],
|
||||
[`getResponses-${surveyId}-${page}`],
|
||||
{
|
||||
tags: [responseCache.tag.bySurveyId(surveyId)],
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
|
||||
Reference in New Issue
Block a user