diff --git a/apps/web/app/(app)/environments/[environmentId]/people/page.tsx b/apps/web/app/(app)/environments/[environmentId]/people/page.tsx
index 25d2dd64c4..bcd734c513 100644
--- a/apps/web/app/(app)/environments/[environmentId]/people/page.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/people/page.tsx
@@ -2,17 +2,35 @@ export const revalidate = REVALIDATION_INTERVAL;
import EmptySpaceFiller from "@/components/shared/EmptySpaceFiller";
import { truncateMiddle } from "@/lib/utils";
-import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
-import { getPeople } from "@formbricks/lib/services/person";
+import { PEOPLE_PER_PAGE, REVALIDATION_INTERVAL } from "@formbricks/lib/constants";
+import { getPeople, getPeopleCount } from "@formbricks/lib/services/person";
import { TPerson } from "@formbricks/types/v1/people";
-import { PersonAvatar } from "@formbricks/ui";
+import { Pagination, PersonAvatar } from "@formbricks/ui";
import Link from "next/link";
const getAttributeValue = (person: TPerson, attributeName: string) =>
person.attributes[attributeName]?.toString();
-export default async function PeoplePage({ params }) {
- const people = await getPeople(params.environmentId);
+export default async function PeoplePage({
+ params,
+ searchParams,
+}: {
+ params: { environmentId: string };
+ searchParams: { [key: string]: string | string[] | undefined };
+}) {
+ const pageNumber = searchParams.page ? parseInt(searchParams.page as string) : 1;
+ const totalPeople = await getPeopleCount(params.environmentId);
+ const maxPageNumber = Math.ceil(totalPeople / PEOPLE_PER_PAGE);
+ let hidePagination = false;
+
+ let people: TPerson[] = [];
+
+ if (pageNumber < 1 || pageNumber > maxPageNumber) {
+ people = [];
+ hidePagination = true;
+ } else {
+ people = await getPeople(params.environmentId, pageNumber);
+ }
return (
<>
@@ -64,6 +82,14 @@ export default async function PeoplePage({ params }) {
))}
)}
+ {hidePagination ? null : (
+
+ )}
>
);
}
diff --git a/apps/web/app/(app)/environments/[environmentId]/people/pagination.tsx b/apps/web/app/(app)/environments/[environmentId]/people/pagination.tsx
new file mode 100644
index 0000000000..f215893e10
--- /dev/null
+++ b/apps/web/app/(app)/environments/[environmentId]/people/pagination.tsx
@@ -0,0 +1,57 @@
+function Pagination({ environmentId, currentPage, totalItems, itemsPerPage }) {
+ const totalPages = Math.ceil(totalItems / itemsPerPage);
+
+ const previousPageLink =
+ currentPage === 1 ? "#" : `/environments/${environmentId}/people?page=${currentPage - 1}`;
+ const nextPageLink =
+ currentPage === totalPages ? "#" : `/environments/${environmentId}/people?page=${currentPage + 1}`;
+
+ return (
+
+ );
+}
+
+export default Pagination;
diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts
index fb30af491f..1062751402 100644
--- a/packages/lib/constants.ts
+++ b/packages/lib/constants.ts
@@ -25,3 +25,5 @@ export const SURVEY_BASE_URL = process.env.NEXT_PUBLIC_SURVEY_BASE_URL
export const INTERNAL_SECRET = process.env.INTERNAL_SECRET || "";
export const CRON_SECRET = process.env.CRON_SECRET;
export const DEFAULT_BRAND_COLOR = "#64748b";
+
+export const PEOPLE_PER_PAGE = 50;
diff --git a/packages/lib/services/person.ts b/packages/lib/services/person.ts
index e3aa9d6c34..ba85839826 100644
--- a/packages/lib/services/person.ts
+++ b/packages/lib/services/person.ts
@@ -2,11 +2,12 @@ import "server-only";
import { prisma } from "@formbricks/database";
import { ZId } from "@formbricks/types/v1/environment";
-import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors";
+import { DatabaseError } from "@formbricks/types/v1/errors";
import { TPerson } from "@formbricks/types/v1/people";
import { Prisma } from "@prisma/client";
import { revalidateTag, unstable_cache } from "next/cache";
import { cache } from "react";
+import { PEOPLE_PER_PAGE } from "../constants";
import { validateInputs } from "../utils/validate";
import { getAttributeClassByName } from "./attributeClass";
@@ -100,20 +101,24 @@ export const getPersonCached = async (personId: string) =>
}
)();
-export const getPeople = cache(async (environmentId: string): Promise => {
+export const getPeople = cache(async (environmentId: string, page: number = 1): Promise => {
validateInputs([environmentId, ZId]);
try {
- const personsPrisma = await prisma.person.findMany({
+ const itemsPerPage = PEOPLE_PER_PAGE;
+ const people = await prisma.person.findMany({
where: {
environmentId: environmentId,
},
select: selectPerson,
+ take: itemsPerPage,
+ skip: itemsPerPage * (page - 1),
});
- if (!personsPrisma) {
- throw new ResourceNotFoundError("Persons", "All Persons");
+
+ if (!people || people.length === 0) {
+ return [];
}
- const transformedPeople: TPerson[] = personsPrisma
+ const transformedPeople: TPerson[] = people
.map(transformPrismaPerson)
.filter((person: TPerson | null): person is TPerson => person !== null);
@@ -127,6 +132,24 @@ export const getPeople = cache(async (environmentId: string): Promise
}
});
+export const getPeopleCount = cache(async (environmentId: string): Promise => {
+ validateInputs([environmentId, ZId]);
+ try {
+ const totalCount = await prisma.person.count({
+ where: {
+ environmentId: environmentId,
+ },
+ });
+ return totalCount;
+ } catch (error) {
+ if (error instanceof Prisma.PrismaClientKnownRequestError) {
+ throw new DatabaseError(error.message);
+ }
+
+ throw error;
+ }
+});
+
export const createPerson = async (environmentId: string): Promise => {
validateInputs([environmentId, ZId]);
try {
diff --git a/packages/ui/components/Pagination.tsx b/packages/ui/components/Pagination.tsx
new file mode 100644
index 0000000000..20cddba4de
--- /dev/null
+++ b/packages/ui/components/Pagination.tsx
@@ -0,0 +1,72 @@
+function Pagination({
+ baseUrl,
+ currentPage,
+ totalItems,
+ itemsPerPage,
+}: {
+ baseUrl: string;
+ currentPage: number;
+ totalItems: number;
+ itemsPerPage: number;
+}) {
+ const totalPages = Math.ceil(totalItems / itemsPerPage);
+
+ const previousPageLink = currentPage === 1 ? "#" : `${baseUrl}?page=${currentPage - 1}`;
+ const nextPageLink = currentPage === totalPages ? "#" : `${baseUrl}?page=${currentPage + 1}`;
+
+ const getDisplayedPages = () => {
+ if (totalPages <= 20) {
+ return Array.from({ length: totalPages }, (_, idx) => idx + 1);
+ } else {
+ let range = [currentPage - 2, currentPage - 1, currentPage, currentPage + 1, currentPage + 2];
+ return [1, ...range.filter((n) => n > 1 && n < totalPages), totalPages];
+ }
+ };
+
+ return (
+
+ );
+}
+
+export { Pagination };
diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx
index 7b56e83371..3ef9e9603f 100644
--- a/packages/ui/index.tsx
+++ b/packages/ui/index.tsx
@@ -50,6 +50,7 @@ export {
export { ErrorComponent } from "./components/ErrorComponent";
export { Input } from "./components/Input";
export { Label } from "./components/Label";
+export { Pagination } from "./components/Pagination";
export { PasswordInput } from "./components/PasswordInput";
export { Popover, PopoverContent, PopoverTrigger } from "./components/Popover";
export { HalfCircle, ProgressBar } from "./components/ProgressBar";