diff --git a/apps/web/src/app/[locale]/(admin)/polls/polls-page.tsx b/apps/web/src/app/[locale]/(admin)/polls/polls-page.tsx
index 8e5d2a2b2..435fd582a 100644
--- a/apps/web/src/app/[locale]/(admin)/polls/polls-page.tsx
+++ b/apps/web/src/app/[locale]/(admin)/polls/polls-page.tsx
@@ -75,12 +75,7 @@ export function PollsPage() {
data.length > 0 ? (
{data.map((poll) => {
- const { title, id: pollId, createdAt, closed: paused } = poll;
- const status = poll.event
- ? "closed"
- : paused
- ? "paused"
- : "live";
+ const { title, id: pollId, createdAt, status } = poll;
return (
{
),
);
- const status = poll?.event ? "closed" : poll?.closed ? "paused" : "live";
-
if (!poll) {
return null;
}
@@ -49,7 +47,7 @@ export const EventCard = () => {
/>
diff --git a/apps/web/src/components/layouts/poll-layout.tsx b/apps/web/src/components/layouts/poll-layout.tsx
index cd3f08116..6d788eccc 100644
--- a/apps/web/src/components/layouts/poll-layout.tsx
+++ b/apps/web/src/components/layouts/poll-layout.tsx
@@ -42,7 +42,7 @@ import {
import ManagePoll from "@/components/poll/manage-poll";
import NotificationsToggle from "@/components/poll/notifications-toggle";
import { LegacyPollContextProvider } from "@/components/poll/poll-context-provider";
-import { PollStatus } from "@/components/poll-status";
+import { PollStatusLabel } from "@/components/poll-status";
import { Skeleton } from "@/components/skeleton";
import { Trans } from "@/components/trans";
import { useUser } from "@/components/user-provider";
@@ -53,7 +53,6 @@ import { NextPageWithLayout } from "../../types";
const StatusControl = () => {
const poll = usePoll();
- const state = poll.event ? "closed" : poll.closed ? "paused" : "live";
const queryClient = trpc.useUtils();
const reopen = trpc.polls.reopen.useMutation({
onMutate: () => {
@@ -110,7 +109,7 @@ const StatusControl = () => {
diff --git a/apps/web/src/components/poll-status.tsx b/apps/web/src/components/poll-status.tsx
index dddd60216..e56f395bd 100644
--- a/apps/web/src/components/poll-status.tsx
+++ b/apps/web/src/components/poll-status.tsx
@@ -1,11 +1,10 @@
+import { PollStatus } from "@rallly/database";
import { cn } from "@rallly/ui";
import { CheckCircleIcon, PauseCircleIcon, RadioIcon } from "lucide-react";
import { Trans } from "@/components/trans";
import { IconComponent } from "@/types";
-export type PollState = "live" | "paused" | "closed";
-
const LabelWithIcon = ({
icon: Icon,
children,
@@ -23,11 +22,11 @@ const LabelWithIcon = ({
);
};
-export const PollStatus = ({
+export const PollStatusLabel = ({
status,
className,
}: {
- status: PollState;
+ status: PollStatus;
className?: string;
}) => {
switch (status) {
@@ -43,7 +42,7 @@ export const PollStatus = ({
);
- case "closed":
+ case "finalized":
return (
@@ -52,13 +51,13 @@ export const PollStatus = ({
}
};
-export const PollStatusBadge = ({ status }: { status: PollState }) => {
+export const PollStatusBadge = ({ status }: { status: PollStatus }) => {
return (
-
diff --git a/apps/web/src/components/poll/notifications-toggle.tsx b/apps/web/src/components/poll/notifications-toggle.tsx
index c00e8d0e7..367f53659 100644
--- a/apps/web/src/components/poll/notifications-toggle.tsx
+++ b/apps/web/src/components/poll/notifications-toggle.tsx
@@ -65,7 +65,7 @@ const NotificationsToggle: React.FunctionComponent = () => {
loading={watch.isLoading || unwatch.isLoading}
icon={isWatching ? BellRingIcon : BellOffIcon}
data-testid="notifications-toggle"
- disabled={poll.demo || user.isGuest}
+ disabled={user.isGuest}
className="flex items-center gap-2 px-2.5"
onClick={async () => {
if (user.isGuest) {
diff --git a/apps/web/src/utils/trpc/types.ts b/apps/web/src/utils/trpc/types.ts
index b91d6cccc..29b46336e 100644
--- a/apps/web/src/utils/trpc/types.ts
+++ b/apps/web/src/utils/trpc/types.ts
@@ -1,4 +1,4 @@
-import { User, VoteType } from "@rallly/database";
+import { PollStatus, User, VoteType } from "@rallly/database";
export type GetPollApiResponse = {
id: string;
@@ -9,10 +9,9 @@ export type GetPollApiResponse = {
user: User | null;
timeZone: string | null;
adminUrlId: string;
+ status: PollStatus;
participantUrlId: string;
closed: boolean;
- legacy: boolean;
- demo: boolean;
createdAt: Date;
deleted: boolean;
};
diff --git a/apps/web/tests/mocks.ts b/apps/web/tests/mocks.ts
deleted file mode 100644
index b2a1a7631..000000000
--- a/apps/web/tests/mocks.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-import { prisma, VoteType } from "@rallly/database";
-import dayjs from "dayjs";
-import { nanoid } from "nanoid";
-
-const participantData: Array<{ name: string; votes: VoteType[] }> = [
- {
- name: "Reed",
- votes: ["yes", "no", "yes", "no"],
- },
- {
- name: "Susan",
- votes: ["yes", "yes", "yes", "no"],
- },
- {
- name: "Johnny",
- votes: ["no", "no", "yes", "yes"],
- },
- {
- name: "Ben",
- votes: ["yes", "yes", "yes", "yes"],
- },
-];
-
-const optionValues = ["2022-12-14", "2022-12-15", "2022-12-16", "2022-12-17"];
-
-export const createPoll = async () => {
- const pollId = nanoid();
-
- const adminUrlId = nanoid();
-
- const options: Array<{ start: Date; id: string }> = [];
-
- for (let i = 0; i < optionValues.length; i++) {
- options.push({ id: nanoid(), start: new Date(optionValues[i]) });
- }
-
- const participants: Array<{
- name: string;
- id: string;
- userId: string;
- createdAt: Date;
- }> = [];
-
- const votes: Array<{
- optionId: string;
- participantId: string;
- type: VoteType;
- }> = [];
-
- for (let i = 0; i < participantData.length; i++) {
- const { name, votes: participantVotes } = participantData[i];
- const participantId = nanoid();
- participants.push({
- id: participantId,
- name,
- userId: "user-demo",
- createdAt: dayjs()
- .add(i * -1, "minutes")
- .toDate(),
- });
-
- options.forEach((option, index) => {
- votes.push({
- optionId: option.id,
- participantId,
- type: participantVotes[index],
- });
- });
- }
-
- await prisma.poll.create({
- data: {
- id: pollId,
- title: "Lunch Meeting",
- location: "Starbucks, 901 New York Avenue",
- description: `Hey everyone, please choose the dates when you are available to meet for our monthly get together. Looking forward to see you all!`,
- demo: true,
- adminUrlId,
- participantUrlId: nanoid(),
- userId: "guest-user",
- options: {
- createMany: {
- data: options,
- },
- },
- participants: {
- createMany: {
- data: participants,
- },
- },
- votes: {
- createMany: {
- data: votes,
- },
- },
- },
- });
-
- return pollId;
-};
-
-export const deletePoll = async (pollId: string) => {
- await prisma.$transaction([
- prisma.vote.deleteMany({
- where: {
- pollId,
- },
- }),
- prisma.option.deleteMany({
- where: {
- pollId,
- },
- }),
- prisma.participant.deleteMany({
- where: {
- pollId,
- },
- }),
- prisma.comment.deleteMany({
- where: {
- pollId,
- },
- }),
- prisma.poll.deleteMany({
- where: {
- id: pollId,
- },
- }),
- ]);
-};
diff --git a/packages/backend/trpc/routers/polls.ts b/packages/backend/trpc/routers/polls.ts
index f0f5281e8..67619f160 100644
--- a/packages/backend/trpc/routers/polls.ts
+++ b/packages/backend/trpc/routers/polls.ts
@@ -87,7 +87,6 @@ export const polls = router({
timeZone: input.timeZone,
location: input.location,
description: input.description,
- demo: input.demo,
adminUrlId: adminToken,
participantUrlId,
userId: ctx.user.id,
@@ -355,12 +354,11 @@ export const polls = router({
adminUrlId: true,
participantUrlId: true,
closed: true,
- legacy: true,
+ status: true,
hideParticipants: true,
disableComments: true,
hideScores: true,
requireParticipantEmail: true,
- demo: true,
options: {
select: {
id: true,
@@ -436,6 +434,7 @@ export const polls = router({
timeZone: true,
adminUrlId: true,
participantUrlId: true,
+ status: true,
event: {
select: {
start: true,
@@ -551,14 +550,20 @@ export const polls = router({
eventStart = eventStart.tz(poll.timeZone, true);
}
- await prisma.event.create({
+ await prisma.poll.update({
+ where: {
+ id: input.pollId,
+ },
data: {
- pollId: poll.id,
- optionId: input.optionId,
- start: eventStart.toDate(),
- duration: option.duration,
- title: poll.title,
- userId: ctx.user.id,
+ event: {
+ create: {
+ optionId: input.optionId,
+ start: eventStart.toDate(),
+ duration: option.duration,
+ title: poll.title,
+ userId: ctx.user.id,
+ },
+ },
},
});
@@ -721,18 +726,16 @@ export const polls = router({
)
.mutation(async ({ input }) => {
await prisma.$transaction([
- prisma.event.delete({
- where: {
- pollId: input.pollId,
- },
- }),
prisma.poll.update({
where: {
id: input.pollId,
},
data: {
- eventId: null,
- closed: false,
+ event: {
+ delete: true,
+ },
+ status: "live",
+ closed: false, // @deprecated
},
}),
]);
@@ -749,7 +752,8 @@ export const polls = router({
id: input.pollId,
},
data: {
- closed: true,
+ closed: true, // TODO (Luke Vella) [2023-12-05]: Remove this
+ status: "paused",
},
});
}),
@@ -827,6 +831,7 @@ export const polls = router({
},
data: {
closed: false,
+ status: "live",
},
});
}),
diff --git a/packages/database/prisma/migrations/20231205043530_poll_status/migration.sql b/packages/database/prisma/migrations/20231205043530_poll_status/migration.sql
new file mode 100644
index 000000000..c3a4cd2b1
--- /dev/null
+++ b/packages/database/prisma/migrations/20231205043530_poll_status/migration.sql
@@ -0,0 +1,50 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `demo` on the `polls` table. All the data in the column will be lost.
+ - You are about to drop the column `legacy` on the `polls` table. All the data in the column will be lost.
+ - A unique constraint covering the columns `[event_id]` on the table `polls` will be added. If there are existing duplicate values, this will fail.
+
+*/
+-- CreateEnum
+CREATE TYPE "poll_status" AS ENUM ('live', 'paused', 'finalized');
+
+-- AlterTable
+ALTER TABLE "polls" DROP COLUMN "demo",
+DROP COLUMN "legacy",
+ADD COLUMN "status" "poll_status";
+
+-- CreateIndex
+CREATE UNIQUE INDEX "polls_event_id_key" ON "polls"("event_id");
+
+-- Fix an issue where the "event_id" column was not being set
+UPDATE "polls"
+SET "event_id" = "events"."id"
+FROM "events"
+WHERE "events"."poll_id" = "polls"."id";
+
+-- Set the "status" column to corressponding enum value
+-- If "closed" is true, set to "paused"
+-- If a poll has an "event_id", set to "finalized"
+-- If a poll has a "deletedAt" date, set to "deleted"
+-- Otherwise set to "live"
+UPDATE "polls"
+SET "status" = CASE
+ WHEN "closed" = true THEN 'paused'::poll_status
+ WHEN "event_id" IS NOT NULL THEN 'finalized'::poll_status
+ ELSE 'live'::poll_status
+END;
+
+-- Make the "status" column non-nullable and default to "live"
+ALTER TABLE "polls"
+ALTER COLUMN "status" SET NOT NULL,
+ALTER COLUMN "status" SET DEFAULT 'live';
+
+
+DROP INDEX "events_poll_id_idx";
+
+-- DropIndex
+DROP INDEX "events_poll_id_key";
+
+-- AlterTable
+ALTER TABLE "events" DROP COLUMN "poll_id";
\ No newline at end of file
diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma
index e1cb017f6..b0cc8d7b3 100644
--- a/packages/database/prisma/schema.prisma
+++ b/packages/database/prisma/schema.prisma
@@ -117,6 +117,14 @@ enum ParticipantVisibility {
@@map("participant_visibility")
}
+enum PollStatus {
+ live
+ paused
+ finalized
+
+ @@map("poll_status")
+}
+
model Poll {
id String @id @unique @map("id")
createdAt DateTime @default(now()) @map("created_at")
@@ -125,46 +133,45 @@ model Poll {
title String
description String?
location String?
- user User? @relation(fields: [userId], references: [id])
userId String @map("user_id")
- votes Vote[]
timeZone String? @map("time_zone")
- options Option[]
- participants Participant[]
- watchers Watcher[]
- demo Boolean @default(false)
- comments Comment[]
- legacy Boolean @default(false) // @deprecated
- closed Boolean @default(false) // we use this to indicate whether a poll is paused
+ closed Boolean @default(false) // @deprecated
+ status PollStatus @default(live)
deleted Boolean @default(false)
deletedAt DateTime? @map("deleted_at")
touchedAt DateTime @default(now()) @map("touched_at")
participantUrlId String @unique @map("participant_url_id")
adminUrlId String @unique @map("admin_url_id")
- eventId String? @map("event_id")
- event Event?
+ eventId String? @unique @map("event_id")
hideParticipants Boolean @default(false) @map("hide_participants")
hideScores Boolean @default(false) @map("hide_scores")
disableComments Boolean @default(false) @map("disable_comments")
requireParticipantEmail Boolean @default(false) @map("require_participant_email")
+ user User? @relation(fields: [userId], references: [id])
+ event Event? @relation(fields: [eventId], references: [id])
+ options Option[]
+ participants Participant[]
+ watchers Watcher[]
+ comments Comment[]
+ votes Vote[]
+
@@index([userId], type: Hash)
@@map("polls")
}
model Event {
id String @id @default(cuid())
- pollId String @unique @map("poll_id")
userId String @map("user_id")
user User @relation(fields: [userId], references: [id])
- poll Poll @relation(fields: [pollId], references: [id])
optionId String @map("option_id")
title String
start DateTime @db.Timestamp(0)
duration Int @default(0) @map("duration_minutes")
createdAt DateTime @default(now()) @map("created_at")
- @@index([pollId], type: Hash)
+ Poll Poll?
+
@@index([userId], type: Hash)
@@map("events")
}