diff --git a/apps/web/src/app/[locale]/(admin)/polls/user-polls.tsx b/apps/web/src/app/[locale]/(admin)/polls/user-polls.tsx
index 795c53f9a..a7e72b515 100644
--- a/apps/web/src/app/[locale]/(admin)/polls/user-polls.tsx
+++ b/apps/web/src/app/[locale]/(admin)/polls/user-polls.tsx
@@ -22,6 +22,7 @@ import {
import { PollStatusBadge } from "@/components/poll-status";
import { Spinner } from "@/components/spinner";
import { Trans } from "@/components/trans";
+import { VisibilityTrigger } from "@/components/visibility-trigger";
import { trpc } from "@/utils/trpc/client";
function PollCount({ count }: { count?: number }) {
@@ -29,26 +30,36 @@ function PollCount({ count }: { count?: number }) {
}
function FilteredPolls({ status }: { status: PollStatus }) {
- const { data, isFetching } = trpc.polls.list.useQuery(
- {
- status,
- },
- {
- keepPreviousData: true,
- },
- );
+ const { data, fetchNextPage, hasNextPage } =
+ trpc.polls.infiniteList.useInfiniteQuery(
+ {
+ status,
+ limit: 30,
+ },
+ {
+ getNextPageParam: (lastPage) => lastPage.nextCursor,
+ keepPreviousData: true,
+ },
+ );
if (!data) {
return ;
}
return (
-
-
+
+
+ {data.pages.map((page, i) => (
+ -
+
+
+ ))}
+
+ {hasNextPage ? (
+
+
+
+ ) : null}
);
}
diff --git a/apps/web/src/components/visibility-trigger.tsx b/apps/web/src/components/visibility-trigger.tsx
new file mode 100644
index 000000000..1ce3949e3
--- /dev/null
+++ b/apps/web/src/components/visibility-trigger.tsx
@@ -0,0 +1,51 @@
+import React from "react";
+
+export function useVisibilityTrigger
(onVisible: () => void) {
+ const triggerRef = React.useRef(null);
+
+ React.useEffect(() => {
+ const currentTriggerRef = triggerRef.current;
+ if (!currentTriggerRef) return;
+
+ const observer = new IntersectionObserver(
+ (entries) => {
+ if (entries[0].isIntersecting) {
+ onVisible();
+ }
+ },
+ {
+ root: null, // Use the viewport as the root
+ rootMargin: "0px",
+ threshold: 1.0, // Trigger when 100% of the element is visible
+ },
+ );
+
+ observer.observe(currentTriggerRef);
+
+ return () => {
+ if (currentTriggerRef) {
+ observer.unobserve(currentTriggerRef);
+ }
+ };
+ }, [onVisible]);
+
+ return triggerRef;
+}
+
+export function VisibilityTrigger({
+ children,
+ onVisible,
+ className,
+}: {
+ children: React.ReactNode;
+ onVisible: () => void;
+ className?: string;
+}) {
+ const triggerRef = useVisibilityTrigger(onVisible);
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/packages/backend/trpc/routers/polls.ts b/packages/backend/trpc/routers/polls.ts
index 2144a9488..3f14ebb9c 100644
--- a/packages/backend/trpc/routers/polls.ts
+++ b/packages/backend/trpc/routers/polls.ts
@@ -93,6 +93,58 @@ export const polls = router({
},
});
}),
+ infiniteList: possiblyPublicProcedure
+ .input(
+ z.object({
+ status: z.enum(["all", "live", "paused", "finalized"]),
+ cursor: z.string().optional(),
+ limit: z.number(),
+ }),
+ )
+ .query(async ({ ctx, input }) => {
+ const { cursor, limit, status } = input;
+ const polls = await prisma.poll.findMany({
+ where: {
+ userId: ctx.user.id,
+ status: status === "all" ? undefined : status,
+ },
+ orderBy: [
+ {
+ createdAt: "desc",
+ },
+ {
+ title: "asc",
+ },
+ ],
+ cursor: cursor ? { id: cursor } : undefined,
+ take: limit + 1,
+ select: {
+ id: true,
+ title: true,
+ location: true,
+ timeZone: true,
+ createdAt: true,
+ status: true,
+ userId: true,
+ participants: {
+ select: {
+ id: true,
+ name: true,
+ },
+ },
+ },
+ });
+
+ let nextCursor: typeof cursor | undefined = undefined;
+ if (polls.length > input.limit) {
+ const nextItem = polls.pop();
+ nextCursor = nextItem!.id;
+ }
+ return {
+ polls,
+ nextCursor,
+ };
+ }),
// START LEGACY ROUTES
create: possiblyPublicProcedure
diff --git a/packages/database/prisma/seed.ts b/packages/database/prisma/seed.ts
index fefe1e2cb..5f7507f3c 100644
--- a/packages/database/prisma/seed.ts
+++ b/packages/database/prisma/seed.ts
@@ -7,62 +7,64 @@ const prisma = new PrismaClient();
const randInt = (max = 1, floor = 0) => {
return Math.round(Math.random() * max) + floor;
};
+async function createPollForUser(userId: string) {
+ // create some polls with no duration (all day) and some with a random duration.
+ const duration = 60 * randInt(8);
+ let cursor = dayjs().add(randInt(30), "day").second(0).minute(0);
+
+ const numberOfOptions = randInt(30, 2);
+
+ const poll = await prisma.poll.create({
+ include: {
+ participants: true,
+ options: true,
+ },
+ data: {
+ id: faker.random.alpha(10),
+ title: `${faker.animal.cat()} Meetup ${faker.date.month()}`,
+ description: faker.lorem.paragraph(),
+ location: faker.address.streetAddress(),
+ deadline: faker.date.future(),
+ user: {
+ connect: {
+ id: userId,
+ },
+ },
+ timeZone: duration !== 0 ? "Europe/London" : undefined,
+ options: {
+ create: Array.from({ length: numberOfOptions }).map(() => {
+ const startTime = cursor.toDate();
+ if (duration !== 0) {
+ cursor = cursor.add(randInt(72, 1), "hour");
+ } else {
+ cursor = cursor.add(randInt(7, 1), "day");
+ }
+ return {
+ startTime,
+ duration,
+ };
+ }),
+ },
+ participants: {
+ create: Array.from({ length: Math.round(Math.random() * 10) }).map(
+ () => ({
+ name: faker.name.fullName(),
+ email: faker.internet.email(),
+ }),
+ ),
+ },
+ adminUrlId: faker.random.alpha(10),
+ participantUrlId: faker.random.alpha(10),
+ },
+ });
+
+ return poll;
+}
async function createPollsForUser(userId: string) {
// Create some polls
const polls = await Promise.all(
- Array.from({ length: 5 }).map(async (_, i) => {
- // create some polls with no duration (all day) and some with a random duration.
- const duration = i % 2 === 0 ? 60 * randInt(8, 1) : 0;
- let cursor = dayjs().add(randInt(30), "day").second(0).minute(0);
-
- const numberOfOptions = randInt(30, 2);
-
- const poll = await prisma.poll.create({
- include: {
- participants: true,
- options: true,
- },
- data: {
- id: faker.random.alpha(10),
- title: `${faker.animal.cat()} Meetup ${faker.date.month()}`,
- description: faker.lorem.paragraph(),
- location: faker.address.streetAddress(),
- deadline: faker.date.future(),
- user: {
- connect: {
- id: userId,
- },
- },
- timeZone: duration !== 0 ? "Europe/London" : undefined,
- options: {
- create: Array.from({ length: numberOfOptions }).map(() => {
- const startTime = cursor.toDate();
- if (duration !== 0) {
- cursor = cursor.add(randInt(72, 1), "hour");
- } else {
- cursor = cursor.add(randInt(7, 1), "day");
- }
- return {
- startTime,
- duration,
- };
- }),
- },
- participants: {
- create: Array.from({ length: Math.round(Math.random() * 10) }).map(
- () => ({
- name: faker.name.fullName(),
- email: faker.internet.email(),
- }),
- ),
- },
- adminUrlId: faker.random.alpha(10),
- participantUrlId: faker.random.alpha(10),
- },
- });
- return poll;
- }),
+ Array.from({ length: 100 }).map(() => createPollForUser(userId)),
);
// Create some votes